Skip to content
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
43be6b7
EA attribute specifying whether copy=False is ignored
Aniketsy Nov 16, 2025
251ff22
EA attribute specifying whether copy=False is ignored
Aniketsy Nov 17, 2025
0d8bae3
Merge remote-tracking branch 'upstream/main' into fix-63040
Aniketsy Nov 20, 2025
0df38db
EA attribute specifying whether copy=False is ignored
Aniketsy Nov 20, 2025
453b25f
EA attribute specifying whether copy=False is ignored
Aniketsy Nov 22, 2025
5421db1
EA attribute specifying whether copy=False is ignored
Aniketsy Nov 22, 2025
3f6333e
EA attribute specifying whether copy=False is ignored
Aniketsy Nov 22, 2025
964a6d2
EA attribute specifying whether copy=False is ignored
Aniketsy Nov 22, 2025
1bf16a2
EA attribute specifying whether copy=False is ignored
Aniketsy Nov 22, 2025
871f164
EA attribute specifying whether copy=False is ignored
Aniketsy Dec 3, 2025
33f796f
EA attribute specifying whether copy=False is ignored
Aniketsy Dec 3, 2025
b7325fa
EA attribute specifying whether copy=False is ignored
Aniketsy Dec 3, 2025
cbf76eb
EA attribute specifying whether copy=False is ignored
Aniketsy Dec 6, 2025
5a373d5
update from support -> respect
Aniketsy Mar 18, 2026
4178f66
EA attribute specifying whether copy=False is ignored
Aniketsy Mar 19, 2026
963bd84
Update pandas/tests/extension/decimal/array.py
Aniketsy Mar 26, 2026
d0bc508
EA attribute specifying whether copy=False is ignored
Aniketsy Mar 26, 2026
e3da0bc
pass copy=copy
Aniketsy Mar 28, 2026
cdfd705
Revert xfail-test and copy=copy
Aniketsy Mar 31, 2026
9584cd1
EA attribute specifying whether copy=False is ignored
Aniketsy Apr 2, 2026
7fedd5c
EA attribute specifying whether copy=False is ignored
Aniketsy Apr 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions pandas/core/arrays/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,8 +1041,6 @@ def fillna(self, value, limit: int | None = None, copy: bool = True) -> Self:
-------
filled : IntervalArray with NA/NaN filled
"""
if copy is False:
raise NotImplementedError
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntervalArray.fillna always copies regardless of the copy keyword, so i think NotImplementedError is not needed removing this will pass without error, and behavior stays, same. I would like to know your thoughts on this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raising is correct here.

if limit is not None:
raise ValueError("limit must be None")

Expand Down
19 changes: 13 additions & 6 deletions pandas/tests/extension/base/missing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@


class BaseMissingTests:
_respects_fillna_copy_false = True

def test_isna(self, data_missing):
expected = np.array([True, False])

Expand Down Expand Up @@ -123,18 +125,23 @@ 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]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you restore this line somewhere (to check that the NAs actually got filled)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tm.assert_extension_array_equal(res_no_copy, res_copy), i think this already check, i also checked by restoring and got this failure on testing

    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
        res_copy = data.fillna(fill_value, copy=True)
>       assert res_copy[0] == fill_value
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       AssertionError

pandas\tests\extension\base\missing.py:134: AssertionError

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That suggests that the fillna isn't actually working. That would indicate a real bug.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've used llm to debug and updated with some changes and restored as per your suggestion.
On running locally all tests were passing

res_copy = data.fillna(fill_value, copy=True)
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._respects_fillna_copy_false:
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:
# EAs that do not respect the copy keyword, copy=False is ignored
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]
Expand Down
9 changes: 5 additions & 4 deletions pandas/tests/extension/decimal/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,11 @@ def convert_values(param):

return np.asarray(res, dtype=bool)

# We override fillna here to simulate a 3rd party EA that has done so. This
# lets us test a 3rd-party EA that has not yet updated to include a "copy"
# keyword in its fillna method.
def fillna(self, value=None, limit=None):
# We override fillna here to simulate a 3rd-party EA that defines its own
# fillna behavior and ignores the copy keyword.
def fillna(self, value=None, limit=None, copy: bool = True):
# We intentionally ignore copy and always perform a copy to ensure
# the original array is not modified.
return super().fillna(value=value, limit=limit, copy=True)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass copy=copy?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or raise/warn if the user passes copy=False

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass copy=copy?

i think we can go with this . I'll update

Comment thread
Aniketsy marked this conversation as resolved.


Expand Down
6 changes: 2 additions & 4 deletions pandas/tests/extension/decimal/test_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def data_for_grouping():


class TestDecimalArray(base.ExtensionTests):
_respects_fillna_copy_false = False

def _get_expected_exception(
self, op_name: str, obj, other
) -> type[Exception] | tuple[type[Exception], ...] | None:
Expand Down Expand Up @@ -171,10 +173,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
Expand Down
17 changes: 2 additions & 15 deletions pandas/tests/extension/test_arrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ def data_for_twos(data):


class TestArrowArray(base.ExtensionTests):
_respects_fillna_copy_false = False

def _construct_for_combine_add(self, left, right):
dtype = left.dtype

Expand Down Expand Up @@ -687,21 +689,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
)
Expand Down
5 changes: 1 addition & 4 deletions pandas/tests/extension/test_interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def data_for_grouping():

class TestIntervalArray(base.ExtensionTests):
divmod_exc = TypeError
_respects_fillna_copy_false = False

def _supports_reduction(self, ser: pd.Series, op_name: str) -> bool:
return op_name in ["min", "max"]
Expand All @@ -100,10 +101,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"
)
Expand Down
5 changes: 0 additions & 5 deletions pandas/tests/extension/test_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,11 +342,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
Expand Down
17 changes: 2 additions & 15 deletions pandas/tests/extension/test_sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ def data_for_compare(request):


class TestSparseArray(base.ExtensionTests):
_respects_fillna_copy_false = False

def _supports_reduction(self, obj, op_name: str) -> bool:
return True

Expand Down Expand Up @@ -237,21 +239,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):
Comment thread
jbrockmendel marked this conversation as resolved.
# 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.
Expand Down
23 changes: 7 additions & 16 deletions pandas/tests/extension/test_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,23 +170,14 @@ def test_fillna_no_op_returns_copy(self, 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]
if data_missing.dtype.storage == "pyarrow":
# pyarrow-backed strings are immutable, copy=False is ignored,
# always returns a new array without raising.
self._respects_fillna_copy_false = False
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)
# python-backed strings respect copy=False and raise on read-only.
self._respects_fillna_copy_false = True
super().test_fillna_readonly(data_missing)

def _get_expected_exception(
self, op_name: str, obj, other
Expand Down
Loading