11from collections import OrderedDict
2+ import os
23
34import xmltodict
45
56import openml ._api_calls
7+ import openml .exceptions
68from ..utils import extract_xml_tags
79
810
@@ -128,7 +130,7 @@ def __init__(self, name, description, model, components, parameters,
128130 self .dependencies = dependencies
129131 self .flow_id = flow_id
130132
131- def _to_xml (self ):
133+ def _to_xml (self ) -> str :
132134 """Generate xml representation of self for upload to server.
133135
134136 Returns
@@ -144,7 +146,7 @@ def _to_xml(self):
144146 flow_xml = flow_xml .split ('\n ' , 1 )[- 1 ]
145147 return flow_xml
146148
147- def _to_dict (self ):
149+ def _to_dict (self ) -> dict :
148150 """ Helper function used by _to_xml and itself.
149151
150152 Creates a dictionary representation of self which can be serialized
@@ -312,8 +314,32 @@ def _from_dict(cls, xml_dict):
312314
313315 return flow
314316
315- def publish (self ):
316- """Publish flow to OpenML server.
317+ def to_filesystem (self , output_directory : str ) -> None :
318+ os .makedirs (output_directory , exist_ok = True )
319+ if 'flow.xml' in os .listdir (output_directory ):
320+ raise ValueError ('Output directory already contains a flow.xml file.' )
321+
322+ run_xml = self ._to_xml ()
323+ with open (os .path .join (output_directory , 'flow.xml' ), 'w' ) as f :
324+ f .write (run_xml )
325+
326+ @classmethod
327+ def from_filesystem (cls , input_directory ) -> 'OpenMLFlow' :
328+ with open (os .path .join (input_directory , 'flow.xml' ), 'r' ) as f :
329+ xml_string = f .read ()
330+ return OpenMLFlow ._from_dict (xmltodict .parse (xml_string ))
331+
332+ def publish (self , raise_error_if_exists : bool = False ) -> 'OpenMLFlow' :
333+ """ Publish this flow to OpenML server.
334+
335+ Raises a PyOpenMLError if the flow exists on the server, but
336+ `self.flow_id` does not match the server known flow id.
337+
338+ Parameters
339+ ----------
340+ raise_error_if_exists : bool, optional (default=False)
341+ If True, raise PyOpenMLError if the flow exists on the server.
342+ If False, update the local flow to match the server flow.
317343
318344 Returns
319345 -------
@@ -326,16 +352,27 @@ def publish(self):
326352 # instantiate an OpenMLFlow.
327353 import openml .flows .functions
328354
329- xml_description = self ._to_xml ()
355+ flow_id = openml .flows .functions .flow_exists (self .name , self .external_version )
356+ if not flow_id :
357+ if self .flow_id :
358+ raise openml .exceptions .PyOpenMLError ("Flow does not exist on the server, "
359+ "but 'flow.flow_id' is not None." )
360+ xml_description = self ._to_xml ()
361+ file_elements = {'description' : xml_description }
362+ return_value = openml ._api_calls ._perform_api_call (
363+ "flow/" ,
364+ 'post' ,
365+ file_elements = file_elements ,
366+ )
367+ server_response = xmltodict .parse (return_value )
368+ flow_id = int (server_response ['oml:upload_flow' ]['oml:id' ])
369+ elif raise_error_if_exists :
370+ error_message = "This OpenMLFlow already exists with id: {}." .format (flow_id )
371+ raise openml .exceptions .PyOpenMLError (error_message )
372+ elif self .flow_id is not None and self .flow_id != flow_id :
373+ raise openml .exceptions .PyOpenMLError ("Local flow_id does not match server flow_id: "
374+ "'{}' vs '{}'" .format (self .flow_id , flow_id ))
330375
331- file_elements = {'description' : xml_description }
332- return_value = openml ._api_calls ._perform_api_call (
333- "flow/" ,
334- 'post' ,
335- file_elements = file_elements ,
336- )
337- server_response = xmltodict .parse (return_value )
338- flow_id = int (server_response ['oml:upload_flow' ]['oml:id' ])
339376 flow = openml .flows .functions .get_flow (flow_id )
340377 _copy_server_fields (flow , self )
341378 try :
0 commit comments