diff --git a/pandas/tests/extension/base/missing.py b/pandas/tests/extension/base/missing.py index b6da5ce91afbd..0873e1537a6d2 100644 --- a/pandas/tests/extension/base/missing.py +++ b/pandas/tests/extension/base/missing.py @@ -6,6 +6,14 @@ class BaseMissingTests: + def _honors_copy_keyword(self, data) -> bool: + """Whether the EA honors the copy keyword in methods like fillna. + + EAs that always return new data regardless of copy=False should + override this to return False. + """ + return True + def test_isna(self, data_missing): expected = np.array([True, False]) @@ -137,10 +145,11 @@ def test_fillna_readonly(self, data_missing): assert result[0] == data_missing[1] tm.assert_extension_array_equal(data, data_missing) - # but with copy=False, this raises for EAs that respect the copy keyword - with pytest.raises(ValueError, match="Cannot modify read-only array"): - data.fillna(data_missing[1], copy=False) - tm.assert_extension_array_equal(data, data_missing) + if self._honors_copy_keyword(data_missing): + # with copy=False, this raises for EAs that respect the copy keyword + with pytest.raises(ValueError, match="Cannot modify read-only array"): + data.fillna(data_missing[1], copy=False) + tm.assert_extension_array_equal(data, data_missing) def test_fillna_series(self, data_missing): fill_value = data_missing[1] diff --git a/pandas/tests/extension/decimal/test_decimal.py b/pandas/tests/extension/decimal/test_decimal.py index 695a122a98abb..122ea7ab64878 100644 --- a/pandas/tests/extension/decimal/test_decimal.py +++ b/pandas/tests/extension/decimal/test_decimal.py @@ -67,6 +67,9 @@ def data_for_grouping(): class TestDecimalArray(base.ExtensionTests): + def _honors_copy_keyword(self, data) -> bool: + return False + def _get_expected_exception( self, op_name: str, obj, other ) -> type[Exception] | tuple[type[Exception], ...] | None: @@ -167,10 +170,6 @@ def test_fillna_limit_series(self, data_missing): ): super().test_fillna_limit_series(data_missing) - @pytest.mark.xfail(reason="copy keyword is missing") - def test_fillna_readonly(self, data_missing): - super().test_fillna_readonly(data_missing) - def test_series_repr(self, data): # Overriding this base test to explicitly test that # the custom _formatter is used diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 053ff6bee5efa..959e4d452c47f 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -271,6 +271,9 @@ def data_for_twos(data): class TestArrowArray(base.ExtensionTests): + def _honors_copy_keyword(self, data) -> bool: + return False + def _construct_for_combine_add(self, left, right): dtype = left.dtype @@ -666,21 +669,6 @@ def test_fillna_no_op_returns_copy(self, data): assert result is not data tm.assert_extension_array_equal(result, data) - def test_fillna_readonly(self, data_missing): - data = data_missing.copy() - data._readonly = True - - # by default fillna(copy=True), then this works fine - result = data.fillna(data_missing[1]) - assert result[0] == data_missing[1] - tm.assert_extension_array_equal(data, data_missing) - - # fillna(copy=False) is generally not honored by Arrow-backed array, - # but always returns new data -> same result as above - result = data.fillna(data_missing[1]) - assert result[0] == data_missing[1] - tm.assert_extension_array_equal(data, data_missing) - @pytest.mark.xfail( reason="GH 45419: pyarrow.ChunkedArray does not support views", run=False ) diff --git a/pandas/tests/extension/test_interval.py b/pandas/tests/extension/test_interval.py index 81d8ee5709bdf..f1767a3577b1e 100644 --- a/pandas/tests/extension/test_interval.py +++ b/pandas/tests/extension/test_interval.py @@ -83,6 +83,9 @@ def data_for_grouping(): class TestIntervalArray(base.ExtensionTests): divmod_exc = TypeError + def _honors_copy_keyword(self, data) -> bool: + return False + def _supports_reduction(self, ser: pd.Series, op_name: str) -> bool: return op_name in ["min", "max", "count"] @@ -103,10 +106,6 @@ def test_fillna_limit_series(self, data_missing): def test_fillna_length_mismatch(self, data_missing): super().test_fillna_length_mismatch(data_missing) - @pytest.mark.xfail(reason="copy=False is not Implemented") - def test_fillna_readonly(self, data_missing): - super().test_fillna_readonly(data_missing) - @pytest.mark.filterwarnings( "ignore:invalid value encountered in cast:RuntimeWarning" ) diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index 68e6935e725ff..6a460b3ef1496 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -97,6 +97,9 @@ def data_for_compare(request): class TestSparseArray(base.ExtensionTests): + def _honors_copy_keyword(self, data) -> bool: + return False + def _supports_reduction(self, obj, op_name: str) -> bool: return True @@ -237,21 +240,6 @@ def test_isna(self, data_missing): def test_fillna_no_op_returns_copy(self, data, request): super().test_fillna_no_op_returns_copy(data) - def test_fillna_readonly(self, data_missing): - # copy keyword is ignored by SparseArray.fillna - # -> copy=True vs False doesn't make a difference - data = data_missing.copy() - data._readonly = True - - result = data.fillna(data_missing[1]) - assert result[0] == data_missing[1] - tm.assert_extension_array_equal(data, data_missing) - - # fillna(copy=False) is ignored -> so same result as above - result = data.fillna(data_missing[1], copy=False) - assert result[0] == data_missing[1] - tm.assert_extension_array_equal(data, data_missing) - @pytest.mark.xfail(reason="Unsupported") def test_fillna_series(self, data_missing): # this one looks doable. diff --git a/pandas/tests/extension/test_string.py b/pandas/tests/extension/test_string.py index 2b3ed37431dd1..3ee1b5789f3a3 100644 --- a/pandas/tests/extension/test_string.py +++ b/pandas/tests/extension/test_string.py @@ -101,6 +101,9 @@ def data_for_grouping(dtype, chunked): class TestStringArray(base.ExtensionTests): + def _honors_copy_keyword(self, data) -> bool: + return data.dtype.storage != "pyarrow" + @pytest.mark.parametrize("na_action", [None, "ignore"]) def test_map(self, data_missing, na_action, request, using_infer_string): if data_missing.dtype.storage == "python" and not using_infer_string: @@ -180,25 +183,6 @@ def test_fillna_no_op_returns_copy(self, data): assert result is not data tm.assert_extension_array_equal(result, data) - def test_fillna_readonly(self, data_missing): - data = data_missing.copy() - data._readonly = True - - # by default fillna(copy=True), then this works fine - result = data.fillna(data_missing[1]) - assert result[0] == data_missing[1] - tm.assert_extension_array_equal(data, data_missing) - - # fillna(copy=False) is generally not honored by Arrow-backed array, - # but always returns new data -> same result as above - if data.dtype.storage == "pyarrow": - result = data.fillna(data_missing[1]) - assert result[0] == data_missing[1] - else: - with pytest.raises(ValueError, match="Cannot modify read-only array"): - data.fillna(data_missing[1], copy=False) - tm.assert_extension_array_equal(data, data_missing) - def _get_expected_exception( self, op_name: str, obj, other ) -> type[Exception] | tuple[type[Exception], ...] | None: