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

55 statements  

1from __future__ import annotations 

2 

3import importlib 

4import sys 

5import types 

6import warnings 

7from typing import Literal, overload 

8 

9if sys.version_info >= (3, 12): 

10 import importlib.metadata as importlib_metadata 

11else: 

12 import importlib_metadata 

13from packaging.version import Version 

14 

15PY_VERSION = Version(".".join(map(str, sys.version_info[:3]))) 

16 

17EMSCRIPTEN = sys.platform == "emscripten" 

18 

19LINUX = sys.platform == "linux" 

20MACOS = sys.platform == "darwin" 

21WINDOWS = sys.platform == "win32" 

22 

23 

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} 

32 

33# A mapping from import name to package name (on PyPI) for packages where 

34# these two names are different. 

35 

36INSTALL_MAPPING = { 

37 "sqlalchemy": "SQLAlchemy", 

38 "tables": "pytables", 

39} 

40 

41 

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 

47 

48 

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: ... 

57 

58 

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: ... 

67 

68 

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. 

78 

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. 

82 

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. 

91 

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"} 

110 

111 package_name = INSTALL_MAPPING.get(name) 

112 install_name = package_name if package_name is not None else name 

113 

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 

130 

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 

153 

154 return module