Repository: mlflow/mlflow

Issue:
[BUG] mlflow.artifacts.download_artifacts - object has no attribute 'values' in latest version
### Issues Policy acknowledgement

- [X] I have read and agree to submit bug reports in accordance with the [issues policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md)

### Willingness to contribute

No. I cannot contribute a bug fix at this time.

### MLflow version

- Client: 2.3.0



### System information

- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Docker, Linux Ubuntu 22.04
- **Python version**: 3.10



### Describe the problem

I received this error many times while loading a 1gb model from Databricks:

The error occured the first time yesterday. 
I have been using the latest version always, so the error happened the same time as version 2.3.0 of MLflow was released.
I have just now downgraded to the previous version, and it works perfectly again.
Therefore, I suspect there is a bug in version 2.3.0 in regards to downloading artifacts.



### Tracking information

_No response_

### Code to reproduce issue

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```
import mlflow, os
from dotenv import load_dotenv
load_dotenv()
mlflow.set_tracking_uri('databricks')
os.environ["MLFLOW_HTTP_REQUEST_TIMEOUT"] = "1200"

# loading classifier (can take 5-10 minutes)
mlflow.artifacts.download_artifacts(artifact_uri = "models:/documentClassifier/Production", dst_path = "/models/documentClassifier")
```


### Stack trace

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```
Traceback (most recent call last):
  File "/code/app/ML/getModels.py", line 7, in <module>
    mlflow.artifacts.download_artifacts(artifact_uri = "models:/documentClassifier/Production", dst_path = "/models/documentClassifier")
  File "/usr/local/lib/python3.10/dist-packages/mlflow/artifacts/__init__.py", line 56, in download_artifacts
    return _download_artifact_from_uri(artifact_uri, output_path=dst_path)
  File "/usr/local/lib/python3.10/dist-packages/mlflow/tracking/artifact_utils.py", line 100, in _download_artifact_from_uri
    return get_artifact_repository(artifact_uri=root_uri).download_artifacts(
  File "/usr/local/lib/python3.10/dist-packages/mlflow/store/artifact/models_artifact_repo.py", line 135, in download_artifacts
    return self.repo.download_artifacts(artifact_path, dst_path)
  File "/usr/local/lib/python3.10/dist-packages/mlflow/store/artifact/artifact_repo.py", line 265, in download_artifacts
    raise MlflowException(
mlflow.exceptions.MlflowException: The following failures occurred while downloading one or more artifacts from models:/documentClassifier/Production: {'artifacts/documentClassifier/pytorch_model.bin': 'MlflowException("\'NoneType\' object has no attribute \'values\'")'}
The command '/bin/sh -c python3 /code/app/ML/getModels.py' returned a non-zero code: 1
```


### Other info / logs

_No response_

### What component(s) does this bug affect?

- [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/recipes`: Recipes, Recipe APIs, Recipe configs, Recipe Templates
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [ ] `area/server-infra`: MLflow Tracking server backend
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

### What interface(s) does this bug affect?

- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [X] `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

### What language(s) does this bug affect?

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

### What integration(s) does this bug affect?

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


Discussion:

Resolving patch:
diff --git a/mlflow/utils/file_utils.py b/mlflow/utils/file_utils.py
--- a/mlflow/utils/file_utils.py
+++ b/mlflow/utils/file_utils.py
@@ -14,7 +14,6 @@
 
 import urllib.parse
 import urllib.request
-from pathlib import Path
 from concurrent.futures import ProcessPoolExecutor
 from urllib.parse import unquote
 from urllib.request import pathname2url
@@ -623,10 +622,10 @@ def parallelized_download_file_using_http_uri(
     Returns a dict of chunk index : exception, if one was thrown for that index.
     """
     num_requests = int(math.ceil(file_size / float(chunk_size)))
-    # Create file if it doesn't exist. We should do this here before sending to the workers
-    # so they can each individually seek to their respective positions and write chunks
-    # without overwriting.
-    Path(download_path).touch()
+    # Create file if it doesn't exist or erase the contents if it does. We should do this here
+    # before sending to the workers so they can each individually seek to their respective positions
+    # and write chunks without overwriting.
+    open(download_path, "w").close()
     futures = {}
     starting_index = 0
     if uri_type == ArtifactCredentialType.GCP_SIGNED_URL or uri_type is None:
@@ -638,7 +637,7 @@ def parallelized_download_file_using_http_uri(
         # If downloaded size was equal to the chunk size it would have been downloaded serially,
         # so we don't need to consider this here
         if downloaded_size > chunk_size:
-            return
+            return {}
         else:
             starting_index = 1
 


Test patch:
diff --git a/tests/utils/test_file_utils.py b/tests/utils/test_file_utils.py
--- a/tests/utils/test_file_utils.py
+++ b/tests/utils/test_file_utils.py
@@ -401,7 +401,8 @@ def mock_request_side_effect(method, url, *args, **kwargs):
 
 
 @pytest.mark.skipif(is_windows(), reason="This test fails on Windows")
-def test_parallelized_download_file_using_http_uri_handles_gcp_transcoding(tmp_path):
+@pytest.mark.parametrize("uri_type", [ArtifactCredentialType.GCP_SIGNED_URL, None])
+def test_parallelized_download_file_using_http_uri_handles_gcp_transcoding(tmp_path, uri_type):
     calls_kwargs = multiprocessing.Manager().list([])
     file_content = b"\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
 
@@ -416,15 +417,16 @@ def mock_request_side_effect(method, url, *args, **kwargs):
     filename = tmp_path / "testfile"
     with mock.patch("requests.Session.request") as request_mock:
         request_mock.side_effect = mock_request_side_effect
-        parallelized_download_file_using_http_uri(
+        failed_downloads = parallelized_download_file_using_http_uri(
             "fake_uri",
             filename,
             file_size=10,
-            uri_type=ArtifactCredentialType.GCP_SIGNED_URL,
+            uri_type=uri_type,
             chunk_size=2,
             headers={},
         )
     # Should only have called once because the whole file was returned
+    assert failed_downloads == {}
     assert len(calls_kwargs) == 1
     with open(filename, "rb") as f:
         f.seek(0)