Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/compat/_optional.py: 50%

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

50 statements  

1from __future__ import annotations 

2 

3import importlib 

4import sys 

5from typing import ( 

6 TYPE_CHECKING, 

7 Literal, 

8 overload, 

9) 

10import warnings 

11 

12from pandas.util._exceptions import find_stack_level 

13 

14from pandas.util.version import Version 

15 

16if TYPE_CHECKING: 

17 import types 

18 

19# Update install.rst, actions-311-minimum_versions.yaml, 

20# deps_minimum.toml & pyproject.toml when updating versions! 

21 

22VERSIONS = { 

23 "adbc-driver-postgresql": "1.2.0", 

24 "adbc-driver-sqlite": "1.2.0", 

25 "bs4": "4.12.3", 

26 "bottleneck": "1.4.2", 

27 "fastparquet": "2024.11.0", 

28 "fsspec": "2024.10.0", 

29 "html5lib": "1.1", 

30 "hypothesis": "6.116.0", 

31 "gcsfs": "2024.10.0", 

32 "jinja2": "3.1.5", 

33 "lxml.etree": "5.3.0", 

34 "matplotlib": "3.9.3", 

35 "numba": "0.60.0", 

36 "numexpr": "2.10.2", 

37 "odfpy": "1.4.1", 

38 "openpyxl": "3.1.5", 

39 "psycopg2": "2.9.10", # (dt dec pq3 ext lo64) 

40 "pymysql": "1.1.1", 

41 "pyarrow": "13.0.0", 

42 "pyiceberg": "0.8.1", 

43 "pyreadstat": "1.2.8", 

44 "pytest": "8.3.4", 

45 "python-calamine": "0.3.0", 

46 "pytz": "2020.1", # keep this pinned (https://github.com/pandas-dev/pandas/pull/65133) 

47 "pyxlsb": "1.0.10", 

48 "s3fs": "2024.10.0", 

49 "scipy": "1.14.1", 

50 "sqlalchemy": "2.0.36", 

51 "tables": "3.10.1", 

52 "tabulate": "0.9.0", 

53 "xarray": "2024.10.0", 

54 "xlrd": "2.0.1", 

55 "xlsxwriter": "3.2.0", 

56 "zstandard": "0.23.0", 

57 "qtpy": "2.4.2", 

58 "pyqt5": "5.15.9", 

59} 

60 

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

62# these two names are different. 

63 

64INSTALL_MAPPING = { 

65 "bs4": "beautifulsoup4", 

66 "bottleneck": "Bottleneck", 

67 "jinja2": "Jinja2", 

68 "lxml.etree": "lxml", 

69 "odf": "odfpy", 

70 "python_calamine": "python-calamine", 

71 "sqlalchemy": "SQLAlchemy", 

72 "tables": "pytables", 

73} 

74 

75 

76def get_version(module: types.ModuleType) -> str: 

77 version = getattr(module, "__version__", None) 

78 

79 if version is None: 

80 raise ImportError(f"Can't determine version for {module.__name__}") 

81 if module.__name__ == "psycopg2": 

82 # psycopg2 appends " (dt dec pq3 ext lo64)" to it's version 

83 version = version.split()[0] 

84 return version 

85 

86 

87@overload 

88def import_optional_dependency( 

89 name: str, 

90 extra: str = ..., 

91 min_version: str | None = ..., 

92 *, 

93 errors: Literal["raise"] = ..., 

94) -> types.ModuleType: ... 

95 

96 

97@overload 

98def import_optional_dependency( 

99 name: str, 

100 extra: str = ..., 

101 min_version: str | None = ..., 

102 *, 

103 errors: Literal["warn", "ignore"], 

104) -> types.ModuleType | None: ... 

105 

106 

107def import_optional_dependency( 

108 name: str, 

109 extra: str = "", 

110 min_version: str | None = None, 

111 *, 

112 errors: Literal["raise", "warn", "ignore"] = "raise", 

113) -> types.ModuleType | None: 

114 """ 

115 Import an optional dependency. 

116 

117 By default, if a dependency is missing an ImportError with a nice 

118 message will be raised. If a dependency is present, but too old, 

119 we raise. 

120 

121 Parameters 

122 ---------- 

123 name : str 

124 The module name. 

125 extra : str 

126 Additional text to include in the ImportError message. 

127 errors : str {'raise', 'warn', 'ignore'} 

128 What to do when a dependency is not found or its version is too old. 

129 

130 * raise : Raise an ImportError 

131 * warn : Only applicable when a module's version is to old. 

132 Warns that the version is too old and returns None 

133 * ignore: If the module is not installed, return None, otherwise, 

134 return the module, even if the version is too old. 

135 It's expected that users validate the version locally when 

136 using ``errors="ignore"`` (see. ``io/html.py``) 

137 min_version : str, default None 

138 Specify a minimum version that is different from the global pandas 

139 minimum version required. 

140 Returns 

141 ------- 

142 maybe_module : Optional[ModuleType] 

143 The imported module, when found and the version is correct. 

144 None is returned when the package is not found and `errors` 

145 is False, or when the package's version is too old and `errors` 

146 is ``'warn'`` or ``'ignore'``. 

147 """ 

148 assert errors in {"warn", "raise", "ignore"} 

149 

150 package_name = INSTALL_MAPPING.get(name) 

151 install_name = package_name if package_name is not None else name 

152 

153 msg = ( 

154 f"`Import {install_name}` failed. {extra} " 

155 f"Use pip or conda to install the {install_name} package." 

156 ) 

157 try: 

158 module = importlib.import_module(name) 

159 except ImportError as err: 

160 if errors == "raise": 

161 raise ImportError(msg) from err 

162 return None 

163 

164 # Handle submodules: if we have submodule, grab parent module from sys.modules 

165 parent = name.split(".", maxsplit=1)[0] 

166 if parent != name: 

167 install_name = parent 

168 module_to_get = sys.modules[install_name] 

169 else: 

170 module_to_get = module 

171 minimum_version = min_version if min_version is not None else VERSIONS.get(parent) 

172 if minimum_version: 

173 version = get_version(module_to_get) 

174 if version and Version(version) < Version(minimum_version): 

175 msg = ( 

176 f"Pandas requires version '{minimum_version}' or newer of '{parent}' " 

177 f"(version '{version}' currently installed)." 

178 ) 

179 if errors == "warn": 

180 warnings.warn( 

181 msg, 

182 UserWarning, 

183 stacklevel=find_stack_level(), 

184 ) 

185 return None 

186 elif errors == "raise": 

187 raise ImportError(msg) 

188 else: 

189 return None 

190 

191 return module