Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dask/_compatibility.py: 40%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import importlib
4import sys
5import types
6import warnings
7from typing import Literal, overload
9if sys.version_info >= (3, 12):
10 import importlib.metadata as importlib_metadata
11else:
12 import importlib_metadata
13from packaging.version import Version
15PY_VERSION = Version(".".join(map(str, sys.version_info[:3])))
17EMSCRIPTEN = sys.platform == "emscripten"
19LINUX = sys.platform == "linux"
20MACOS = sys.platform == "darwin"
21WINDOWS = sys.platform == "win32"
24VERSIONS = {
25 "numpy": "1.21.0",
26 "pandas": "2.0.0",
27 "bokeh": "3.1.0",
28 "jinja2": "2.10.3",
29 "pyarrow": "16.0.0",
30 "lz4": "4.3.2",
31}
33# A mapping from import name to package name (on PyPI) for packages where
34# these two names are different.
36INSTALL_MAPPING = {
37 "sqlalchemy": "SQLAlchemy",
38 "tables": "pytables",
39}
42def get_version(module: types.ModuleType) -> str:
43 try:
44 return module.__version__
45 except AttributeError as e: # pragma: no cover
46 raise ImportError(f"Can't determine version for {module.__name__}") from e
49@overload
50def import_optional_dependency(
51 name: str,
52 extra: str = "",
53 min_version: str | None = None,
54 *,
55 errors: Literal["raise"] = "raise",
56) -> types.ModuleType: ...
59@overload
60def import_optional_dependency(
61 name: str,
62 extra: str = "",
63 min_version: str | None = None,
64 *,
65 errors: Literal["warn", "ignore"],
66) -> types.ModuleType | None: ...
69def import_optional_dependency(
70 name: str,
71 extra: str = "",
72 min_version: str | None = None,
73 *,
74 errors: Literal["raise", "warn", "ignore"] = "raise",
75) -> types.ModuleType | None:
76 """
77 Import an optional dependency.
79 By default, if a dependency is missing, an ImportError with a nice
80 message will be raised. If a dependency is present, but too old,
81 we raise.
83 Parameters
84 ----------
85 name : str
86 The module name.
87 extra : str
88 Additional text to include in the ImportError message.
89 errors : str {'raise', 'warn', 'ignore'}
90 What to do when a dependency is not found or its version is too old.
92 * raise : Raise an ImportError
93 * warn : Only applicable when a module's version is too old.
94 Warns that the version is too old and returns None
95 * ignore: If the module is not installed, return None, otherwise,
96 return the module, even if the version is too old.
97 It's expected that users validate the version locally when
98 using ``errors="ignore"`` (see ``io/html.py``)
99 min_version : str, default None
100 Specify a minimum version that is different from the global
101 minimum version required.
102 Returns
103 -------
104 maybe_module : ModuleType | None
105 The imported module, when found and the version is correct.
106 None can be returned when `errors` is ``'warn'`` or ``'ignore'``
107 and the package is not found or the package's version is too old.
108 """
109 assert errors in {"warn", "raise", "ignore"}
111 package_name = INSTALL_MAPPING.get(name)
112 install_name = package_name if package_name is not None else name
114 msg = (
115 f"Missing optional dependency '{install_name}'. {extra} "
116 f"Use pip or conda to install {install_name}."
117 )
118 try:
119 # NOTE: Use `importlib_metadata.distribution` check to differentiate
120 # between something that's importable (e.g. a directory named `xarray`)
121 # and the library we want to check for (i.e. the `xarray` library)
122 importlib_metadata.distribution(name)
123 module = importlib.import_module(name)
124 except (importlib_metadata.PackageNotFoundError, ImportError) as err:
125 if errors == "raise":
126 if isinstance(err, ImportError):
127 raise type(err)(msg) from err # Typically ModuleNotFoundError
128 raise ImportError(msg) from err
129 return None
131 # Handle submodules: if we have submodule, grab parent module from sys.modules
132 parent = name.split(".")[0]
133 if parent != name:
134 install_name = parent
135 module_to_get = sys.modules[install_name]
136 else:
137 module_to_get = module
138 minimum_version = min_version if min_version is not None else VERSIONS.get(parent)
139 if minimum_version:
140 version = get_version(module_to_get)
141 if version and Version(version) < Version(minimum_version):
142 msg = (
143 f"Dask requires version '{minimum_version}' or newer of '{parent}' "
144 f"(version '{version}' currently installed)."
145 )
146 if errors == "warn":
147 warnings.warn(msg, UserWarning)
148 return None
149 elif errors == "raise":
150 raise ImportError(msg)
151 else:
152 return None
154 return module