{"action":"created","comment":{"url":"https://api.github.com/repos/openml/openml-python/pulls/comments/81772419","pull_request_review_id":2727409,"id":81772419,"diff_hunk":"@@ -1,80 +1,358 @@\n from collections import OrderedDict\n+import re\n+\n+import six\n import xmltodict\n-import sklearn\n \n from .._api_calls import _perform_api_call\n-from .functions import _check_flow_exists\n+from openml.util import oml_cusual_string\n \n \n class OpenMLFlow(object):\n     \"\"\"OpenML Flow. Stores machine learning models.\n \n+    Flows should not be generated manually, but by the function\n+    :meth:`openml.flows.create_flow_from_model`. Using this helper function\n+    ensures that all relevant fields are filled in.\n+\n     Parameters\n     ----------\n-    model : scikit-learn compatible model\n-        The model the flow consists of. The model needs to have fit and predict methods.\n-    description : string\n+    name : str\n+        Name of the flow. Is used together with the attribute `external_version`\n+        as a unique identifier of the flow.\n+    description : str\n         Description of the flow (free text).\n-    creator : string\n-        FIXME\n-    contributor : string\n-        FIXME\n-    tag : string\n-        FIXME\n+    model : object\n+        ML model which is described by this flow.\n+    components : OrderedDict\n+        Mapping from component identifier to an OpenMLFlow object.\n+    parameters : OrderedDict\n+        Mapping from parameter name to the parameter default value. The\n+        parameter default value must be of type `str`, so that the respective\n+        toolbox plugin can take care of casting the parameter default value to\n+        the correct type.\n+    parameters_meta_info : OrderedDict\n+        Mapping from parameter name to `dict`. Stores additional information for\n+        each parameter. Required keys are `data_type` and `description`.\n+    external_version : str\n+        Version number of the software the flow is implemented in. Is used\n+        together with the attribute `name` as a uniquer identifier of the flow.\n+    uploader : str\n+        OpenML user ID of the uploader. Filled in by the server.\n+    tags : list\n+        List of tags. Created on the server by other API calls.\n+    binary_url : str\n+        ??? - don't use - implemented because it is used by flows on the server\n+    binary_format : str\n+        ??? - don't use - implemented because it is used by flows on the server\n+    binary_md5 : str\n+        ??? - don't use - implemented because it is used by flows on the server\n+    version : str\n+        OpenML version of the flow.\n+    upload_date : str\n+        Date the flow was uploaded. Filled in by the server.\n+    language : str\n+        Natural language the flow is described in (not the programming\n+        language).\n+    dependencies : str\n+        A list of dependencies necessary to run the flow.\n     flow_id : int, optional\n         Flow ID. Assigned by the server (fixme shouldn't be here?)\n-    uploader : string, optional\n-        User uploading the model (fixme shouldn't be here?). Assigned by the server.\n-\n-\n     \"\"\"\n-    def __init__(self, model, flow_id=None, uploader=None,\n-                 description='Flow generated by run_task', creator=None,\n-                 contributor=None, tag=None):\n-        self.flow_id = flow_id\n-        self.upoader = uploader\n+    # TODO @Jan can you find better descriptions for binary_url, binary_md5,\n+    # binary_format and version?\n+    def __init__(self, name, description=None, model=None, components=None,\n+                 parameters=None, parameters_meta_info=None,\n+                 external_version=None, uploader=None, tags=None,\n+                 binary_url=None, binary_format=None, binary_md5=None,\n+                 version=None, upload_date=None, language=None,\n+                 dependencies=None, flow_id=None):\n+        self.name = name\n         self.description = description\n-        self.creator = creator\n-        self.tag = tag\n         self.model = model\n-        self.source = \"FIXME DEFINE PYTHON FLOW\"\n-        self.name = (model.__module__ + \".\" +\n-                     model.__class__.__name__)\n-        self.external_version = 'sklearn_' + sklearn.__version__\n \n-    def _generate_flow_xml(self):\n+        if components is None:\n+            components = OrderedDict()\n+        elif not isinstance(components, OrderedDict):\n+            raise TypeError('components must be of type OrderedDict, '\n+                            'but is %s.' % type(components))\n+        self.components = components\n+\n+        if parameters is None:\n+            parameters = OrderedDict()\n+        elif not isinstance(parameters, OrderedDict):\n+            raise TypeError('parameters must be of type OrderedDict, '\n+                            'but is %s.' % type(parameters))\n+\n+        if parameters_meta_info is None:\n+            parameters_meta_info = OrderedDict()\n+        elif not isinstance(parameters_meta_info, OrderedDict):\n+            raise TypeError('parameters_meta_info must be of type OrderedDict, '\n+                            'but is %s.' % type(parameters_meta_info))\n+\n+        keys_parameters = set(parameters.keys())\n+        keys_parameters_meta_info = set(parameters_meta_info.keys())\n+        if len(keys_parameters.difference(keys_parameters_meta_info)) > 0:\n+            raise ValueError('Parameter %s only in parameters, but not in'\n+                             'parameters_meta_info.' %\n+                             str(keys_parameters.difference(\n+                                 keys_parameters_meta_info)))\n+        if len(keys_parameters_meta_info.difference(keys_parameters)) > 0:\n+            raise ValueError('Parameter %s only in parameters_meta_info, '\n+                             'but not in parameters.' %\n+                             str(keys_parameters_meta_info.difference(\n+                                 keys_parameters)))\n+\n+        self.parameters = parameters\n+        self.parameters_meta_info = parameters_meta_info\n+\n+        self.external_version = external_version\n+        self.uploader = uploader\n+\n+        if tags is None:\n+            tags = []\n+        self.tags = tags\n+        self.binary_url = binary_url\n+        self.binary_format = binary_format\n+        self.binary_md5 = binary_md5\n+        self.version = version\n+        self.upload_date = upload_date\n+        self.language = language\n+        self.dependencies = dependencies\n+        self.flow_id = flow_id\n+\n+    def _to_xml(self):\n         \"\"\"Generate xml representation of self for upload to server.\n \n         Returns\n         -------\n-        flow_xml : string\n+        str\n             Flow represented as XML string.\n         \"\"\"\n-        model = self.model\n+        flow_dict = self.__to_dict()\n+        flow_xml = xmltodict.unparse(flow_dict, pretty=True)\n+\n+        # A flow may not be uploaded with the encoding specification..\n+        flow_xml = flow_xml.split('\\n', 1)[-1]\n+        return flow_xml\n+\n+    def __to_dict(self):\n+        \"\"\" Helper function used by _to_xml and __to_dict.\n \n+        Creates a dictionary representation of self which can be serialized\n+        to xml by the function _to_xml.\n+\n+        Returns\n+        -------\n+        OrderedDict","path":"openml/flows/flow.py","position":176,"original_position":176,"commit_id":"f9aa442404833e6f399e7489eba929f612da71d1","original_commit_id":"4c7673fc14c37fa1bed85e9296007272ab1039c5","user":{"login":"mfeurer","id":5320498,"avatar_url":"https://avatars.githubusercontent.com/u/5320498?v=3","gravatar_id":"","url":"https://api.github.com/users/mfeurer","html_url":"https://github.com/mfeurer","followers_url":"https://api.github.com/users/mfeurer/followers","following_url":"https://api.github.com/users/mfeurer/following{/other_user}","gists_url":"https://api.github.com/users/mfeurer/gists{/gist_id}","starred_url":"https://api.github.com/users/mfeurer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mfeurer/subscriptions","organizations_url":"https://api.github.com/users/mfeurer/orgs","repos_url":"https://api.github.com/users/mfeurer/repos","events_url":"https://api.github.com/users/mfeurer/events{/privacy}","received_events_url":"https://api.github.com/users/mfeurer/received_events","type":"User","site_admin":false},"body":"In this function I use OrderedDict to retain the order in which I add things to the flow_dict which will be serialized by xmltodict. If the order of the xml is different than specified in the xsd, the server will reject the xml file. In general I use OrderedDict everywhere because it keeps parameters and components in the same order as they were for example retrieved from the server.\r\n\r\nI will add a further comment on this.","created_at":"2016-10-04T14:41:39Z","updated_at":"2016-10-04T14:41:39Z","html_url":"https://github.com/openml/openml-python/pull/167#discussion_r81772419","pull_request_url":"https://api.github.com/repos/openml/openml-python/pulls/167","_links":{"self":{"href":"https://api.github.com/repos/openml/openml-python/pulls/comments/81772419"},"html":{"href":"https://github.com/openml/openml-python/pull/167#discussion_r81772419"},"pull_request":{"href":"https://api.github.com/repos/openml/openml-python/pulls/167"}}},"pull_request":{"url":"https://api.github.com/repos/openml/openml-python/pulls/167","id":83822523,"html_url":"https://github.com/openml/openml-python/pull/167","diff_url":"https://github.com/openml/openml-python/pull/167.diff","patch_url":"https://github.com/openml/openml-python/pull/167.patch","issue_url":"https://api.github.com/repos/openml/openml-python/issues/167","number":167,"state":"open","locked":false,"title":"Feature/upload flow","user":{"login":"mfeurer","id":5320498,"avatar_url":"https://avatars.githubusercontent.com/u/5320498?v=3","gravatar_id":"","url":"https://api.github.com/users/mfeurer","html_url":"https://github.com/mfeurer","followers_url":"https://api.github.com/users/mfeurer/followers","following_url":"https://api.github.com/users/mfeurer/following{/other_user}","gists_url":"https://api.github.com/users/mfeurer/gists{/gist_id}","starred_url":"https://api.github.com/users/mfeurer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mfeurer/subscriptions","organizations_url":"https://api.github.com/users/mfeurer/orgs","repos_url":"https://api.github.com/users/mfeurer/repos","events_url":"https://api.github.com/users/mfeurer/events{/privacy}","received_events_url":"https://api.github.com/users/mfeurer/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-09-02T15:40:11Z","updated_at":"2016-10-04T14:41:39Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7270a8b4bb476c697172aba67038a5cfe41abe18","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/openml/openml-python/pulls/167/commits","review_comments_url":"https://api.github.com/repos/openml/openml-python/pulls/167/comments","review_comment_url":"https://api.github.com/repos/openml/openml-python/pulls/comments{/number}","comments_url":"https://api.github.com/repos/openml/openml-python/issues/167/comments","statuses_url":"https://api.github.com/repos/openml/openml-python/statuses/f9aa442404833e6f399e7489eba929f612da71d1","head":{"label":"openml:feature/upload-flow","ref":"feature/upload-flow","sha":"f9aa442404833e6f399e7489eba929f612da71d1","user":{"login":"openml","id":4984562,"avatar_url":"https://avatars.githubusercontent.com/u/4984562?v=3","gravatar_id":"","url":"https://api.github.com/users/openml","html_url":"https://github.com/openml","followers_url":"https://api.github.com/users/openml/followers","following_url":"https://api.github.com/users/openml/following{/other_user}","gists_url":"https://api.github.com/users/openml/gists{/gist_id}","starred_url":"https://api.github.com/users/openml/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openml/subscriptions","organizations_url":"https://api.github.com/users/openml/orgs","repos_url":"https://api.github.com/users/openml/repos","events_url":"https://api.github.com/users/openml/events{/privacy}","received_events_url":"https://api.github.com/users/openml/received_events","type":"Organization","site_admin":false},"repo":{"id":17939040,"name":"openml-python","full_name":"openml/openml-python","owner":{"login":"openml","id":4984562,"avatar_url":"https://avatars.githubusercontent.com/u/4984562?v=3","gravatar_id":"","url":"https://api.github.com/users/openml","html_url":"https://github.com/openml","followers_url":"https://api.github.com/users/openml/followers","following_url":"https://api.github.com/users/openml/following{/other_user}","gists_url":"https://api.github.com/users/openml/gists{/gist_id}","starred_url":"https://api.github.com/users/openml/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openml/subscriptions","organizations_url":"https://api.github.com/users/openml/orgs","repos_url":"https://api.github.com/users/openml/repos","events_url":"https://api.github.com/users/openml/events{/privacy}","received_events_url":"https://api.github.com/users/openml/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/openml/openml-python","description":"Python module to interface with OpenML","fork":false,"url":"https://api.github.com/repos/openml/openml-python","forks_url":"https://api.github.com/repos/openml/openml-python/forks","keys_url":"https://api.github.com/repos/openml/openml-python/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openml/openml-python/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openml/openml-python/teams","hooks_url":"https://api.github.com/repos/openml/openml-python/hooks","issue_events_url":"https://api.github.com/repos/openml/openml-python/issues/events{/number}","events_url":"https://api.github.com/repos/openml/openml-python/events","assignees_url":"https://api.github.com/repos/openml/openml-python/assignees{/user}","branches_url":"https://api.github.com/repos/openml/openml-python/branches{/branch}","tags_url":"https://api.github.com/repos/openml/openml-python/tags","blobs_url":"https://api.github.com/repos/openml/openml-python/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openml/openml-python/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openml/openml-python/git/refs{/sha}","trees_url":"https://api.github.com/repos/openml/openml-python/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openml/openml-python/statuses/{sha}","languages_url":"https://api.github.com/repos/openml/openml-python/languages","stargazers_url":"https://api.github.com/repos/openml/openml-python/stargazers","contributors_url":"https://api.github.com/repos/openml/openml-python/contributors","subscribers_url":"https://api.github.com/repos/openml/openml-python/subscribers","subscription_url":"https://api.github.com/repos/openml/openml-python/subscription","commits_url":"https://api.github.com/repos/openml/openml-python/commits{/sha}","git_commits_url":"https://api.github.com/repos/openml/openml-python/git/commits{/sha}","comments_url":"https://api.github.com/repos/openml/openml-python/comments{/number}","issue_comment_url":"https://api.github.com/repos/openml/openml-python/issues/comments{/number}","contents_url":"https://api.github.com/repos/openml/openml-python/contents/{+path}","compare_url":"https://api.github.com/repos/openml/openml-python/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openml/openml-python/merges","archive_url":"https://api.github.com/repos/openml/openml-python/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openml/openml-python/downloads","issues_url":"https://api.github.com/repos/openml/openml-python/issues{/number}","pulls_url":"https://api.github.com/repos/openml/openml-python/pulls{/number}","milestones_url":"https://api.github.com/repos/openml/openml-python/milestones{/number}","notifications_url":"https://api.github.com/repos/openml/openml-python/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openml/openml-python/labels{/name}","releases_url":"https://api.github.com/repos/openml/openml-python/releases{/id}","deployments_url":"https://api.github.com/repos/openml/openml-python/deployments","created_at":"2014-03-20T10:46:41Z","updated_at":"2016-10-02T00:21:04Z","pushed_at":"2016-10-04T12:46:37Z","git_url":"git://github.com/openml/openml-python.git","ssh_url":"git@github.com:openml/openml-python.git","clone_url":"https://github.com/openml/openml-python.git","svn_url":"https://github.com/openml/openml-python","homepage":null,"size":2291,"stargazers_count":30,"watchers_count":30,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":11,"mirror_url":null,"open_issues_count":58,"forks":11,"open_issues":58,"watchers":30,"default_branch":"develop"}},"base":{"label":"openml:develop","ref":"develop","sha":"a407b752094a92c3f42f9938664e7b73da78f6ed","user":{"login":"openml","id":4984562,"avatar_url":"https://avatars.githubusercontent.com/u/4984562?v=3","gravatar_id":"","url":"https://api.github.com/users/openml","html_url":"https://github.com/openml","followers_url":"https://api.github.com/users/openml/followers","following_url":"https://api.github.com/users/openml/following{/other_user}","gists_url":"https://api.github.com/users/openml/gists{/gist_id}","starred_url":"https://api.github.com/users/openml/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openml/subscriptions","organizations_url":"https://api.github.com/users/openml/orgs","repos_url":"https://api.github.com/users/openml/repos","events_url":"https://api.github.com/users/openml/events{/privacy}","received_events_url":"https://api.github.com/users/openml/received_events","type":"Organization","site_admin":false},"repo":{"id":17939040,"name":"openml-python","full_name":"openml/openml-python","owner":{"login":"openml","id":4984562,"avatar_url":"https://avatars.githubusercontent.com/u/4984562?v=3","gravatar_id":"","url":"https://api.github.com/users/openml","html_url":"https://github.com/openml","followers_url":"https://api.github.com/users/openml/followers","following_url":"https://api.github.com/users/openml/following{/other_user}","gists_url":"https://api.github.com/users/openml/gists{/gist_id}","starred_url":"https://api.github.com/users/openml/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openml/subscriptions","organizations_url":"https://api.github.com/users/openml/orgs","repos_url":"https://api.github.com/users/openml/repos","events_url":"https://api.github.com/users/openml/events{/privacy}","received_events_url":"https://api.github.com/users/openml/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/openml/openml-python","description":"Python module to interface with OpenML","fork":false,"url":"https://api.github.com/repos/openml/openml-python","forks_url":"https://api.github.com/repos/openml/openml-python/forks","keys_url":"https://api.github.com/repos/openml/openml-python/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openml/openml-python/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openml/openml-python/teams","hooks_url":"https://api.github.com/repos/openml/openml-python/hooks","issue_events_url":"https://api.github.com/repos/openml/openml-python/issues/events{/number}","events_url":"https://api.github.com/repos/openml/openml-python/events","assignees_url":"https://api.github.com/repos/openml/openml-python/assignees{/user}","branches_url":"https://api.github.com/repos/openml/openml-python/branches{/branch}","tags_url":"https://api.github.com/repos/openml/openml-python/tags","blobs_url":"https://api.github.com/repos/openml/openml-python/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openml/openml-python/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openml/openml-python/git/refs{/sha}","trees_url":"https://api.github.com/repos/openml/openml-python/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openml/openml-python/statuses/{sha}","languages_url":"https://api.github.com/repos/openml/openml-python/languages","stargazers_url":"https://api.github.com/repos/openml/openml-python/stargazers","contributors_url":"https://api.github.com/repos/openml/openml-python/contributors","subscribers_url":"https://api.github.com/repos/openml/openml-python/subscribers","subscription_url":"https://api.github.com/repos/openml/openml-python/subscription","commits_url":"https://api.github.com/repos/openml/openml-python/commits{/sha}","git_commits_url":"https://api.github.com/repos/openml/openml-python/git/commits{/sha}","comments_url":"https://api.github.com/repos/openml/openml-python/comments{/number}","issue_comment_url":"https://api.github.com/repos/openml/openml-python/issues/comments{/number}","contents_url":"https://api.github.com/repos/openml/openml-python/contents/{+path}","compare_url":"https://api.github.com/repos/openml/openml-python/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openml/openml-python/merges","archive_url":"https://api.github.com/repos/openml/openml-python/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openml/openml-python/downloads","issues_url":"https://api.github.com/repos/openml/openml-python/issues{/number}","pulls_url":"https://api.github.com/repos/openml/openml-python/pulls{/number}","milestones_url":"https://api.github.com/repos/openml/openml-python/milestones{/number}","notifications_url":"https://api.github.com/repos/openml/openml-python/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openml/openml-python/labels{/name}","releases_url":"https://api.github.com/repos/openml/openml-python/releases{/id}","deployments_url":"https://api.github.com/repos/openml/openml-python/deployments","created_at":"2014-03-20T10:46:41Z","updated_at":"2016-10-02T00:21:04Z","pushed_at":"2016-10-04T12:46:37Z","git_url":"git://github.com/openml/openml-python.git","ssh_url":"git@github.com:openml/openml-python.git","clone_url":"https://github.com/openml/openml-python.git","svn_url":"https://github.com/openml/openml-python","homepage":null,"size":2291,"stargazers_count":30,"watchers_count":30,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":11,"mirror_url":null,"open_issues_count":58,"forks":11,"open_issues":58,"watchers":30,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/openml/openml-python/pulls/167"},"html":{"href":"https://github.com/openml/openml-python/pull/167"},"issue":{"href":"https://api.github.com/repos/openml/openml-python/issues/167"},"comments":{"href":"https://api.github.com/repos/openml/openml-python/issues/167/comments"},"review_comments":{"href":"https://api.github.com/repos/openml/openml-python/pulls/167/comments"},"review_comment":{"href":"https://api.github.com/repos/openml/openml-python/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/openml/openml-python/pulls/167/commits"},"statuses":{"href":"https://api.github.com/repos/openml/openml-python/statuses/f9aa442404833e6f399e7489eba929f612da71d1"}}}}
{"actor":{"display_login":"mfeurer"}}