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, pixi.toml, 

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

21 

22VERSIONS = { 

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

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

25 "bs4": "4.13.4", 

26 "bottleneck": "1.5.0", 

27 "fastparquet": "2024.11.0", 

28 "fsspec": "2025.7.0", 

29 "html5lib": "1.1", 

30 "gcsfs": "2025.7.0", 

31 "jinja2": "3.1.6", 

32 "lxml.etree": "6.0.0", 

33 "matplotlib": "3.10.5", 

34 "numba": "0.61.2", 

35 "numexpr": "2.11.0", 

36 "odfpy": "1.4.1", 

37 "openpyxl": "3.1.5", 

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

39 "pymysql": "1.1.1", 

40 "pyarrow": "16.0.0", 

41 "pyiceberg": "0.9.1", 

42 "pyreadstat": "1.3.0", 

43 "pytest": "8.3.4", 

44 "python-calamine": "0.4.0", 

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

46 "pyxlsb": "1.0.10", 

47 "s3fs": "2025.7.0", 

48 "scipy": "1.16.1", 

49 "sqlalchemy": "2.0.42", 

50 "tables": "3.10.2", 

51 "tabulate": "0.9.0", 

52 "xarray": "2025.7.1", 

53 "xlrd": "2.0.2", 

54 "xlsxwriter": "3.2.5", 

55 "zstandard": "0.23.0", 

56 "qtpy": "2.4.3", 

57 "pyqt5": "5.15.11", 

58} 

59 

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

61# these two names are different. 

62 

63INSTALL_MAPPING = { 

64 "bs4": "beautifulsoup4", 

65 "bottleneck": "Bottleneck", 

66 "jinja2": "Jinja2", 

67 "lxml.etree": "lxml", 

68 "odf": "odfpy", 

69 "python_calamine": "python-calamine", 

70 "sqlalchemy": "SQLAlchemy", 

71 "tables": "pytables", 

72} 

73 

74 

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

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

77 

78 if version is None: 

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

80 if module.__name__ == "psycopg2": 

81 # psycopg2 appends " (dt dec pq3 ext lo64)" to its version 

82 version = version.split()[0] 

83 return version 

84 

85 

86@overload 

87def import_optional_dependency( 

88 name: str, 

89 extra: str = ..., 

90 min_version: str | None = ..., 

91 *, 

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

93) -> types.ModuleType: ... 

94 

95 

96@overload 

97def import_optional_dependency( 

98 name: str, 

99 extra: str = ..., 

100 min_version: str | None = ..., 

101 *, 

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

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

104 

105 

106def import_optional_dependency( 

107 name: str, 

108 extra: str = "", 

109 min_version: str | None = None, 

110 *, 

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

112) -> types.ModuleType | None: 

113 """ 

114 Import an optional dependency. 

115 

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

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

118 we raise. 

119 

120 Parameters 

121 ---------- 

122 name : str 

123 The module name. 

124 extra : str 

125 Additional text to include in the ImportError message. 

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

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

128 

129 * raise : Raise an ImportError 

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

131 Warns that the version is too old and returns None 

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

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

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

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

136 min_version : str, default None 

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

138 minimum version required. 

139 Returns 

140 ------- 

141 maybe_module : Optional[ModuleType] 

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

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

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

145 is ``'warn'`` or ``'ignore'``. 

146 """ 

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

148 

149 package_name = INSTALL_MAPPING.get(name) 

150 install_name = package_name if package_name is not None else name 

151 

152 msg = ( 

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

154 f"Use pip, conda, or your preferred package management tool " 

155 f"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