We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] Pyfunc artifact paths created on Windows use Windows path separators.
### Willingness to contribute
The MLflow Community encourages bug fix contributions. Would you or another member of your organization be willing to contribute a fix
for this bug to the MLflow code base?

- [ ] Yes. I can contribute a fix for this bug independently.
- [x] Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.
- [ ] No. I cannot contribute a bug fix at this time.

### System information
- **Have I written custom code (as opposed to using a stock example script provided in MLflow)**: No
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Windows 10
- **MLflow installed from (source or binary)**: Binary
- **MLflow version (run ``mlflow --version``)**: 1.8.0
- **Python version**: 3.6
- **Exact command to reproduce**:
N/A


### Describe the problem
Creating pyfunc model on windows using the `artifacts` arguments results in an MLModel file with windows separators in the paths: 

```
flavors:
  python_function:
    artifacts:
      artifact:
        path: artifacts\some_artifact
        uri: some_artifact
    cloudpickle_version: 1.3.0
    env: conda.yaml
    loader_module: mlflow.pyfunc.model
    python_model: python_model.pkl
    python_version: 3.6.10
utc_time_created: '2020-04-28 15:59:25.211458'
```

The problem with this is portability. E.g. running a docker container fails miserably. 

The fix is probably to always use forward slashes for this. 

### Code to reproduce issue
```python
import mlflow.pyfunc

class PythonModelWrapper(mlflow.pyfunc.PythonModel):
    pass

artifact_path='some_artifact'
with open(artifact_path, 'w') as f:
    f.write('example')

artifacts = {
    'artifact' : artifact_path
}

mlflow_pyfunc_model_path = "mlflow_pyfunc"
mlflow.pyfunc.save_model(
        path=mlflow_pyfunc_model_path, python_model=PythonModelWrapper(), artifacts=artifacts)
```



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mlflow/pyfunc/model.py`
Content:
```
1 """
2 The ``mlflow.pyfunc.model`` module defines logic for saving and loading custom "python_function"
3 models with a user-defined ``PythonModel`` subclass.
4 """
5 
6 import os
7 import shutil
8 import yaml
9 from abc import ABCMeta, abstractmethod
10 
11 import cloudpickle
12 
13 import mlflow.pyfunc
14 import mlflow.utils
15 from mlflow.exceptions import MlflowException
16 from mlflow.models import Model
17 from mlflow.protos.databricks_pb2 import INVALID_PARAMETER_VALUE, RESOURCE_ALREADY_EXISTS
18 from mlflow.tracking.artifact_utils import _download_artifact_from_uri
19 from mlflow.utils.environment import _mlflow_conda_env
20 from mlflow.utils.model_utils import _get_flavor_configuration
21 from mlflow.utils.file_utils import TempDir, _copy_file_or_tree
22 
23 CONFIG_KEY_ARTIFACTS = "artifacts"
24 CONFIG_KEY_ARTIFACT_RELATIVE_PATH = "path"
25 CONFIG_KEY_ARTIFACT_URI = "uri"
26 CONFIG_KEY_PYTHON_MODEL = "python_model"
27 CONFIG_KEY_CLOUDPICKLE_VERSION = "cloudpickle_version"
28 
29 
30 def get_default_conda_env():
31     """
32     :return: The default Conda environment for MLflow Models produced by calls to
33              :func:`save_model() <mlflow.pyfunc.save_model>`
34              and :func:`log_model() <mlflow.pyfunc.log_model>` when a user-defined subclass of
35              :class:`PythonModel` is provided.
36     """
37     return _mlflow_conda_env(
38         additional_conda_deps=None,
39         additional_pip_deps=[
40             "cloudpickle=={}".format(cloudpickle.__version__),
41         ],
42         additional_conda_channels=None)
43 
44 
45 class PythonModel(object):
46     """
47     Represents a generic Python model that evaluates inputs and produces API-compatible outputs.
48     By subclassing :class:`~PythonModel`, users can create customized MLflow models with the
49     "python_function" ("pyfunc") flavor, leveraging custom inference logic and artifact
50     dependencies.
51     """
52     __metaclass__ = ABCMeta
53 
54     def load_context(self, context):
55         """
56         Loads artifacts from the specified :class:`~PythonModelContext` that can be used by
57         :func:`~PythonModel.predict` when evaluating inputs. When loading an MLflow model with
58         :func:`~load_pyfunc`, this method is called as soon as the :class:`~PythonModel` is
59         constructed.
60 
61         The same :class:`~PythonModelContext` will also be available during calls to
62         :func:`~PythonModel.predict`, but it may be more efficient to override this method
63         and load artifacts from the context at model load time.
64 
65         :param context: A :class:`~PythonModelContext` instance containing artifacts that the model
66                         can use to perform inference.
67         """
68 
69     @abstractmethod
70     def predict(self, context, model_input):
71         """
72         Evaluates a pyfunc-compatible input and produces a pyfunc-compatible output.
73         For more information about the pyfunc input/output API, see the :ref:`pyfunc-inference-api`.
74 
75         :param context: A :class:`~PythonModelContext` instance containing artifacts that the model
76                         can use to perform inference.
77         :param model_input: A pyfunc-compatible input for the model to evaluate.
78         """
79 
80 
81 class PythonModelContext(object):
82     """
83     A collection of artifacts that a :class:`~PythonModel` can use when performing inference.
84     :class:`~PythonModelContext` objects are created *implicitly* by the
85     :func:`save_model() <mlflow.pyfunc.save_model>` and
86     :func:`log_model() <mlflow.pyfunc.log_model>` persistence methods, using the contents specified
87     by the ``artifacts`` parameter of these methods.
88     """
89 
90     def __init__(self, artifacts):
91         """
92         :param artifacts: A dictionary of ``<name, artifact_path>`` entries, where ``artifact_path``
93                           is an absolute filesystem path to a given artifact.
94         """
95         self._artifacts = artifacts
96 
97     @property
98     def artifacts(self):
99         """
100         A dictionary containing ``<name, artifact_path>`` entries, where ``artifact_path`` is an
101         absolute filesystem path to the artifact.
102         """
103         return self._artifacts
104 
105 
106 def _save_model_with_class_artifacts_params(path, python_model, artifacts=None, conda_env=None,
107                                             code_paths=None, mlflow_model=Model()):
108     """
109     :param path: The path to which to save the Python model.
110     :param python_model: An instance of a subclass of :class:`~PythonModel`. ``python_model``
111                         defines how the model loads artifacts and how it performs inference.
112     :param artifacts: A dictionary containing ``<name, artifact_uri>`` entries.
113                       Remote artifact URIs
114                       are resolved to absolute filesystem paths, producing a dictionary of
115                       ``<name, absolute_path>`` entries. ``python_model`` can reference these
116                       resolved entries as the ``artifacts`` property of the ``context``
117                       attribute. If ``None``, no artifacts are added to the model.
118     :param conda_env: Either a dictionary representation of a Conda environment or the
119                       path to a Conda environment yaml file. If provided, this decsribes the
120                       environment this model should be run in. At minimum, it should specify
121                       the dependencies
122                       contained in :func:`get_default_conda_env()`. If ``None``, the default
123                       :func:`get_default_conda_env()` environment is added to the model.
124     :param code_paths: A list of local filesystem paths to Python file dependencies (or directories
125                        containing file dependencies). These files are *prepended* to the system
126                        path before the model is loaded.
127     :param mlflow_model: The model configuration to which to add the ``mlflow.pyfunc`` flavor.
128     """
129     if os.path.exists(path):
130         raise MlflowException(
131                 message="Path '{}' already exists".format(path),
132                 error_code=RESOURCE_ALREADY_EXISTS)
133     os.makedirs(path)
134 
135     custom_model_config_kwargs = {
136         CONFIG_KEY_CLOUDPICKLE_VERSION: cloudpickle.__version__,
137     }
138     if isinstance(python_model, PythonModel):
139         saved_python_model_subpath = "python_model.pkl"
140         with open(os.path.join(path, saved_python_model_subpath), "wb") as out:
141             cloudpickle.dump(python_model, out)
142         custom_model_config_kwargs[CONFIG_KEY_PYTHON_MODEL] = saved_python_model_subpath
143     else:
144         raise MlflowException(
145                 message=("`python_model` must be a subclass of `PythonModel`. Instead, found an"
146                          " object of type: {python_model_type}".format(
147                              python_model_type=type(python_model))),
148                 error_code=INVALID_PARAMETER_VALUE)
149 
150     if artifacts:
151         saved_artifacts_config = {}
152         with TempDir() as tmp_artifacts_dir:
153             tmp_artifacts_config = {}
154             saved_artifacts_dir_subpath = "artifacts"
155             for artifact_name, artifact_uri in artifacts.items():
156                 tmp_artifact_path = _download_artifact_from_uri(
157                     artifact_uri=artifact_uri, output_path=tmp_artifacts_dir.path())
158                 tmp_artifacts_config[artifact_name] = tmp_artifact_path
159                 saved_artifact_subpath = os.path.join(
160                     saved_artifacts_dir_subpath,
161                     os.path.relpath(path=tmp_artifact_path, start=tmp_artifacts_dir.path()))
162                 saved_artifacts_config[artifact_name] = {
163                     CONFIG_KEY_ARTIFACT_RELATIVE_PATH: saved_artifact_subpath,
164                     CONFIG_KEY_ARTIFACT_URI: artifact_uri,
165                 }
166 
167             shutil.move(tmp_artifacts_dir.path(), os.path.join(path, saved_artifacts_dir_subpath))
168         custom_model_config_kwargs[CONFIG_KEY_ARTIFACTS] = saved_artifacts_config
169 
170     conda_env_subpath = "conda.yaml"
171     if conda_env is None:
172         conda_env = get_default_conda_env()
173     elif not isinstance(conda_env, dict):
174         with open(conda_env, "r") as f:
175             conda_env = yaml.safe_load(f)
176     with open(os.path.join(path, conda_env_subpath), "w") as f:
177         yaml.safe_dump(conda_env, stream=f, default_flow_style=False)
178 
179     saved_code_subpath = None
180     if code_paths is not None:
181         saved_code_subpath = "code"
182         for code_path in code_paths:
183             _copy_file_or_tree(src=code_path, dst=path, dst_dir=saved_code_subpath)
184 
185     mlflow.pyfunc.add_to_model(model=mlflow_model, loader_module=__name__, code=saved_code_subpath,
186                                env=conda_env_subpath, **custom_model_config_kwargs)
187     mlflow_model.save(os.path.join(path, 'MLmodel'))
188 
189 
190 def _load_pyfunc(model_path):
191     pyfunc_config = _get_flavor_configuration(
192             model_path=model_path, flavor_name=mlflow.pyfunc.FLAVOR_NAME)
193 
194     python_model_cloudpickle_version = pyfunc_config.get(CONFIG_KEY_CLOUDPICKLE_VERSION, None)
195     if python_model_cloudpickle_version is None:
196         mlflow.pyfunc._logger.warning(
197             "The version of CloudPickle used to save the model could not be found in the MLmodel"
198             " configuration")
199     elif python_model_cloudpickle_version != cloudpickle.__version__:
200         # CloudPickle does not have a well-defined cross-version compatibility policy. Micro version
201         # releases have been known to cause incompatibilities. Therefore, we match on the full
202         # library version
203         mlflow.pyfunc._logger.warning(
204             "The version of CloudPickle that was used to save the model, `CloudPickle %s`, differs"
205             " from the version of CloudPickle that is currently running, `CloudPickle %s`, and may"
206             " be incompatible",
207             python_model_cloudpickle_version, cloudpickle.__version__)
208 
209     python_model_subpath = pyfunc_config.get(CONFIG_KEY_PYTHON_MODEL, None)
210     if python_model_subpath is None:
211         raise MlflowException(
212             "Python model path was not specified in the model configuration")
213     with open(os.path.join(model_path, python_model_subpath), "rb") as f:
214         python_model = cloudpickle.load(f)
215 
216     artifacts = {}
217     for saved_artifact_name, saved_artifact_info in\
218             pyfunc_config.get(CONFIG_KEY_ARTIFACTS, {}).items():
219         artifacts[saved_artifact_name] = os.path.join(
220             model_path, saved_artifact_info[CONFIG_KEY_ARTIFACT_RELATIVE_PATH])
221 
222     context = PythonModelContext(artifacts=artifacts)
223     python_model.load_context(context=context)
224     return _PythonModelPyfuncWrapper(python_model=python_model, context=context)
225 
226 
227 class _PythonModelPyfuncWrapper(object):
228     """
229     Wrapper class that creates a predict function such that
230     predict(model_input: pd.DataFrame) -> model's output as pd.DataFrame (pandas DataFrame)
231     """
232 
233     def __init__(self, python_model, context):
234         """
235         :param python_model: An instance of a subclass of :class:`~PythonModel`.
236         :param context: A :class:`~PythonModelContext` instance containing artifacts that
237                         ``python_model`` may use when performing inference.
238         """
239         self.python_model = python_model
240         self.context = context
241 
242     def predict(self, model_input):
243         return self.python_model.predict(self.context, model_input)
244
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mlflow/pyfunc/model.py b/mlflow/pyfunc/model.py
--- a/mlflow/pyfunc/model.py
+++ b/mlflow/pyfunc/model.py
@@ -4,6 +4,7 @@
 """
 
 import os
+import posixpath
 import shutil
 import yaml
 from abc import ABCMeta, abstractmethod
@@ -156,7 +157,7 @@
                 tmp_artifact_path = _download_artifact_from_uri(
                     artifact_uri=artifact_uri, output_path=tmp_artifacts_dir.path())
                 tmp_artifacts_config[artifact_name] = tmp_artifact_path
-                saved_artifact_subpath = os.path.join(
+                saved_artifact_subpath = posixpath.join(
                     saved_artifacts_dir_subpath,
                     os.path.relpath(path=tmp_artifact_path, start=tmp_artifacts_dir.path()))
                 saved_artifacts_config[artifact_name] = {

{"golden_diff": "diff --git a/mlflow/pyfunc/model.py b/mlflow/pyfunc/model.py\n--- a/mlflow/pyfunc/model.py\n+++ b/mlflow/pyfunc/model.py\n@@ -4,6 +4,7 @@\n \"\"\"\n \n import os\n+import posixpath\n import shutil\n import yaml\n from abc import ABCMeta, abstractmethod\n@@ -156,7 +157,7 @@\n                 tmp_artifact_path = _download_artifact_from_uri(\n                     artifact_uri=artifact_uri, output_path=tmp_artifacts_dir.path())\n                 tmp_artifacts_config[artifact_name] = tmp_artifact_path\n-                saved_artifact_subpath = os.path.join(\n+                saved_artifact_subpath = posixpath.join(\n                     saved_artifacts_dir_subpath,\n                     os.path.relpath(path=tmp_artifact_path, start=tmp_artifacts_dir.path()))\n                 saved_artifacts_config[artifact_name] = {\n", "issue": "[BUG] Pyfunc artifact paths created on Windows use Windows path separators.\n### Willingness to contribute\r\nThe MLflow Community encourages bug fix contributions. Would you or another member of your organization be willing to contribute a fix\r\nfor this bug to the MLflow code base?\r\n\r\n- [ ] Yes. I can contribute a fix for this bug independently.\r\n- [x] Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.\r\n- [ ] No. I cannot contribute a bug fix at this time.\r\n\r\n### System information\r\n- **Have I written custom code (as opposed to using a stock example script provided in MLflow)**: No\r\n- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Windows 10\r\n- **MLflow installed from (source or binary)**: Binary\r\n- **MLflow version (run ``mlflow --version``)**: 1.8.0\r\n- **Python version**: 3.6\r\n- **Exact command to reproduce**:\r\nN/A\r\n\r\n\r\n### Describe the problem\r\nCreating pyfunc model on windows using the `artifacts` arguments results in an MLModel file with windows separators in the paths: \r\n\r\n```\r\nflavors:\r\n  python_function:\r\n    artifacts:\r\n      artifact:\r\n        path: artifacts\\some_artifact\r\n        uri: some_artifact\r\n    cloudpickle_version: 1.3.0\r\n    env: conda.yaml\r\n    loader_module: mlflow.pyfunc.model\r\n    python_model: python_model.pkl\r\n    python_version: 3.6.10\r\nutc_time_created: '2020-04-28 15:59:25.211458'\r\n```\r\n\r\nThe problem with this is portability. E.g. running a docker container fails miserably. \r\n\r\nThe fix is probably to always use forward slashes for this. \r\n\r\n### Code to reproduce issue\r\n```python\r\nimport mlflow.pyfunc\r\n\r\nclass PythonModelWrapper(mlflow.pyfunc.PythonModel):\r\n    pass\r\n\r\nartifact_path='some_artifact'\r\nwith open(artifact_path, 'w') as f:\r\n    f.write('example')\r\n\r\nartifacts = {\r\n    'artifact' : artifact_path\r\n}\r\n\r\nmlflow_pyfunc_model_path = \"mlflow_pyfunc\"\r\nmlflow.pyfunc.save_model(\r\n        path=mlflow_pyfunc_model_path, python_model=PythonModelWrapper(), artifacts=artifacts)\r\n```\r\n\r\n\n", "before_files": [{"content": "\"\"\"\nThe ``mlflow.pyfunc.model`` module defines logic for saving and loading custom \"python_function\"\nmodels with a user-defined ``PythonModel`` subclass.\n\"\"\"\n\nimport os\nimport shutil\nimport yaml\nfrom abc import ABCMeta, abstractmethod\n\nimport cloudpickle\n\nimport mlflow.pyfunc\nimport mlflow.utils\nfrom mlflow.exceptions import MlflowException\nfrom mlflow.models import Model\nfrom mlflow.protos.databricks_pb2 import INVALID_PARAMETER_VALUE, RESOURCE_ALREADY_EXISTS\nfrom mlflow.tracking.artifact_utils import _download_artifact_from_uri\nfrom mlflow.utils.environment import _mlflow_conda_env\nfrom mlflow.utils.model_utils import _get_flavor_configuration\nfrom mlflow.utils.file_utils import TempDir, _copy_file_or_tree\n\nCONFIG_KEY_ARTIFACTS = \"artifacts\"\nCONFIG_KEY_ARTIFACT_RELATIVE_PATH = \"path\"\nCONFIG_KEY_ARTIFACT_URI = \"uri\"\nCONFIG_KEY_PYTHON_MODEL = \"python_model\"\nCONFIG_KEY_CLOUDPICKLE_VERSION = \"cloudpickle_version\"\n\n\ndef get_default_conda_env():\n    \"\"\"\n    :return: The default Conda environment for MLflow Models produced by calls to\n             :func:`save_model() <mlflow.pyfunc.save_model>`\n             and :func:`log_model() <mlflow.pyfunc.log_model>` when a user-defined subclass of\n             :class:`PythonModel` is provided.\n    \"\"\"\n    return _mlflow_conda_env(\n        additional_conda_deps=None,\n        additional_pip_deps=[\n            \"cloudpickle=={}\".format(cloudpickle.__version__),\n        ],\n        additional_conda_channels=None)\n\n\nclass PythonModel(object):\n    \"\"\"\n    Represents a generic Python model that evaluates inputs and produces API-compatible outputs.\n    By subclassing :class:`~PythonModel`, users can create customized MLflow models with the\n    \"python_function\" (\"pyfunc\") flavor, leveraging custom inference logic and artifact\n    dependencies.\n    \"\"\"\n    __metaclass__ = ABCMeta\n\n    def load_context(self, context):\n        \"\"\"\n        Loads artifacts from the specified :class:`~PythonModelContext` that can be used by\n        :func:`~PythonModel.predict` when evaluating inputs. When loading an MLflow model with\n        :func:`~load_pyfunc`, this method is called as soon as the :class:`~PythonModel` is\n        constructed.\n\n        The same :class:`~PythonModelContext` will also be available during calls to\n        :func:`~PythonModel.predict`, but it may be more efficient to override this method\n        and load artifacts from the context at model load time.\n\n        :param context: A :class:`~PythonModelContext` instance containing artifacts that the model\n                        can use to perform inference.\n        \"\"\"\n\n    @abstractmethod\n    def predict(self, context, model_input):\n        \"\"\"\n        Evaluates a pyfunc-compatible input and produces a pyfunc-compatible output.\n        For more information about the pyfunc input/output API, see the :ref:`pyfunc-inference-api`.\n\n        :param context: A :class:`~PythonModelContext` instance containing artifacts that the model\n                        can use to perform inference.\n        :param model_input: A pyfunc-compatible input for the model to evaluate.\n        \"\"\"\n\n\nclass PythonModelContext(object):\n    \"\"\"\n    A collection of artifacts that a :class:`~PythonModel` can use when performing inference.\n    :class:`~PythonModelContext` objects are created *implicitly* by the\n    :func:`save_model() <mlflow.pyfunc.save_model>` and\n    :func:`log_model() <mlflow.pyfunc.log_model>` persistence methods, using the contents specified\n    by the ``artifacts`` parameter of these methods.\n    \"\"\"\n\n    def __init__(self, artifacts):\n        \"\"\"\n        :param artifacts: A dictionary of ``<name, artifact_path>`` entries, where ``artifact_path``\n                          is an absolute filesystem path to a given artifact.\n        \"\"\"\n        self._artifacts = artifacts\n\n    @property\n    def artifacts(self):\n        \"\"\"\n        A dictionary containing ``<name, artifact_path>`` entries, where ``artifact_path`` is an\n        absolute filesystem path to the artifact.\n        \"\"\"\n        return self._artifacts\n\n\ndef _save_model_with_class_artifacts_params(path, python_model, artifacts=None, conda_env=None,\n                                            code_paths=None, mlflow_model=Model()):\n    \"\"\"\n    :param path: The path to which to save the Python model.\n    :param python_model: An instance of a subclass of :class:`~PythonModel`. ``python_model``\n                        defines how the model loads artifacts and how it performs inference.\n    :param artifacts: A dictionary containing ``<name, artifact_uri>`` entries.\n                      Remote artifact URIs\n                      are resolved to absolute filesystem paths, producing a dictionary of\n                      ``<name, absolute_path>`` entries. ``python_model`` can reference these\n                      resolved entries as the ``artifacts`` property of the ``context``\n                      attribute. If ``None``, no artifacts are added to the model.\n    :param conda_env: Either a dictionary representation of a Conda environment or the\n                      path to a Conda environment yaml file. If provided, this decsribes the\n                      environment this model should be run in. At minimum, it should specify\n                      the dependencies\n                      contained in :func:`get_default_conda_env()`. If ``None``, the default\n                      :func:`get_default_conda_env()` environment is added to the model.\n    :param code_paths: A list of local filesystem paths to Python file dependencies (or directories\n                       containing file dependencies). These files are *prepended* to the system\n                       path before the model is loaded.\n    :param mlflow_model: The model configuration to which to add the ``mlflow.pyfunc`` flavor.\n    \"\"\"\n    if os.path.exists(path):\n        raise MlflowException(\n                message=\"Path '{}' already exists\".format(path),\n                error_code=RESOURCE_ALREADY_EXISTS)\n    os.makedirs(path)\n\n    custom_model_config_kwargs = {\n        CONFIG_KEY_CLOUDPICKLE_VERSION: cloudpickle.__version__,\n    }\n    if isinstance(python_model, PythonModel):\n        saved_python_model_subpath = \"python_model.pkl\"\n        with open(os.path.join(path, saved_python_model_subpath), \"wb\") as out:\n            cloudpickle.dump(python_model, out)\n        custom_model_config_kwargs[CONFIG_KEY_PYTHON_MODEL] = saved_python_model_subpath\n    else:\n        raise MlflowException(\n                message=(\"`python_model` must be a subclass of `PythonModel`. Instead, found an\"\n                         \" object of type: {python_model_type}\".format(\n                             python_model_type=type(python_model))),\n                error_code=INVALID_PARAMETER_VALUE)\n\n    if artifacts:\n        saved_artifacts_config = {}\n        with TempDir() as tmp_artifacts_dir:\n            tmp_artifacts_config = {}\n            saved_artifacts_dir_subpath = \"artifacts\"\n            for artifact_name, artifact_uri in artifacts.items():\n                tmp_artifact_path = _download_artifact_from_uri(\n                    artifact_uri=artifact_uri, output_path=tmp_artifacts_dir.path())\n                tmp_artifacts_config[artifact_name] = tmp_artifact_path\n                saved_artifact_subpath = os.path.join(\n                    saved_artifacts_dir_subpath,\n                    os.path.relpath(path=tmp_artifact_path, start=tmp_artifacts_dir.path()))\n                saved_artifacts_config[artifact_name] = {\n                    CONFIG_KEY_ARTIFACT_RELATIVE_PATH: saved_artifact_subpath,\n                    CONFIG_KEY_ARTIFACT_URI: artifact_uri,\n                }\n\n            shutil.move(tmp_artifacts_dir.path(), os.path.join(path, saved_artifacts_dir_subpath))\n        custom_model_config_kwargs[CONFIG_KEY_ARTIFACTS] = saved_artifacts_config\n\n    conda_env_subpath = \"conda.yaml\"\n    if conda_env is None:\n        conda_env = get_default_conda_env()\n    elif not isinstance(conda_env, dict):\n        with open(conda_env, \"r\") as f:\n            conda_env = yaml.safe_load(f)\n    with open(os.path.join(path, conda_env_subpath), \"w\") as f:\n        yaml.safe_dump(conda_env, stream=f, default_flow_style=False)\n\n    saved_code_subpath = None\n    if code_paths is not None:\n        saved_code_subpath = \"code\"\n        for code_path in code_paths:\n            _copy_file_or_tree(src=code_path, dst=path, dst_dir=saved_code_subpath)\n\n    mlflow.pyfunc.add_to_model(model=mlflow_model, loader_module=__name__, code=saved_code_subpath,\n                               env=conda_env_subpath, **custom_model_config_kwargs)\n    mlflow_model.save(os.path.join(path, 'MLmodel'))\n\n\ndef _load_pyfunc(model_path):\n    pyfunc_config = _get_flavor_configuration(\n            model_path=model_path, flavor_name=mlflow.pyfunc.FLAVOR_NAME)\n\n    python_model_cloudpickle_version = pyfunc_config.get(CONFIG_KEY_CLOUDPICKLE_VERSION, None)\n    if python_model_cloudpickle_version is None:\n        mlflow.pyfunc._logger.warning(\n            \"The version of CloudPickle used to save the model could not be found in the MLmodel\"\n            \" configuration\")\n    elif python_model_cloudpickle_version != cloudpickle.__version__:\n        # CloudPickle does not have a well-defined cross-version compatibility policy. Micro version\n        # releases have been known to cause incompatibilities. Therefore, we match on the full\n        # library version\n        mlflow.pyfunc._logger.warning(\n            \"The version of CloudPickle that was used to save the model, `CloudPickle %s`, differs\"\n            \" from the version of CloudPickle that is currently running, `CloudPickle %s`, and may\"\n            \" be incompatible\",\n            python_model_cloudpickle_version, cloudpickle.__version__)\n\n    python_model_subpath = pyfunc_config.get(CONFIG_KEY_PYTHON_MODEL, None)\n    if python_model_subpath is None:\n        raise MlflowException(\n            \"Python model path was not specified in the model configuration\")\n    with open(os.path.join(model_path, python_model_subpath), \"rb\") as f:\n        python_model = cloudpickle.load(f)\n\n    artifacts = {}\n    for saved_artifact_name, saved_artifact_info in\\\n            pyfunc_config.get(CONFIG_KEY_ARTIFACTS, {}).items():\n        artifacts[saved_artifact_name] = os.path.join(\n            model_path, saved_artifact_info[CONFIG_KEY_ARTIFACT_RELATIVE_PATH])\n\n    context = PythonModelContext(artifacts=artifacts)\n    python_model.load_context(context=context)\n    return _PythonModelPyfuncWrapper(python_model=python_model, context=context)\n\n\nclass _PythonModelPyfuncWrapper(object):\n    \"\"\"\n    Wrapper class that creates a predict function such that\n    predict(model_input: pd.DataFrame) -> model's output as pd.DataFrame (pandas DataFrame)\n    \"\"\"\n\n    def __init__(self, python_model, context):\n        \"\"\"\n        :param python_model: An instance of a subclass of :class:`~PythonModel`.\n        :param context: A :class:`~PythonModelContext` instance containing artifacts that\n                        ``python_model`` may use when performing inference.\n        \"\"\"\n        self.python_model = python_model\n        self.context = context\n\n    def predict(self, model_input):\n        return self.python_model.predict(self.context, model_input)\n", "path": "mlflow/pyfunc/model.py"}], "after_files": [{"content": "\"\"\"\nThe ``mlflow.pyfunc.model`` module defines logic for saving and loading custom \"python_function\"\nmodels with a user-defined ``PythonModel`` subclass.\n\"\"\"\n\nimport os\nimport posixpath\nimport shutil\nimport yaml\nfrom abc import ABCMeta, abstractmethod\n\nimport cloudpickle\n\nimport mlflow.pyfunc\nimport mlflow.utils\nfrom mlflow.exceptions import MlflowException\nfrom mlflow.models import Model\nfrom mlflow.protos.databricks_pb2 import INVALID_PARAMETER_VALUE, RESOURCE_ALREADY_EXISTS\nfrom mlflow.tracking.artifact_utils import _download_artifact_from_uri\nfrom mlflow.utils.environment import _mlflow_conda_env\nfrom mlflow.utils.model_utils import _get_flavor_configuration\nfrom mlflow.utils.file_utils import TempDir, _copy_file_or_tree\n\nCONFIG_KEY_ARTIFACTS = \"artifacts\"\nCONFIG_KEY_ARTIFACT_RELATIVE_PATH = \"path\"\nCONFIG_KEY_ARTIFACT_URI = \"uri\"\nCONFIG_KEY_PYTHON_MODEL = \"python_model\"\nCONFIG_KEY_CLOUDPICKLE_VERSION = \"cloudpickle_version\"\n\n\ndef get_default_conda_env():\n    \"\"\"\n    :return: The default Conda environment for MLflow Models produced by calls to\n             :func:`save_model() <mlflow.pyfunc.save_model>`\n             and :func:`log_model() <mlflow.pyfunc.log_model>` when a user-defined subclass of\n             :class:`PythonModel` is provided.\n    \"\"\"\n    return _mlflow_conda_env(\n        additional_conda_deps=None,\n        additional_pip_deps=[\n            \"cloudpickle=={}\".format(cloudpickle.__version__),\n        ],\n        additional_conda_channels=None)\n\n\nclass PythonModel(object):\n    \"\"\"\n    Represents a generic Python model that evaluates inputs and produces API-compatible outputs.\n    By subclassing :class:`~PythonModel`, users can create customized MLflow models with the\n    \"python_function\" (\"pyfunc\") flavor, leveraging custom inference logic and artifact\n    dependencies.\n    \"\"\"\n    __metaclass__ = ABCMeta\n\n    def load_context(self, context):\n        \"\"\"\n        Loads artifacts from the specified :class:`~PythonModelContext` that can be used by\n        :func:`~PythonModel.predict` when evaluating inputs. When loading an MLflow model with\n        :func:`~load_pyfunc`, this method is called as soon as the :class:`~PythonModel` is\n        constructed.\n\n        The same :class:`~PythonModelContext` will also be available during calls to\n        :func:`~PythonModel.predict`, but it may be more efficient to override this method\n        and load artifacts from the context at model load time.\n\n        :param context: A :class:`~PythonModelContext` instance containing artifacts that the model\n                        can use to perform inference.\n        \"\"\"\n\n    @abstractmethod\n    def predict(self, context, model_input):\n        \"\"\"\n        Evaluates a pyfunc-compatible input and produces a pyfunc-compatible output.\n        For more information about the pyfunc input/output API, see the :ref:`pyfunc-inference-api`.\n\n        :param context: A :class:`~PythonModelContext` instance containing artifacts that the model\n                        can use to perform inference.\n        :param model_input: A pyfunc-compatible input for the model to evaluate.\n        \"\"\"\n\n\nclass PythonModelContext(object):\n    \"\"\"\n    A collection of artifacts that a :class:`~PythonModel` can use when performing inference.\n    :class:`~PythonModelContext` objects are created *implicitly* by the\n    :func:`save_model() <mlflow.pyfunc.save_model>` and\n    :func:`log_model() <mlflow.pyfunc.log_model>` persistence methods, using the contents specified\n    by the ``artifacts`` parameter of these methods.\n    \"\"\"\n\n    def __init__(self, artifacts):\n        \"\"\"\n        :param artifacts: A dictionary of ``<name, artifact_path>`` entries, where ``artifact_path``\n                          is an absolute filesystem path to a given artifact.\n        \"\"\"\n        self._artifacts = artifacts\n\n    @property\n    def artifacts(self):\n        \"\"\"\n        A dictionary containing ``<name, artifact_path>`` entries, where ``artifact_path`` is an\n        absolute filesystem path to the artifact.\n        \"\"\"\n        return self._artifacts\n\n\ndef _save_model_with_class_artifacts_params(path, python_model, artifacts=None, conda_env=None,\n                                            code_paths=None, mlflow_model=Model()):\n    \"\"\"\n    :param path: The path to which to save the Python model.\n    :param python_model: An instance of a subclass of :class:`~PythonModel`. ``python_model``\n                        defines how the model loads artifacts and how it performs inference.\n    :param artifacts: A dictionary containing ``<name, artifact_uri>`` entries.\n                      Remote artifact URIs\n                      are resolved to absolute filesystem paths, producing a dictionary of\n                      ``<name, absolute_path>`` entries. ``python_model`` can reference these\n                      resolved entries as the ``artifacts`` property of the ``context``\n                      attribute. If ``None``, no artifacts are added to the model.\n    :param conda_env: Either a dictionary representation of a Conda environment or the\n                      path to a Conda environment yaml file. If provided, this decsribes the\n                      environment this model should be run in. At minimum, it should specify\n                      the dependencies\n                      contained in :func:`get_default_conda_env()`. If ``None``, the default\n                      :func:`get_default_conda_env()` environment is added to the model.\n    :param code_paths: A list of local filesystem paths to Python file dependencies (or directories\n                       containing file dependencies). These files are *prepended* to the system\n                       path before the model is loaded.\n    :param mlflow_model: The model configuration to which to add the ``mlflow.pyfunc`` flavor.\n    \"\"\"\n    if os.path.exists(path):\n        raise MlflowException(\n                message=\"Path '{}' already exists\".format(path),\n                error_code=RESOURCE_ALREADY_EXISTS)\n    os.makedirs(path)\n\n    custom_model_config_kwargs = {\n        CONFIG_KEY_CLOUDPICKLE_VERSION: cloudpickle.__version__,\n    }\n    if isinstance(python_model, PythonModel):\n        saved_python_model_subpath = \"python_model.pkl\"\n        with open(os.path.join(path, saved_python_model_subpath), \"wb\") as out:\n            cloudpickle.dump(python_model, out)\n        custom_model_config_kwargs[CONFIG_KEY_PYTHON_MODEL] = saved_python_model_subpath\n    else:\n        raise MlflowException(\n                message=(\"`python_model` must be a subclass of `PythonModel`. Instead, found an\"\n                         \" object of type: {python_model_type}\".format(\n                             python_model_type=type(python_model))),\n                error_code=INVALID_PARAMETER_VALUE)\n\n    if artifacts:\n        saved_artifacts_config = {}\n        with TempDir() as tmp_artifacts_dir:\n            tmp_artifacts_config = {}\n            saved_artifacts_dir_subpath = \"artifacts\"\n            for artifact_name, artifact_uri in artifacts.items():\n                tmp_artifact_path = _download_artifact_from_uri(\n                    artifact_uri=artifact_uri, output_path=tmp_artifacts_dir.path())\n                tmp_artifacts_config[artifact_name] = tmp_artifact_path\n                saved_artifact_subpath = posixpath.join(\n                    saved_artifacts_dir_subpath,\n                    os.path.relpath(path=tmp_artifact_path, start=tmp_artifacts_dir.path()))\n                saved_artifacts_config[artifact_name] = {\n                    CONFIG_KEY_ARTIFACT_RELATIVE_PATH: saved_artifact_subpath,\n                    CONFIG_KEY_ARTIFACT_URI: artifact_uri,\n                }\n\n            shutil.move(tmp_artifacts_dir.path(), os.path.join(path, saved_artifacts_dir_subpath))\n        custom_model_config_kwargs[CONFIG_KEY_ARTIFACTS] = saved_artifacts_config\n\n    conda_env_subpath = \"conda.yaml\"\n    if conda_env is None:\n        conda_env = get_default_conda_env()\n    elif not isinstance(conda_env, dict):\n        with open(conda_env, \"r\") as f:\n            conda_env = yaml.safe_load(f)\n    with open(os.path.join(path, conda_env_subpath), \"w\") as f:\n        yaml.safe_dump(conda_env, stream=f, default_flow_style=False)\n\n    saved_code_subpath = None\n    if code_paths is not None:\n        saved_code_subpath = \"code\"\n        for code_path in code_paths:\n            _copy_file_or_tree(src=code_path, dst=path, dst_dir=saved_code_subpath)\n\n    mlflow.pyfunc.add_to_model(model=mlflow_model, loader_module=__name__, code=saved_code_subpath,\n                               env=conda_env_subpath, **custom_model_config_kwargs)\n    mlflow_model.save(os.path.join(path, 'MLmodel'))\n\n\ndef _load_pyfunc(model_path):\n    pyfunc_config = _get_flavor_configuration(\n            model_path=model_path, flavor_name=mlflow.pyfunc.FLAVOR_NAME)\n\n    python_model_cloudpickle_version = pyfunc_config.get(CONFIG_KEY_CLOUDPICKLE_VERSION, None)\n    if python_model_cloudpickle_version is None:\n        mlflow.pyfunc._logger.warning(\n            \"The version of CloudPickle used to save the model could not be found in the MLmodel\"\n            \" configuration\")\n    elif python_model_cloudpickle_version != cloudpickle.__version__:\n        # CloudPickle does not have a well-defined cross-version compatibility policy. Micro version\n        # releases have been known to cause incompatibilities. Therefore, we match on the full\n        # library version\n        mlflow.pyfunc._logger.warning(\n            \"The version of CloudPickle that was used to save the model, `CloudPickle %s`, differs\"\n            \" from the version of CloudPickle that is currently running, `CloudPickle %s`, and may\"\n            \" be incompatible\",\n            python_model_cloudpickle_version, cloudpickle.__version__)\n\n    python_model_subpath = pyfunc_config.get(CONFIG_KEY_PYTHON_MODEL, None)\n    if python_model_subpath is None:\n        raise MlflowException(\n            \"Python model path was not specified in the model configuration\")\n    with open(os.path.join(model_path, python_model_subpath), \"rb\") as f:\n        python_model = cloudpickle.load(f)\n\n    artifacts = {}\n    for saved_artifact_name, saved_artifact_info in\\\n            pyfunc_config.get(CONFIG_KEY_ARTIFACTS, {}).items():\n        artifacts[saved_artifact_name] = os.path.join(\n            model_path, saved_artifact_info[CONFIG_KEY_ARTIFACT_RELATIVE_PATH])\n\n    context = PythonModelContext(artifacts=artifacts)\n    python_model.load_context(context=context)\n    return _PythonModelPyfuncWrapper(python_model=python_model, context=context)\n\n\nclass _PythonModelPyfuncWrapper(object):\n    \"\"\"\n    Wrapper class that creates a predict function such that\n    predict(model_input: pd.DataFrame) -> model's output as pd.DataFrame (pandas DataFrame)\n    \"\"\"\n\n    def __init__(self, python_model, context):\n        \"\"\"\n        :param python_model: An instance of a subclass of :class:`~PythonModel`.\n        :param context: A :class:`~PythonModelContext` instance containing artifacts that\n                        ``python_model`` may use when performing inference.\n        \"\"\"\n        self.python_model = python_model\n        self.context = context\n\n    def predict(self, model_input):\n        return self.python_model.predict(self.context, model_input)\n", "path": "mlflow/pyfunc/model.py"}]}