From 01623f8f733fff3bec26a06a82e2c216f03fb226 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Sat, 1 Feb 2020 16:13:05 +0100 Subject: [PATCH] CLN: Refactor pandas/tests/base - part3 (#30147) --- pandas/conftest.py | 10 ++ pandas/core/ops/__init__.py | 2 +- pandas/tests/base/test_ops.py | 112 ++++++-------------- pandas/tests/indexes/conftest.py | 2 +- pandas/tests/indexes/datetimes/test_ops.py | 29 +---- pandas/tests/indexes/period/test_ops.py | 18 +--- pandas/tests/indexes/timedeltas/test_ops.py | 15 +-- pandas/tests/series/conftest.py | 10 -- 8 files changed, 53 insertions(+), 145 deletions(-) diff --git a/pandas/conftest.py b/pandas/conftest.py index 0c964452df5da..131a011c5a101 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -868,6 +868,16 @@ def tick_classes(request): ) +@pytest.fixture +def datetime_series(): + """ + Fixture for Series of floats with DatetimeIndex + """ + s = tm.makeTimeSeries() + s.name = "ts" + return s + + @pytest.fixture def float_frame(): """ diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 6d2253c5dc87d..0312c11a6d590 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -266,7 +266,7 @@ def _get_opstr(op): rtruediv: "/", operator.floordiv: "//", rfloordiv: "//", - operator.mod: None, # TODO: Why None for mod but '%' for rmod? + operator.mod: "%", rmod: "%", operator.pow: "**", rpow: "**", diff --git a/pandas/tests/base/test_ops.py b/pandas/tests/base/test_ops.py index 08ec57bd69ad4..e522c7f743a05 100644 --- a/pandas/tests/base/test_ops.py +++ b/pandas/tests/base/test_ops.py @@ -1,6 +1,7 @@ from datetime import datetime, timedelta from io import StringIO import sys +from typing import Any import numpy as np import pytest @@ -30,17 +31,15 @@ Timestamp, ) import pandas._testing as tm -from pandas.core.indexes.datetimelike import DatetimeIndexOpsMixin -class Ops: - def _allow_na_ops(self, obj): - """Whether to skip test cases including NaN""" - if (isinstance(obj, Index) and obj.is_boolean()) or not obj._can_hold_na: - # don't test boolean / integer dtypes - return False - return True +def allow_na_ops(obj: Any) -> bool: + """Whether to skip test cases including NaN""" + is_bool_index = isinstance(obj, Index) and obj.is_boolean() + return not is_bool_index and obj._can_hold_na + +class Ops: def setup_method(self, method): self.bool_index = tm.makeBoolIndex(10, name="a") self.int_index = tm.makeIntIndex(10, name="a") @@ -83,74 +82,31 @@ def setup_method(self, method): self.objs = self.indexes + self.series + self.narrow_series - def check_ops_properties(self, props, filter=None, ignore_failures=False): - for op in props: - for o in self.is_valid_objs: - - # if a filter, skip if it doesn't match - if filter is not None: - filt = o.index if isinstance(o, Series) else o - if not filter(filt): - continue - - try: - if isinstance(o, Series): - expected = Series(getattr(o.index, op), index=o.index, name="a") - else: - expected = getattr(o, op) - except (AttributeError): - if ignore_failures: - continue - - result = getattr(o, op) - - # these could be series, arrays or scalars - if isinstance(result, Series) and isinstance(expected, Series): - tm.assert_series_equal(result, expected) - elif isinstance(result, Index) and isinstance(expected, Index): - tm.assert_index_equal(result, expected) - elif isinstance(result, np.ndarray) and isinstance( - expected, np.ndarray - ): - tm.assert_numpy_array_equal(result, expected) - else: - assert result == expected - - # freq raises AttributeError on an Int64Index because its not - # defined we mostly care about Series here anyhow - if not ignore_failures: - for o in self.not_valid_objs: - - # an object that is datetimelike will raise a TypeError, - # otherwise an AttributeError - msg = "no attribute" - err = AttributeError - if issubclass(type(o), DatetimeIndexOpsMixin): - err = TypeError - with pytest.raises(err, match=msg): - getattr(o, op) - - @pytest.mark.parametrize("klass", [Series, DataFrame]) - def test_binary_ops_docs(self, klass): - op_map = { - "add": "+", - "sub": "-", - "mul": "*", - "mod": "%", - "pow": "**", - "truediv": "/", - "floordiv": "//", - } - for op_name in op_map: - operand1 = klass.__name__.lower() - operand2 = "other" - op = op_map[op_name] - expected_str = " ".join([operand1, op, operand2]) - assert expected_str in getattr(klass, op_name).__doc__ - - # reverse version of the binary ops - expected_str = " ".join([operand2, op, operand1]) - assert expected_str in getattr(klass, "r" + op_name).__doc__ + +@pytest.mark.parametrize( + "op_name, op", + [ + ("add", "+"), + ("sub", "-"), + ("mul", "*"), + ("mod", "%"), + ("pow", "**"), + ("truediv", "/"), + ("floordiv", "//"), + ], +) +@pytest.mark.parametrize("klass", [Series, DataFrame]) +def test_binary_ops(klass, op_name, op): + # not using the all_arithmetic_functions fixture with _get_opstr + # as _get_opstr is used internally in the dynamic implementation of the docstring + operand1 = klass.__name__.lower() + operand2 = "other" + expected_str = " ".join([operand1, op, operand2]) + assert expected_str in getattr(klass, op_name).__doc__ + + # reverse version of the binary ops + expected_str = " ".join([operand2, op, operand1]) + assert expected_str in getattr(klass, "r" + op_name).__doc__ class TestTranspose(Ops): @@ -313,7 +269,7 @@ def test_value_counts_unique_nunique_null(self, null_obj): klass = type(o) values = o._ndarray_values - if not self._allow_na_ops(o): + if not allow_na_ops(o): continue # special assign to the numpy array @@ -794,7 +750,7 @@ def test_fillna(self): o = orig.copy() klass = type(o) - if not self._allow_na_ops(o): + if not allow_na_ops(o): continue if needs_i8_conversion(o): diff --git a/pandas/tests/indexes/conftest.py b/pandas/tests/indexes/conftest.py index 8e0366138f71e..57174f206b70d 100644 --- a/pandas/tests/indexes/conftest.py +++ b/pandas/tests/indexes/conftest.py @@ -14,7 +14,7 @@ "uint": tm.makeUIntIndex(100), "range": tm.makeRangeIndex(100), "float": tm.makeFloatIndex(100), - "bool": Index([True, False]), + "bool": tm.makeBoolIndex(2), "categorical": tm.makeCategoricalIndex(100), "interval": tm.makeIntervalIndex(100), "empty": Index([]), diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index ecd4ace705e9e..8ed98410ad9a4 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -7,44 +7,23 @@ from pandas.core.dtypes.generic import ABCDateOffset import pandas as pd -from pandas import ( - DatetimeIndex, - Index, - PeriodIndex, - Series, - Timestamp, - bdate_range, - date_range, -) +from pandas import DatetimeIndex, Index, Series, Timestamp, bdate_range, date_range import pandas._testing as tm -from pandas.tests.base.test_ops import Ops from pandas.tseries.offsets import BDay, BMonthEnd, CDay, Day, Hour START, END = datetime(2009, 1, 1), datetime(2010, 1, 1) -class TestDatetimeIndexOps(Ops): - def setup_method(self, method): - super().setup_method(method) - mask = lambda x: (isinstance(x, DatetimeIndex) or isinstance(x, PeriodIndex)) - self.is_valid_objs = [o for o in self.objs if mask(o)] - self.not_valid_objs = [o for o in self.objs if not mask(o)] - - def test_ops_properties(self): - f = lambda x: isinstance(x, DatetimeIndex) - self.check_ops_properties(DatetimeIndex._field_ops, f) - self.check_ops_properties(DatetimeIndex._object_ops, f) - self.check_ops_properties(DatetimeIndex._bool_ops, f) - - def test_ops_properties_basic(self): +class TestDatetimeIndexOps: + def test_ops_properties_basic(self, datetime_series): # sanity check that the behavior didn't change # GH#7206 for op in ["year", "day", "second", "weekday"]: msg = f"'Series' object has no attribute '{op}'" with pytest.raises(AttributeError, match=msg): - getattr(self.dt_series, op) + getattr(datetime_series, op) # attribute access should still work! s = Series(dict(year=2000, month=1, day=10)) diff --git a/pandas/tests/indexes/period/test_ops.py b/pandas/tests/indexes/period/test_ops.py index 427d9ab712320..2e4bed598b807 100644 --- a/pandas/tests/indexes/period/test_ops.py +++ b/pandas/tests/indexes/period/test_ops.py @@ -2,25 +2,11 @@ import pytest import pandas as pd -from pandas import DatetimeIndex, Index, NaT, PeriodIndex, Series +from pandas import Index, NaT, PeriodIndex, Series import pandas._testing as tm -from pandas.core.arrays import PeriodArray -from pandas.tests.base.test_ops import Ops -class TestPeriodIndexOps(Ops): - def setup_method(self, method): - super().setup_method(method) - mask = lambda x: (isinstance(x, DatetimeIndex) or isinstance(x, PeriodIndex)) - self.is_valid_objs = [o for o in self.objs if mask(o)] - self.not_valid_objs = [o for o in self.objs if not mask(o)] - - def test_ops_properties(self): - f = lambda x: isinstance(x, PeriodIndex) - self.check_ops_properties(PeriodArray._field_ops, f) - self.check_ops_properties(PeriodArray._object_ops, f) - self.check_ops_properties(PeriodArray._bool_ops, f) - +class TestPeriodIndexOps: def test_resolution(self): for freq, expected in zip( ["A", "Q", "M", "D", "H", "T", "S", "L", "U"], diff --git a/pandas/tests/indexes/timedeltas/test_ops.py b/pandas/tests/indexes/timedeltas/test_ops.py index 25f27da758ad8..a3e390fc941c7 100644 --- a/pandas/tests/indexes/timedeltas/test_ops.py +++ b/pandas/tests/indexes/timedeltas/test_ops.py @@ -8,26 +8,13 @@ import pandas as pd from pandas import Series, TimedeltaIndex, timedelta_range import pandas._testing as tm -from pandas.tests.base.test_ops import Ops from pandas.tseries.offsets import Day, Hour -class TestTimedeltaIndexOps(Ops): - def setup_method(self, method): - super().setup_method(method) - mask = lambda x: isinstance(x, TimedeltaIndex) - self.is_valid_objs = [o for o in self.objs if mask(o)] - self.not_valid_objs = [] - - def test_ops_properties(self): - f = lambda x: isinstance(x, TimedeltaIndex) - self.check_ops_properties(TimedeltaIndex._field_ops, f) - self.check_ops_properties(TimedeltaIndex._object_ops, f) - +class TestTimedeltaIndexOps: def test_value_counts_unique(self): # GH 7735 - idx = timedelta_range("1 days 09:00:00", freq="H", periods=10) # create repeated values, 'n'th element is repeated by n+1 times idx = TimedeltaIndex(np.repeat(idx.values, range(1, len(idx) + 1))) diff --git a/pandas/tests/series/conftest.py b/pandas/tests/series/conftest.py index ff0b0c71f88b0..87aefec559311 100644 --- a/pandas/tests/series/conftest.py +++ b/pandas/tests/series/conftest.py @@ -3,16 +3,6 @@ import pandas._testing as tm -@pytest.fixture -def datetime_series(): - """ - Fixture for Series of floats with DatetimeIndex - """ - s = tm.makeTimeSeries() - s.name = "ts" - return s - - @pytest.fixture def string_series(): """