From 43be6b747e0960e3bf41163cbccaabb48737143b Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Sun, 16 Nov 2025 21:36:34 +0530 Subject: [PATCH 1/8] EA attribute specifying whether copy=False is ignored --- pandas/tests/extension/base/missing.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pandas/tests/extension/base/missing.py b/pandas/tests/extension/base/missing.py index cee565d4f7c1e..3a149025421b2 100644 --- a/pandas/tests/extension/base/missing.py +++ b/pandas/tests/extension/base/missing.py @@ -6,6 +6,8 @@ class BaseMissingTests: + _supports_fillna_copy_false = True + def test_isna(self, data_missing): expected = np.array([True, False]) @@ -179,3 +181,9 @@ def test_fillna_fill_other(self, data): expected = pd.DataFrame({"A": data, "B": [0.0] * len(result)}) tm.assert_frame_equal(result, expected) + + def test_fillna_readonly(self, data_missing): + fill_value = data_missing[1] + result = data_missing.fillna(fill_value, copy=False) + expected = data_missing.fillna(fill_value, copy=True) + tm.assert_extension_array_equal(result, expected) From 251ff22a05eaf4b706095b9f2593d18dadf146d4 Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Tue, 18 Nov 2025 01:48:46 +0530 Subject: [PATCH 2/8] EA attribute specifying whether copy=False is ignored --- pandas/tests/extension/test_numpy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/tests/extension/test_numpy.py b/pandas/tests/extension/test_numpy.py index 691ce9341b788..e19d1d7471ea6 100644 --- a/pandas/tests/extension/test_numpy.py +++ b/pandas/tests/extension/test_numpy.py @@ -167,6 +167,8 @@ def skip_numpy_object(dtype, request): class TestNumpyExtensionArray(base.ExtensionTests): + _supports_fillna_copy_false = False + @pytest.mark.skip(reason="We don't register our dtype") # We don't want to register. This test should probably be split in two. def test_from_dtype(self, data): From 0df38dbe0da71715d562e07797be50a452a92852 Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Thu, 20 Nov 2025 19:04:46 +0530 Subject: [PATCH 3/8] EA attribute specifying whether copy=False is ignored --- pandas/tests/extension/test_sparse.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index 4cbbda990b294..72f9a13910cc2 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -237,21 +237,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. From 453b25f39b5cbbbb4494a63adb3defd3d538901d Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Sat, 22 Nov 2025 10:15:44 +0530 Subject: [PATCH 4/8] EA attribute specifying whether copy=False is ignored --- pandas/tests/extension/base/missing.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/pandas/tests/extension/base/missing.py b/pandas/tests/extension/base/missing.py index 85154862245ad..96783987581ae 100644 --- a/pandas/tests/extension/base/missing.py +++ b/pandas/tests/extension/base/missing.py @@ -125,18 +125,24 @@ def test_fillna_no_op_returns_copy(self, data): tm.assert_extension_array_equal(result, data) def test_fillna_readonly(self, data_missing): + fill_value = data_missing[1] 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) - - # 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) + res_copy = data.fillna(fill_value, copy=True) + assert res_copy[0] == fill_value tm.assert_extension_array_equal(data, data_missing) + + if self._supports_fillna_copy_false: + # 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(fill_value, copy=False) + tm.assert_extension_array_equal(data, data_missing) + else: + res_no_copy = data.fillna(fill_value, copy=False) + tm.assert_extension_array_equal(res_no_copy, res_copy) + tm.assert_extension_array_equal(data, data_missing) def test_fillna_series(self, data_missing): fill_value = data_missing[1] @@ -196,8 +202,3 @@ def test_fillna_fill_other(self, data): tm.assert_frame_equal(result, expected) - def test_fillna_readonly(self, data_missing): - fill_value = data_missing[1] - result = data_missing.fillna(fill_value, copy=False) - expected = data_missing.fillna(fill_value, copy=True) - tm.assert_extension_array_equal(result, expected) From 5421db19ddaf2f662276ed6a33e24ae0a8176e16 Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Sat, 22 Nov 2025 10:58:03 +0530 Subject: [PATCH 5/8] EA attribute specifying whether copy=False is ignored --- pandas/tests/extension/test_numpy.py | 5 ----- pandas/tests/extension/test_sparse.py | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pandas/tests/extension/test_numpy.py b/pandas/tests/extension/test_numpy.py index c24027e4489eb..9e0311699c5bb 100644 --- a/pandas/tests/extension/test_numpy.py +++ b/pandas/tests/extension/test_numpy.py @@ -344,11 +344,6 @@ def test_fillna_frame(self, data_missing): # Non-scalar "scalar" values. super().test_fillna_frame(data_missing) - @skip_nested - def test_fillna_readonly(self, data_missing): - # Non-scalar "scalar" values. - super().test_fillna_readonly(data_missing) - @skip_nested def test_setitem_invalid(self, data, invalid_scalar): # object dtype can hold anything, so doesn't raise diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index 72f9a13910cc2..12b205bc1e68a 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -97,6 +97,8 @@ def data_for_compare(request): class TestSparseArray(base.ExtensionTests): + _supports_fillna_copy_false = False + def _supports_reduction(self, obj, op_name: str) -> bool: return True From 3f6333e2e63556b3482875220babdd4fe1897e07 Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Sat, 22 Nov 2025 11:26:20 +0530 Subject: [PATCH 6/8] EA attribute specifying whether copy=False is ignored --- pandas/tests/extension/test_numpy.py | 2 -- pandas/tests/extension/test_sparse.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/pandas/tests/extension/test_numpy.py b/pandas/tests/extension/test_numpy.py index 9e0311699c5bb..ba71e5c963de0 100644 --- a/pandas/tests/extension/test_numpy.py +++ b/pandas/tests/extension/test_numpy.py @@ -169,8 +169,6 @@ def skip_numpy_object(dtype, request): class TestNumpyExtensionArray(base.ExtensionTests): - _supports_fillna_copy_false = False - @pytest.mark.skip(reason="We don't register our dtype") # We don't want to register. This test should probably be split in two. def test_from_dtype(self, data): diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index 12b205bc1e68a..72f9a13910cc2 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -97,8 +97,6 @@ def data_for_compare(request): class TestSparseArray(base.ExtensionTests): - _supports_fillna_copy_false = False - def _supports_reduction(self, obj, op_name: str) -> bool: return True From 964a6d246aa000b691435c3eba5caa566d86d14c Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Sat, 22 Nov 2025 12:17:36 +0530 Subject: [PATCH 7/8] EA attribute specifying whether copy=False is ignored --- pandas/tests/extension/base/missing.py | 9 ++++++--- pandas/tests/extension/test_sparse.py | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pandas/tests/extension/base/missing.py b/pandas/tests/extension/base/missing.py index 96783987581ae..18deea30323de 100644 --- a/pandas/tests/extension/base/missing.py +++ b/pandas/tests/extension/base/missing.py @@ -129,11 +129,15 @@ def test_fillna_readonly(self, data_missing): data = data_missing.copy() data._readonly = True + expected = data_missing._from_sequence( + [fill_value, fill_value], dtype=data_missing.dtype + ) + # by default fillna(copy=True), then this works fine res_copy = data.fillna(fill_value, copy=True) - assert res_copy[0] == fill_value + tm.assert_extension_array_equal(res_copy, expected) tm.assert_extension_array_equal(data, data_missing) - + if self._supports_fillna_copy_false: # but with copy=False, this raises for EAs that respect the copy keyword with pytest.raises(ValueError, match="Cannot modify read-only array"): @@ -201,4 +205,3 @@ def test_fillna_fill_other(self, data): expected = pd.DataFrame({"A": data, "B": [0.0] * len(result)}) tm.assert_frame_equal(result, expected) - diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index 72f9a13910cc2..12b205bc1e68a 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -97,6 +97,8 @@ def data_for_compare(request): class TestSparseArray(base.ExtensionTests): + _supports_fillna_copy_false = False + def _supports_reduction(self, obj, op_name: str) -> bool: return True From 1bf16a2d21ed20bd0801892e49d4fb617e44941f Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Sat, 22 Nov 2025 13:13:28 +0530 Subject: [PATCH 8/8] EA attribute specifying whether copy=False is ignored --- pandas/tests/extension/base/missing.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pandas/tests/extension/base/missing.py b/pandas/tests/extension/base/missing.py index 18deea30323de..5e10f4a665bd4 100644 --- a/pandas/tests/extension/base/missing.py +++ b/pandas/tests/extension/base/missing.py @@ -129,9 +129,7 @@ def test_fillna_readonly(self, data_missing): data = data_missing.copy() data._readonly = True - expected = data_missing._from_sequence( - [fill_value, fill_value], dtype=data_missing.dtype - ) + expected = data_missing.fillna(fill_value, copy=True) # by default fillna(copy=True), then this works fine res_copy = data.fillna(fill_value, copy=True)