Repository: mlflow/mlflow

Issue:
Refactor `is_dbfs_fuse_available`
### Summary

Refactor `is_dbfs_fuse_available` using `subprocess.DEVNULL`:

```diff
diff --git a/mlflow/utils/databricks_utils.py b/mlflow/utils/databricks_utils.py
index fd5f0263a..9332a71d3 100644
--- a/mlflow/utils/databricks_utils.py
+++ b/mlflow/utils/databricks_utils.py
@@ -251,16 +251,17 @@ def is_in_databricks_serverless():
 
 
 def is_dbfs_fuse_available():
-    with open(os.devnull, "w") as devnull_stderr, open(os.devnull, "w") as devnull_stdout:
-        try:
-            return (
-                subprocess.call(
-                    ["mountpoint", "/dbfs"], stderr=devnull_stderr, stdout=devnull_stdout
-                )
-                == 0
+    try:
+        return (
+            subprocess.call(
+                ["mountpoint", "/dbfs"],
+                stderr=subprocess.DEVNULL,
+                stdout=subprocess.DEVNULL,
             )
-        except Exception:
-            return False
+            == 0
+        )
+    except Exception:
+        return False
```

### Notes

- Make sure to open a PR from a **non-master** branch.
- Sign off the commit using the `-s` flag when making a commit:

  ```sh
  git commit -s -m "..."
           # ^^ make sure to use this
  ```

- Include `#{issue_number}` (e.g. `#123`) in the PR description when opening a PR.


Discussion:
github-actions[bot]: <!-- documentation preview -->


Documentation preview for 7fb417907213fef2c61e7370b66f12619407196f will be available when [this CircleCI job](https://circleci.com/gh/mlflow/mlflow/122191)
completes successfully.

- [Top page](https://output.circle-artifacts.com/output/job/4b0c94dd-9290-4d89-a12b-ee084615f758/artifacts/0/docs/build/html/index.html)
- [Changed pages](https://output.circle-artifacts.com/output/job/4b0c94dd-9290-4d89-a12b-ee084615f758/artifacts/0/docs/build/html/diff.html)

<details>
<summary>More info</summary>

- Ignore this comment if this PR does not change the documentation.
- It takes a few minutes for the preview to be available.
- The preview is updated when a new commit is pushed to this PR.
- This comment was created by https://github.com/mlflow/mlflow/actions/runs/10926276352.

</details>

varshinimuthukumar1: @harupy  Could you please review it? :)  

Patch:
diff --git a/mlflow/utils/databricks_utils.py b/mlflow/utils/databricks_utils.py
index e13bd67c1..e02507669 100644
--- a/mlflow/utils/databricks_utils.py
+++ b/mlflow/utils/databricks_utils.py
@@ -254,16 +254,17 @@ def is_dbfs_fuse_available():
     if not is_in_databricks_runtime():
         return False
 
-    with open(os.devnull, "w") as devnull_stderr, open(os.devnull, "w") as devnull_stdout:
-        try:
-            return (
-                subprocess.call(
-                    ["mountpoint", "/dbfs"], stderr=devnull_stderr, stdout=devnull_stdout
-                )
-                == 0
+    try:
+        return (
+            subprocess.call(
+                ["mountpoint", "/dbfs"],
+                stderr=subprocess.DEVNULL,
+                stdout=subprocess.DEVNULL,
             )
-        except Exception:
-            return False
+            == 0
+        )
+    except Exception:
+        return False
 
 
 def is_uc_volume_fuse_available():
diff --git a/mlflow/utils/validation.py b/mlflow/utils/validation.py
index 928df4cd5..1b9380c33 100644
--- a/mlflow/utils/validation.py
+++ b/mlflow/utils/validation.py
@@ -116,6 +116,10 @@ def missing_value(path):
     return f"Missing value for required parameter '{path}'."
 
 
+def exceeds_maximum_length(path, limit):
+    return f"'{path}' exceeds the maximum length of {limit} characters"
+
+
 def append_to_json_path(currenPath, value):
     if not currenPath:
         return value
@@ -440,7 +444,7 @@ def _validate_experiment_name(experiment_name):
 
     if len(experiment_name) > MAX_EXPERIMENT_NAME_LENGTH:
         raise MlflowException.invalid_parameter_value(
-            f"Experiment name exceeds the maximum length of {MAX_EXPERIMENT_NAME_LENGTH} characters"
+            exceeds_maximum_length("name", MAX_EXPERIMENT_NAME_LENGTH)
         )
 
 
@@ -546,27 +550,27 @@ def _validate_dataset(dataset: Dataset):
         raise MlflowException("Dataset source cannot be None", INVALID_PARAMETER_VALUE)
     if len(dataset.name) > MAX_DATASET_NAME_SIZE:
         raise MlflowException(
-            f"Dataset name exceeds the maximum length of {MAX_DATASET_NAME_SIZE}",
+            exceeds_maximum_length("name", MAX_DATASET_NAME_SIZE),
             INVALID_PARAMETER_VALUE,
         )
     if len(dataset.digest) > MAX_DATASET_DIGEST_SIZE:
         raise MlflowException(
-            f"Dataset digest exceeds the maximum length of {MAX_DATASET_DIGEST_SIZE}",
+            exceeds_maximum_length("digest", MAX_DATASET_DIGEST_SIZE),
             INVALID_PARAMETER_VALUE,
         )
     if len(dataset.source) > MAX_DATASET_SOURCE_SIZE:
         raise MlflowException(
-            f"Dataset source exceeds the maximum length of {MAX_DATASET_SOURCE_SIZE}",
+            exceeds_maximum_length("source", MAX_DATASET_SOURCE_SIZE),
             INVALID_PARAMETER_VALUE,
         )
     if dataset.schema is not None and len(dataset.schema) > MAX_DATASET_SCHEMA_SIZE:
         raise MlflowException(
-            f"Dataset schema exceeds the maximum length of {MAX_DATASET_SCHEMA_SIZE}",
+            exceeds_maximum_length("schema", MAX_DATASET_SCHEMA_SIZE),
             INVALID_PARAMETER_VALUE,
         )
     if dataset.profile is not None and len(dataset.profile) > MAX_DATASET_PROFILE_SIZE:
         raise MlflowException(
-            f"Dataset profile exceeds the maximum length of {MAX_DATASET_PROFILE_SIZE}",
+            exceeds_maximum_length("profile", MAX_DATASET_PROFILE_SIZE),
             INVALID_PARAMETER_VALUE,
         )
 
@@ -585,12 +589,12 @@ def _validate_input_tag(input_tag: InputTag):
         raise MlflowException("InputTag value cannot be None", INVALID_PARAMETER_VALUE)
     if len(input_tag.key) > MAX_INPUT_TAG_KEY_SIZE:
         raise MlflowException(
-            f"InputTag key exceeds the maximum length of {MAX_INPUT_TAG_KEY_SIZE}",
+            exceeds_maximum_length("key", MAX_INPUT_TAG_KEY_SIZE),
             INVALID_PARAMETER_VALUE,
         )
     if len(input_tag.value) > MAX_INPUT_TAG_VALUE_SIZE:
         raise MlflowException(
-            f"InputTag value exceeds the maximum length of {MAX_INPUT_TAG_VALUE_SIZE}",
+            exceeds_maximum_length("value", MAX_INPUT_TAG_VALUE_SIZE),
             INVALID_PARAMETER_VALUE,
         )
 


Test patch:
diff --git a/tests/store/tracking/test_file_store.py b/tests/store/tracking/test_file_store.py
index 7bea18996..4e703d9c1 100644
--- a/tests/store/tracking/test_file_store.py
+++ b/tests/store/tracking/test_file_store.py
@@ -663,7 +663,7 @@ def test_create_experiment(store):
         store.create_experiment(None)
     with pytest.raises(Exception, match="Invalid experiment name: ''"):
         store.create_experiment("")
-    with pytest.raises(MlflowException, match=r"Experiment name exceeds the maximum length"):
+    with pytest.raises(MlflowException, match=r"'name' exceeds the maximum length"):
         store.create_experiment(name="x" * (MAX_EXPERIMENT_NAME_LENGTH + 1))
     name = random_str(25)  # since existing experiments are 10 chars long
     time_before_create = get_current_time_millis()
diff --git a/tests/store/tracking/test_sqlalchemy_store.py b/tests/store/tracking/test_sqlalchemy_store.py
index 4ab709c4a..9ebcc6678 100644
--- a/tests/store/tracking/test_sqlalchemy_store.py
+++ b/tests/store/tracking/test_sqlalchemy_store.py
@@ -689,7 +689,7 @@ def test_create_experiments(store: SqlAlchemyStore):
     assert actual.creation_time >= time_before_create
     assert actual.last_update_time == actual.creation_time
 
-    with pytest.raises(MlflowException, match=r"Experiment name exceeds the maximum length"):
+    with pytest.raises(MlflowException, match=r"'name' exceeds the maximum length"):
         store.create_experiment(name="x" * (MAX_EXPERIMENT_NAME_LENGTH + 1))
 
 
@@ -3492,7 +3492,7 @@ def test_log_inputs_with_large_inputs_limit_check(store: SqlAlchemyStore):
                 dataset=dataset,
             )
         ],
-        f"InputTag key exceeds the maximum length of {MAX_INPUT_TAG_KEY_SIZE}",
+        f"'key' exceeds the maximum length of {MAX_INPUT_TAG_KEY_SIZE}",
     )
 
     # Test input value
@@ -3516,7 +3516,7 @@ def test_log_inputs_with_large_inputs_limit_check(store: SqlAlchemyStore):
                 dataset=dataset,
             )
         ],
-        f"InputTag value exceeds the maximum length of {MAX_INPUT_TAG_VALUE_SIZE}",
+        f"'value' exceeds the maximum length of {MAX_INPUT_TAG_VALUE_SIZE}",
     )
 
     # Test dataset name
@@ -3550,7 +3550,7 @@ def test_log_inputs_with_large_inputs_limit_check(store: SqlAlchemyStore):
                 ),
             )
         ],
-        f"Dataset name exceeds the maximum length of {MAX_DATASET_NAME_SIZE}",
+        f"'name' exceeds the maximum length of {MAX_DATASET_NAME_SIZE}",
     )
 
     # Test dataset digest
@@ -3583,7 +3583,7 @@ def test_log_inputs_with_large_inputs_limit_check(store: SqlAlchemyStore):
                 ),
             )
         ],
-        f"Dataset digest exceeds the maximum length of {MAX_DATASET_DIGEST_SIZE}",
+        f"'digest' exceeds the maximum length of {MAX_DATASET_DIGEST_SIZE}",
     )
 
     # Test dataset source
@@ -3616,7 +3616,7 @@ def test_log_inputs_with_large_inputs_limit_check(store: SqlAlchemyStore):
                 ),
             )
         ],
-        f"Dataset source exceeds the maximum length of {MAX_DATASET_SOURCE_SIZE}",
+        f"'source' exceeds the maximum length of {MAX_DATASET_SOURCE_SIZE}",
     )
 
     # Test dataset schema
@@ -3651,7 +3651,7 @@ def test_log_inputs_with_large_inputs_limit_check(store: SqlAlchemyStore):
                 ),
             )
         ],
-        f"Dataset schema exceeds the maximum length of {MAX_DATASET_SCHEMA_SIZE}",
+        f"'schema' exceeds the maximum length of {MAX_DATASET_SCHEMA_SIZE}",
     )
 
     # Test dataset profile
@@ -3686,7 +3686,7 @@ def test_log_inputs_with_large_inputs_limit_check(store: SqlAlchemyStore):
                 ),
             )
         ],
-        f"Dataset profile exceeds the maximum length of {MAX_DATASET_PROFILE_SIZE}",
+        f"'profile' exceeds the maximum length of {MAX_DATASET_PROFILE_SIZE}",
     )