We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] change in 1.12.0 causes S3 SSLError
Thank you for submitting an issue. Please refer to our [issue policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md) for additional information about bug reports. For help with debugging your code, please refer to [Stack Overflow](https://stackoverflow.com/questions/tagged/mlflow).

**Please fill in this bug report template to ensure a timely and thorough response.**

### 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.
- [ ] Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.
- [ x] 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)**:
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**:  macOS 10.15.6
- **MLflow installed from (source or binary)**: binary
- **MLflow version (run ``mlflow --version``)**: 1.12.0
- **Python version**: 3.8.5
- **npm version, if running the dev UI**:
- **Exact command to reproduce**:
S3ArtifactRepository(my_uri).list_artifacts(my_path)

### Describe the problem
Describe the problem clearly here. Include descriptions of the expected behavior and the actual behavior.

As of this version I'm getting an error:
"certificate verify failed: self signed certificate in certificate chain." 

I'm working with private corporate endpoints. This worked fine before 1.12.0. I've isolated it to changes in mlflow.store.artifact.s3_artifact_repo, line 54. Simply adding verify=True explicitly seems to have caused this, even though this should be the default in boto3. 

### Code to reproduce issue
Provide a reproducible test case that is the bare minimum necessary to generate the problem.

Tricky 

### Other info / logs
Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached.


### What component(s), interfaces, languages, and integrations does this bug affect?
Components 
- [x ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: Local serving, model deployment tools, spark UDFs
- [ ] `area/server-infra`: MLflow server, JavaScript dev server
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

Interface 
- [ ] `area/uiux`: Front-end, user experience, JavaScript, plotting
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

Language 
- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

Integrations
- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations


--- 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/store/artifact/s3_artifact_repo.py`
Content:
```
1 import os
2 from mimetypes import guess_type
3 
4 import posixpath
5 import urllib.parse
6 
7 from mlflow import data
8 from mlflow.entities import FileInfo
9 from mlflow.exceptions import MlflowException
10 from mlflow.store.artifact.artifact_repo import ArtifactRepository
11 from mlflow.utils.file_utils import relative_path_to_artifact_path
12 
13 
14 class S3ArtifactRepository(ArtifactRepository):
15     """Stores artifacts on Amazon S3."""
16 
17     @staticmethod
18     def parse_s3_uri(uri):
19         """Parse an S3 URI, returning (bucket, path)"""
20         parsed = urllib.parse.urlparse(uri)
21         if parsed.scheme != "s3":
22             raise Exception("Not an S3 URI: %s" % uri)
23         path = parsed.path
24         if path.startswith("/"):
25             path = path[1:]
26         return parsed.netloc, path
27 
28     @staticmethod
29     def get_s3_file_upload_extra_args():
30         import json
31 
32         s3_file_upload_extra_args = os.environ.get("MLFLOW_S3_UPLOAD_EXTRA_ARGS")
33         if s3_file_upload_extra_args:
34             return json.loads(s3_file_upload_extra_args)
35         else:
36             return None
37 
38     def _get_s3_client(self):
39         import boto3
40         from botocore.client import Config
41 
42         s3_endpoint_url = os.environ.get("MLFLOW_S3_ENDPOINT_URL")
43         ignore_tls = os.environ.get("MLFLOW_S3_IGNORE_TLS")
44 
45         verify = True
46         if ignore_tls:
47             verify = ignore_tls.lower() not in ["true", "yes", "1"]
48 
49         # NOTE: If you need to specify this env variable, please file an issue at
50         # https://github.com/mlflow/mlflow/issues so we know your use-case!
51         signature_version = os.environ.get("MLFLOW_EXPERIMENTAL_S3_SIGNATURE_VERSION", "s3v4")
52         return boto3.client(
53             "s3",
54             config=Config(signature_version=signature_version),
55             endpoint_url=s3_endpoint_url,
56             verify=verify,
57         )
58 
59     def _upload_file(self, s3_client, local_file, bucket, key):
60         extra_args = dict()
61         guessed_type, guessed_encoding = guess_type(local_file)
62         if guessed_type is not None:
63             extra_args["ContentType"] = guessed_type
64         if guessed_encoding is not None:
65             extra_args["ContentEncoding"] = guessed_encoding
66         environ_extra_args = self.get_s3_file_upload_extra_args()
67         if environ_extra_args is not None:
68             extra_args.update(environ_extra_args)
69         s3_client.upload_file(Filename=local_file, Bucket=bucket, Key=key, ExtraArgs=extra_args)
70 
71     def log_artifact(self, local_file, artifact_path=None):
72         (bucket, dest_path) = data.parse_s3_uri(self.artifact_uri)
73         if artifact_path:
74             dest_path = posixpath.join(dest_path, artifact_path)
75         dest_path = posixpath.join(dest_path, os.path.basename(local_file))
76         self._upload_file(
77             s3_client=self._get_s3_client(), local_file=local_file, bucket=bucket, key=dest_path
78         )
79 
80     def log_artifacts(self, local_dir, artifact_path=None):
81         (bucket, dest_path) = data.parse_s3_uri(self.artifact_uri)
82         if artifact_path:
83             dest_path = posixpath.join(dest_path, artifact_path)
84         s3_client = self._get_s3_client()
85         local_dir = os.path.abspath(local_dir)
86         for (root, _, filenames) in os.walk(local_dir):
87             upload_path = dest_path
88             if root != local_dir:
89                 rel_path = os.path.relpath(root, local_dir)
90                 rel_path = relative_path_to_artifact_path(rel_path)
91                 upload_path = posixpath.join(dest_path, rel_path)
92             for f in filenames:
93                 self._upload_file(
94                     s3_client=s3_client,
95                     local_file=os.path.join(root, f),
96                     bucket=bucket,
97                     key=posixpath.join(upload_path, f),
98                 )
99 
100     def list_artifacts(self, path=None):
101         (bucket, artifact_path) = data.parse_s3_uri(self.artifact_uri)
102         dest_path = artifact_path
103         if path:
104             dest_path = posixpath.join(dest_path, path)
105         infos = []
106         prefix = dest_path + "/" if dest_path else ""
107         s3_client = self._get_s3_client()
108         paginator = s3_client.get_paginator("list_objects_v2")
109         results = paginator.paginate(Bucket=bucket, Prefix=prefix, Delimiter="/")
110         for result in results:
111             # Subdirectories will be listed as "common prefixes" due to the way we made the request
112             for obj in result.get("CommonPrefixes", []):
113                 subdir_path = obj.get("Prefix")
114                 self._verify_listed_object_contains_artifact_path_prefix(
115                     listed_object_path=subdir_path, artifact_path=artifact_path
116                 )
117                 subdir_rel_path = posixpath.relpath(path=subdir_path, start=artifact_path)
118                 if subdir_rel_path.endswith("/"):
119                     subdir_rel_path = subdir_rel_path[:-1]
120                 infos.append(FileInfo(subdir_rel_path, True, None))
121             # Objects listed directly will be files
122             for obj in result.get("Contents", []):
123                 file_path = obj.get("Key")
124                 self._verify_listed_object_contains_artifact_path_prefix(
125                     listed_object_path=file_path, artifact_path=artifact_path
126                 )
127                 file_rel_path = posixpath.relpath(path=file_path, start=artifact_path)
128                 file_size = int(obj.get("Size"))
129                 infos.append(FileInfo(file_rel_path, False, file_size))
130         return sorted(infos, key=lambda f: f.path)
131 
132     @staticmethod
133     def _verify_listed_object_contains_artifact_path_prefix(listed_object_path, artifact_path):
134         if not listed_object_path.startswith(artifact_path):
135             raise MlflowException(
136                 "The path of the listed S3 object does not begin with the specified"
137                 " artifact path. Artifact path: {artifact_path}. Object path:"
138                 " {object_path}.".format(
139                     artifact_path=artifact_path, object_path=listed_object_path
140                 )
141             )
142 
143     def _download_file(self, remote_file_path, local_path):
144         (bucket, s3_root_path) = data.parse_s3_uri(self.artifact_uri)
145         s3_full_path = posixpath.join(s3_root_path, remote_file_path)
146         s3_client = self._get_s3_client()
147         s3_client.download_file(bucket, s3_full_path, local_path)
148 
149     def delete_artifacts(self, artifact_path=None):
150         (bucket, dest_path) = data.parse_s3_uri(self.artifact_uri)
151         if artifact_path:
152             dest_path = posixpath.join(dest_path, artifact_path)
153 
154         s3_client = self._get_s3_client()
155         list_objects = s3_client.list_objects(Bucket=bucket, Prefix=dest_path).get("Contents", [])
156         for to_delete_obj in list_objects:
157             file_path = to_delete_obj.get("Key")
158             self._verify_listed_object_contains_artifact_path_prefix(
159                 listed_object_path=file_path, artifact_path=dest_path
160             )
161             s3_client.delete_object(Bucket=bucket, Key=file_path)
162
```
--- 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/store/artifact/s3_artifact_repo.py b/mlflow/store/artifact/s3_artifact_repo.py
--- a/mlflow/store/artifact/s3_artifact_repo.py
+++ b/mlflow/store/artifact/s3_artifact_repo.py
@@ -42,9 +42,14 @@
         s3_endpoint_url = os.environ.get("MLFLOW_S3_ENDPOINT_URL")
         ignore_tls = os.environ.get("MLFLOW_S3_IGNORE_TLS")
 
-        verify = True
+        do_verify = True
         if ignore_tls:
-            verify = ignore_tls.lower() not in ["true", "yes", "1"]
+            do_verify = ignore_tls.lower() not in ["true", "yes", "1"]
+
+        # The valid verify argument value is None/False/path to cert bundle file, See
+        # https://github.com/boto/boto3/blob/73865126cad3938ca80a2f567a1c79cb248169a7/
+        # boto3/session.py#L212
+        verify = None if do_verify else False
 
         # NOTE: If you need to specify this env variable, please file an issue at
         # https://github.com/mlflow/mlflow/issues so we know your use-case!

{"golden_diff": "diff --git a/mlflow/store/artifact/s3_artifact_repo.py b/mlflow/store/artifact/s3_artifact_repo.py\n--- a/mlflow/store/artifact/s3_artifact_repo.py\n+++ b/mlflow/store/artifact/s3_artifact_repo.py\n@@ -42,9 +42,14 @@\n         s3_endpoint_url = os.environ.get(\"MLFLOW_S3_ENDPOINT_URL\")\n         ignore_tls = os.environ.get(\"MLFLOW_S3_IGNORE_TLS\")\n \n-        verify = True\n+        do_verify = True\n         if ignore_tls:\n-            verify = ignore_tls.lower() not in [\"true\", \"yes\", \"1\"]\n+            do_verify = ignore_tls.lower() not in [\"true\", \"yes\", \"1\"]\n+\n+        # The valid verify argument value is None/False/path to cert bundle file, See\n+        # https://github.com/boto/boto3/blob/73865126cad3938ca80a2f567a1c79cb248169a7/\n+        # boto3/session.py#L212\n+        verify = None if do_verify else False\n \n         # NOTE: If you need to specify this env variable, please file an issue at\n         # https://github.com/mlflow/mlflow/issues so we know your use-case!\n", "issue": "[BUG] change in 1.12.0 causes S3 SSLError\nThank you for submitting an issue. Please refer to our [issue policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md) for additional information about bug reports. For help with debugging your code, please refer to [Stack Overflow](https://stackoverflow.com/questions/tagged/mlflow).\r\n\r\n**Please fill in this bug report template to ensure a timely and thorough response.**\r\n\r\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 for this bug to the MLflow code base?\r\n\r\n- [ ] Yes. I can contribute a fix for this bug independently.\r\n- [ ] Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.\r\n- [ x] 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)**:\r\n- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**:  macOS 10.15.6\r\n- **MLflow installed from (source or binary)**: binary\r\n- **MLflow version (run ``mlflow --version``)**: 1.12.0\r\n- **Python version**: 3.8.5\r\n- **npm version, if running the dev UI**:\r\n- **Exact command to reproduce**:\r\nS3ArtifactRepository(my_uri).list_artifacts(my_path)\r\n\r\n### Describe the problem\r\nDescribe the problem clearly here. Include descriptions of the expected behavior and the actual behavior.\r\n\r\nAs of this version I'm getting an error:\r\n\"certificate verify failed: self signed certificate in certificate chain.\" \r\n\r\nI'm working with private corporate endpoints. This worked fine before 1.12.0. I've isolated it to changes in mlflow.store.artifact.s3_artifact_repo, line 54. Simply adding verify=True explicitly seems to have caused this, even though this should be the default in boto3. \r\n\r\n### Code to reproduce issue\r\nProvide a reproducible test case that is the bare minimum necessary to generate the problem.\r\n\r\nTricky \r\n\r\n### Other info / logs\r\nInclude any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached.\r\n\r\n\r\n### What component(s), interfaces, languages, and integrations does this bug affect?\r\nComponents \r\n- [x ] `area/artifacts`: Artifact stores and artifact logging\r\n- [ ] `area/build`: Build and test infrastructure for MLflow\r\n- [ ] `area/docs`: MLflow documentation pages\r\n- [ ] `area/examples`: Example code\r\n- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry\r\n- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors\r\n- [ ] `area/projects`: MLproject format, project running backends\r\n- [ ] `area/scoring`: Local serving, model deployment tools, spark UDFs\r\n- [ ] `area/server-infra`: MLflow server, JavaScript dev server\r\n- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging\r\n\r\nInterface \r\n- [ ] `area/uiux`: Front-end, user experience, JavaScript, plotting\r\n- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models\r\n- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry\r\n- [ ] `area/windows`: Windows support\r\n\r\nLanguage \r\n- [ ] `language/r`: R APIs and clients\r\n- [ ] `language/java`: Java APIs and clients\r\n- [ ] `language/new`: Proposals for new client languages\r\n\r\nIntegrations\r\n- [ ] `integrations/azure`: Azure and Azure ML integrations\r\n- [ ] `integrations/sagemaker`: SageMaker integrations\r\n- [ ] `integrations/databricks`: Databricks integrations\r\n\n", "before_files": [{"content": "import os\nfrom mimetypes import guess_type\n\nimport posixpath\nimport urllib.parse\n\nfrom mlflow import data\nfrom mlflow.entities import FileInfo\nfrom mlflow.exceptions import MlflowException\nfrom mlflow.store.artifact.artifact_repo import ArtifactRepository\nfrom mlflow.utils.file_utils import relative_path_to_artifact_path\n\n\nclass S3ArtifactRepository(ArtifactRepository):\n    \"\"\"Stores artifacts on Amazon S3.\"\"\"\n\n    @staticmethod\n    def parse_s3_uri(uri):\n        \"\"\"Parse an S3 URI, returning (bucket, path)\"\"\"\n        parsed = urllib.parse.urlparse(uri)\n        if parsed.scheme != \"s3\":\n            raise Exception(\"Not an S3 URI: %s\" % uri)\n        path = parsed.path\n        if path.startswith(\"/\"):\n            path = path[1:]\n        return parsed.netloc, path\n\n    @staticmethod\n    def get_s3_file_upload_extra_args():\n        import json\n\n        s3_file_upload_extra_args = os.environ.get(\"MLFLOW_S3_UPLOAD_EXTRA_ARGS\")\n        if s3_file_upload_extra_args:\n            return json.loads(s3_file_upload_extra_args)\n        else:\n            return None\n\n    def _get_s3_client(self):\n        import boto3\n        from botocore.client import Config\n\n        s3_endpoint_url = os.environ.get(\"MLFLOW_S3_ENDPOINT_URL\")\n        ignore_tls = os.environ.get(\"MLFLOW_S3_IGNORE_TLS\")\n\n        verify = True\n        if ignore_tls:\n            verify = ignore_tls.lower() not in [\"true\", \"yes\", \"1\"]\n\n        # NOTE: If you need to specify this env variable, please file an issue at\n        # https://github.com/mlflow/mlflow/issues so we know your use-case!\n        signature_version = os.environ.get(\"MLFLOW_EXPERIMENTAL_S3_SIGNATURE_VERSION\", \"s3v4\")\n        return boto3.client(\n            \"s3\",\n            config=Config(signature_version=signature_version),\n            endpoint_url=s3_endpoint_url,\n            verify=verify,\n        )\n\n    def _upload_file(self, s3_client, local_file, bucket, key):\n        extra_args = dict()\n        guessed_type, guessed_encoding = guess_type(local_file)\n        if guessed_type is not None:\n            extra_args[\"ContentType\"] = guessed_type\n        if guessed_encoding is not None:\n            extra_args[\"ContentEncoding\"] = guessed_encoding\n        environ_extra_args = self.get_s3_file_upload_extra_args()\n        if environ_extra_args is not None:\n            extra_args.update(environ_extra_args)\n        s3_client.upload_file(Filename=local_file, Bucket=bucket, Key=key, ExtraArgs=extra_args)\n\n    def log_artifact(self, local_file, artifact_path=None):\n        (bucket, dest_path) = data.parse_s3_uri(self.artifact_uri)\n        if artifact_path:\n            dest_path = posixpath.join(dest_path, artifact_path)\n        dest_path = posixpath.join(dest_path, os.path.basename(local_file))\n        self._upload_file(\n            s3_client=self._get_s3_client(), local_file=local_file, bucket=bucket, key=dest_path\n        )\n\n    def log_artifacts(self, local_dir, artifact_path=None):\n        (bucket, dest_path) = data.parse_s3_uri(self.artifact_uri)\n        if artifact_path:\n            dest_path = posixpath.join(dest_path, artifact_path)\n        s3_client = self._get_s3_client()\n        local_dir = os.path.abspath(local_dir)\n        for (root, _, filenames) in os.walk(local_dir):\n            upload_path = dest_path\n            if root != local_dir:\n                rel_path = os.path.relpath(root, local_dir)\n                rel_path = relative_path_to_artifact_path(rel_path)\n                upload_path = posixpath.join(dest_path, rel_path)\n            for f in filenames:\n                self._upload_file(\n                    s3_client=s3_client,\n                    local_file=os.path.join(root, f),\n                    bucket=bucket,\n                    key=posixpath.join(upload_path, f),\n                )\n\n    def list_artifacts(self, path=None):\n        (bucket, artifact_path) = data.parse_s3_uri(self.artifact_uri)\n        dest_path = artifact_path\n        if path:\n            dest_path = posixpath.join(dest_path, path)\n        infos = []\n        prefix = dest_path + \"/\" if dest_path else \"\"\n        s3_client = self._get_s3_client()\n        paginator = s3_client.get_paginator(\"list_objects_v2\")\n        results = paginator.paginate(Bucket=bucket, Prefix=prefix, Delimiter=\"/\")\n        for result in results:\n            # Subdirectories will be listed as \"common prefixes\" due to the way we made the request\n            for obj in result.get(\"CommonPrefixes\", []):\n                subdir_path = obj.get(\"Prefix\")\n                self._verify_listed_object_contains_artifact_path_prefix(\n                    listed_object_path=subdir_path, artifact_path=artifact_path\n                )\n                subdir_rel_path = posixpath.relpath(path=subdir_path, start=artifact_path)\n                if subdir_rel_path.endswith(\"/\"):\n                    subdir_rel_path = subdir_rel_path[:-1]\n                infos.append(FileInfo(subdir_rel_path, True, None))\n            # Objects listed directly will be files\n            for obj in result.get(\"Contents\", []):\n                file_path = obj.get(\"Key\")\n                self._verify_listed_object_contains_artifact_path_prefix(\n                    listed_object_path=file_path, artifact_path=artifact_path\n                )\n                file_rel_path = posixpath.relpath(path=file_path, start=artifact_path)\n                file_size = int(obj.get(\"Size\"))\n                infos.append(FileInfo(file_rel_path, False, file_size))\n        return sorted(infos, key=lambda f: f.path)\n\n    @staticmethod\n    def _verify_listed_object_contains_artifact_path_prefix(listed_object_path, artifact_path):\n        if not listed_object_path.startswith(artifact_path):\n            raise MlflowException(\n                \"The path of the listed S3 object does not begin with the specified\"\n                \" artifact path. Artifact path: {artifact_path}. Object path:\"\n                \" {object_path}.\".format(\n                    artifact_path=artifact_path, object_path=listed_object_path\n                )\n            )\n\n    def _download_file(self, remote_file_path, local_path):\n        (bucket, s3_root_path) = data.parse_s3_uri(self.artifact_uri)\n        s3_full_path = posixpath.join(s3_root_path, remote_file_path)\n        s3_client = self._get_s3_client()\n        s3_client.download_file(bucket, s3_full_path, local_path)\n\n    def delete_artifacts(self, artifact_path=None):\n        (bucket, dest_path) = data.parse_s3_uri(self.artifact_uri)\n        if artifact_path:\n            dest_path = posixpath.join(dest_path, artifact_path)\n\n        s3_client = self._get_s3_client()\n        list_objects = s3_client.list_objects(Bucket=bucket, Prefix=dest_path).get(\"Contents\", [])\n        for to_delete_obj in list_objects:\n            file_path = to_delete_obj.get(\"Key\")\n            self._verify_listed_object_contains_artifact_path_prefix(\n                listed_object_path=file_path, artifact_path=dest_path\n            )\n            s3_client.delete_object(Bucket=bucket, Key=file_path)\n", "path": "mlflow/store/artifact/s3_artifact_repo.py"}], "after_files": [{"content": "import os\nfrom mimetypes import guess_type\n\nimport posixpath\nimport urllib.parse\n\nfrom mlflow import data\nfrom mlflow.entities import FileInfo\nfrom mlflow.exceptions import MlflowException\nfrom mlflow.store.artifact.artifact_repo import ArtifactRepository\nfrom mlflow.utils.file_utils import relative_path_to_artifact_path\n\n\nclass S3ArtifactRepository(ArtifactRepository):\n    \"\"\"Stores artifacts on Amazon S3.\"\"\"\n\n    @staticmethod\n    def parse_s3_uri(uri):\n        \"\"\"Parse an S3 URI, returning (bucket, path)\"\"\"\n        parsed = urllib.parse.urlparse(uri)\n        if parsed.scheme != \"s3\":\n            raise Exception(\"Not an S3 URI: %s\" % uri)\n        path = parsed.path\n        if path.startswith(\"/\"):\n            path = path[1:]\n        return parsed.netloc, path\n\n    @staticmethod\n    def get_s3_file_upload_extra_args():\n        import json\n\n        s3_file_upload_extra_args = os.environ.get(\"MLFLOW_S3_UPLOAD_EXTRA_ARGS\")\n        if s3_file_upload_extra_args:\n            return json.loads(s3_file_upload_extra_args)\n        else:\n            return None\n\n    def _get_s3_client(self):\n        import boto3\n        from botocore.client import Config\n\n        s3_endpoint_url = os.environ.get(\"MLFLOW_S3_ENDPOINT_URL\")\n        ignore_tls = os.environ.get(\"MLFLOW_S3_IGNORE_TLS\")\n\n        do_verify = True\n        if ignore_tls:\n            do_verify = ignore_tls.lower() not in [\"true\", \"yes\", \"1\"]\n\n        # The valid verify argument value is None/False/path to cert bundle file, See\n        # https://github.com/boto/boto3/blob/73865126cad3938ca80a2f567a1c79cb248169a7/\n        # boto3/session.py#L212\n        verify = None if do_verify else False\n\n        # NOTE: If you need to specify this env variable, please file an issue at\n        # https://github.com/mlflow/mlflow/issues so we know your use-case!\n        signature_version = os.environ.get(\"MLFLOW_EXPERIMENTAL_S3_SIGNATURE_VERSION\", \"s3v4\")\n        return boto3.client(\n            \"s3\",\n            config=Config(signature_version=signature_version),\n            endpoint_url=s3_endpoint_url,\n            verify=verify,\n        )\n\n    def _upload_file(self, s3_client, local_file, bucket, key):\n        extra_args = dict()\n        guessed_type, guessed_encoding = guess_type(local_file)\n        if guessed_type is not None:\n            extra_args[\"ContentType\"] = guessed_type\n        if guessed_encoding is not None:\n            extra_args[\"ContentEncoding\"] = guessed_encoding\n        environ_extra_args = self.get_s3_file_upload_extra_args()\n        if environ_extra_args is not None:\n            extra_args.update(environ_extra_args)\n        s3_client.upload_file(Filename=local_file, Bucket=bucket, Key=key, ExtraArgs=extra_args)\n\n    def log_artifact(self, local_file, artifact_path=None):\n        (bucket, dest_path) = data.parse_s3_uri(self.artifact_uri)\n        if artifact_path:\n            dest_path = posixpath.join(dest_path, artifact_path)\n        dest_path = posixpath.join(dest_path, os.path.basename(local_file))\n        self._upload_file(\n            s3_client=self._get_s3_client(), local_file=local_file, bucket=bucket, key=dest_path\n        )\n\n    def log_artifacts(self, local_dir, artifact_path=None):\n        (bucket, dest_path) = data.parse_s3_uri(self.artifact_uri)\n        if artifact_path:\n            dest_path = posixpath.join(dest_path, artifact_path)\n        s3_client = self._get_s3_client()\n        local_dir = os.path.abspath(local_dir)\n        for (root, _, filenames) in os.walk(local_dir):\n            upload_path = dest_path\n            if root != local_dir:\n                rel_path = os.path.relpath(root, local_dir)\n                rel_path = relative_path_to_artifact_path(rel_path)\n                upload_path = posixpath.join(dest_path, rel_path)\n            for f in filenames:\n                self._upload_file(\n                    s3_client=s3_client,\n                    local_file=os.path.join(root, f),\n                    bucket=bucket,\n                    key=posixpath.join(upload_path, f),\n                )\n\n    def list_artifacts(self, path=None):\n        (bucket, artifact_path) = data.parse_s3_uri(self.artifact_uri)\n        dest_path = artifact_path\n        if path:\n            dest_path = posixpath.join(dest_path, path)\n        infos = []\n        prefix = dest_path + \"/\" if dest_path else \"\"\n        s3_client = self._get_s3_client()\n        paginator = s3_client.get_paginator(\"list_objects_v2\")\n        results = paginator.paginate(Bucket=bucket, Prefix=prefix, Delimiter=\"/\")\n        for result in results:\n            # Subdirectories will be listed as \"common prefixes\" due to the way we made the request\n            for obj in result.get(\"CommonPrefixes\", []):\n                subdir_path = obj.get(\"Prefix\")\n                self._verify_listed_object_contains_artifact_path_prefix(\n                    listed_object_path=subdir_path, artifact_path=artifact_path\n                )\n                subdir_rel_path = posixpath.relpath(path=subdir_path, start=artifact_path)\n                if subdir_rel_path.endswith(\"/\"):\n                    subdir_rel_path = subdir_rel_path[:-1]\n                infos.append(FileInfo(subdir_rel_path, True, None))\n            # Objects listed directly will be files\n            for obj in result.get(\"Contents\", []):\n                file_path = obj.get(\"Key\")\n                self._verify_listed_object_contains_artifact_path_prefix(\n                    listed_object_path=file_path, artifact_path=artifact_path\n                )\n                file_rel_path = posixpath.relpath(path=file_path, start=artifact_path)\n                file_size = int(obj.get(\"Size\"))\n                infos.append(FileInfo(file_rel_path, False, file_size))\n        return sorted(infos, key=lambda f: f.path)\n\n    @staticmethod\n    def _verify_listed_object_contains_artifact_path_prefix(listed_object_path, artifact_path):\n        if not listed_object_path.startswith(artifact_path):\n            raise MlflowException(\n                \"The path of the listed S3 object does not begin with the specified\"\n                \" artifact path. Artifact path: {artifact_path}. Object path:\"\n                \" {object_path}.\".format(\n                    artifact_path=artifact_path, object_path=listed_object_path\n                )\n            )\n\n    def _download_file(self, remote_file_path, local_path):\n        (bucket, s3_root_path) = data.parse_s3_uri(self.artifact_uri)\n        s3_full_path = posixpath.join(s3_root_path, remote_file_path)\n        s3_client = self._get_s3_client()\n        s3_client.download_file(bucket, s3_full_path, local_path)\n\n    def delete_artifacts(self, artifact_path=None):\n        (bucket, dest_path) = data.parse_s3_uri(self.artifact_uri)\n        if artifact_path:\n            dest_path = posixpath.join(dest_path, artifact_path)\n\n        s3_client = self._get_s3_client()\n        list_objects = s3_client.list_objects(Bucket=bucket, Prefix=dest_path).get(\"Contents\", [])\n        for to_delete_obj in list_objects:\n            file_path = to_delete_obj.get(\"Key\")\n            self._verify_listed_object_contains_artifact_path_prefix(\n                listed_object_path=file_path, artifact_path=dest_path\n            )\n            s3_client.delete_object(Bucket=bucket, Key=file_path)\n", "path": "mlflow/store/artifact/s3_artifact_repo.py"}]}