From 4d1750c7bf53df7883ab312e9db796497f3c33b1 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Sun, 8 Dec 2019 22:46:00 +0100 Subject: [PATCH 01/23] added new fixtures that can replace the Ops mixin --- pandas/conftest.py | 72 +++++++++++++++++++++++++++++++++++ pandas/tests/base/test_ops.py | 43 +++++++++++---------- pandas/tests/base/utils.py | 59 ++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 21 deletions(-) create mode 100644 pandas/tests/base/utils.py diff --git a/pandas/conftest.py b/pandas/conftest.py index 3553a411a27f8..b0b56899e674c 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -881,3 +881,75 @@ def index_or_series(request): See GH#29725 """ return request.param + + +_int_index = tm.makeIntIndex(10, name="a") +_indexes = [ + tm.makeBoolIndex(10, name="a"), + _int_index, + tm.makeFloatIndex(10, name="a"), + tm.makeDateIndex(10, name="a"), + tm.makeDateIndex(10, name="a", tz="US/Eastern"), + tm.makePeriodIndex(10, name="a"), + tm.makeStringIndex(10, name="a"), + tm.makeUnicodeIndex(10, name="a"), +] +_index_ids = [ + "bool-index", + "int-index", + "float-index", + "date-index", + "localized-date-index", + "period-index", + "string-index", + "unicode-index", +] + + +@pytest.fixture(params=_indexes, ids=_index_ids) +def index(request): + """ Fixture for tests on indexes """ + return request.param.copy(deep=True) + + +_arr = arr = np.random.randn(10) + + +@pytest.fixture +def int_series(): + """ Fixture for Series with random numbers and integer index """ + index = _int_index.copy(deep=True) + return pd.Series(_arr, index=index, name=index.name) + + +_series = [pd.Series(_arr, index=index, name=index.name) for index in _indexes] +_series_ids = [f"series-with-{index_id}" for index_id in _index_ids] + +_arr_int = np.random.choice(10, size=10, replace=False) +_narrow_series = [ + pd.Series(_arr.astype(np.float32), index=_int_index, name="a"), + pd.Series(_arr_int.astype(np.int8), index=_int_index, name="a"), + pd.Series(_arr_int.astype(np.int16), index=_int_index, name="a"), + pd.Series(_arr_int.astype(np.int32), index=_int_index, name="a"), + pd.Series(_arr_int.astype(np.uint8), index=_int_index, name="a"), + pd.Series(_arr_int.astype(np.uint16), index=_int_index, name="a"), + pd.Series(_arr_int.astype(np.uint32), index=_int_index, name="a"), +] +_narrow_series_ids = [ + "float32-series", + "int8-series", + "int16-series", + "int32-series", + "uint8-series", + "uint16-series", + "uint32-series", +] + +_all_objs = _indexes + _series + _narrow_series +_all_obj_ids = _index_ids + _series_ids + _narrow_series_ids + + +@pytest.fixture(params=_all_objs, ids=_all_obj_ids) +def index_or_series_obj(request): + """ Fixture for tests on indexes, series and series with a narrow dtype """ + return request.param.copy(deep=True) diff --git a/pandas/tests/base/test_ops.py b/pandas/tests/base/test_ops.py index bcd6b931a0f85..14a3975e94f3d 100644 --- a/pandas/tests/base/test_ops.py +++ b/pandas/tests/base/test_ops.py @@ -130,27 +130,28 @@ def check_ops_properties(self, props, filter=None, ignore_failures=False): with pytest.raises(err): 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("klass", [Series, DataFrame]) +def test_binary_ops_docs(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__ class TestTranspose(Ops): diff --git a/pandas/tests/base/utils.py b/pandas/tests/base/utils.py new file mode 100644 index 0000000000000..02dbbe14335a6 --- /dev/null +++ b/pandas/tests/base/utils.py @@ -0,0 +1,59 @@ +from typing import Any, Callable, List + +import numpy as np +import pytest + +from pandas import Index, Series +from pandas.core.indexes.datetimelike import DatetimeIndexOpsMixin +import pandas.util.testing as tm + + +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 + + +def check_ops_properties_valid(obj: Any, props: List[str], filter: Callable) -> None: + """ Validates that certain properties are available """ + for op in props: + # if a filter, skip if it doesn't match + index = obj.index if isinstance(obj, Series) else obj + if not filter(index): + continue + + try: + if isinstance(obj, Series): + expected = Series(getattr(obj.index, op), index=obj.index, name="a") + else: + expected = getattr(obj, op) + except AttributeError: + continue + + result = getattr(obj, 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 + + +def check_ops_properties_invalid(obj: Any, props: List[str]) -> None: + """ Validates that certain properties are not available """ + for op in props: + # freq raises AttributeError on an Int64Index because its not + # defined we mostly care about Series here anyhow + + # an object that is datetimelike will raise a TypeError, + # otherwise an AttributeError + err = AttributeError + if issubclass(type(obj), DatetimeIndexOpsMixin): + err = TypeError + + with pytest.raises(err): + getattr(obj, op) From 3d04ff2a47cbf82413d62161c157ceba6ac561d0 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Sun, 8 Dec 2019 23:59:44 +0100 Subject: [PATCH 02/23] fixed type hinting --- pandas/tests/base/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/base/utils.py b/pandas/tests/base/utils.py index 02dbbe14335a6..303e9f8990b7b 100644 --- a/pandas/tests/base/utils.py +++ b/pandas/tests/base/utils.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, List +from typing import Any, Callable, List, Type import numpy as np import pytest @@ -51,7 +51,7 @@ def check_ops_properties_invalid(obj: Any, props: List[str]) -> None: # an object that is datetimelike will raise a TypeError, # otherwise an AttributeError - err = AttributeError + err: Type[Exception] = AttributeError if issubclass(type(obj), DatetimeIndexOpsMixin): err = TypeError From 89d84b4891cc7bcd09d5ced67bda28b46e4b906b Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Mon, 9 Dec 2019 20:02:33 +0100 Subject: [PATCH 03/23] refactoring according to review comments --- pandas/conftest.py | 100 ++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/pandas/conftest.py b/pandas/conftest.py index b0b56899e674c..9e2bf6d6d402e 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -883,33 +883,26 @@ def index_or_series(request): return request.param -_int_index = tm.makeIntIndex(10, name="a") -_indexes = [ - tm.makeBoolIndex(10, name="a"), - _int_index, - tm.makeFloatIndex(10, name="a"), - tm.makeDateIndex(10, name="a"), - tm.makeDateIndex(10, name="a", tz="US/Eastern"), - tm.makePeriodIndex(10, name="a"), - tm.makeStringIndex(10, name="a"), - tm.makeUnicodeIndex(10, name="a"), -] -_index_ids = [ - "bool-index", - "int-index", - "float-index", - "date-index", - "localized-date-index", - "period-index", - "string-index", - "unicode-index", -] - - -@pytest.fixture(params=_indexes, ids=_index_ids) +_indexes = { + "bool-index": tm.makeBoolIndex(10, name="a"), + "int-index": tm.makeIntIndex(10, name="a"), + "float-index": tm.makeFloatIndex(10, name="a"), + "date-index": tm.makeDateIndex(10, name="a"), + "localized-date-index": tm.makeDateIndex(10, name="a", tz="US/Eastern"), + "period-index": tm.makePeriodIndex(10, name="a"), + "string-index": tm.makeStringIndex(10, name="a"), + "unicode-index": tm.makeUnicodeIndex(10, name="a"), +} + + +@pytest.fixture(params=_indexes.keys()) def index(request): - """ Fixture for tests on indexes """ - return request.param.copy(deep=True) + """ + Fixture for tests on indexes + + copy to avoid mutation, e.g. setting .name + """ + return _indexes[request.param].copy() _arr = arr = np.random.randn(10) @@ -918,38 +911,41 @@ def index(request): @pytest.fixture def int_series(): """ Fixture for Series with random numbers and integer index """ - index = _int_index.copy(deep=True) + index = _indexes["int-index"].copy() return pd.Series(_arr, index=index, name=index.name) -_series = [pd.Series(_arr, index=index, name=index.name) for index in _indexes] -_series_ids = [f"series-with-{index_id}" for index_id in _index_ids] +_series = { + f"series-with-{i_id}": pd.Series(_arr, index=i, name=i.name) + for i_id, i in _indexes.items() +} _arr_int = np.random.choice(10, size=10, replace=False) -_narrow_series = [ - pd.Series(_arr.astype(np.float32), index=_int_index, name="a"), - pd.Series(_arr_int.astype(np.int8), index=_int_index, name="a"), - pd.Series(_arr_int.astype(np.int16), index=_int_index, name="a"), - pd.Series(_arr_int.astype(np.int32), index=_int_index, name="a"), - pd.Series(_arr_int.astype(np.uint8), index=_int_index, name="a"), - pd.Series(_arr_int.astype(np.uint16), index=_int_index, name="a"), - pd.Series(_arr_int.astype(np.uint32), index=_int_index, name="a"), -] -_narrow_series_ids = [ - "float32-series", - "int8-series", - "int16-series", - "int32-series", - "uint8-series", - "uint16-series", - "uint32-series", -] -_all_objs = _indexes + _series + _narrow_series -_all_obj_ids = _index_ids + _series_ids + _narrow_series_ids +def _create_narrow_series(data_dtype): + index = _indexes["int-index"].copy() + return pd.Series(_arr.astype(data_dtype), index=index, name="a") + + +_narrow_series = { + "float32-series": _create_narrow_series(np.float32), + "int8-series": _create_narrow_series(np.int8), + "int16-series": _create_narrow_series(np.int16), + "int32-series": _create_narrow_series(np.int32), + "uint8-series": _create_narrow_series(np.uint8), + "uint16-series": _create_narrow_series(np.uint16), + "uint32-series": _create_narrow_series(np.uint32), +} + +_all_objs = {**_indexes, **_series, **_narrow_series} -@pytest.fixture(params=_all_objs, ids=_all_obj_ids) + +@pytest.fixture(params=_all_objs.keys()) def index_or_series_obj(request): - """ Fixture for tests on indexes, series and series with a narrow dtype """ - return request.param.copy(deep=True) + """ + Fixture for tests on indexes, series and series with a narrow dtype + + copy to avoid mutation, e.g. setting .name + """ + return _all_objs[request.param].copy(deep=True) From 199896f83be8560f7c7782d004b593ae0f17bf65 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Mon, 9 Dec 2019 22:57:13 +0100 Subject: [PATCH 04/23] removing unused variable arr and fixing the _narrow_series data --- pandas/conftest.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pandas/conftest.py b/pandas/conftest.py index 9e2bf6d6d402e..c76bce40871ae 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -905,7 +905,7 @@ def index(request): return _indexes[request.param].copy() -_arr = arr = np.random.randn(10) +_arr = np.random.randn(10) @pytest.fixture @@ -923,19 +923,20 @@ def int_series(): _arr_int = np.random.choice(10, size=10, replace=False) -def _create_narrow_series(data_dtype): +def _create_narrow_series(data, data_dtype): + """ Helper for the _narrow_series dict """ index = _indexes["int-index"].copy() - return pd.Series(_arr.astype(data_dtype), index=index, name="a") + return pd.Series(data.astype(data_dtype), index=index, name="a") _narrow_series = { - "float32-series": _create_narrow_series(np.float32), - "int8-series": _create_narrow_series(np.int8), - "int16-series": _create_narrow_series(np.int16), - "int32-series": _create_narrow_series(np.int32), - "uint8-series": _create_narrow_series(np.uint8), - "uint16-series": _create_narrow_series(np.uint16), - "uint32-series": _create_narrow_series(np.uint32), + "float32-series": _create_narrow_series(_arr, np.float32), + "int8-series": _create_narrow_series(_arr_int, np.int8), + "int16-series": _create_narrow_series(_arr_int, np.int16), + "int32-series": _create_narrow_series(_arr_int, np.int32), + "uint8-series": _create_narrow_series(_arr_int, np.uint8), + "uint16-series": _create_narrow_series(_arr_int, np.uint16), + "uint32-series": _create_narrow_series(_arr_int, np.uint32), } _all_objs = {**_indexes, **_series, **_narrow_series} From e269b09773377939fe114380302b800430136814 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Tue, 10 Dec 2019 14:43:49 +0100 Subject: [PATCH 05/23] removing usage of Ops mixin in tests/indexes --- pandas/conftest.py | 10 ++++++ pandas/tests/indexes/datetimes/test_ops.py | 34 ++++++++++++--------- pandas/tests/indexes/period/test_ops.py | 30 ++++++++++-------- pandas/tests/indexes/timedeltas/test_ops.py | 21 ++++++------- pandas/tests/series/conftest.py | 10 ------ 5 files changed, 55 insertions(+), 50 deletions(-) diff --git a/pandas/conftest.py b/pandas/conftest.py index c76bce40871ae..0023410b9b761 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -841,6 +841,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/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index 5d3c541a4def6..ebfc169c3fdc1 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -16,7 +16,10 @@ bdate_range, date_range, ) -from pandas.tests.base.test_ops import Ops +from pandas.tests.base.utils import ( + check_ops_properties_invalid, + check_ops_properties_valid, +) import pandas.util.testing as tm from pandas.tseries.offsets import BDay, BMonthEnd, CDay, Day, Hour @@ -24,27 +27,28 @@ 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) +class TestDatetimeIndexOps: + def test_ops_properties(self, index_or_series_obj): + obj = index_or_series_obj + is_valid = isinstance(obj, (DatetimeIndex, PeriodIndex)) + if is_valid: + filter_ = lambda x: isinstance(x, DatetimeIndex) + check_ops_properties_valid(obj, DatetimeIndex._field_ops, filter_) + check_ops_properties_valid(obj, DatetimeIndex._object_ops, filter_) + check_ops_properties_valid(obj, DatetimeIndex._bool_ops, filter_) + else: + check_ops_properties_invalid(obj, DatetimeIndex._field_ops) + check_ops_properties_invalid(obj, DatetimeIndex._object_ops) + check_ops_properties_invalid(obj, DatetimeIndex._bool_ops) - def test_ops_properties_basic(self): + def test_ops_properties_basic(self, datetime_series): # sanity check that the behavior didn't change # GH#7206 msg = "'Series' object has no attribute '{}'" for op in ["year", "day", "second", "weekday"]: with pytest.raises(AttributeError, match=msg.format(op)): - 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 962e674fa607f..57e3c92a70d8b 100644 --- a/pandas/tests/indexes/period/test_ops.py +++ b/pandas/tests/indexes/period/test_ops.py @@ -4,22 +4,26 @@ import pandas as pd from pandas import DatetimeIndex, Index, NaT, PeriodIndex, Series from pandas.core.arrays import PeriodArray -from pandas.tests.base.test_ops import Ops +from pandas.tests.base.utils import ( + check_ops_properties_invalid, + check_ops_properties_valid, +) import pandas.util.testing as tm -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_ops_properties(self, index_or_series_obj): + obj = index_or_series_obj + is_valid = isinstance(obj, (DatetimeIndex, PeriodIndex)) + if is_valid: + filter_ = lambda x: isinstance(x, PeriodIndex) + check_ops_properties_valid(obj, PeriodArray._field_ops, filter_) + check_ops_properties_valid(obj, PeriodArray._object_ops, filter_) + check_ops_properties_valid(obj, PeriodArray._bool_ops, filter_) + else: + check_ops_properties_invalid(obj, PeriodArray._field_ops) + check_ops_properties_invalid(obj, PeriodArray._object_ops) + check_ops_properties_invalid(obj, PeriodArray._bool_ops) def test_resolution(self): for freq, expected in zip( diff --git a/pandas/tests/indexes/timedeltas/test_ops.py b/pandas/tests/indexes/timedeltas/test_ops.py index 56043cf3edb2d..4121b826506c2 100644 --- a/pandas/tests/indexes/timedeltas/test_ops.py +++ b/pandas/tests/indexes/timedeltas/test_ops.py @@ -7,23 +7,20 @@ import pandas as pd from pandas import Series, TimedeltaIndex, timedelta_range -from pandas.tests.base.test_ops import Ops +from pandas.tests.base.utils import check_ops_properties_valid import pandas.util.testing as tm 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_ops_properties(self, index_or_series_obj): + obj = index_or_series_obj + is_valid = isinstance(obj, TimedeltaIndex) + if is_valid: + filter_ = lambda x: isinstance(x, TimedeltaIndex) + check_ops_properties_valid(obj, TimedeltaIndex._field_ops, filter_) + check_ops_properties_valid(obj, TimedeltaIndex._object_ops, filter_) def test_value_counts_unique(self): # GH 7735 diff --git a/pandas/tests/series/conftest.py b/pandas/tests/series/conftest.py index 18d3c87a01f87..50fcc33c0ab70 100644 --- a/pandas/tests/series/conftest.py +++ b/pandas/tests/series/conftest.py @@ -3,16 +3,6 @@ import pandas.util.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(): """ From baab8279e031dbd10d2b784feb4756dd07aba631 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Tue, 10 Dec 2019 16:35:25 +0100 Subject: [PATCH 06/23] moved new fixtures to tests/indexes/conftest.py as they're not used anywhere else for now --- pandas/conftest.py | 69 -------------------------------- pandas/tests/indexes/conftest.py | 48 +++++++++++++++++++++- 2 files changed, 47 insertions(+), 70 deletions(-) diff --git a/pandas/conftest.py b/pandas/conftest.py index 0023410b9b761..c6f36eee60421 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -891,72 +891,3 @@ def index_or_series(request): See GH#29725 """ return request.param - - -_indexes = { - "bool-index": tm.makeBoolIndex(10, name="a"), - "int-index": tm.makeIntIndex(10, name="a"), - "float-index": tm.makeFloatIndex(10, name="a"), - "date-index": tm.makeDateIndex(10, name="a"), - "localized-date-index": tm.makeDateIndex(10, name="a", tz="US/Eastern"), - "period-index": tm.makePeriodIndex(10, name="a"), - "string-index": tm.makeStringIndex(10, name="a"), - "unicode-index": tm.makeUnicodeIndex(10, name="a"), -} - - -@pytest.fixture(params=_indexes.keys()) -def index(request): - """ - Fixture for tests on indexes - - copy to avoid mutation, e.g. setting .name - """ - return _indexes[request.param].copy() - - -_arr = np.random.randn(10) - - -@pytest.fixture -def int_series(): - """ Fixture for Series with random numbers and integer index """ - index = _indexes["int-index"].copy() - return pd.Series(_arr, index=index, name=index.name) - - -_series = { - f"series-with-{i_id}": pd.Series(_arr, index=i, name=i.name) - for i_id, i in _indexes.items() -} - -_arr_int = np.random.choice(10, size=10, replace=False) - - -def _create_narrow_series(data, data_dtype): - """ Helper for the _narrow_series dict """ - index = _indexes["int-index"].copy() - return pd.Series(data.astype(data_dtype), index=index, name="a") - - -_narrow_series = { - "float32-series": _create_narrow_series(_arr, np.float32), - "int8-series": _create_narrow_series(_arr_int, np.int8), - "int16-series": _create_narrow_series(_arr_int, np.int16), - "int32-series": _create_narrow_series(_arr_int, np.int32), - "uint8-series": _create_narrow_series(_arr_int, np.uint8), - "uint16-series": _create_narrow_series(_arr_int, np.uint16), - "uint32-series": _create_narrow_series(_arr_int, np.uint32), -} - -_all_objs = {**_indexes, **_series, **_narrow_series} - - -@pytest.fixture(params=_all_objs.keys()) -def index_or_series_obj(request): - """ - Fixture for tests on indexes, series and series with a narrow dtype - - copy to avoid mutation, e.g. setting .name - """ - return _all_objs[request.param].copy(deep=True) diff --git a/pandas/tests/indexes/conftest.py b/pandas/tests/indexes/conftest.py index 2a9a8bf8d824f..ceed61a539a57 100644 --- a/pandas/tests/indexes/conftest.py +++ b/pandas/tests/indexes/conftest.py @@ -9,13 +9,14 @@ "unicode": tm.makeUnicodeIndex(100), "string": tm.makeStringIndex(100), "datetime": tm.makeDateIndex(100), + "localized-datetime": tm.makeDateIndex(100, tz="US/Eastern"), "period": tm.makePeriodIndex(100), "timedelta": tm.makeTimedeltaIndex(100), "int": tm.makeIntIndex(100), "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([]), @@ -50,3 +51,48 @@ def zero(request): # For testing division by (or of) zero for Index with length 5, this # gives several scalar-zeros and length-5 vector-zeros return request.param + + +_series = { + f"series-with-{i_id}-index": pd.Series( + np.random.randn(len(i)), index=i, name=i.name + ) + for i_id, i in indices_dict.items() +} + + +def _create_narrow_series(data_dtype): + """ Helper for the _narrow_series dict """ + index = indices_dict["int"].copy() + size = len(index) + if np.issubdtype(data_dtype, np.float): + data = np.random.choice(size, size=size, replace=False) + elif np.issubdtype(data_dtype, np.integer): + data = np.random.randn(size) + else: + raise ValueError(f"Received an unexpected data_dtype: {data_dtype}") + + return pd.Series(data.astype(data_dtype), index=index, name="a") + + +_narrow_series = { + "float32-series": _create_narrow_series(np.float32), + "int8-series": _create_narrow_series(np.int8), + "int16-series": _create_narrow_series(np.int16), + "int32-series": _create_narrow_series(np.int32), + "uint8-series": _create_narrow_series(np.uint8), + "uint16-series": _create_narrow_series(np.uint16), + "uint32-series": _create_narrow_series(np.uint32), +} + +_all_objs = {**indices_dict, **_series, **_narrow_series} + + +@pytest.fixture(params=_all_objs.keys()) +def index_or_series_obj(request): + """ + Fixture for tests on indexes, series and series with a narrow dtype + + copy to avoid mutation, e.g. setting .name + """ + return _all_objs[request.param].copy(deep=True) From 28291f1e4863a11c3fb04a73288e59094237102c Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Tue, 10 Dec 2019 16:36:47 +0100 Subject: [PATCH 07/23] using new utils and fixtures in tests/indexes --- pandas/tests/base/test_ops.py | 60 +------------------- pandas/tests/base/utils.py | 63 ++++++--------------- pandas/tests/indexes/datetimes/test_ops.py | 32 ++++++----- pandas/tests/indexes/period/test_ops.py | 33 +++++------ pandas/tests/indexes/timedeltas/test_ops.py | 23 +++++--- 5 files changed, 71 insertions(+), 140 deletions(-) diff --git a/pandas/tests/base/test_ops.py b/pandas/tests/base/test_ops.py index 14a3975e94f3d..8701ea3ec179a 100644 --- a/pandas/tests/base/test_ops.py +++ b/pandas/tests/base/test_ops.py @@ -29,18 +29,11 @@ TimedeltaIndex, Timestamp, ) -from pandas.core.indexes.datetimelike import DatetimeIndexOpsMixin +from pandas.tests.base.utils import allow_na_ops import pandas.util.testing as tm 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 setup_method(self, method): self.bool_index = tm.makeBoolIndex(10, name="a") self.int_index = tm.makeIntIndex(10, name="a") @@ -83,53 +76,6 @@ 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 - err = AttributeError - if issubclass(type(o), DatetimeIndexOpsMixin): - err = TypeError - - with pytest.raises(err): - getattr(o, op) - @pytest.mark.parametrize("klass", [Series, DataFrame]) def test_binary_ops_docs(klass): @@ -314,7 +260,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 @@ -796,7 +742,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/base/utils.py b/pandas/tests/base/utils.py index 303e9f8990b7b..de94a6bf23e30 100644 --- a/pandas/tests/base/utils.py +++ b/pandas/tests/base/utils.py @@ -1,10 +1,8 @@ -from typing import Any, Callable, List, Type +from typing import Any import numpy as np -import pytest from pandas import Index, Series -from pandas.core.indexes.datetimelike import DatetimeIndexOpsMixin import pandas.util.testing as tm @@ -14,46 +12,21 @@ def allow_na_ops(obj: Any) -> bool: return not is_bool_index and obj._can_hold_na -def check_ops_properties_valid(obj: Any, props: List[str], filter: Callable) -> None: +def check_ops_properties_valid(obj: Any, op: str) -> None: """ Validates that certain properties are available """ - for op in props: - # if a filter, skip if it doesn't match - index = obj.index if isinstance(obj, Series) else obj - if not filter(index): - continue - - try: - if isinstance(obj, Series): - expected = Series(getattr(obj.index, op), index=obj.index, name="a") - else: - expected = getattr(obj, op) - except AttributeError: - continue - - result = getattr(obj, 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 - - -def check_ops_properties_invalid(obj: Any, props: List[str]) -> None: - """ Validates that certain properties are not available """ - for op in props: - # freq raises AttributeError on an Int64Index because its not - # defined we mostly care about Series here anyhow - - # an object that is datetimelike will raise a TypeError, - # otherwise an AttributeError - err: Type[Exception] = AttributeError - if issubclass(type(obj), DatetimeIndexOpsMixin): - err = TypeError - - with pytest.raises(err): - getattr(obj, op) + if isinstance(obj, Series): + expected = Series(getattr(obj.index, op), index=obj.index, name="a") + else: + expected = getattr(obj, op) + + result = getattr(obj, 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 diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index ebfc169c3fdc1..d75a17c4eefe5 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -12,14 +12,12 @@ Index, PeriodIndex, Series, + TimedeltaIndex, Timestamp, bdate_range, date_range, ) -from pandas.tests.base.utils import ( - check_ops_properties_invalid, - check_ops_properties_valid, -) +from pandas.tests.base.utils import check_ops_properties_valid import pandas.util.testing as tm from pandas.tseries.offsets import BDay, BMonthEnd, CDay, Day, Hour @@ -28,18 +26,22 @@ class TestDatetimeIndexOps: - def test_ops_properties(self, index_or_series_obj): + @pytest.mark.parametrize("op", DatetimeIndex._datetimelike_ops) + def test_valid_ops_properties(self, op, index_or_series_obj): + obj = index_or_series_obj + if isinstance(obj, DatetimeIndex): + check_ops_properties_valid(obj, op) + + @pytest.mark.parametrize("op", DatetimeIndex._datetimelike_ops) + def test_invalid_ops_properties(self, op, index_or_series_obj): obj = index_or_series_obj - is_valid = isinstance(obj, (DatetimeIndex, PeriodIndex)) - if is_valid: - filter_ = lambda x: isinstance(x, DatetimeIndex) - check_ops_properties_valid(obj, DatetimeIndex._field_ops, filter_) - check_ops_properties_valid(obj, DatetimeIndex._object_ops, filter_) - check_ops_properties_valid(obj, DatetimeIndex._bool_ops, filter_) - else: - check_ops_properties_invalid(obj, DatetimeIndex._field_ops) - check_ops_properties_invalid(obj, DatetimeIndex._object_ops) - check_ops_properties_invalid(obj, DatetimeIndex._bool_ops) + if isinstance(obj, (DatetimeIndex, PeriodIndex)): + return + if op == "freq" and isinstance(obj, TimedeltaIndex): + return + + with pytest.raises((AttributeError, TypeError)): + getattr(obj, op) def test_ops_properties_basic(self, datetime_series): diff --git a/pandas/tests/indexes/period/test_ops.py b/pandas/tests/indexes/period/test_ops.py index 57e3c92a70d8b..cdbb38744bb46 100644 --- a/pandas/tests/indexes/period/test_ops.py +++ b/pandas/tests/indexes/period/test_ops.py @@ -2,28 +2,29 @@ import pytest import pandas as pd -from pandas import DatetimeIndex, Index, NaT, PeriodIndex, Series +from pandas import DatetimeIndex, Index, NaT, PeriodIndex, Series, TimedeltaIndex from pandas.core.arrays import PeriodArray -from pandas.tests.base.utils import ( - check_ops_properties_invalid, - check_ops_properties_valid, -) +from pandas.tests.base.utils import check_ops_properties_valid import pandas.util.testing as tm class TestPeriodIndexOps: - def test_ops_properties(self, index_or_series_obj): + @pytest.mark.parametrize("op", PeriodArray._datetimelike_ops) + def test_valid_ops_properties(self, op, index_or_series_obj): obj = index_or_series_obj - is_valid = isinstance(obj, (DatetimeIndex, PeriodIndex)) - if is_valid: - filter_ = lambda x: isinstance(x, PeriodIndex) - check_ops_properties_valid(obj, PeriodArray._field_ops, filter_) - check_ops_properties_valid(obj, PeriodArray._object_ops, filter_) - check_ops_properties_valid(obj, PeriodArray._bool_ops, filter_) - else: - check_ops_properties_invalid(obj, PeriodArray._field_ops) - check_ops_properties_invalid(obj, PeriodArray._object_ops) - check_ops_properties_invalid(obj, PeriodArray._bool_ops) + if isinstance(obj, PeriodIndex): + check_ops_properties_valid(obj, op) + + @pytest.mark.parametrize("op", PeriodArray._datetimelike_ops) + def test_invalid_ops_properties(self, op, index_or_series_obj): + obj = index_or_series_obj + if isinstance(obj, (PeriodIndex, DatetimeIndex)): + return + if op == "freq" and isinstance(obj, TimedeltaIndex): + return + + with pytest.raises((AttributeError, TypeError)): + getattr(obj, op) def test_resolution(self): for freq, expected in zip( diff --git a/pandas/tests/indexes/timedeltas/test_ops.py b/pandas/tests/indexes/timedeltas/test_ops.py index 4121b826506c2..bdace77faf98b 100644 --- a/pandas/tests/indexes/timedeltas/test_ops.py +++ b/pandas/tests/indexes/timedeltas/test_ops.py @@ -6,7 +6,7 @@ from pandas.core.dtypes.generic import ABCDateOffset import pandas as pd -from pandas import Series, TimedeltaIndex, timedelta_range +from pandas import DatetimeIndex, PeriodIndex, Series, TimedeltaIndex, timedelta_range from pandas.tests.base.utils import check_ops_properties_valid import pandas.util.testing as tm @@ -14,13 +14,22 @@ class TestTimedeltaIndexOps: - def test_ops_properties(self, index_or_series_obj): + @pytest.mark.parametrize("op", TimedeltaIndex._datetimelike_ops) + def test_valid_ops_properties(self, op, index_or_series_obj): obj = index_or_series_obj - is_valid = isinstance(obj, TimedeltaIndex) - if is_valid: - filter_ = lambda x: isinstance(x, TimedeltaIndex) - check_ops_properties_valid(obj, TimedeltaIndex._field_ops, filter_) - check_ops_properties_valid(obj, TimedeltaIndex._object_ops, filter_) + if isinstance(obj, TimedeltaIndex): + check_ops_properties_valid(obj, op) + + @pytest.mark.parametrize("op", TimedeltaIndex._datetimelike_ops) + def test_invalid_ops_properties(self, op, index_or_series_obj): + obj = index_or_series_obj + if isinstance(obj, TimedeltaIndex): + return + if op == "freq" and isinstance(obj, (DatetimeIndex, PeriodIndex)): + return + + with pytest.raises((AttributeError, TypeError)): + getattr(obj, op) def test_value_counts_unique(self): # GH 7735 From 589ae3b29ed1dc8496f5ffc0961bf4a8527db03c Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Tue, 10 Dec 2019 16:41:40 +0100 Subject: [PATCH 08/23] fixturizing tests/base/test_ops.py::test_binary_ops_docs --- pandas/tests/base/test_ops.py | 41 ++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/pandas/tests/base/test_ops.py b/pandas/tests/base/test_ops.py index 8701ea3ec179a..a2440bd0cae63 100644 --- a/pandas/tests/base/test_ops.py +++ b/pandas/tests/base/test_ops.py @@ -78,26 +78,27 @@ def setup_method(self, method): @pytest.mark.parametrize("klass", [Series, DataFrame]) -def test_binary_ops_docs(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", "//"), + ], +) +def test_binary_ops_docs(klass, op_name, op): + 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): From 2bbc3fdc81774b6ba9bbbf123fc8b4cb226acdd0 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Tue, 10 Dec 2019 16:46:27 +0100 Subject: [PATCH 09/23] refactoring of tests/indexes/conftest.py --- pandas/tests/indexes/conftest.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pandas/tests/indexes/conftest.py b/pandas/tests/indexes/conftest.py index ceed61a539a57..a8b029603193e 100644 --- a/pandas/tests/indexes/conftest.py +++ b/pandas/tests/indexes/conftest.py @@ -53,11 +53,14 @@ def zero(request): return request.param +def _create_series(index): + """ Helper for the _series dict """ + data = np.random.randn(len(index)) + return pd.Series(data, index=index, name=index.name) + + _series = { - f"series-with-{i_id}-index": pd.Series( - np.random.randn(len(i)), index=i, name=i.name - ) - for i_id, i in indices_dict.items() + f"series-with-{i_id}-index": _create_series(i) for i_id, i in indices_dict.items() } From b3d0252745dd8a87624134d60278a2e2daeba892 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Tue, 10 Dec 2019 22:57:44 +0100 Subject: [PATCH 10/23] using pytest.skip instead of early return --- pandas/tests/indexes/datetimes/test_ops.py | 2 +- pandas/tests/indexes/period/test_ops.py | 2 +- pandas/tests/indexes/timedeltas/test_ops.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index d75a17c4eefe5..2ee98fa35a334 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -38,7 +38,7 @@ def test_invalid_ops_properties(self, op, index_or_series_obj): if isinstance(obj, (DatetimeIndex, PeriodIndex)): return if op == "freq" and isinstance(obj, TimedeltaIndex): - return + pytest.skip(f"{op} is available on {type(obj)}") with pytest.raises((AttributeError, TypeError)): getattr(obj, op) diff --git a/pandas/tests/indexes/period/test_ops.py b/pandas/tests/indexes/period/test_ops.py index cdbb38744bb46..5ba93db895f76 100644 --- a/pandas/tests/indexes/period/test_ops.py +++ b/pandas/tests/indexes/period/test_ops.py @@ -21,7 +21,7 @@ def test_invalid_ops_properties(self, op, index_or_series_obj): if isinstance(obj, (PeriodIndex, DatetimeIndex)): return if op == "freq" and isinstance(obj, TimedeltaIndex): - return + pytest.skip(f"{op} is available on {type(obj)}") with pytest.raises((AttributeError, TypeError)): getattr(obj, op) diff --git a/pandas/tests/indexes/timedeltas/test_ops.py b/pandas/tests/indexes/timedeltas/test_ops.py index bdace77faf98b..c9ab5a262e2d6 100644 --- a/pandas/tests/indexes/timedeltas/test_ops.py +++ b/pandas/tests/indexes/timedeltas/test_ops.py @@ -26,7 +26,7 @@ def test_invalid_ops_properties(self, op, index_or_series_obj): if isinstance(obj, TimedeltaIndex): return if op == "freq" and isinstance(obj, (DatetimeIndex, PeriodIndex)): - return + pytest.skip(f"{op} is available on {type(obj)}") with pytest.raises((AttributeError, TypeError)): getattr(obj, op) From 53db63f412e981124775eef36689ee5f41e79bc8 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Tue, 10 Dec 2019 23:45:50 +0100 Subject: [PATCH 11/23] skipping broken tests --- pandas/tests/indexes/common.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pandas/tests/indexes/common.py b/pandas/tests/indexes/common.py index 102949fe3f05e..33807683ecb1b 100644 --- a/pandas/tests/indexes/common.py +++ b/pandas/tests/indexes/common.py @@ -287,10 +287,12 @@ def test_ensure_copied_data(self, indices): if isinstance(indices, PeriodIndex): # Needs "freq" specification: init_kwargs["freq"] = indices.freq + elif isinstance(indices, DatetimeIndex) and indices.tz is not None: + pytest.skip() elif isinstance(indices, (RangeIndex, MultiIndex, CategoricalIndex)): # RangeIndex cannot be initialized from data # MultiIndex and CategoricalIndex are tested separately - return + pytest.skip() index_type = type(indices) result = index_type(indices.values, copy=True, **init_kwargs) @@ -450,7 +452,10 @@ def test_set_ops_error_cases(self, case, method, indices): def test_intersection_base(self, indices): if isinstance(indices, CategoricalIndex): - return + pytest.skip() + if isinstance(indices, DatetimeIndex) and indices.tz is not None: + # TODO: This should probably be fixed + pytest.skip() first = indices[:5] second = indices[:3] @@ -469,6 +474,10 @@ def test_intersection_base(self, indices): first.intersection([1, 2, 3]) def test_union_base(self, indices): + if isinstance(indices, DatetimeIndex) and indices.tz is not None: + # TODO: This should probably be fixed + pytest.skip() + first = indices[3:] second = indices[:5] everything = indices From 891b24cc041c7b1935a4fc5468c7a538b20f15e4 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Tue, 10 Dec 2019 23:48:03 +0100 Subject: [PATCH 12/23] replaced more return statements with pytest.skip --- pandas/tests/indexes/datetimes/test_ops.py | 4 ++-- pandas/tests/indexes/period/test_ops.py | 4 ++-- pandas/tests/indexes/timedeltas/test_ops.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index 2ee98fa35a334..1932736a68cd1 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -36,9 +36,9 @@ def test_valid_ops_properties(self, op, index_or_series_obj): def test_invalid_ops_properties(self, op, index_or_series_obj): obj = index_or_series_obj if isinstance(obj, (DatetimeIndex, PeriodIndex)): - return + pytest.skip() if op == "freq" and isinstance(obj, TimedeltaIndex): - pytest.skip(f"{op} is available on {type(obj)}") + pytest.skip() with pytest.raises((AttributeError, TypeError)): getattr(obj, op) diff --git a/pandas/tests/indexes/period/test_ops.py b/pandas/tests/indexes/period/test_ops.py index 5ba93db895f76..ed7b954e19b3d 100644 --- a/pandas/tests/indexes/period/test_ops.py +++ b/pandas/tests/indexes/period/test_ops.py @@ -19,9 +19,9 @@ def test_valid_ops_properties(self, op, index_or_series_obj): def test_invalid_ops_properties(self, op, index_or_series_obj): obj = index_or_series_obj if isinstance(obj, (PeriodIndex, DatetimeIndex)): - return + pytest.skip() if op == "freq" and isinstance(obj, TimedeltaIndex): - pytest.skip(f"{op} is available on {type(obj)}") + pytest.skip() with pytest.raises((AttributeError, TypeError)): getattr(obj, op) diff --git a/pandas/tests/indexes/timedeltas/test_ops.py b/pandas/tests/indexes/timedeltas/test_ops.py index c9ab5a262e2d6..bc2dc36822bb8 100644 --- a/pandas/tests/indexes/timedeltas/test_ops.py +++ b/pandas/tests/indexes/timedeltas/test_ops.py @@ -24,9 +24,9 @@ def test_valid_ops_properties(self, op, index_or_series_obj): def test_invalid_ops_properties(self, op, index_or_series_obj): obj = index_or_series_obj if isinstance(obj, TimedeltaIndex): - return + pytest.skip() if op == "freq" and isinstance(obj, (DatetimeIndex, PeriodIndex)): - pytest.skip(f"{op} is available on {type(obj)}") + pytest.skip() with pytest.raises((AttributeError, TypeError)): getattr(obj, op) From 69a0a0ddef731f095479c8aa927522b4ecadae9e Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Fri, 24 Jan 2020 00:11:26 +0100 Subject: [PATCH 13/23] took care of review comments --- pandas/_testing.py | 26 +++++++++++++++++ pandas/tests/base/test_ops.py | 5 ++-- pandas/tests/base/utils.py | 32 --------------------- pandas/tests/indexes/datetimes/test_ops.py | 3 +- pandas/tests/indexes/period/test_ops.py | 3 +- pandas/tests/indexes/timedeltas/test_ops.py | 3 +- 6 files changed, 31 insertions(+), 41 deletions(-) delete mode 100644 pandas/tests/base/utils.py diff --git a/pandas/_testing.py b/pandas/_testing.py index 631d550c60534..a41c8018ded6a 100644 --- a/pandas/_testing.py +++ b/pandas/_testing.py @@ -2757,3 +2757,29 @@ def convert_rows_list_to_csv_str(rows_list: List[str]): sep = os.linesep expected = sep.join(rows_list) + sep return expected + + +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 + + +def check_ops_properties_valid(obj: Any, op: str) -> None: + """ Validates that certain properties are available """ + if isinstance(obj, Series): + expected = Series(getattr(obj.index, op), index=obj.index, name="a") + else: + expected = getattr(obj, op) + + result = getattr(obj, op) + + # these could be series, arrays or scalars + if isinstance(result, Series) and isinstance(expected, Series): + assert_series_equal(result, expected) + elif isinstance(result, Index) and isinstance(expected, Index): + assert_index_equal(result, expected) + elif isinstance(result, np.ndarray) and isinstance(expected, np.ndarray): + assert_numpy_array_equal(result, expected) + else: + assert result == expected diff --git a/pandas/tests/base/test_ops.py b/pandas/tests/base/test_ops.py index a0448ed06a10f..cc408b130bf41 100644 --- a/pandas/tests/base/test_ops.py +++ b/pandas/tests/base/test_ops.py @@ -30,7 +30,6 @@ Timestamp, ) import pandas._testing as tm -from pandas.tests.base.utils import allow_na_ops class Ops: @@ -261,7 +260,7 @@ def test_value_counts_unique_nunique_null(self, null_obj): klass = type(o) values = o._ndarray_values - if not allow_na_ops(o): + if not tm.allow_na_ops(o): continue # special assign to the numpy array @@ -742,7 +741,7 @@ def test_fillna(self): o = orig.copy() klass = type(o) - if not allow_na_ops(o): + if not tm.allow_na_ops(o): continue if needs_i8_conversion(o): diff --git a/pandas/tests/base/utils.py b/pandas/tests/base/utils.py deleted file mode 100644 index de94a6bf23e30..0000000000000 --- a/pandas/tests/base/utils.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Any - -import numpy as np - -from pandas import Index, Series -import pandas.util.testing as tm - - -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 - - -def check_ops_properties_valid(obj: Any, op: str) -> None: - """ Validates that certain properties are available """ - if isinstance(obj, Series): - expected = Series(getattr(obj.index, op), index=obj.index, name="a") - else: - expected = getattr(obj, op) - - result = getattr(obj, 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 diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index 76376726657d7..484f02b2fc781 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -18,7 +18,6 @@ date_range, ) import pandas._testing as tm -from pandas.tests.base.utils import check_ops_properties_valid from pandas.tseries.offsets import BDay, BMonthEnd, CDay, Day, Hour @@ -30,7 +29,7 @@ class TestDatetimeIndexOps: def test_valid_ops_properties(self, op, index_or_series_obj): obj = index_or_series_obj if isinstance(obj, DatetimeIndex): - check_ops_properties_valid(obj, op) + tm.check_ops_properties_valid(obj, op) @pytest.mark.parametrize("op", DatetimeIndex._datetimelike_ops) def test_invalid_ops_properties(self, op, index_or_series_obj): diff --git a/pandas/tests/indexes/period/test_ops.py b/pandas/tests/indexes/period/test_ops.py index fe935cf87a48d..d048bb5f4b923 100644 --- a/pandas/tests/indexes/period/test_ops.py +++ b/pandas/tests/indexes/period/test_ops.py @@ -5,7 +5,6 @@ from pandas import DatetimeIndex, Index, NaT, PeriodIndex, Series, TimedeltaIndex import pandas._testing as tm from pandas.core.arrays import PeriodArray -from pandas.tests.base.utils import check_ops_properties_valid class TestPeriodIndexOps: @@ -13,7 +12,7 @@ class TestPeriodIndexOps: def test_valid_ops_properties(self, op, index_or_series_obj): obj = index_or_series_obj if isinstance(obj, PeriodIndex): - check_ops_properties_valid(obj, op) + tm.check_ops_properties_valid(obj, op) @pytest.mark.parametrize("op", PeriodArray._datetimelike_ops) def test_invalid_ops_properties(self, op, index_or_series_obj): diff --git a/pandas/tests/indexes/timedeltas/test_ops.py b/pandas/tests/indexes/timedeltas/test_ops.py index 73370b814c550..3d2e27c1fc9a5 100644 --- a/pandas/tests/indexes/timedeltas/test_ops.py +++ b/pandas/tests/indexes/timedeltas/test_ops.py @@ -8,7 +8,6 @@ import pandas as pd from pandas import DatetimeIndex, PeriodIndex, Series, TimedeltaIndex, timedelta_range import pandas._testing as tm -from pandas.tests.base.utils import check_ops_properties_valid from pandas.tseries.offsets import Day, Hour @@ -18,7 +17,7 @@ class TestTimedeltaIndexOps: def test_valid_ops_properties(self, op, index_or_series_obj): obj = index_or_series_obj if isinstance(obj, TimedeltaIndex): - check_ops_properties_valid(obj, op) + tm.check_ops_properties_valid(obj, op) @pytest.mark.parametrize("op", TimedeltaIndex._datetimelike_ops) def test_invalid_ops_properties(self, op, index_or_series_obj): From 0fce4c5527b715ac4050a8e9e56b5109a8cf6c02 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Fri, 24 Jan 2020 11:16:38 +0100 Subject: [PATCH 14/23] removed redundant tests --- pandas/_testing.py | 20 --------------- pandas/tests/indexes/datetimes/test_ops.py | 28 +-------------------- pandas/tests/indexes/period/test_ops.py | 20 +-------------- pandas/tests/indexes/timedeltas/test_ops.py | 20 +-------------- 4 files changed, 3 insertions(+), 85 deletions(-) diff --git a/pandas/_testing.py b/pandas/_testing.py index a41c8018ded6a..12bf36db15229 100644 --- a/pandas/_testing.py +++ b/pandas/_testing.py @@ -2763,23 +2763,3 @@ 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 - - -def check_ops_properties_valid(obj: Any, op: str) -> None: - """ Validates that certain properties are available """ - if isinstance(obj, Series): - expected = Series(getattr(obj.index, op), index=obj.index, name="a") - else: - expected = getattr(obj, op) - - result = getattr(obj, op) - - # these could be series, arrays or scalars - if isinstance(result, Series) and isinstance(expected, Series): - assert_series_equal(result, expected) - elif isinstance(result, Index) and isinstance(expected, Index): - assert_index_equal(result, expected) - elif isinstance(result, np.ndarray) and isinstance(expected, np.ndarray): - assert_numpy_array_equal(result, expected) - else: - assert result == expected diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index 484f02b2fc781..ebc4c82f7299a 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -7,16 +7,7 @@ from pandas.core.dtypes.generic import ABCDateOffset import pandas as pd -from pandas import ( - DatetimeIndex, - Index, - PeriodIndex, - Series, - TimedeltaIndex, - Timestamp, - bdate_range, - date_range, -) +from pandas import DatetimeIndex, Index, Series, Timestamp, bdate_range, date_range import pandas._testing as tm from pandas.tseries.offsets import BDay, BMonthEnd, CDay, Day, Hour @@ -25,23 +16,6 @@ class TestDatetimeIndexOps: - @pytest.mark.parametrize("op", DatetimeIndex._datetimelike_ops) - def test_valid_ops_properties(self, op, index_or_series_obj): - obj = index_or_series_obj - if isinstance(obj, DatetimeIndex): - tm.check_ops_properties_valid(obj, op) - - @pytest.mark.parametrize("op", DatetimeIndex._datetimelike_ops) - def test_invalid_ops_properties(self, op, index_or_series_obj): - obj = index_or_series_obj - if isinstance(obj, (DatetimeIndex, PeriodIndex)): - pytest.skip() - if op == "freq" and isinstance(obj, TimedeltaIndex): - pytest.skip() - - with pytest.raises((AttributeError, TypeError)): - getattr(obj, op) - def test_ops_properties_basic(self, datetime_series): # sanity check that the behavior didn't change diff --git a/pandas/tests/indexes/period/test_ops.py b/pandas/tests/indexes/period/test_ops.py index d048bb5f4b923..2e4bed598b807 100644 --- a/pandas/tests/indexes/period/test_ops.py +++ b/pandas/tests/indexes/period/test_ops.py @@ -2,29 +2,11 @@ import pytest import pandas as pd -from pandas import DatetimeIndex, Index, NaT, PeriodIndex, Series, TimedeltaIndex +from pandas import Index, NaT, PeriodIndex, Series import pandas._testing as tm -from pandas.core.arrays import PeriodArray class TestPeriodIndexOps: - @pytest.mark.parametrize("op", PeriodArray._datetimelike_ops) - def test_valid_ops_properties(self, op, index_or_series_obj): - obj = index_or_series_obj - if isinstance(obj, PeriodIndex): - tm.check_ops_properties_valid(obj, op) - - @pytest.mark.parametrize("op", PeriodArray._datetimelike_ops) - def test_invalid_ops_properties(self, op, index_or_series_obj): - obj = index_or_series_obj - if isinstance(obj, (PeriodIndex, DatetimeIndex)): - pytest.skip() - if op == "freq" and isinstance(obj, TimedeltaIndex): - pytest.skip() - - with pytest.raises((AttributeError, TypeError)): - getattr(obj, op) - 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 3d2e27c1fc9a5..a3e390fc941c7 100644 --- a/pandas/tests/indexes/timedeltas/test_ops.py +++ b/pandas/tests/indexes/timedeltas/test_ops.py @@ -6,33 +6,15 @@ from pandas.core.dtypes.generic import ABCDateOffset import pandas as pd -from pandas import DatetimeIndex, PeriodIndex, Series, TimedeltaIndex, timedelta_range +from pandas import Series, TimedeltaIndex, timedelta_range import pandas._testing as tm from pandas.tseries.offsets import Day, Hour class TestTimedeltaIndexOps: - @pytest.mark.parametrize("op", TimedeltaIndex._datetimelike_ops) - def test_valid_ops_properties(self, op, index_or_series_obj): - obj = index_or_series_obj - if isinstance(obj, TimedeltaIndex): - tm.check_ops_properties_valid(obj, op) - - @pytest.mark.parametrize("op", TimedeltaIndex._datetimelike_ops) - def test_invalid_ops_properties(self, op, index_or_series_obj): - obj = index_or_series_obj - if isinstance(obj, TimedeltaIndex): - pytest.skip() - if op == "freq" and isinstance(obj, (DatetimeIndex, PeriodIndex)): - pytest.skip() - - with pytest.raises((AttributeError, TypeError)): - getattr(obj, op) - 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))) From b7892fa0036a4e397b41a0d57a8217634fa4d14a Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Fri, 24 Jan 2020 11:38:29 +0100 Subject: [PATCH 15/23] removed changes that aren't needed for this PR anymore --- pandas/conftest.py | 10 ------- pandas/tests/indexes/conftest.py | 50 -------------------------------- pandas/tests/series/conftest.py | 10 +++++++ 3 files changed, 10 insertions(+), 60 deletions(-) diff --git a/pandas/conftest.py b/pandas/conftest.py index 131a011c5a101..0c964452df5da 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -868,16 +868,6 @@ 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/tests/indexes/conftest.py b/pandas/tests/indexes/conftest.py index a67670fcf895e..8595e0693a94e 100644 --- a/pandas/tests/indexes/conftest.py +++ b/pandas/tests/indexes/conftest.py @@ -1,7 +1,5 @@ -import numpy as np import pytest -import pandas as pd import pandas._testing as tm from pandas.core.indexes.api import Index, MultiIndex @@ -29,51 +27,3 @@ def indices(request): # copy to avoid mutation, e.g. setting .name return indices_dict[request.param].copy() - - -def _create_series(index): - """ Helper for the _series dict """ - data = np.random.randn(len(index)) - return pd.Series(data, index=index, name=index.name) - - -_series = { - f"series-with-{i_id}-index": _create_series(i) for i_id, i in indices_dict.items() -} - - -def _create_narrow_series(data_dtype): - """ Helper for the _narrow_series dict """ - index = indices_dict["int"].copy() - size = len(index) - if np.issubdtype(data_dtype, np.float): - data = np.random.choice(size, size=size, replace=False) - elif np.issubdtype(data_dtype, np.integer): - data = np.random.randn(size) - else: - raise ValueError(f"Received an unexpected data_dtype: {data_dtype}") - - return pd.Series(data.astype(data_dtype), index=index, name="a") - - -_narrow_series = { - "float32-series": _create_narrow_series(np.float32), - "int8-series": _create_narrow_series(np.int8), - "int16-series": _create_narrow_series(np.int16), - "int32-series": _create_narrow_series(np.int32), - "uint8-series": _create_narrow_series(np.uint8), - "uint16-series": _create_narrow_series(np.uint16), - "uint32-series": _create_narrow_series(np.uint32), -} - -_all_objs = {**indices_dict, **_series, **_narrow_series} - - -@pytest.fixture(params=_all_objs.keys()) -def index_or_series_obj(request): - """ - Fixture for tests on indexes, series and series with a narrow dtype - - copy to avoid mutation, e.g. setting .name - """ - return _all_objs[request.param].copy(deep=True) diff --git a/pandas/tests/series/conftest.py b/pandas/tests/series/conftest.py index 87aefec559311..ff0b0c71f88b0 100644 --- a/pandas/tests/series/conftest.py +++ b/pandas/tests/series/conftest.py @@ -3,6 +3,16 @@ 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(): """ From 471f2172da4d986674ebe7926aa4c4e1adb61c65 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Fri, 24 Jan 2020 11:41:03 +0100 Subject: [PATCH 16/23] moved helper to more appropriate location --- pandas/_testing.py | 6 ------ pandas/tests/base/test_ops.py | 11 +++++++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pandas/_testing.py b/pandas/_testing.py index 12bf36db15229..631d550c60534 100644 --- a/pandas/_testing.py +++ b/pandas/_testing.py @@ -2757,9 +2757,3 @@ def convert_rows_list_to_csv_str(rows_list: List[str]): sep = os.linesep expected = sep.join(rows_list) + sep return expected - - -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 diff --git a/pandas/tests/base/test_ops.py b/pandas/tests/base/test_ops.py index cc408b130bf41..82b1eaf969564 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 @@ -32,6 +33,12 @@ import pandas._testing as tm +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") @@ -260,7 +267,7 @@ def test_value_counts_unique_nunique_null(self, null_obj): klass = type(o) values = o._ndarray_values - if not tm.allow_na_ops(o): + if not allow_na_ops(o): continue # special assign to the numpy array @@ -741,7 +748,7 @@ def test_fillna(self): o = orig.copy() klass = type(o) - if not tm.allow_na_ops(o): + if not allow_na_ops(o): continue if needs_i8_conversion(o): From 7562479d300aed854e740e148458f4d75ee77a01 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Fri, 24 Jan 2020 11:46:31 +0100 Subject: [PATCH 17/23] removed some outdated changes --- pandas/tests/indexes/common.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pandas/tests/indexes/common.py b/pandas/tests/indexes/common.py index 55a76bcec169b..0a6122e78dbcf 100644 --- a/pandas/tests/indexes/common.py +++ b/pandas/tests/indexes/common.py @@ -295,8 +295,6 @@ def test_ensure_copied_data(self, indices): if isinstance(indices, PeriodIndex): # Needs "freq" specification: init_kwargs["freq"] = indices.freq - elif isinstance(indices, DatetimeIndex) and indices.tz is not None: - pytest.skip() elif isinstance(indices, (RangeIndex, MultiIndex, CategoricalIndex)): # RangeIndex cannot be initialized from data # MultiIndex and CategoricalIndex are tested separately @@ -464,9 +462,6 @@ def test_set_ops_error_cases(self, case, method, indices): def test_intersection_base(self, indices): if isinstance(indices, CategoricalIndex): pytest.skip() - if isinstance(indices, DatetimeIndex) and indices.tz is not None: - # TODO: This should probably be fixed - pytest.skip() first = indices[:5] second = indices[:3] @@ -490,10 +485,6 @@ def test_intersection_base(self, indices): first.intersection([1, 2, 3]) def test_union_base(self, indices): - if isinstance(indices, DatetimeIndex) and indices.tz is not None: - # TODO: This should probably be fixed - pytest.skip() - first = indices[3:] second = indices[:5] everything = indices From 85b16cbb121fbfd62ee73924f1c48d6eff84a669 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Fri, 24 Jan 2020 12:14:13 +0100 Subject: [PATCH 18/23] moved datetime_series fixture to the root conftest so it's available for index tests --- pandas/conftest.py | 10 ++++++++++ pandas/tests/indexes/datetimes/test_ops.py | 2 +- pandas/tests/series/conftest.py | 10 ---------- 3 files changed, 11 insertions(+), 11 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/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index ebc4c82f7299a..8ed98410ad9a4 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -23,7 +23,7 @@ def test_ops_properties_basic(self, datetime_series): 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/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(): """ From 87e0a5bdc63306b07a8b0517cc86ce95b884d5e0 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Sun, 26 Jan 2020 15:14:39 +0100 Subject: [PATCH 19/23] using all_arithmetic_functions in test_binary_ops_docs now --- pandas/core/ops/__init__.py | 2 +- pandas/tests/base/test_ops.py | 31 +++++++++++++------------------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 9ed233cad65ce..d963eb2a67316 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -265,7 +265,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 82b1eaf969564..0041afd0b958b 100644 --- a/pandas/tests/base/test_ops.py +++ b/pandas/tests/base/test_ops.py @@ -31,6 +31,7 @@ Timestamp, ) import pandas._testing as tm +from pandas.core.ops import _get_op_name, _get_opstr def allow_na_ops(obj: Any) -> bool: @@ -84,27 +85,21 @@ def setup_method(self, method): @pytest.mark.parametrize("klass", [Series, DataFrame]) -@pytest.mark.parametrize( - "op_name, op", - [ - ("add", "+"), - ("sub", "-"), - ("mul", "*"), - ("mod", "%"), - ("pow", "**"), - ("truediv", "/"), - ("floordiv", "//"), - ], -) -def test_binary_ops_docs(klass, op_name, op): +def test_binary_ops_docs(klass, all_arithmetic_functions): + operator = all_arithmetic_functions + operator_name = _get_op_name(operator, special=False) + operator_symbol = _get_opstr(operator) + is_reverse = operator_name.startswith("r") + operand1 = klass.__name__.lower() operand2 = "other" - expected_str = " ".join([operand1, op, operand2]) - assert expected_str in getattr(klass, op_name).__doc__ + order = [operand1, operator_symbol, operand2] + if is_reverse: + # switch order of operand1 and operand2 + order[0], order[2] = order[2], order[0] - # reverse version of the binary ops - expected_str = " ".join([operand2, op, operand1]) - assert expected_str in getattr(klass, "r" + op_name).__doc__ + expected_str = " ".join(order) + assert expected_str in getattr(klass, operator_name).__doc__ class TestTranspose(Ops): From 3979b3dbf6cdc600e3d172349cbaa89b24c6a301 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Sun, 26 Jan 2020 15:18:47 +0100 Subject: [PATCH 20/23] undid some unrelated changes --- pandas/tests/indexes/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/indexes/common.py b/pandas/tests/indexes/common.py index 0a6122e78dbcf..26d120619defc 100644 --- a/pandas/tests/indexes/common.py +++ b/pandas/tests/indexes/common.py @@ -298,7 +298,7 @@ def test_ensure_copied_data(self, indices): elif isinstance(indices, (RangeIndex, MultiIndex, CategoricalIndex)): # RangeIndex cannot be initialized from data # MultiIndex and CategoricalIndex are tested separately - pytest.skip() + return index_type = type(indices) result = index_type(indices.values, copy=True, **init_kwargs) @@ -461,7 +461,7 @@ def test_set_ops_error_cases(self, case, method, indices): def test_intersection_base(self, indices): if isinstance(indices, CategoricalIndex): - pytest.skip() + return first = indices[:5] second = indices[:3] From 8bf1142d7f0790288071f450903ff5d516618c09 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Mon, 27 Jan 2020 09:48:40 +0100 Subject: [PATCH 21/23] undid my change in pandas/core/ops/__init__.py and handle the case differently in pandas/tests/base/test_ops.py --- pandas/core/ops/__init__.py | 2 +- pandas/tests/base/test_ops.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index bd7c6490d0130..76e90a26874fc 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -265,7 +265,7 @@ def _get_opstr(op): rtruediv: "/", operator.floordiv: "//", rfloordiv: "//", - operator.mod: "%", + operator.mod: None, # TODO: Why None for mod but '%' for rmod? rmod: "%", operator.pow: "**", rpow: "**", diff --git a/pandas/tests/base/test_ops.py b/pandas/tests/base/test_ops.py index 0041afd0b958b..fe167df26dab7 100644 --- a/pandas/tests/base/test_ops.py +++ b/pandas/tests/base/test_ops.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta from io import StringIO +import operator import sys from typing import Any @@ -86,9 +87,13 @@ def setup_method(self, method): @pytest.mark.parametrize("klass", [Series, DataFrame]) def test_binary_ops_docs(klass, all_arithmetic_functions): - operator = all_arithmetic_functions - operator_name = _get_op_name(operator, special=False) - operator_symbol = _get_opstr(operator) + op = all_arithmetic_functions + operator_name = _get_op_name(op, special=False) + if op is operator.mod: + # _get_opstr returns 'None' for this operator + operator_symbol = "%" + else: + operator_symbol = _get_opstr(op) is_reverse = operator_name.startswith("r") operand1 = klass.__name__.lower() From c1e9f28840ca66c4d368c370340c248256309b37 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Mon, 27 Jan 2020 15:55:36 +0100 Subject: [PATCH 22/23] Revert "undid my change in pandas/core/ops/__init__.py and handle the case differently in pandas/tests/base/test_ops.py" This reverts commit 8bf1142d7f0790288071f450903ff5d516618c09. --- pandas/core/ops/__init__.py | 2 +- pandas/tests/base/test_ops.py | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 76e90a26874fc..bd7c6490d0130 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -265,7 +265,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 fe167df26dab7..0041afd0b958b 100644 --- a/pandas/tests/base/test_ops.py +++ b/pandas/tests/base/test_ops.py @@ -1,6 +1,5 @@ from datetime import datetime, timedelta from io import StringIO -import operator import sys from typing import Any @@ -87,13 +86,9 @@ def setup_method(self, method): @pytest.mark.parametrize("klass", [Series, DataFrame]) def test_binary_ops_docs(klass, all_arithmetic_functions): - op = all_arithmetic_functions - operator_name = _get_op_name(op, special=False) - if op is operator.mod: - # _get_opstr returns 'None' for this operator - operator_symbol = "%" - else: - operator_symbol = _get_opstr(op) + operator = all_arithmetic_functions + operator_name = _get_op_name(operator, special=False) + operator_symbol = _get_opstr(operator) is_reverse = operator_name.startswith("r") operand1 = klass.__name__.lower() From 87247a5d64111b0dd8baf3a792470827758f5e61 Mon Sep 17 00:00:00 2001 From: Martin Winkel Date: Sat, 1 Feb 2020 12:49:18 +0100 Subject: [PATCH 23/23] using hard-coded values to parametrize test_binary_ops --- pandas/tests/base/test_ops.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/pandas/tests/base/test_ops.py b/pandas/tests/base/test_ops.py index 0041afd0b958b..e522c7f743a05 100644 --- a/pandas/tests/base/test_ops.py +++ b/pandas/tests/base/test_ops.py @@ -31,7 +31,6 @@ Timestamp, ) import pandas._testing as tm -from pandas.core.ops import _get_op_name, _get_opstr def allow_na_ops(obj: Any) -> bool: @@ -84,22 +83,30 @@ def setup_method(self, method): self.objs = self.indexes + self.series + self.narrow_series +@pytest.mark.parametrize( + "op_name, op", + [ + ("add", "+"), + ("sub", "-"), + ("mul", "*"), + ("mod", "%"), + ("pow", "**"), + ("truediv", "/"), + ("floordiv", "//"), + ], +) @pytest.mark.parametrize("klass", [Series, DataFrame]) -def test_binary_ops_docs(klass, all_arithmetic_functions): - operator = all_arithmetic_functions - operator_name = _get_op_name(operator, special=False) - operator_symbol = _get_opstr(operator) - is_reverse = operator_name.startswith("r") - +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" - order = [operand1, operator_symbol, operand2] - if is_reverse: - # switch order of operand1 and operand2 - order[0], order[2] = order[2], order[0] + expected_str = " ".join([operand1, op, operand2]) + assert expected_str in getattr(klass, op_name).__doc__ - expected_str = " ".join(order) - assert expected_str in getattr(klass, operator_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):