We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Avoid extensive copying of model files & artifacts in pyfunc
### Issue
I was trying out mlflow to serve a ML model with large model files (totalling about 2 GB) on sagemaker. I used `mlflow.pyfunc` to load the model data etc. specified via the artifacts-dictionary. 

However, when I built the Docker container and ran it locally, gunicorn always timed out (due to the hard-coded 60s timeout). 
Observing the `/tmp` folder in the Docker container during the run as well as digging into the mlflow code, I noticed that temporary folders containing the **full** model artifacts (in my case amounting to about 2 GB) were copied **twice** per worker, while the model files were already all lying safe and sound in the `/opt/ml/model` directory in the Docker container.

### Reason
The reason for this behavior is that each worker calls upon initialization `pyfunc.load_pyfunc("/opt/ml/model/")` (see `wsgi.py` in `mlflow/sagemaker/container/scoring_server`).
In `pyfunc.load_pyfunc`
i) the model files are "downloaded" (copied to a temporary directory in `/tmp` via `local_model_path = _download_artifact_from_uri(artifact_uri=model_uri)`. Then, the `_load_pyfunc` in the pyfunc loader_module (`model.py`) is called. This again,
ii) copies all artifact files to a temporary directory with suffix "artifacts"

### My workaround
As a quick fix for myself, I
i) set `local_model_path = local_file_uri_to_path(model_uri)`in `mlflow/pyfunc/__init__.py`
ii) set `artifacts[saved_artifact_name] = os.path.join(model_path, saved_artifact_info[CONFIG_KEY_ARTIFACT_RELATIVE_PATH])` instead of `tmp_artifact_path` in `mlflow/pyfunc/model.py` and commented out the creation of `tmp_artifact_path` which involves `_copy_file_or_tree` to copy the artifact files.

### Request
Since I believe I'm not the only one working with larger model artifacts, I suggest improving the handling of the model artifacts. Besides, it might be reasonable to allow for a custom gunicorn timeout in case model initialization is more involved.

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

{"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,7 +4,6 @@\n \"\"\"\n \n import os\n-import tempfile\n import shutil\n import yaml\n from abc import ABCMeta, abstractmethod\n@@ -212,20 +211,11 @@\n     with open(os.path.join(model_path, python_model_subpath), \"rb\") as f:\n         python_model = cloudpickle.load(f)\n \n-    # TODO: If the longevity of the temporary directory prior becomes problematic, consider using\n-    # an alternative solution.\n-    tmp_artifacts_dir_path = tempfile.mkdtemp(suffix=\"artifacts\")\n     artifacts = {}\n     for saved_artifact_name, saved_artifact_info in\\\n             pyfunc_config.get(CONFIG_KEY_ARTIFACTS, {}).items():\n-        tmp_artifact_path = os.path.join(\n-                tmp_artifacts_dir_path,\n-                _copy_file_or_tree(\n-                    src=os.path.join(\n-                        model_path, saved_artifact_info[CONFIG_KEY_ARTIFACT_RELATIVE_PATH]),\n-                    dst=tmp_artifacts_dir_path,\n-                    dst_dir=saved_artifact_name))\n-        artifacts[saved_artifact_name] = tmp_artifact_path\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", "issue": "Avoid extensive copying of model files & artifacts in pyfunc\n### Issue\r\nI was trying out mlflow to serve a ML model with large model files (totalling about 2 GB) on sagemaker. I used `mlflow.pyfunc` to load the model data etc. specified via the artifacts-dictionary. \r\n\r\nHowever, when I built the Docker container and ran it locally, gunicorn always timed out (due to the hard-coded 60s timeout). \r\nObserving the `/tmp` folder in the Docker container during the run as well as digging into the mlflow code, I noticed that temporary folders containing the **full** model artifacts (in my case amounting to about 2 GB) were copied **twice** per worker, while the model files were already all lying safe and sound in the `/opt/ml/model` directory in the Docker container.\r\n\r\n### Reason\r\nThe reason for this behavior is that each worker calls upon initialization `pyfunc.load_pyfunc(\"/opt/ml/model/\")` (see `wsgi.py` in `mlflow/sagemaker/container/scoring_server`).\r\nIn `pyfunc.load_pyfunc`\r\ni) the model files are \"downloaded\" (copied to a temporary directory in `/tmp` via `local_model_path = _download_artifact_from_uri(artifact_uri=model_uri)`. Then, the `_load_pyfunc` in the pyfunc loader_module (`model.py`) is called. This again,\r\nii) copies all artifact files to a temporary directory with suffix \"artifacts\"\r\n\r\n### My workaround\r\nAs a quick fix for myself, I\r\ni) set `local_model_path = local_file_uri_to_path(model_uri)`in `mlflow/pyfunc/__init__.py`\r\nii) set `artifacts[saved_artifact_name] = os.path.join(model_path, saved_artifact_info[CONFIG_KEY_ARTIFACT_RELATIVE_PATH])` instead of `tmp_artifact_path` in `mlflow/pyfunc/model.py` and commented out the creation of `tmp_artifact_path` which involves `_copy_file_or_tree` to copy the artifact files.\r\n\r\n### Request\r\nSince I believe I'm not the only one working with larger model artifacts, I suggest improving the handling of the model artifacts. Besides, it might be reasonable to allow for a custom gunicorn timeout in case model initialization is more involved.\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 tempfile\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        :return: A dictionary containing ``<name, artifact_path>`` entries, where ``artifact_path``\n                 is an 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. 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`` attribute.\n                      If *None*, no artifacts are added to the model.\n    :param conda_env: Either a dictionary representation of a Conda environment or the path to a\n                      Conda environment yaml file. If provided, this decribes the environment\n                      this model should be run in. At minimum, it should specify 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    # TODO: If the longevity of the temporary directory prior becomes problematic, consider using\n    # an alternative solution.\n    tmp_artifacts_dir_path = tempfile.mkdtemp(suffix=\"artifacts\")\n    artifacts = {}\n    for saved_artifact_name, saved_artifact_info in\\\n            pyfunc_config.get(CONFIG_KEY_ARTIFACTS, {}).items():\n        tmp_artifact_path = os.path.join(\n                tmp_artifacts_dir_path,\n                _copy_file_or_tree(\n                    src=os.path.join(\n                        model_path, saved_artifact_info[CONFIG_KEY_ARTIFACT_RELATIVE_PATH]),\n                    dst=tmp_artifacts_dir_path,\n                    dst_dir=saved_artifact_name))\n        artifacts[saved_artifact_name] = tmp_artifact_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 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        :return: A dictionary containing ``<name, artifact_path>`` entries, where ``artifact_path``\n                 is an 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. 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`` attribute.\n                      If *None*, no artifacts are added to the model.\n    :param conda_env: Either a dictionary representation of a Conda environment or the path to a\n                      Conda environment yaml file. If provided, this decribes the environment\n                      this model should be run in. At minimum, it should specify 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"}]}