diff --git a/doc/source/install.rst b/doc/source/install.rst index 1c1f0c1d4cf8e..ee4b36f898e31 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -286,6 +286,7 @@ psycopg2 PostgreSQL engine for sqlalchemy pyarrow 0.9.0 Parquet and feather reading / writing pymysql MySQL engine for sqlalchemy pyreadstat SPSS files (.sav) reading +pytables 3.4.2 HDF5 reading / writing qtpy Clipboard I/O s3fs 0.0.8 Amazon S3 access xarray 0.8.2 pandas-like API for N-dimensional data diff --git a/pandas/compat/_optional.py b/pandas/compat/_optional.py index 4a7b8c4e88649..875edb3d3f1dd 100644 --- a/pandas/compat/_optional.py +++ b/pandas/compat/_optional.py @@ -19,6 +19,7 @@ "s3fs": "0.0.8", "scipy": "0.19.0", "sqlalchemy": "1.1.4", + "tables": "3.4.2", "xarray": "0.8.2", "xlrd": "1.1.0", "xlwt": "1.2.0", diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py index 983b1286eec91..79d6d8563a162 100644 --- a/pandas/io/pytables.py +++ b/pandas/io/pytables.py @@ -19,6 +19,7 @@ from pandas._libs import lib, writers as libwriters from pandas._libs.tslibs import timezones +from pandas.compat._optional import import_optional_dependency from pandas.errors import PerformanceWarning from pandas.core.dtypes.common import ( @@ -448,11 +449,7 @@ def __init__(self, path, mode=None, complevel=None, complib=None, if 'format' in kwargs: raise ValueError('format is not a defined argument for HDFStore') - try: - import tables # noqa - except ImportError as ex: # pragma: no cover - raise ImportError('HDFStore requires PyTables, "{ex!s}" problem ' - 'importing'.format(ex=ex)) + tables = import_optional_dependency("tables") if complib is not None and complib not in tables.filters.all_complibs: raise ValueError( diff --git a/pandas/tests/io/test_pytables_missing.py b/pandas/tests/io/test_pytables_missing.py new file mode 100644 index 0000000000000..4ceb80889c989 --- /dev/null +++ b/pandas/tests/io/test_pytables_missing.py @@ -0,0 +1,14 @@ +import pytest + +import pandas.util._test_decorators as td + +import pandas as pd +import pandas.util.testing as tm + + +@td.skip_if_installed("tables") +def test_pytables_raises(): + df = pd.DataFrame({"A": [1, 2]}) + with pytest.raises(ImportError, match="tables"): + with tm.ensure_clean("foo.h5") as path: + df.to_hdf(path, "df") diff --git a/pandas/util/_print_versions.py b/pandas/util/_print_versions.py index a5c86c2cc80b3..5e2e013c4afcc 100644 --- a/pandas/util/_print_versions.py +++ b/pandas/util/_print_versions.py @@ -1,5 +1,4 @@ import codecs -import importlib import locale import os import platform @@ -7,6 +6,9 @@ import subprocess import sys +from pandas.compat._optional import ( + VERSIONS, _get_version, import_optional_dependency) + def get_sys_info(): "Returns system information as a dict" @@ -58,60 +60,49 @@ def get_sys_info(): def show_versions(as_json=False): sys_info = get_sys_info() - deps = [ - # (MODULE_NAME, f(mod) -> mod version) - ("pandas", lambda mod: mod.__version__), - ("pytest", lambda mod: mod.__version__), - ("pip", lambda mod: mod.__version__), - ("setuptools", lambda mod: mod.__version__), - ("Cython", lambda mod: mod.__version__), - ("numpy", lambda mod: mod.version.version), - ("scipy", lambda mod: mod.version.version), - ("pyarrow", lambda mod: mod.__version__), - ("xarray", lambda mod: mod.__version__), - ("IPython", lambda mod: mod.__version__), - ("sphinx", lambda mod: mod.__version__), - ("patsy", lambda mod: mod.__version__), - ("dateutil", lambda mod: mod.__version__), - ("pytz", lambda mod: mod.VERSION), - ("blosc", lambda mod: mod.__version__), - ("bottleneck", lambda mod: mod.__version__), - ("tables", lambda mod: mod.__version__), - ("numexpr", lambda mod: mod.__version__), - ("feather", lambda mod: mod.__version__), - ("matplotlib", lambda mod: mod.__version__), - ("openpyxl", lambda mod: mod.__version__), - ("xlrd", lambda mod: mod.__VERSION__), - ("xlwt", lambda mod: mod.__VERSION__), - ("xlsxwriter", lambda mod: mod.__version__), - ("lxml.etree", lambda mod: mod.__version__), - ("bs4", lambda mod: mod.__version__), - ("html5lib", lambda mod: mod.__version__), - ("sqlalchemy", lambda mod: mod.__version__), - ("pymysql", lambda mod: mod.__version__), - ("psycopg2", lambda mod: mod.__version__), - ("jinja2", lambda mod: mod.__version__), - ("s3fs", lambda mod: mod.__version__), - ("fastparquet", lambda mod: mod.__version__), - ("pandas_gbq", lambda mod: mod.__version__), - ("pandas_datareader", lambda mod: mod.__version__), - ("gcsfs", lambda mod: mod.__version__), + 'pandas', + # required + 'numpy', + 'pytz', + 'dateutil', + # install / build, + 'pip', + 'setuptools', + 'Cython', + # test + 'pytest', + 'hypothesis', + # docs + "sphinx", + # Other, need a min version + "blosc", + "feather", + "xlsxwriter", + "lxml.etree", + "html5lib", + "pymysql", + "psycopg2", + "jinja2", + # Other, not imported. + "IPython", + "pandas_datareader", ] - deps_blob = list() - for (modname, ver_f) in deps: - try: - if modname in sys.modules: - mod = sys.modules[modname] - else: - mod = importlib.import_module(modname) - ver = ver_f(mod) - deps_blob.append((modname, ver)) - except ImportError: - deps_blob.append((modname, None)) + deps.extend(list(VERSIONS)) + deps_blob = [] - if (as_json): + for modname in deps: + mod = import_optional_dependency(modname, + raise_on_missing=False, + on_version="ignore") + if mod: + ver = _get_version(mod) + else: + ver = None + deps_blob.append((modname, ver)) + + if as_json: try: import json except ImportError: @@ -126,16 +117,15 @@ def show_versions(as_json=False): json.dump(j, f, indent=2) else: - + maxlen = max(len(x) for x in deps) + tpl = '{{k:<{maxlen}}}: {{stat}}'.format(maxlen=maxlen) print("\nINSTALLED VERSIONS") print("------------------") - for k, stat in sys_info: - print("{k}: {stat}".format(k=k, stat=stat)) - + print(tpl.format(k=k, stat=stat)) print("") for k, stat in deps_blob: - print("{k}: {stat}".format(k=k, stat=stat)) + print(tpl.format(k=k, stat=stat)) def main(): diff --git a/pandas/util/_test_decorators.py b/pandas/util/_test_decorators.py index 0cb82c0028c90..fd9c9d07a974e 100644 --- a/pandas/util/_test_decorators.py +++ b/pandas/util/_test_decorators.py @@ -100,6 +100,23 @@ def _skip_if_no_scipy(): safe_import('scipy.signal')) +def skip_if_installed( + package: str, +) -> MarkDecorator: + """ + Skip a test if a package is installed. + + Parameters + ---------- + package : str + The name of the package. + """ + return pytest.mark.skipif( + safe_import(package), + reason="Skipping because {} is installed.".format(package) + ) + + def skip_if_no( package: str, min_version: Optional[str] = None