From 5e3fdd7ae7d9160e63a25963ea986e6d30cfaacd Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 1 Aug 2023 14:51:29 -0700 Subject: [PATCH 1/8] REF: dont pass exception to check_opname --- pandas/tests/extension/base/ops.py | 39 ++++++++++++++++--- .../tests/extension/decimal/test_decimal.py | 10 ++++- pandas/tests/extension/test_arrow.py | 30 +++++++------- pandas/tests/extension/test_boolean.py | 19 ++------- pandas/tests/extension/test_categorical.py | 3 -- pandas/tests/extension/test_datetime.py | 28 ++----------- pandas/tests/extension/test_masked_numeric.py | 17 ++++---- pandas/tests/extension/test_period.py | 29 ++------------ 8 files changed, 74 insertions(+), 101 deletions(-) diff --git a/pandas/tests/extension/base/ops.py b/pandas/tests/extension/base/ops.py index 49598f014fbf6..e727be05706a6 100644 --- a/pandas/tests/extension/base/ops.py +++ b/pandas/tests/extension/base/ops.py @@ -10,10 +10,32 @@ class BaseOpsUtil(BaseExtensionTests): + series_scalar_exc: type[Exception] | None = TypeError + frame_scalar_exc: type[Exception] | None = TypeError + series_array_exc: type[Exception] | None = TypeError + + def get_expected_exception( + self, op_name: str, obj, other + ) -> type[Exception] | None: + # Find the Exception, if any we expect to raise calling + # obj.__op_name__(other) + + # The self.foo_bar_exc pattern isn't great in part because it can depend + # on op_name or dtypes, but we use it here for backward-compatibility. + if op_name in ["divmod", "rdivmod"]: + return self.divmod_exc + if isinstance(obj, pd.Series) and isinstance(other, pd.Series): + return self.series_array_exc + elif isinstance(obj, pd.Series): + return self.series_scalar_exc + else: + return self.frame_scalar_exc + def get_op_from_name(self, op_name: str): return tm.get_op_from_name(op_name) - def check_opname(self, ser: pd.Series, op_name: str, other, exc=Exception): + def check_opname(self, ser: pd.Series, op_name: str, other): + exc = self.get_expected_exception(op_name, ser, other) op = self.get_op_from_name(op_name) self._check_op(ser, op, other, op_name, exc) @@ -30,6 +52,9 @@ def _combine(self, obj, other, op): def _check_op( self, ser: pd.Series, op, other, op_name: str, exc=NotImplementedError ): + # Check that the Series/DataFrame arithmetic/comparison method matches + # the pointwise result from _combine. + if exc is None: result = op(ser, other) expected = self._combine(ser, other, op) @@ -41,6 +66,10 @@ def _check_op( def _check_divmod_op(self, ser: pd.Series, op, other, exc=Exception): # divmod has multiple return values, so check separately + if op is divmod: + exc = self.get_expected_exception("divmod", ser, other) + else: + exc = self.get_expected_exception("rdivmod", ser, other) if exc is None: result_div, result_mod = op(ser, other) if op is divmod: @@ -76,21 +105,19 @@ def test_arith_series_with_scalar(self, data, all_arithmetic_operators): # series & scalar op_name = all_arithmetic_operators ser = pd.Series(data) - self.check_opname(ser, op_name, ser.iloc[0], exc=self.series_scalar_exc) + self.check_opname(ser, op_name, ser.iloc[0]) def test_arith_frame_with_scalar(self, data, all_arithmetic_operators): # frame & scalar op_name = all_arithmetic_operators df = pd.DataFrame({"A": data}) - self.check_opname(df, op_name, data[0], exc=self.frame_scalar_exc) + self.check_opname(df, op_name, data[0]) def test_arith_series_with_array(self, data, all_arithmetic_operators): # ndarray & other series op_name = all_arithmetic_operators ser = pd.Series(data) - self.check_opname( - ser, op_name, pd.Series([ser.iloc[0]] * len(ser)), exc=self.series_array_exc - ) + self.check_opname(ser, op_name, pd.Series([ser.iloc[0]] * len(ser))) def test_divmod(self, data): ser = pd.Series(data) diff --git a/pandas/tests/extension/decimal/test_decimal.py b/pandas/tests/extension/decimal/test_decimal.py index b2d47ec7d8f32..66ecb08e15dd1 100644 --- a/pandas/tests/extension/decimal/test_decimal.py +++ b/pandas/tests/extension/decimal/test_decimal.py @@ -325,8 +325,14 @@ def test_astype_dispatches(frame): class TestArithmeticOps(base.BaseArithmeticOpsTests): - def check_opname(self, s, op_name, other, exc=None): - super().check_opname(s, op_name, other, exc=None) + series_scalar_exc = None + frame_scalar_exc = None + series_array_exc = None + + def get_expected_exception( + self, op_name: str, obj, other + ) -> type[Exception] | None: + return None def test_arith_series_with_array(self, data, all_arithmetic_operators): op_name = all_arithmetic_operators diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 266c1236f20d2..186e00ca91d58 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -955,16 +955,24 @@ def _is_temporal_supported(self, opname, pa_dtype): and pa.types.is_temporal(pa_dtype) ) - def _get_scalar_exception(self, opname, pa_dtype): - arrow_temporal_supported = self._is_temporal_supported(opname, pa_dtype) - if opname in { + def get_expected_exception( + self, op_name: str, obj, other + ) -> type[Exception] | None: + if op_name == "divmod" or op_name == "rdivmod": + return self.divmod_exc + + dtype = tm.get_dtype(obj) + pa_dtype = dtype.pyarrow_dtype + + arrow_temporal_supported = self._is_temporal_supported(op_name, pa_dtype) + if op_name in { "__mod__", "__rmod__", }: exc = NotImplementedError elif arrow_temporal_supported: exc = None - elif opname in ["__add__", "__radd__"] and ( + elif op_name in ["__add__", "__radd__"] and ( pa.types.is_string(pa_dtype) or pa.types.is_binary(pa_dtype) ): exc = None @@ -1053,10 +1061,6 @@ def test_arith_series_with_scalar( ): pytest.skip("Skip testing Python string formatting") - self.series_scalar_exc = self._get_scalar_exception( - all_arithmetic_operators, pa_dtype - ) - mark = self._get_arith_xfail_marker(all_arithmetic_operators, pa_dtype) if mark is not None: request.node.add_marker(mark) @@ -1086,10 +1090,6 @@ def test_arith_frame_with_scalar( ): pytest.skip("Skip testing Python string formatting") - self.frame_scalar_exc = self._get_scalar_exception( - all_arithmetic_operators, pa_dtype - ) - mark = self._get_arith_xfail_marker(all_arithmetic_operators, pa_dtype) if mark is not None: request.node.add_marker(mark) @@ -1114,10 +1114,6 @@ def test_arith_series_with_array( ): pa_dtype = data.dtype.pyarrow_dtype - self.series_array_exc = self._get_scalar_exception( - all_arithmetic_operators, pa_dtype - ) - if ( all_arithmetic_operators in ( @@ -1159,7 +1155,7 @@ def test_arith_series_with_array( or pa.types.is_decimal(pa_dtype) ): monkeypatch.setattr(TestBaseArithmeticOps, "_combine", self._patch_combine) - self.check_opname(ser, op_name, other, exc=self.series_array_exc) + self.check_opname(ser, op_name, other) def test_add_series_with_extension_array(self, data, request): pa_dtype = data.dtype.pyarrow_dtype diff --git a/pandas/tests/extension/test_boolean.py b/pandas/tests/extension/test_boolean.py index 24095f807d4ae..79b5bbca37377 100644 --- a/pandas/tests/extension/test_boolean.py +++ b/pandas/tests/extension/test_boolean.py @@ -119,13 +119,11 @@ class TestMissing(base.BaseMissingTests): class TestArithmeticOps(base.BaseArithmeticOpsTests): implements = {"__sub__", "__rsub__"} - def check_opname(self, s, op_name, other, exc=None): - # overwriting to indicate ops don't raise an error - exc = None + def get_expected_exception(self, op_name, obj, other): if op_name.strip("_").lstrip("r") in ["pow", "truediv", "floordiv"]: # match behavior with non-masked bool dtype - exc = NotImplementedError - super().check_opname(s, op_name, other, exc=exc) + return NotImplementedError + return None def _check_op(self, obj, op, other, op_name, exc=NotImplementedError): if exc is None: @@ -168,18 +166,9 @@ def _check_op(self, obj, op, other, op_name, exc=NotImplementedError): def test_divmod_series_array(self, data, data_for_twos): super().test_divmod_series_array(data, data_for_twos) - @pytest.mark.xfail( - reason="Inconsistency between floordiv and divmod; we raise for floordiv " - "but not for divmod. This matches what we do for non-masked bool dtype." - ) - def test_divmod(self, data): - super().test_divmod(data) - class TestComparisonOps(base.BaseComparisonOpsTests): - def check_opname(self, s, op_name, other, exc=None): - # overwriting to indicate ops don't raise an error - super().check_opname(s, op_name, other, exc=None) + pass class TestReshaping(base.BaseReshapingTests): diff --git a/pandas/tests/extension/test_categorical.py b/pandas/tests/extension/test_categorical.py index a712e2992c120..f0dcd0124e163 100644 --- a/pandas/tests/extension/test_categorical.py +++ b/pandas/tests/extension/test_categorical.py @@ -270,9 +270,6 @@ def test_divmod_series_array(self): # skipping because it is not implemented pass - def _check_divmod_op(self, s, op, other, exc=NotImplementedError): - return super()._check_divmod_op(s, op, other, exc=TypeError) - class TestComparisonOps(base.BaseComparisonOpsTests): def _compare_other(self, s, data, op, other): diff --git a/pandas/tests/extension/test_datetime.py b/pandas/tests/extension/test_datetime.py index d8adc4c8c91a5..122fec5bf48d6 100644 --- a/pandas/tests/extension/test_datetime.py +++ b/pandas/tests/extension/test_datetime.py @@ -130,22 +130,10 @@ class TestInterface(BaseDatetimeTests, base.BaseInterfaceTests): class TestArithmeticOps(BaseDatetimeTests, base.BaseArithmeticOpsTests): implements = {"__sub__", "__rsub__"} - def test_arith_frame_with_scalar(self, data, all_arithmetic_operators): - # frame & scalar - if all_arithmetic_operators in self.implements: - df = pd.DataFrame({"A": data}) - self.check_opname(df, all_arithmetic_operators, data[0], exc=None) - else: - # ... but not the rest. - super().test_arith_frame_with_scalar(data, all_arithmetic_operators) - - def test_arith_series_with_scalar(self, data, all_arithmetic_operators): - if all_arithmetic_operators in self.implements: - ser = pd.Series(data) - self.check_opname(ser, all_arithmetic_operators, ser.iloc[0], exc=None) - else: - # ... but not the rest. - super().test_arith_series_with_scalar(data, all_arithmetic_operators) + def get_expected_exception(self, op_name, obj, other): + if op_name in self.implements: + return None + return super().get_expected_exception(op_name, obj, other) def test_add_series_with_extension_array(self, data): # Datetime + Datetime not implemented @@ -154,14 +142,6 @@ def test_add_series_with_extension_array(self, data): with pytest.raises(TypeError, match=msg): ser + data - def test_arith_series_with_array(self, data, all_arithmetic_operators): - if all_arithmetic_operators in self.implements: - ser = pd.Series(data) - self.check_opname(ser, all_arithmetic_operators, ser.iloc[0], exc=None) - else: - # ... but not the rest. - super().test_arith_series_with_scalar(data, all_arithmetic_operators) - def test_divmod_series_array(self): # GH 23287 # skipping because it is not implemented diff --git a/pandas/tests/extension/test_masked_numeric.py b/pandas/tests/extension/test_masked_numeric.py index 321321e0760d5..50ad822adadaa 100644 --- a/pandas/tests/extension/test_masked_numeric.py +++ b/pandas/tests/extension/test_masked_numeric.py @@ -186,15 +186,17 @@ def _check_op(self, s, op, other, op_name, exc=NotImplementedError): with pytest.raises(exc): op(s, other) - def check_opname(self, ser: pd.Series, op_name: str, other, exc=None): - # overwriting to indicate ops don't raise an error - super().check_opname(ser, op_name, other, exc=None) - - def _check_divmod_op(self, ser: pd.Series, op, other, exc=None): - super()._check_divmod_op(ser, op, other, None) + series_scalar_exc = None + series_array_exc = None + frame_scalar_exc = None + divmod_exc = None class TestComparisonOps(base.BaseComparisonOpsTests): + series_scalar_exc = None + series_array_exc = None + frame_scalar_exc = None + def _check_op( self, ser: pd.Series, op, other, op_name: str, exc=NotImplementedError ): @@ -207,9 +209,6 @@ def _check_op( with pytest.raises(exc): op(ser, other) - def check_opname(self, ser: pd.Series, op_name: str, other, exc=None): - super().check_opname(ser, op_name, other, exc=None) - def _compare_other(self, ser: pd.Series, data, op, other): op_name = f"__{op.__name__}__" self.check_opname(ser, op_name, other) diff --git a/pandas/tests/extension/test_period.py b/pandas/tests/extension/test_period.py index bc0872d359d47..af4bd785635eb 100644 --- a/pandas/tests/extension/test_period.py +++ b/pandas/tests/extension/test_period.py @@ -118,31 +118,10 @@ class TestInterface(BasePeriodTests, base.BaseInterfaceTests): class TestArithmeticOps(BasePeriodTests, base.BaseArithmeticOpsTests): implements = {"__sub__", "__rsub__"} - def test_arith_frame_with_scalar(self, data, all_arithmetic_operators): - # frame & scalar - if all_arithmetic_operators in self.implements: - df = pd.DataFrame({"A": data}) - self.check_opname(df, all_arithmetic_operators, data[0], exc=None) - else: - # ... but not the rest. - super().test_arith_frame_with_scalar(data, all_arithmetic_operators) - - def test_arith_series_with_scalar(self, data, all_arithmetic_operators): - # we implement substitution... - if all_arithmetic_operators in self.implements: - s = pd.Series(data) - self.check_opname(s, all_arithmetic_operators, s.iloc[0], exc=None) - else: - # ... but not the rest. - super().test_arith_series_with_scalar(data, all_arithmetic_operators) - - def test_arith_series_with_array(self, data, all_arithmetic_operators): - if all_arithmetic_operators in self.implements: - s = pd.Series(data) - self.check_opname(s, all_arithmetic_operators, s.iloc[0], exc=None) - else: - # ... but not the rest. - super().test_arith_series_with_scalar(data, all_arithmetic_operators) + def get_expected_exception(self, op_name, obj, other): + if op_name in self.implements: + return None + return super().get_expected_exception(op_name, obj, other) def _check_divmod_op(self, s, op, other, exc=NotImplementedError): super()._check_divmod_op(s, op, other, exc=TypeError) From a45e34920cac1d0e0816702542b361d6c3c72379 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 1 Aug 2023 18:02:03 -0700 Subject: [PATCH 2/8] future imports --- pandas/tests/extension/decimal/test_decimal.py | 2 ++ pandas/tests/extension/test_arrow.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pandas/tests/extension/decimal/test_decimal.py b/pandas/tests/extension/decimal/test_decimal.py index 66ecb08e15dd1..32981a8274b82 100644 --- a/pandas/tests/extension/decimal/test_decimal.py +++ b/pandas/tests/extension/decimal/test_decimal.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import decimal import operator diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 62228af1c6234..0641d1a553bac 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -10,6 +10,8 @@ classes (if they are relevant for the extension interface for all dtypes), or be added to the array-specific tests in `pandas/tests/arrays/`. """ +from __future__ import annotations + from datetime import ( date, datetime, From 29665214032c026a528de20a51276586b18882fb Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 2 Aug 2023 07:57:47 -0700 Subject: [PATCH 3/8] privatize --- pandas/tests/extension/base/ops.py | 8 ++++---- pandas/tests/extension/decimal/test_decimal.py | 2 +- pandas/tests/extension/test_arrow.py | 2 +- pandas/tests/extension/test_boolean.py | 2 +- pandas/tests/extension/test_datetime.py | 4 ++-- pandas/tests/extension/test_period.py | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pandas/tests/extension/base/ops.py b/pandas/tests/extension/base/ops.py index e727be05706a6..58d9579bc7472 100644 --- a/pandas/tests/extension/base/ops.py +++ b/pandas/tests/extension/base/ops.py @@ -14,7 +14,7 @@ class BaseOpsUtil(BaseExtensionTests): frame_scalar_exc: type[Exception] | None = TypeError series_array_exc: type[Exception] | None = TypeError - def get_expected_exception( + def _get_expected_exception( self, op_name: str, obj, other ) -> type[Exception] | None: # Find the Exception, if any we expect to raise calling @@ -35,7 +35,7 @@ def get_op_from_name(self, op_name: str): return tm.get_op_from_name(op_name) def check_opname(self, ser: pd.Series, op_name: str, other): - exc = self.get_expected_exception(op_name, ser, other) + exc = self._get_expected_exception(op_name, ser, other) op = self.get_op_from_name(op_name) self._check_op(ser, op, other, op_name, exc) @@ -67,9 +67,9 @@ def _check_op( def _check_divmod_op(self, ser: pd.Series, op, other, exc=Exception): # divmod has multiple return values, so check separately if op is divmod: - exc = self.get_expected_exception("divmod", ser, other) + exc = self._get_expected_exception("divmod", ser, other) else: - exc = self.get_expected_exception("rdivmod", ser, other) + exc = self._get_expected_exception("rdivmod", ser, other) if exc is None: result_div, result_mod = op(ser, other) if op is divmod: diff --git a/pandas/tests/extension/decimal/test_decimal.py b/pandas/tests/extension/decimal/test_decimal.py index 32981a8274b82..3a1d6a5d01baf 100644 --- a/pandas/tests/extension/decimal/test_decimal.py +++ b/pandas/tests/extension/decimal/test_decimal.py @@ -331,7 +331,7 @@ class TestArithmeticOps(base.BaseArithmeticOpsTests): frame_scalar_exc = None series_array_exc = None - def get_expected_exception( + def _get_expected_exception( self, op_name: str, obj, other ) -> type[Exception] | None: return None diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 0641d1a553bac..693e98dd89c8f 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -989,7 +989,7 @@ def _is_temporal_supported(self, opname, pa_dtype): and pa.types.is_temporal(pa_dtype) ) - def get_expected_exception( + def _get_expected_exception( self, op_name: str, obj, other ) -> type[Exception] | None: if op_name == "divmod" or op_name == "rdivmod": diff --git a/pandas/tests/extension/test_boolean.py b/pandas/tests/extension/test_boolean.py index 79b5bbca37377..7e2ea3147c01f 100644 --- a/pandas/tests/extension/test_boolean.py +++ b/pandas/tests/extension/test_boolean.py @@ -119,7 +119,7 @@ class TestMissing(base.BaseMissingTests): class TestArithmeticOps(base.BaseArithmeticOpsTests): implements = {"__sub__", "__rsub__"} - def get_expected_exception(self, op_name, obj, other): + def _get_expected_exception(self, op_name, obj, other): if op_name.strip("_").lstrip("r") in ["pow", "truediv", "floordiv"]: # match behavior with non-masked bool dtype return NotImplementedError diff --git a/pandas/tests/extension/test_datetime.py b/pandas/tests/extension/test_datetime.py index 122fec5bf48d6..ab21f768e6521 100644 --- a/pandas/tests/extension/test_datetime.py +++ b/pandas/tests/extension/test_datetime.py @@ -130,10 +130,10 @@ class TestInterface(BaseDatetimeTests, base.BaseInterfaceTests): class TestArithmeticOps(BaseDatetimeTests, base.BaseArithmeticOpsTests): implements = {"__sub__", "__rsub__"} - def get_expected_exception(self, op_name, obj, other): + def _get_expected_exception(self, op_name, obj, other): if op_name in self.implements: return None - return super().get_expected_exception(op_name, obj, other) + return super()._get_expected_exception(op_name, obj, other) def test_add_series_with_extension_array(self, data): # Datetime + Datetime not implemented diff --git a/pandas/tests/extension/test_period.py b/pandas/tests/extension/test_period.py index af4bd785635eb..7deb9d5f9be5d 100644 --- a/pandas/tests/extension/test_period.py +++ b/pandas/tests/extension/test_period.py @@ -118,10 +118,10 @@ class TestInterface(BasePeriodTests, base.BaseInterfaceTests): class TestArithmeticOps(BasePeriodTests, base.BaseArithmeticOpsTests): implements = {"__sub__", "__rsub__"} - def get_expected_exception(self, op_name, obj, other): + def _get_expected_exception(self, op_name, obj, other): if op_name in self.implements: return None - return super().get_expected_exception(op_name, obj, other) + return super()._get_expected_exception(op_name, obj, other) def _check_divmod_op(self, s, op, other, exc=NotImplementedError): super()._check_divmod_op(s, op, other, exc=TypeError) From ec140245bd7438a1af97ad5bde0fee351598712d Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 2 Aug 2023 11:15:13 -0700 Subject: [PATCH 4/8] REF: update pattern for check_divmod_op --- pandas/tests/extension/base/ops.py | 29 +++++++++++-------- .../tests/extension/decimal/test_decimal.py | 4 --- pandas/tests/extension/json/test_json.py | 3 -- pandas/tests/extension/test_arrow.py | 2 +- pandas/tests/extension/test_numpy.py | 2 +- pandas/tests/extension/test_period.py | 7 +---- pandas/tests/extension/test_sparse.py | 4 --- 7 files changed, 20 insertions(+), 31 deletions(-) diff --git a/pandas/tests/extension/base/ops.py b/pandas/tests/extension/base/ops.py index f1f99d5ff0d6c..b91c2109ef525 100644 --- a/pandas/tests/extension/base/ops.py +++ b/pandas/tests/extension/base/ops.py @@ -45,17 +45,20 @@ def _cast_pointwise_result(self, op_name: str, obj, other, pointwise_result): def get_op_from_name(self, op_name: str): return tm.get_op_from_name(op_name) + # Subclasses are not expected to need to override check_opname, _check_op, + # _check_divmod_op, or _combine. + # Ideally any relevant overriding can be done in _cast_pointwise_result, + # get_op_from_name, and the specification of `exc`. If you find a use + # case that still requires overriding _check_op or _combine, please let + # us know at github.com/pandas-dev/pandas/issues + @final def check_opname(self, ser: pd.Series, op_name: str, other): exc = self._get_expected_exception(op_name, ser, other) op = self.get_op_from_name(op_name) self._check_op(ser, op, other, op_name, exc) - # Subclasses are not expected to need to override _check_op or _combine. - # Ideally any relevant overriding can be done in _cast_pointwise_result, - # get_op_from_name, and the specification of `exc`. If you find a use - # case that still requires overriding _check_op or _combine, please let - # us know at github.com/pandas-dev/pandas/issues + # see comment on check_opname @final def _combine(self, obj, other, op): if isinstance(obj, pd.DataFrame): @@ -66,7 +69,7 @@ def _combine(self, obj, other, op): expected = obj.combine(other, op) return expected - # see comment on _combine + # see comment on check_opname @final def _check_op( self, ser: pd.Series, op, other, op_name: str, exc=NotImplementedError @@ -84,12 +87,14 @@ def _check_op( with pytest.raises(exc): op(ser, other) - def _check_divmod_op(self, ser: pd.Series, op, other, exc=Exception): - # divmod has multiple return values, so check separately + # see comment on check_opname + @final + def _check_divmod_op(self, ser: pd.Series, op, other): + # check that divmod behavior matches behavior of floordiv+mod if op is divmod: - exc = self._get_expected_exception("divmod", ser, other) + exc = self._get_expected_exception("__divmod__", ser, other) else: - exc = self._get_expected_exception("rdivmod", ser, other) + exc = self._get_expected_exception("__rdivmod__", ser, other) if exc is None: result_div, result_mod = op(ser, other) if op is divmod: @@ -141,8 +146,8 @@ def test_arith_series_with_array(self, data, all_arithmetic_operators): def test_divmod(self, data): ser = pd.Series(data) - self._check_divmod_op(ser, divmod, 1, exc=self.divmod_exc) - self._check_divmod_op(1, ops.rdivmod, ser, exc=self.divmod_exc) + self._check_divmod_op(ser, divmod, 1) + self._check_divmod_op(1, ops.rdivmod, ser) def test_divmod_series_array(self, data, data_for_twos): ser = pd.Series(data) diff --git a/pandas/tests/extension/decimal/test_decimal.py b/pandas/tests/extension/decimal/test_decimal.py index 3a1d6a5d01baf..d1994f378fb85 100644 --- a/pandas/tests/extension/decimal/test_decimal.py +++ b/pandas/tests/extension/decimal/test_decimal.py @@ -358,10 +358,6 @@ def test_arith_series_with_array(self, data, all_arithmetic_operators): context.traps[decimal.DivisionByZero] = divbyzerotrap context.traps[decimal.InvalidOperation] = invalidoptrap - def _check_divmod_op(self, s, op, other, exc=NotImplementedError): - # We implement divmod - super()._check_divmod_op(s, op, other, exc=None) - class TestComparisonOps(base.BaseComparisonOpsTests): def test_compare_scalar(self, data, comparison_op): diff --git a/pandas/tests/extension/json/test_json.py b/pandas/tests/extension/json/test_json.py index 8a571d9295e1f..0c9abd45a51a5 100644 --- a/pandas/tests/extension/json/test_json.py +++ b/pandas/tests/extension/json/test_json.py @@ -323,9 +323,6 @@ def test_divmod_series_array(self): # skipping because it is not implemented super().test_divmod_series_array() - def _check_divmod_op(self, s, op, other, exc=NotImplementedError): - return super()._check_divmod_op(s, op, other, exc=TypeError) - class TestComparisonOps(BaseJSON, base.BaseComparisonOpsTests): pass diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index c9aa201ad3bac..00e0c1ccf519b 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -977,7 +977,7 @@ def _is_temporal_supported(self, opname, pa_dtype): def _get_expected_exception( self, op_name: str, obj, other ) -> type[Exception] | None: - if op_name == "divmod" or op_name == "rdivmod": + if op_name == "__divmod__" or op_name == "__rdivmod__": return self.divmod_exc dtype = tm.get_dtype(obj) diff --git a/pandas/tests/extension/test_numpy.py b/pandas/tests/extension/test_numpy.py index f4ff423ad485b..c5b9f79bf3185 100644 --- a/pandas/tests/extension/test_numpy.py +++ b/pandas/tests/extension/test_numpy.py @@ -281,7 +281,7 @@ def test_divmod(self, data): @skip_nested def test_divmod_series_array(self, data): ser = pd.Series(data) - self._check_divmod_op(ser, divmod, data, exc=None) + self._check_divmod_op(ser, divmod, data) @skip_nested def test_arith_series_with_scalar(self, data, all_arithmetic_operators): diff --git a/pandas/tests/extension/test_period.py b/pandas/tests/extension/test_period.py index 7deb9d5f9be5d..7b6bc98ee8c05 100644 --- a/pandas/tests/extension/test_period.py +++ b/pandas/tests/extension/test_period.py @@ -116,16 +116,11 @@ class TestInterface(BasePeriodTests, base.BaseInterfaceTests): class TestArithmeticOps(BasePeriodTests, base.BaseArithmeticOpsTests): - implements = {"__sub__", "__rsub__"} - def _get_expected_exception(self, op_name, obj, other): - if op_name in self.implements: + if op_name in ("__sub__", "__rsub__"): return None return super()._get_expected_exception(op_name, obj, other) - def _check_divmod_op(self, s, op, other, exc=NotImplementedError): - super()._check_divmod_op(s, op, other, exc=TypeError) - def test_add_series_with_extension_array(self, data): # we don't implement + for Period s = pd.Series(data) diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index 77898abb70f4f..a39133c784380 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -415,10 +415,6 @@ def test_arith_frame_with_scalar(self, data, all_arithmetic_operators, request): request.node.add_marker(mark) super().test_arith_frame_with_scalar(data, all_arithmetic_operators) - def _check_divmod_op(self, ser, op, other, exc=NotImplementedError): - # We implement divmod - super()._check_divmod_op(ser, op, other, exc=None) - class TestComparisonOps(BaseSparseTests): def _compare_other(self, data_for_compare: SparseArray, comparison_op, other): From 54bc249c564178bdbcefde30fc0e539abefccad5 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 3 Aug 2023 13:43:23 -0700 Subject: [PATCH 5/8] typo fixup --- pandas/tests/extension/base/ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/extension/base/ops.py b/pandas/tests/extension/base/ops.py index b91c2109ef525..86e01dc240ccb 100644 --- a/pandas/tests/extension/base/ops.py +++ b/pandas/tests/extension/base/ops.py @@ -24,7 +24,7 @@ def _get_expected_exception( # The self.foo_bar_exc pattern isn't great in part because it can depend # on op_name or dtypes, but we use it here for backward-compatibility. - if op_name in ["divmod", "rdivmod"]: + if op_name in ["__divmod__", "__rdivmod__"]: return self.divmod_exc if isinstance(obj, pd.Series) and isinstance(other, pd.Series): return self.series_array_exc From 051c44fef4b061d83dd3ace890fe38bad176f0b8 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 3 Aug 2023 14:42:39 -0700 Subject: [PATCH 6/8] suggested edit --- pandas/tests/extension/base/ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/extension/base/ops.py b/pandas/tests/extension/base/ops.py index 86e01dc240ccb..58da0430d5d81 100644 --- a/pandas/tests/extension/base/ops.py +++ b/pandas/tests/extension/base/ops.py @@ -22,7 +22,7 @@ def _get_expected_exception( # Find the Exception, if any we expect to raise calling # obj.__op_name__(other) - # The self.foo_bar_exc pattern isn't great in part because it can depend + # The self.obj_bar_exc pattern isn't great in part because it can depend # on op_name or dtypes, but we use it here for backward-compatibility. if op_name in ["__divmod__", "__rdivmod__"]: return self.divmod_exc From 9c04ee9914f14dbec0aad64dfc1cd8b9deb3c29a Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 3 Aug 2023 15:56:39 -0700 Subject: [PATCH 7/8] mypy fixup --- pandas/tests/extension/base/ops.py | 1 + pandas/tests/extension/test_arrow.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pandas/tests/extension/base/ops.py b/pandas/tests/extension/base/ops.py index 58da0430d5d81..aafb1900a4236 100644 --- a/pandas/tests/extension/base/ops.py +++ b/pandas/tests/extension/base/ops.py @@ -15,6 +15,7 @@ class BaseOpsUtil(BaseExtensionTests): series_scalar_exc: type[Exception] | None = TypeError frame_scalar_exc: type[Exception] | None = TypeError series_array_exc: type[Exception] | None = TypeError + divmod_exc: type[Exception] | None = TypeError def _get_expected_exception( self, op_name: str, obj, other diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 00e0c1ccf519b..bcbc8db51e133 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -981,7 +981,9 @@ def _get_expected_exception( return self.divmod_exc dtype = tm.get_dtype(obj) - pa_dtype = dtype.pyarrow_dtype + # error: Item "dtype[Any]" of "dtype[Any] | ExtensionDtype" has no + # attribute "pyarrow_dtype" + pa_dtype = dtype.pyarrow_dtype # type: ignore[union-attr] arrow_temporal_supported = self._is_temporal_supported(op_name, pa_dtype) if op_name in { From 73a56edb49f44b136fe717f091b1002523fa7e65 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 3 Aug 2023 19:16:42 -0700 Subject: [PATCH 8/8] lint fixup --- pandas/tests/extension/test_arrow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 8e2f7fb468f0b..2438626cf0347 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -969,7 +969,7 @@ def _is_temporal_supported(self, opname, pa_dtype): def _get_expected_exception( self, op_name: str, obj, other ) -> type[Exception] | None: - if op_name == "__divmod__" or op_name == "__rdivmod__": + if op_name in ("__divmod__", "__rdivmod__"): return self.divmod_exc dtype = tm.get_dtype(obj)