/ mlflow / utils / autologging_utils / versioning.py
versioning.py
 1  import importlib
 2  import importlib.metadata
 3  import re
 4  from typing import Literal
 5  
 6  from packaging.version import InvalidVersion, Version
 7  
 8  from mlflow.ml_package_versions import _ML_PACKAGE_VERSIONS, FLAVOR_TO_MODULE_NAME
 9  from mlflow.utils.databricks_utils import is_in_databricks_runtime
10  
11  
12  def _check_version_in_range(ver, min_ver, max_ver):
13      return Version(min_ver) <= Version(ver) <= Version(max_ver)
14  
15  
16  def _check_spark_version_in_range(ver, min_ver, max_ver):
17      """
18      Utility function for allowing late addition release changes to PySpark minor version increments
19      to be accepted, provided that the previous minor version has been previously validated.
20      For example, if version 3.2.1 has been validated as functional with MLflow, an upgrade of
21      PySpark's minor version to 3.2.2 will still provide a valid version check.
22      """
23      parsed_ver = Version(ver)
24      if parsed_ver > Version(min_ver):
25          ver = f"{parsed_ver.major}.{parsed_ver.minor}"
26      return _check_version_in_range(ver, min_ver, max_ver)
27  
28  
29  def _violates_pep_440(ver):
30      try:
31          Version(ver)
32          return False
33      except InvalidVersion:
34          return True
35  
36  
37  def _is_pre_or_dev_release(ver):
38      v = Version(ver)
39      return v.is_devrelease or v.is_prerelease
40  
41  
42  def _strip_dev_version_suffix(version):
43      return re.sub(r"(\.?)dev.*", "", version)
44  
45  
46  def get_min_max_version_and_pip_release(
47      flavor_name: str, category: Literal["autologging", "models"] = "autologging"
48  ):
49      if flavor_name == "pyspark.ml":
50          # pyspark.ml is a special case of spark flavor
51          flavor_name = "spark"
52  
53      min_version = _ML_PACKAGE_VERSIONS[flavor_name][category]["minimum"]
54      max_version = _ML_PACKAGE_VERSIONS[flavor_name][category]["maximum"]
55      pip_release = _ML_PACKAGE_VERSIONS[flavor_name]["package_info"]["pip_release"]
56      return min_version, max_version, pip_release
57  
58  
59  def is_flavor_supported_for_associated_package_versions(flavor_name, check_max_version=True):
60      """
61      Returns:
62          True if the specified flavor is supported for the currently-installed versions of its
63          associated packages.
64      """
65      module_name = FLAVOR_TO_MODULE_NAME[flavor_name]
66  
67      try:
68          actual_version = importlib.import_module(module_name).__version__
69      except AttributeError:
70          try:
71              # NB: Module name is not necessarily the same as the package name. However,
72              # we assume they are the same here for simplicity. If they are not the same,
73              # this will fail and fallback to 'True', which is not a disaster.
74              actual_version = importlib.metadata.version(module_name)
75          except importlib.metadata.PackageNotFoundError:
76              # Some package (e.g. dspy) do not publish version info in a standard format.
77              # For this case, we assume the package version is supported by MLflow.
78              return True
79  
80      # In Databricks, treat 'pyspark 3.x.y.dev0' as 'pyspark 3.x.y'
81      if module_name == "pyspark" and is_in_databricks_runtime():
82          actual_version = _strip_dev_version_suffix(actual_version)
83  
84      if _violates_pep_440(actual_version) or _is_pre_or_dev_release(actual_version):
85          return False
86      min_version, max_version, _ = get_min_max_version_and_pip_release(flavor_name)
87  
88      if module_name == "pyspark" and is_in_databricks_runtime():
89          # MLflow 1.25.0 is known to be compatible with PySpark 3.3.0 on Databricks, despite the
90          # fact that PySpark 3.3.0 was not available in PyPI at the time of the MLflow 1.25.0 release
91          if Version(max_version) < Version("3.3.0"):
92              max_version = "3.3.0"
93          return _check_spark_version_in_range(actual_version, min_version, max_version)
94      else:
95          return (
96              _check_version_in_range(actual_version, min_version, max_version)
97              if check_max_version
98              else Version(min_version) <= Version(actual_version)
99          )