From 239b0a7cf9c4e65fd001c0039980b02f8779f58c Mon Sep 17 00:00:00 2001 From: Thomas Li Date: Tue, 5 Jan 2021 16:33:46 -0800 Subject: [PATCH 1/2] WIP: Change raise_on_missing and on_version to errors --- pandas/compat/_optional.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/pandas/compat/_optional.py b/pandas/compat/_optional.py index def881b8fd863..6110ca794e2c2 100644 --- a/pandas/compat/_optional.py +++ b/pandas/compat/_optional.py @@ -62,8 +62,7 @@ def _get_version(module: types.ModuleType) -> str: def import_optional_dependency( name: str, extra: str = "", - raise_on_missing: bool = True, - on_version: str = "raise", + errors: str = "raise", min_version: Optional[str] = None, ): """ @@ -79,17 +78,16 @@ def import_optional_dependency( The module name. extra : str Additional text to include in the ImportError message. - raise_on_missing : bool, default True - Whether to raise if the optional dependency is not found. - When False and the module is not present, None is returned. - on_version : str {'raise', 'warn'} - What to do when a dependency's version is too old. + errors : str {'raise', 'warn', 'ignore'} + What to do when a dependency is not found or its version is too old. * raise : Raise an ImportError - * warn : Warn that the version is too old. Returns None - * ignore: Return the module, even if the version is too old. + * warn : Only applicable when a module's version is to old. + Warns that the version is too old and returns None + * ignore: If the module is not installed, return None, otherwise, + return the module, even if the version is too old. It's expected that users validate the version locally when - using ``on_version="ignore"`` (see. ``io/html.py``) + using ``errors="ignore"`` (see. ``io/html.py``) min_version : str, default None Specify a minimum version that is different from the global pandas minimum version required. @@ -97,11 +95,13 @@ def import_optional_dependency( ------- maybe_module : Optional[ModuleType] The imported module, when found and the version is correct. - None is returned when the package is not found and `raise_on_missing` - is False, or when the package's version is too old and `on_version` + None is returned when the package is not found and `errors` + is False, or when the package's version is too old and `errors` is ``'warn'``. """ + assert errors in {"warn", "raise", "ignore"} + package_name = INSTALL_MAPPING.get(name) install_name = package_name if package_name is not None else name @@ -112,7 +112,7 @@ def import_optional_dependency( try: module = importlib.import_module(name) except ImportError: - if raise_on_missing: + if errors == "raise": raise ImportError(msg) from None else: return None @@ -128,15 +128,14 @@ def import_optional_dependency( if minimum_version: version = _get_version(module_to_get) if distutils.version.LooseVersion(version) < minimum_version: - assert on_version in {"warn", "raise", "ignore"} msg = ( f"Pandas requires version '{minimum_version}' or newer of '{parent}' " f"(version '{version}' currently installed)." ) - if on_version == "warn": + if errors == "warn": warnings.warn(msg, UserWarning) return None - elif on_version == "raise": + elif errors == "raise": raise ImportError(msg) return module From 4edb404a181e9d2eabcee088f8cc426a29daf71f Mon Sep 17 00:00:00 2001 From: Thomas Li Date: Sat, 9 Jan 2021 19:04:45 -0800 Subject: [PATCH 2/2] Update all usages --- pandas/core/computation/check.py | 2 +- pandas/core/nanops.py | 2 +- pandas/io/excel/_base.py | 7 +------ pandas/io/excel/_util.py | 14 +++----------- pandas/io/html.py | 10 +++------- pandas/tests/io/excel/__init__.py | 5 +---- pandas/tests/io/excel/test_xlrd.py | 14 ++------------ pandas/tests/test_optional_dependency.py | 6 +++--- pandas/util/_print_versions.py | 4 +--- 9 files changed, 16 insertions(+), 48 deletions(-) diff --git a/pandas/core/computation/check.py b/pandas/core/computation/check.py index 6c7261b3b33c9..7be617de63a40 100644 --- a/pandas/core/computation/check.py +++ b/pandas/core/computation/check.py @@ -1,6 +1,6 @@ from pandas.compat._optional import import_optional_dependency -ne = import_optional_dependency("numexpr", raise_on_missing=False, on_version="warn") +ne = import_optional_dependency("numexpr", errors="warn") NUMEXPR_INSTALLED = ne is not None if NUMEXPR_INSTALLED: NUMEXPR_VERSION = ne.__version__ diff --git a/pandas/core/nanops.py b/pandas/core/nanops.py index 0c01ef21efbb5..fb9b20bd43d7c 100644 --- a/pandas/core/nanops.py +++ b/pandas/core/nanops.py @@ -34,7 +34,7 @@ from pandas.core.construction import extract_array -bn = import_optional_dependency("bottleneck", raise_on_missing=False, on_version="warn") +bn = import_optional_dependency("bottleneck", errors="warn") _BOTTLENECK_INSTALLED = bn is not None _USE_BOTTLENECK = False diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index b0ec8a1082a0e..aa6b4697f4d2c 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -1053,12 +1053,7 @@ def __init__( self._io = stringify_path(path_or_buffer) # Determine xlrd version if installed - if ( - import_optional_dependency( - "xlrd", raise_on_missing=False, on_version="ignore" - ) - is None - ): + if import_optional_dependency("xlrd", errors="ignore") is None: xlrd_version = None else: import xlrd diff --git a/pandas/io/excel/_util.py b/pandas/io/excel/_util.py index b5d0d1347f119..ab8e0fca4bf38 100644 --- a/pandas/io/excel/_util.py +++ b/pandas/io/excel/_util.py @@ -57,22 +57,14 @@ def get_default_engine(ext, mode="reader"): assert mode in ["reader", "writer"] if mode == "writer": # Prefer xlsxwriter over openpyxl if installed - xlsxwriter = import_optional_dependency( - "xlsxwriter", raise_on_missing=False, on_version="warn" - ) + xlsxwriter = import_optional_dependency("xlsxwriter", errors="warn") if xlsxwriter: _default_writers["xlsx"] = "xlsxwriter" return _default_writers[ext] else: if ( - import_optional_dependency( - "openpyxl", raise_on_missing=False, on_version="ignore" - ) - is None - and import_optional_dependency( - "xlrd", raise_on_missing=False, on_version="ignore" - ) - is not None + import_optional_dependency("openpyxl", errors="ignore") is None + and import_optional_dependency("xlrd", errors="ignore") is not None ): # if no openpyxl but xlrd installed, return xlrd # the version is handled elsewhere diff --git a/pandas/io/html.py b/pandas/io/html.py index 4a2d4af62f3e9..c445ee81ec8ed 100644 --- a/pandas/io/html.py +++ b/pandas/io/html.py @@ -39,17 +39,13 @@ def _importers(): return global _HAS_BS4, _HAS_LXML, _HAS_HTML5LIB - bs4 = import_optional_dependency("bs4", raise_on_missing=False, on_version="ignore") + bs4 = import_optional_dependency("bs4", errors="ignore") _HAS_BS4 = bs4 is not None - lxml = import_optional_dependency( - "lxml.etree", raise_on_missing=False, on_version="ignore" - ) + lxml = import_optional_dependency("lxml.etree", errors="ignore") _HAS_LXML = lxml is not None - html5lib = import_optional_dependency( - "html5lib", raise_on_missing=False, on_version="ignore" - ) + html5lib = import_optional_dependency("html5lib", errors="ignore") _HAS_HTML5LIB = html5lib is not None _IMPORTS = True diff --git a/pandas/tests/io/excel/__init__.py b/pandas/tests/io/excel/__init__.py index b7ceb28573484..7df035e6da17a 100644 --- a/pandas/tests/io/excel/__init__.py +++ b/pandas/tests/io/excel/__init__.py @@ -24,10 +24,7 @@ ] -if ( - import_optional_dependency("xlrd", raise_on_missing=False, on_version="ignore") - is None -): +if import_optional_dependency("xlrd", errors="ignore") is None: xlrd_version = None else: import xlrd diff --git a/pandas/tests/io/excel/test_xlrd.py b/pandas/tests/io/excel/test_xlrd.py index 19949cefa13d9..2d19b570e5d06 100644 --- a/pandas/tests/io/excel/test_xlrd.py +++ b/pandas/tests/io/excel/test_xlrd.py @@ -52,12 +52,7 @@ def test_excel_table_sheet_by_index(datapath, read_ext): def test_excel_file_warning_with_xlsx_file(datapath): # GH 29375 path = datapath("io", "data", "excel", "test1.xlsx") - has_openpyxl = ( - import_optional_dependency( - "openpyxl", raise_on_missing=False, on_version="ignore" - ) - is not None - ) + has_openpyxl = import_optional_dependency("openpyxl", errors="ignore") is not None if not has_openpyxl: with tm.assert_produces_warning( FutureWarning, @@ -73,12 +68,7 @@ def test_excel_file_warning_with_xlsx_file(datapath): def test_read_excel_warning_with_xlsx_file(datapath): # GH 29375 path = datapath("io", "data", "excel", "test1.xlsx") - has_openpyxl = ( - import_optional_dependency( - "openpyxl", raise_on_missing=False, on_version="ignore" - ) - is not None - ) + has_openpyxl = import_optional_dependency("openpyxl", errors="ignore") is not None if not has_openpyxl: if xlrd_version >= "2": with pytest.raises( diff --git a/pandas/tests/test_optional_dependency.py b/pandas/tests/test_optional_dependency.py index 304ec124ac8c5..b9cab2428c0d1 100644 --- a/pandas/tests/test_optional_dependency.py +++ b/pandas/tests/test_optional_dependency.py @@ -13,7 +13,7 @@ def test_import_optional(): with pytest.raises(ImportError, match=match): import_optional_dependency("notapackage") - result = import_optional_dependency("notapackage", raise_on_missing=False) + result = import_optional_dependency("notapackage", errors="ignore") assert result is None @@ -38,7 +38,7 @@ def test_bad_version(monkeypatch): assert result is module with tm.assert_produces_warning(UserWarning): - result = import_optional_dependency("fakemodule", on_version="warn") + result = import_optional_dependency("fakemodule", errors="warn") assert result is None module.__version__ = "1.0.0" # exact match is OK @@ -63,7 +63,7 @@ def test_submodule(monkeypatch): import_optional_dependency("fakemodule.submodule") with tm.assert_produces_warning(UserWarning): - result = import_optional_dependency("fakemodule.submodule", on_version="warn") + result = import_optional_dependency("fakemodule.submodule", errors="warn") assert result is None module.__version__ = "1.0.0" # exact match is OK diff --git a/pandas/util/_print_versions.py b/pandas/util/_print_versions.py index 5256cc29d5543..381dab4e3ce45 100644 --- a/pandas/util/_print_versions.py +++ b/pandas/util/_print_versions.py @@ -80,9 +80,7 @@ def _get_dependency_info() -> Dict[str, JSONSerializable]: result: Dict[str, JSONSerializable] = {} for modname in deps: - mod = import_optional_dependency( - modname, raise_on_missing=False, on_version="ignore" - ) + mod = import_optional_dependency(modname, errors="ignore") result[modname] = _get_version(mod) if mod else None return result