Repository: mlflow/mlflow

Issue:
[BUG] Invalid Requirement error with custom wheel on dbfs path
### 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

- mlflow: 2.0.1


### System information

- **Databricks Runtime 12.0 for Machine Learning**



### Describe the problem

I am logging a model with a custom library via `pip_requirements ` parameter as described in the Databricks documentation in the [Use custom Python libraries with Model Serving](https://learn.microsoft.com/de-de/azure/databricks/machine-learning/model-serving/private-libraries-model-serving) section. My wheel file is stored on DBFS (for example under `/dbfs/path/to/dependency.whl`).

When loading the model the parsing of the custom library requirement fails with `InvalidRequirement('Parse error at "\'/dbfs/pa\'": Expected W:(abcd...)') `, see code example and full stack trace below.

Would appreciate any hints or advise on how I can get the wheel file recognized in my pip requirements!


### Code to reproduce issue

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```python
import numpy as np
from sklearn.linear_model import LogisticRegression

import logging
import mlflow
import mlflow.sklearn

logging.getLogger("mlflow").setLevel(logging.DEBUG)

X = np.array([-2, -1, 0, 1, 2, 1]).reshape(-1, 1)
y = np.array([0, 0, 1, 1, 1, 0])
lr = LogisticRegression()
lr.fit(X, y)

pip_requirements=["scikit-learn", "numpy", "/dbfs/path/to/dependency.whl"]
mlflow.sklearn.log_model(lr, "model", pip_requirements=pip_requirements)

logged_model = f"runs:/{mlflow.active_run().info.run_uuid}/model"

# Load model as a PyFuncModel.
loaded_model = mlflow.pyfunc.load_model(logged_model)
```


### Stack trace

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```
2023/04/14 06:45:26 WARNING mlflow.pyfunc: Encountered an unexpected error (InvalidRequirement('Parse error at "\'/dbfs/pa\'": Expected W:(abcd...)')) while detecting model dependency mismatches. Set logging level to DEBUG to see the full traceback.
2023/04/14 06:45:26 DEBUG mlflow.pyfunc: 
Traceback (most recent call last):
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/packaging/requirements.py", line 102, in __init__
    req = REQUIREMENT.parseString(requirement_string)
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/pyparsing.py", line 1654, in parseString
    raise exc
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/pyparsing.py", line 1644, in parseString
    loc, tokens = self._parse( instring, 0 )
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/pyparsing.py", line 1402, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/pyparsing.py", line 3417, in parseImpl
    loc, exprtokens = e._parse( instring, loc, doActions )
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/pyparsing.py", line 1402, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/pyparsing.py", line 3739, in parseImpl
    return self.expr._parse( instring, loc, doActions, callPreParse=False )
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/pyparsing.py", line 1402, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/pyparsing.py", line 3400, in parseImpl
    loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False )
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/pyparsing.py", line 1406, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/pyparsing.py", line 2711, in parseImpl
    raise ParseException(instring, loc, self.errmsg, self)
pkg_resources._vendor.pyparsing.ParseException: Expected W:(abcd...) (at char 0), (line:1, col:1)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py", line 458, in _warn_dependency_requirement_mismatches
    mismatch_info = _check_requirement_satisfied(req_line)
  File "/databricks/python/lib/python3.9/site-packages/mlflow/utils/requirements_utils.py", line 494, in _check_requirement_satisfied
    req = pkg_resources.Requirement.parse(requirement_str)
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/__init__.py", line 3134, in parse
    req, = parse_requirements(s)
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/__init__.py", line 3089, in __init__
    super(Requirement, self).__init__(requirement_string)
  File "/databricks/python/lib/python3.9/site-packages/pkg_resources/_vendor/packaging/requirements.py", line 104, in __init__
    raise InvalidRequirement(
pkg_resources.extern.packaging.requirements.InvalidRequirement: Parse error at "'/dbfs/pa'": Expected W:(abcd...)
```



### What component(s) does this bug affect?

- [ ] `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
- [X] `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
- [ ] `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
- [X] `integrations/databricks`: Databricks integrations


Discussion:
@benjaminbluhm Thanks for reporting this issue. It looks like we should avoid parsing file paths. I'll file a PR.

Resolving patch:
diff --git a/mlflow/utils/requirements_utils.py b/mlflow/utils/requirements_utils.py
--- a/mlflow/utils/requirements_utils.py
+++ b/mlflow/utils/requirements_utils.py
@@ -488,11 +488,19 @@ def __str__(self):
 
 def _check_requirement_satisfied(requirement_str):
     """
-    Returns None if the current python environment satisfies the given requirement.
-    Otherwise, returns an instance of `_MismatchedPackageInfo`.
+    Checks whether the current python environment satisfies the given requirement if it is parsable
+    as a package name and a set of version specifiers, and returns a `_MismatchedPackageInfo`
+    object containing the mismatched package name, installed version, and requirement if the
+    requirement is not satisfied. Otherwise, returns None.
     """
     _init_packages_to_modules_map()
-    req = pkg_resources.Requirement.parse(requirement_str)
+    try:
+        req = pkg_resources.Requirement.parse(requirement_str)
+    except Exception:
+        # We reach here if the requirement string is a file path or a URL.
+        # Extracting the package name from the requirement string is not trivial,
+        # so we skip the check.
+        return
     pkg_name = req.name
 
     try:


Test patch:
diff --git a/tests/pyfunc/test_dependencies_functions.py b/tests/pyfunc/test_dependencies_functions.py
--- a/tests/pyfunc/test_dependencies_functions.py
+++ b/tests/pyfunc/test_dependencies_functions.py
@@ -243,3 +243,11 @@ def test_get_model_dependencies_with_model_version_uri():
 
     deps = get_model_dependencies("models:/linear/1", format="pip")
     assert f"scikit-learn=={sklearn.__version__}" in Path(deps).read_text()
+
+
+def test_warn_dependency_requirement_mismatches_ignores_file_path(tmp_path):
+    req_file = tmp_path / "requirements.txt"
+    req_file.write_text("/path/to/my.whl")
+    with mock.patch("mlflow.pyfunc._logger.warning") as mock_warning:
+        _warn_dependency_requirement_mismatches(model_path=tmp_path)
+        mock_warning.assert_not_called()