Skip to content

Commit 983a3ed

Browse files
jbrockmendelclaude
andauthored
TST: add _honors_copy_keyword to extension test base class (#65179)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6490f20 commit 983a3ed

6 files changed

Lines changed: 28 additions & 61 deletions

File tree

pandas/tests/extension/base/missing.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66

77

88
class BaseMissingTests:
9+
def _honors_copy_keyword(self, data) -> bool:
10+
"""Whether the EA honors the copy keyword in methods like fillna.
11+
12+
EAs that always return new data regardless of copy=False should
13+
override this to return False.
14+
"""
15+
return True
16+
917
def test_isna(self, data_missing):
1018
expected = np.array([True, False])
1119

@@ -137,10 +145,11 @@ def test_fillna_readonly(self, data_missing):
137145
assert result[0] == data_missing[1]
138146
tm.assert_extension_array_equal(data, data_missing)
139147

140-
# but with copy=False, this raises for EAs that respect the copy keyword
141-
with pytest.raises(ValueError, match="Cannot modify read-only array"):
142-
data.fillna(data_missing[1], copy=False)
143-
tm.assert_extension_array_equal(data, data_missing)
148+
if self._honors_copy_keyword(data_missing):
149+
# with copy=False, this raises for EAs that respect the copy keyword
150+
with pytest.raises(ValueError, match="Cannot modify read-only array"):
151+
data.fillna(data_missing[1], copy=False)
152+
tm.assert_extension_array_equal(data, data_missing)
144153

145154
def test_fillna_series(self, data_missing):
146155
fill_value = data_missing[1]

pandas/tests/extension/decimal/test_decimal.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ def data_for_grouping():
6767

6868

6969
class TestDecimalArray(base.ExtensionTests):
70+
def _honors_copy_keyword(self, data) -> bool:
71+
return False
72+
7073
def _get_expected_exception(
7174
self, op_name: str, obj, other
7275
) -> type[Exception] | tuple[type[Exception], ...] | None:
@@ -167,10 +170,6 @@ def test_fillna_limit_series(self, data_missing):
167170
):
168171
super().test_fillna_limit_series(data_missing)
169172

170-
@pytest.mark.xfail(reason="copy keyword is missing")
171-
def test_fillna_readonly(self, data_missing):
172-
super().test_fillna_readonly(data_missing)
173-
174173
def test_series_repr(self, data):
175174
# Overriding this base test to explicitly test that
176175
# the custom _formatter is used

pandas/tests/extension/test_arrow.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@ def data_for_twos(data):
271271

272272

273273
class TestArrowArray(base.ExtensionTests):
274+
def _honors_copy_keyword(self, data) -> bool:
275+
return False
276+
274277
def _construct_for_combine_add(self, left, right):
275278
dtype = left.dtype
276279

@@ -666,21 +669,6 @@ def test_fillna_no_op_returns_copy(self, data):
666669
assert result is not data
667670
tm.assert_extension_array_equal(result, data)
668671

669-
def test_fillna_readonly(self, data_missing):
670-
data = data_missing.copy()
671-
data._readonly = True
672-
673-
# by default fillna(copy=True), then this works fine
674-
result = data.fillna(data_missing[1])
675-
assert result[0] == data_missing[1]
676-
tm.assert_extension_array_equal(data, data_missing)
677-
678-
# fillna(copy=False) is generally not honored by Arrow-backed array,
679-
# but always returns new data -> same result as above
680-
result = data.fillna(data_missing[1])
681-
assert result[0] == data_missing[1]
682-
tm.assert_extension_array_equal(data, data_missing)
683-
684672
@pytest.mark.xfail(
685673
reason="GH 45419: pyarrow.ChunkedArray does not support views", run=False
686674
)

pandas/tests/extension/test_interval.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ def data_for_grouping():
8383
class TestIntervalArray(base.ExtensionTests):
8484
divmod_exc = TypeError
8585

86+
def _honors_copy_keyword(self, data) -> bool:
87+
return False
88+
8689
def _supports_reduction(self, ser: pd.Series, op_name: str) -> bool:
8790
return op_name in ["min", "max", "count"]
8891

@@ -103,10 +106,6 @@ def test_fillna_limit_series(self, data_missing):
103106
def test_fillna_length_mismatch(self, data_missing):
104107
super().test_fillna_length_mismatch(data_missing)
105108

106-
@pytest.mark.xfail(reason="copy=False is not Implemented")
107-
def test_fillna_readonly(self, data_missing):
108-
super().test_fillna_readonly(data_missing)
109-
110109
@pytest.mark.filterwarnings(
111110
"ignore:invalid value encountered in cast:RuntimeWarning"
112111
)

pandas/tests/extension/test_sparse.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ def data_for_compare(request):
9797

9898

9999
class TestSparseArray(base.ExtensionTests):
100+
def _honors_copy_keyword(self, data) -> bool:
101+
return False
102+
100103
def _supports_reduction(self, obj, op_name: str) -> bool:
101104
return True
102105

@@ -237,21 +240,6 @@ def test_isna(self, data_missing):
237240
def test_fillna_no_op_returns_copy(self, data, request):
238241
super().test_fillna_no_op_returns_copy(data)
239242

240-
def test_fillna_readonly(self, data_missing):
241-
# copy keyword is ignored by SparseArray.fillna
242-
# -> copy=True vs False doesn't make a difference
243-
data = data_missing.copy()
244-
data._readonly = True
245-
246-
result = data.fillna(data_missing[1])
247-
assert result[0] == data_missing[1]
248-
tm.assert_extension_array_equal(data, data_missing)
249-
250-
# fillna(copy=False) is ignored -> so same result as above
251-
result = data.fillna(data_missing[1], copy=False)
252-
assert result[0] == data_missing[1]
253-
tm.assert_extension_array_equal(data, data_missing)
254-
255243
@pytest.mark.xfail(reason="Unsupported")
256244
def test_fillna_series(self, data_missing):
257245
# this one looks doable.

pandas/tests/extension/test_string.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ def data_for_grouping(dtype, chunked):
101101

102102

103103
class TestStringArray(base.ExtensionTests):
104+
def _honors_copy_keyword(self, data) -> bool:
105+
return data.dtype.storage != "pyarrow"
106+
104107
@pytest.mark.parametrize("na_action", [None, "ignore"])
105108
def test_map(self, data_missing, na_action, request, using_infer_string):
106109
if data_missing.dtype.storage == "python" and not using_infer_string:
@@ -180,25 +183,6 @@ def test_fillna_no_op_returns_copy(self, data):
180183
assert result is not data
181184
tm.assert_extension_array_equal(result, data)
182185

183-
def test_fillna_readonly(self, data_missing):
184-
data = data_missing.copy()
185-
data._readonly = True
186-
187-
# by default fillna(copy=True), then this works fine
188-
result = data.fillna(data_missing[1])
189-
assert result[0] == data_missing[1]
190-
tm.assert_extension_array_equal(data, data_missing)
191-
192-
# fillna(copy=False) is generally not honored by Arrow-backed array,
193-
# but always returns new data -> same result as above
194-
if data.dtype.storage == "pyarrow":
195-
result = data.fillna(data_missing[1])
196-
assert result[0] == data_missing[1]
197-
else:
198-
with pytest.raises(ValueError, match="Cannot modify read-only array"):
199-
data.fillna(data_missing[1], copy=False)
200-
tm.assert_extension_array_equal(data, data_missing)
201-
202186
def _get_expected_exception(
203187
self, op_name: str, obj, other
204188
) -> type[Exception] | tuple[type[Exception], ...] | None:

0 commit comments

Comments
 (0)