diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index a62bebec61ada..fae0a7a0d95f8 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -414,7 +414,7 @@ def test_dti_cmp_datetimelike(self, other, tz_naive_fixture): dti = date_range("2016-01-01", periods=2, tz=tz) if tz is not None: if isinstance(other, np.datetime64): - pytest.skip("no tzaware version available") + pytest.skip(f"{type(other).__name__} is not tz aware") other = localize_pydatetime(other, dti.tzinfo) result = dti == other diff --git a/pandas/tests/arithmetic/test_numeric.py b/pandas/tests/arithmetic/test_numeric.py index 8d49171cc43f3..c2fba3c775de9 100644 --- a/pandas/tests/arithmetic/test_numeric.py +++ b/pandas/tests/arithmetic/test_numeric.py @@ -384,7 +384,7 @@ def test_divmod_zero(self, zero, numeric_idx): def test_div_negative_zero(self, zero, numeric_idx, op): # Check that -1 / -0.0 returns np.inf, not -np.inf if numeric_idx.dtype == np.uint64: - pytest.skip(f"Not relevant for {numeric_idx.dtype}") + pytest.skip(f"Div by negative 0 not relevant for {numeric_idx.dtype}") idx = numeric_idx - 3 expected = Index([-np.inf, -np.inf, -np.inf, np.nan, np.inf], dtype=np.float64) diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py index 40c3069dfeebf..fe49446424de1 100644 --- a/pandas/tests/computation/test_eval.py +++ b/pandas/tests/computation/test_eval.py @@ -63,7 +63,7 @@ reason=f"numexpr enabled->{USE_NUMEXPR}, " f"installed->{NUMEXPR_INSTALLED}", ), - td.skip_if_no_ne, + td.skip_if_no("numexpr"), ], ) for engine in ENGINES @@ -1687,14 +1687,14 @@ def test_empty_globals(self, engine, parser): pd.eval(e, engine=engine, parser=parser, global_dict={}) -@td.skip_if_no_ne +@td.skip_if_no("numexpr") def test_invalid_engine(): msg = "Invalid engine 'asdf' passed" with pytest.raises(KeyError, match=msg): pd.eval("x + y", local_dict={"x": 1, "y": 2}, engine="asdf") -@td.skip_if_no_ne +@td.skip_if_no("numexpr") @pytest.mark.parametrize( ("use_numexpr", "expected"), ( @@ -1711,7 +1711,7 @@ def test_numexpr_option_respected(use_numexpr, expected): assert result == expected -@td.skip_if_no_ne +@td.skip_if_no("numexpr") def test_numexpr_option_incompatible_op(): # GH 32556 with pd.option_context("compute.use_numexpr", False): @@ -1723,7 +1723,7 @@ def test_numexpr_option_incompatible_op(): tm.assert_frame_equal(result, expected) -@td.skip_if_no_ne +@td.skip_if_no("numexpr") def test_invalid_parser(): msg = "Invalid parser 'asdf' passed" with pytest.raises(KeyError, match=msg): diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py index 8e99c074a2c55..d946b8da01fad 100644 --- a/pandas/tests/dtypes/test_common.py +++ b/pandas/tests/dtypes/test_common.py @@ -196,7 +196,7 @@ def test_is_object(): @pytest.mark.parametrize( - "check_scipy", [False, pytest.param(True, marks=td.skip_if_no_scipy)] + "check_scipy", [False, pytest.param(True, marks=td.skip_if_no("scipy"))] ) def test_is_sparse(check_scipy): msg = "is_sparse is deprecated" @@ -632,7 +632,7 @@ def test_is_bool_dtype_numpy_error(): @pytest.mark.parametrize( - "check_scipy", [False, pytest.param(True, marks=td.skip_if_no_scipy)] + "check_scipy", [False, pytest.param(True, marks=td.skip_if_no("scipy"))] ) def test_is_extension_array_dtype(check_scipy): assert not com.is_extension_array_dtype([1, 2, 3]) diff --git a/pandas/tests/extension/base/dim2.py b/pandas/tests/extension/base/dim2.py index 12006248b1db3..132cda5a94ed0 100644 --- a/pandas/tests/extension/base/dim2.py +++ b/pandas/tests/extension/base/dim2.py @@ -29,7 +29,7 @@ def skip_if_doesnt_support_2d(self, dtype, request): test_func = node._obj if test_func.__qualname__.startswith("Dim2CompatTests"): # TODO: is there a less hacky way of checking this? - pytest.skip("Test is only for EAs that support 2D.") + pytest.skip(f"{dtype} does not support 2D.") def test_transpose(self, data): arr2d = data.repeat(2).reshape(-1, 2) diff --git a/pandas/tests/extension/base/interface.py b/pandas/tests/extension/base/interface.py index f19561e5a862b..08e385c9389d9 100644 --- a/pandas/tests/extension/base/interface.py +++ b/pandas/tests/extension/base/interface.py @@ -103,7 +103,7 @@ def test_copy(self, data): result = data.copy() if data.dtype._is_immutable: - pytest.skip("test_copy assumes mutability") + pytest.skip(f"test_copy assumes mutability and {data.dtype} is immutable") data[1] = data[0] assert result[1] != result[0] @@ -118,7 +118,7 @@ def test_view(self, data): assert type(result) == type(data) if data.dtype._is_immutable: - pytest.skip("test_view assumes mutability") + pytest.skip(f"test_view assumes mutability and {data.dtype} is immutable") result[1] = result[0] assert data[1] == data[0] diff --git a/pandas/tests/extension/base/reduce.py b/pandas/tests/extension/base/reduce.py index 4477ba17c1683..6ea1b3a6fbe9d 100644 --- a/pandas/tests/extension/base/reduce.py +++ b/pandas/tests/extension/base/reduce.py @@ -120,7 +120,7 @@ def test_reduce_frame(self, data, all_numeric_reductions, skipna): op_name = all_numeric_reductions ser = pd.Series(data) if not is_numeric_dtype(ser.dtype): - pytest.skip("not numeric dtype") + pytest.skip(f"{ser.dtype} is not numeric dtype") if op_name in ["count", "kurt", "sem"]: pytest.skip(f"{op_name} not an array method") diff --git a/pandas/tests/extension/base/reshaping.py b/pandas/tests/extension/base/reshaping.py index c320ccfe4c548..227a1b76088cb 100644 --- a/pandas/tests/extension/base/reshaping.py +++ b/pandas/tests/extension/base/reshaping.py @@ -343,7 +343,7 @@ def test_ravel(self, data): assert type(result) == type(data) if data.dtype._is_immutable: - pytest.skip("test_ravel assumes mutability") + pytest.skip(f"test_ravel assumes mutability and {data.dtype} is immutable") # Check that we have a view, not a copy result[0] = result[1] @@ -360,7 +360,9 @@ def test_transpose(self, data): assert result.shape == data.shape[::-1] if data.dtype._is_immutable: - pytest.skip("test_transpose assumes mutability") + pytest.skip( + f"test_transpose assumes mutability and {data.dtype} is immutable" + ) # Check that we have a view, not a copy result[0] = result[1] diff --git a/pandas/tests/extension/base/setitem.py b/pandas/tests/extension/base/setitem.py index b8ede450b3c1a..2e2cbf5bf0d83 100644 --- a/pandas/tests/extension/base/setitem.py +++ b/pandas/tests/extension/base/setitem.py @@ -43,7 +43,7 @@ def skip_if_immutable(self, dtype, request): # This fixture is auto-used, but we want to not-skip # test_is_immutable. return - pytest.skip("__setitem__ test not applicable with immutable dtype") + pytest.skip(f"__setitem__ test not applicable with immutable dtype {dtype}") def test_is_immutable(self, data): if data.dtype._is_immutable: diff --git a/pandas/tests/extension/conftest.py b/pandas/tests/extension/conftest.py index 30b10541c8f66..27f6eabfd126e 100644 --- a/pandas/tests/extension/conftest.py +++ b/pandas/tests/extension/conftest.py @@ -37,7 +37,7 @@ def data_for_twos(dtype): if not (dtype._is_numeric or dtype.kind == "m"): # Object-dtypes may want to allow this, but for the most part # only numeric and timedelta-like dtypes will need to implement this. - pytest.skip("Not a numeric dtype") + pytest.skip(f"{dtype} is not a numeric dtype") raise NotImplementedError diff --git a/pandas/tests/extension/test_interval.py b/pandas/tests/extension/test_interval.py index f37ac4b289852..b427c12d2d40c 100644 --- a/pandas/tests/extension/test_interval.py +++ b/pandas/tests/extension/test_interval.py @@ -56,7 +56,7 @@ def data_missing(): @pytest.fixture def data_for_twos(): - pytest.skip("Not a numeric dtype") + pytest.skip("Interval is not a numeric dtype") @pytest.fixture diff --git a/pandas/tests/extension/test_numpy.py b/pandas/tests/extension/test_numpy.py index 04b25fd0566da..f1939ea174841 100644 --- a/pandas/tests/extension/test_numpy.py +++ b/pandas/tests/extension/test_numpy.py @@ -151,7 +151,7 @@ def data_for_grouping(allow_in_pandas, dtype): @pytest.fixture def data_for_twos(dtype): if dtype.kind == "O": - pytest.skip("Not a numeric dtype") + pytest.skip(f"{dtype} is not a numeric dtype") arr = np.ones(100) * 2 return NumpyExtensionArray._from_sequence(arr, dtype=dtype) @@ -323,7 +323,7 @@ def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool): expected = exp_op(skipna=skipna) tm.assert_almost_equal(result, expected) - @pytest.mark.skip("tests not written yet") + @pytest.mark.skip("TODO: tests not written yet") @pytest.mark.parametrize("skipna", [True, False]) def test_reduce_frame(self, data, all_numeric_reductions, skipna): pass diff --git a/pandas/tests/extension/test_string.py b/pandas/tests/extension/test_string.py index 840dd1057745f..8c5098a53adfc 100644 --- a/pandas/tests/extension/test_string.py +++ b/pandas/tests/extension/test_string.py @@ -26,22 +26,21 @@ from pandas.tests.extension import base -def split_array(arr): - if arr.dtype.storage != "pyarrow": - pytest.skip("only applicable for pyarrow chunked array n/a") - - def _split_array(arr): - import pyarrow as pa - - arrow_array = arr._pa_array - split = len(arrow_array) // 2 - arrow_array = pa.chunked_array( - [*arrow_array[:split].chunks, *arrow_array[split:].chunks] - ) - assert arrow_array.num_chunks == 2 - return type(arr)(arrow_array) - - return _split_array(arr) +def maybe_split_array(arr, chunked): + if not chunked: + return arr + elif arr.dtype.storage != "pyarrow": + return arr + + pa = pytest.importorskip("pyarrow") + + arrow_array = arr._pa_array + split = len(arrow_array) // 2 + arrow_array = pa.chunked_array( + [*arrow_array[:split].chunks, *arrow_array[split:].chunks] + ) + assert arrow_array.num_chunks == 2 + return type(arr)(arrow_array) @pytest.fixture(params=[True, False]) @@ -61,26 +60,26 @@ def data(dtype, chunked): strings = np.random.default_rng(2).choice(list(string.ascii_letters), size=100) arr = dtype.construct_array_type()._from_sequence(strings) - return split_array(arr) if chunked else arr + return maybe_split_array(arr, chunked) @pytest.fixture def data_missing(dtype, chunked): """Length 2 array with [NA, Valid]""" arr = dtype.construct_array_type()._from_sequence([pd.NA, "A"]) - return split_array(arr) if chunked else arr + return maybe_split_array(arr, chunked) @pytest.fixture def data_for_sorting(dtype, chunked): arr = dtype.construct_array_type()._from_sequence(["B", "C", "A"]) - return split_array(arr) if chunked else arr + return maybe_split_array(arr, chunked) @pytest.fixture def data_missing_for_sorting(dtype, chunked): arr = dtype.construct_array_type()._from_sequence(["B", pd.NA, "A"]) - return split_array(arr) if chunked else arr + return maybe_split_array(arr, chunked) @pytest.fixture @@ -88,7 +87,7 @@ def data_for_grouping(dtype, chunked): arr = dtype.construct_array_type()._from_sequence( ["B", "B", pd.NA, pd.NA, "A", "A", "B", "C"] ) - return split_array(arr) if chunked else arr + return maybe_split_array(arr, chunked) class TestDtype(base.BaseDtypeTests): diff --git a/pandas/tests/frame/methods/test_interpolate.py b/pandas/tests/frame/methods/test_interpolate.py index bb12d7e202e09..1ddab99947163 100644 --- a/pandas/tests/frame/methods/test_interpolate.py +++ b/pandas/tests/frame/methods/test_interpolate.py @@ -322,7 +322,7 @@ def test_rowwise_alt(self): # TODO: assert something? @pytest.mark.parametrize( - "check_scipy", [False, pytest.param(True, marks=td.skip_if_no_scipy)] + "check_scipy", [False, pytest.param(True, marks=td.skip_if_no("scipy"))] ) def test_interp_leading_nans(self, check_scipy): df = DataFrame( diff --git a/pandas/tests/frame/test_query_eval.py b/pandas/tests/frame/test_query_eval.py index 72e8236159bda..2e8c5258547c3 100644 --- a/pandas/tests/frame/test_query_eval.py +++ b/pandas/tests/frame/test_query_eval.py @@ -27,7 +27,8 @@ def parser(request): @pytest.fixture( - params=["python", pytest.param("numexpr", marks=td.skip_if_no_ne)], ids=lambda x: x + params=["python", pytest.param("numexpr", marks=td.skip_if_no("numexpr"))], + ids=lambda x: x, ) def engine(request): return request.param @@ -35,7 +36,7 @@ def engine(request): def skip_if_no_pandas_parser(parser): if parser != "pandas": - pytest.skip(f"cannot evaluate with parser {repr(parser)}") + pytest.skip(f"cannot evaluate with parser={parser}") class TestCompat: @@ -387,7 +388,7 @@ def to_series(mi, level): raise AssertionError("object must be a Series or Index") -@td.skip_if_no_ne +@td.skip_if_no("numexpr") class TestDataFrameQueryNumExprPandas: @pytest.fixture def engine(self): @@ -765,7 +766,7 @@ def test_method_calls_in_query(self, engine, parser): tm.assert_frame_equal(result, expected) -@td.skip_if_no_ne +@td.skip_if_no("numexpr") class TestDataFrameQueryNumExprPython(TestDataFrameQueryNumExprPandas): @pytest.fixture def engine(self): diff --git a/pandas/tests/frame/test_reductions.py b/pandas/tests/frame/test_reductions.py index 25d6dcaf53bfb..fc7c1b0f01fed 100644 --- a/pandas/tests/frame/test_reductions.py +++ b/pandas/tests/frame/test_reductions.py @@ -165,8 +165,8 @@ class TestDataFrameAnalytics: "var", "std", "sem", - pytest.param("skew", marks=td.skip_if_no_scipy), - pytest.param("kurt", marks=td.skip_if_no_scipy), + pytest.param("skew", marks=td.skip_if_no("scipy")), + pytest.param("kurt", marks=td.skip_if_no("scipy")), ], ) def test_stat_op_api_float_string_frame(self, float_string_frame, axis, opname): @@ -219,8 +219,8 @@ def test_stat_op_api_float_string_frame(self, float_string_frame, axis, opname): "var", "std", "sem", - pytest.param("skew", marks=td.skip_if_no_scipy), - pytest.param("kurt", marks=td.skip_if_no_scipy), + pytest.param("skew", marks=td.skip_if_no("scipy")), + pytest.param("kurt", marks=td.skip_if_no("scipy")), ], ) def test_stat_op_api_float_frame(self, float_frame, axis, opname): diff --git a/pandas/tests/groupby/test_reductions.py b/pandas/tests/groupby/test_reductions.py index e5836ce1e61c9..30ca3110ac2fa 100644 --- a/pandas/tests/groupby/test_reductions.py +++ b/pandas/tests/groupby/test_reductions.py @@ -933,7 +933,7 @@ def scipy_sem(*args, **kwargs): ("first", lambda x: x.iloc[0]), ("last", lambda x: x.iloc[-1]), ("count", np.size), - pytest.param("sem", scipy_sem, marks=td.skip_if_no_scipy), + pytest.param("sem", scipy_sem, marks=td.skip_if_no("scipy")), ], ) def test_ops_general(op, targop): diff --git a/pandas/tests/indexes/test_setops.py b/pandas/tests/indexes/test_setops.py index 1dfeb4f2c6b5b..d126d32e627cd 100644 --- a/pandas/tests/indexes/test_setops.py +++ b/pandas/tests/indexes/test_setops.py @@ -320,8 +320,9 @@ def test_corner_union(self, index_flat, fname, sname, expected_name): # Test unions with various name combinations # Do not test MultiIndex or repeats if not index_flat.is_unique: - pytest.skip("Randomly generated index_flat was not unique.") - index = index_flat + index = index_flat.unique() + else: + index = index_flat # Test copy.union(copy) first = index.copy().set_names(fname) @@ -363,8 +364,9 @@ def test_corner_union(self, index_flat, fname, sname, expected_name): ) def test_union_unequal(self, index_flat, fname, sname, expected_name): if not index_flat.is_unique: - pytest.skip("Randomly generated index_flat was not unique.") - index = index_flat + index = index_flat.unique() + else: + index = index_flat # test copy.union(subset) - need sort for unicode and string first = index.copy().set_names(fname) @@ -387,8 +389,9 @@ def test_corner_intersect(self, index_flat, fname, sname, expected_name): # GH#35847 # Test intersections with various name combinations if not index_flat.is_unique: - pytest.skip("Randomly generated index_flat was not unique.") - index = index_flat + index = index_flat.unique() + else: + index = index_flat # Test copy.intersection(copy) first = index.copy().set_names(fname) @@ -430,8 +433,9 @@ def test_corner_intersect(self, index_flat, fname, sname, expected_name): ) def test_intersect_unequal(self, index_flat, fname, sname, expected_name): if not index_flat.is_unique: - pytest.skip("Randomly generated index_flat was not unique.") - index = index_flat + index = index_flat.unique() + else: + index = index_flat # test copy.intersection(subset) - need sort for unicode and string first = index.copy().set_names(fname) diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index 946c621ae2b6e..b9ea440b3e859 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -12,6 +12,7 @@ import pytest from pandas.compat._constants import PY310 +from pandas.compat._optional import import_optional_dependency import pandas.util._test_decorators as td import pandas as pd @@ -1309,7 +1310,9 @@ class TestExcelWriterEngineTests: def test_ExcelWriter_dispatch(self, klass, ext): with tm.ensure_clean(ext) as path: with ExcelWriter(path) as writer: - if ext == ".xlsx" and td.safe_import("xlsxwriter"): + if ext == ".xlsx" and bool( + import_optional_dependency("xlsxwriter", errors="ignore") + ): # xlsxwriter has preference over openpyxl if both installed assert isinstance(writer, _XlsxWriter) else: diff --git a/pandas/tests/plotting/frame/test_frame_legend.py b/pandas/tests/plotting/frame/test_frame_legend.py index d2924930667b6..402a4b9531e5d 100644 --- a/pandas/tests/plotting/frame/test_frame_legend.py +++ b/pandas/tests/plotting/frame/test_frame_legend.py @@ -231,7 +231,7 @@ def test_legend_name(self): "line", "bar", "barh", - pytest.param("kde", marks=td.skip_if_no_scipy), + pytest.param("kde", marks=td.skip_if_no("scipy")), "area", "hist", ], diff --git a/pandas/tests/plotting/test_backend.py b/pandas/tests/plotting/test_backend.py index c0ad8e0c9608d..8a8643415ae12 100644 --- a/pandas/tests/plotting/test_backend.py +++ b/pandas/tests/plotting/test_backend.py @@ -80,7 +80,7 @@ def test_setting_backend_without_plot_raises(monkeypatch): assert pandas.options.plotting.backend == "matplotlib" -@td.skip_if_mpl +@td.skip_if_installed("matplotlib") def test_no_matplotlib_ok(): msg = ( 'matplotlib is required for plotting when the default backend "matplotlib" is ' diff --git a/pandas/tests/plotting/test_misc.py b/pandas/tests/plotting/test_misc.py index 5b06cd1ab296a..84f9cd87db97c 100644 --- a/pandas/tests/plotting/test_misc.py +++ b/pandas/tests/plotting/test_misc.py @@ -30,7 +30,7 @@ cm = pytest.importorskip("matplotlib.cm") -@td.skip_if_mpl +@td.skip_if_installed("matplotlib") def test_import_error_message(): # GH-19810 df = DataFrame({"A": [1, 2]}) diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py index 6719e39612f5d..f78f5c4879b9f 100644 --- a/pandas/tests/plotting/test_series.py +++ b/pandas/tests/plotting/test_series.py @@ -91,7 +91,7 @@ def test_plot_iseries(self, iseries): "line", "bar", "barh", - pytest.param("kde", marks=td.skip_if_no_scipy), + pytest.param("kde", marks=td.skip_if_no("scipy")), "hist", "box", ], diff --git a/pandas/tests/series/methods/test_interpolate.py b/pandas/tests/series/methods/test_interpolate.py index 426885bf41a1f..d854f0b787759 100644 --- a/pandas/tests/series/methods/test_interpolate.py +++ b/pandas/tests/series/methods/test_interpolate.py @@ -205,7 +205,7 @@ def test_interpolate_from_derivatives(self): [ {}, pytest.param( - {"method": "polynomial", "order": 1}, marks=td.skip_if_no_scipy + {"method": "polynomial", "order": 1}, marks=td.skip_if_no("scipy") ), ], ) @@ -253,7 +253,7 @@ def test_interpolate_non_ts(self): [ {}, pytest.param( - {"method": "polynomial", "order": 1}, marks=td.skip_if_no_scipy + {"method": "polynomial", "order": 1}, marks=td.skip_if_no("scipy") ), ], ) @@ -628,7 +628,7 @@ def test_interp_all_good(self): tm.assert_series_equal(result, s) @pytest.mark.parametrize( - "check_scipy", [False, pytest.param(True, marks=td.skip_if_no_scipy)] + "check_scipy", [False, pytest.param(True, marks=td.skip_if_no("scipy"))] ) def test_interp_multiIndex(self, check_scipy): idx = MultiIndex.from_tuples([(0, "a"), (1, "b"), (2, "c")]) diff --git a/pandas/tests/strings/test_extract.py b/pandas/tests/strings/test_extract.py index b8319e90e09a8..9ad9b1eca41d9 100644 --- a/pandas/tests/strings/test_extract.py +++ b/pandas/tests/strings/test_extract.py @@ -360,7 +360,7 @@ def test_extract_dataframe_capture_groups_index(index, any_string_dtype): data = ["A1", "B2", "C"] if len(index) < len(data): - pytest.skip("Index too short") + pytest.skip(f"Index needs more than {len(data)} values") index = index[: len(data)] s = Series(data, index=index, dtype=any_string_dtype) diff --git a/pandas/tests/util/test_safe_import.py b/pandas/tests/util/test_safe_import.py deleted file mode 100644 index bd07bea934ed3..0000000000000 --- a/pandas/tests/util/test_safe_import.py +++ /dev/null @@ -1,39 +0,0 @@ -import sys -import types - -import pytest - -import pandas.util._test_decorators as td - - -@pytest.mark.parametrize("name", ["foo", "hello123"]) -def test_safe_import_non_existent(name): - assert not td.safe_import(name) - - -def test_safe_import_exists(): - assert td.safe_import("pandas") - - -@pytest.mark.parametrize("min_version,valid", [("0.0.0", True), ("99.99.99", False)]) -def test_safe_import_versions(min_version, valid): - result = td.safe_import("pandas", min_version=min_version) - result = result if valid else not result - assert result - - -@pytest.mark.parametrize( - "min_version,valid", [(None, False), ("1.0", True), ("2.0", False)] -) -def test_safe_import_dummy(monkeypatch, min_version, valid): - mod_name = "hello123" - - mod = types.ModuleType(mod_name) - mod.__version__ = "1.5" - - if min_version is not None: - monkeypatch.setitem(sys.modules, mod_name, mod) - - result = td.safe_import(mod_name, min_version=min_version) - result = result if valid else not result - assert result diff --git a/pandas/tests/window/test_rolling_functions.py b/pandas/tests/window/test_rolling_functions.py index 51f801ab3761b..5906ff52db098 100644 --- a/pandas/tests/window/test_rolling_functions.py +++ b/pandas/tests/window/test_rolling_functions.py @@ -346,7 +346,7 @@ def test_center_reindex_frame(frame, roll_func, kwargs, minp, fill_value): lambda x: x.rolling(window=10, min_periods=5).apply(sum, raw=True), pytest.param( lambda x: x.rolling(win_type="boxcar", window=10, min_periods=5).mean(), - marks=td.skip_if_no_scipy, + marks=td.skip_if_no("scipy"), ), ], ) @@ -508,7 +508,7 @@ def test_rolling_min_max_numeric_types(data_type): lambda x: x.rolling(window=10, min_periods=5).apply(sum, raw=True), pytest.param( lambda x: x.rolling(win_type="boxcar", window=10, min_periods=5).mean(), - marks=td.skip_if_no_scipy, + marks=td.skip_if_no("scipy"), ), ], ) diff --git a/pandas/util/_test_decorators.py b/pandas/util/_test_decorators.py index e71b4881a58a4..2c1912bce856d 100644 --- a/pandas/util/_test_decorators.py +++ b/pandas/util/_test_decorators.py @@ -2,8 +2,8 @@ This module provides decorator functions which can be applied to test objects in order to skip those objects when certain conditions occur. A sample use case is to detect if the platform is missing ``matplotlib``. If so, any test objects -which require ``matplotlib`` and decorated with ``@td.skip_if_no_mpl`` will be -skipped by ``pytest`` during the execution of the test suite. +which require ``matplotlib`` and decorated with ``@td.skip_if_no("matplotlib")`` +will be skipped by ``pytest`` during the execution of the test suite. To illustrate, after importing this module: @@ -11,13 +11,13 @@ The decorators can be applied to classes: -@td.skip_if_some_reason +@td.skip_if_no("package") class Foo: ... Or individual functions: -@td.skip_if_some_reason +@td.skip_if_no("package") def test_foo(): ... @@ -44,59 +44,7 @@ def test_foo(): IS64, is_platform_windows, ) - -from pandas.core.computation.expressions import ( - NUMEXPR_INSTALLED, - USE_NUMEXPR, -) -from pandas.util.version import Version - - -def safe_import(mod_name: str, min_version: str | None = None): - """ - Parameters - ---------- - mod_name : str - Name of the module to be imported - min_version : str, default None - Minimum required version of the specified mod_name - - Returns - ------- - object - The imported module if successful, or False - """ - try: - mod = __import__(mod_name) - except ImportError: - return False - - if not min_version: - return mod - else: - import sys - - version = getattr(sys.modules[mod_name], "__version__") - if version and Version(version) >= Version(min_version): - return mod - - return False - - -def _skip_if_not_us_locale() -> bool: - lang, _ = locale.getlocale() - if lang != "en_US": - return True - return False - - -def _skip_if_no_scipy() -> bool: - return not ( - safe_import("scipy.stats") - and safe_import("scipy.sparse") - and safe_import("scipy.interpolate") - and safe_import("scipy.signal") - ) +from pandas.compat._optional import import_optional_dependency def skip_if_installed(package: str) -> pytest.MarkDecorator: @@ -115,7 +63,8 @@ def skip_if_installed(package: str) -> pytest.MarkDecorator: parametrization mark. """ return pytest.mark.skipif( - safe_import(package), reason=f"Skipping because {package} is installed." + bool(import_optional_dependency(package, errors="ignore")), + reason=f"Skipping because {package} is installed.", ) @@ -154,25 +103,20 @@ def skip_if_no(package: str, min_version: str | None = None) -> pytest.MarkDecor if min_version: msg += f" satisfying a min_version of {min_version}" return pytest.mark.skipif( - not safe_import(package, min_version=min_version), reason=msg + not bool( + import_optional_dependency( + package, errors="ignore", min_version=min_version + ) + ), + reason=msg, ) -skip_if_mpl = pytest.mark.skipif( - bool(safe_import("matplotlib")), reason="matplotlib is present" -) skip_if_32bit = pytest.mark.skipif(not IS64, reason="skipping for 32 bit") skip_if_windows = pytest.mark.skipif(is_platform_windows(), reason="Running on Windows") skip_if_not_us_locale = pytest.mark.skipif( - _skip_if_not_us_locale(), - reason=f"Specific locale is set {locale.getlocale()[0]}", -) -skip_if_no_scipy = pytest.mark.skipif( - _skip_if_no_scipy(), reason="Missing SciPy requirement" -) -skip_if_no_ne = pytest.mark.skipif( - not USE_NUMEXPR, - reason=f"numexpr enabled->{USE_NUMEXPR}, installed->{NUMEXPR_INSTALLED}", + locale.getlocale()[0] != "en_US", + reason=f"Set local {locale.getlocale()[0]} is not en_US", )