Skip to content

Commit f1286a7

Browse files
authored
TST: tests using invalid_scalar fixture (pandas-dev#44175)
1 parent 90d02bd commit f1286a7

File tree

8 files changed

+99
-8
lines changed

8 files changed

+99
-8
lines changed

pandas/core/arrays/sparse/array.py

+9
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
is_datetime64tz_dtype,
5858
is_dtype_equal,
5959
is_integer,
60+
is_list_like,
6061
is_object_dtype,
6162
is_scalar,
6263
is_string_dtype,
@@ -928,6 +929,14 @@ def __getitem__(
928929
indices = np.arange(len(self), dtype=np.int32)[key]
929930
return self.take(indices)
930931

932+
elif not is_list_like(key):
933+
# e.g. "foo" or 2.5
934+
# exception message copied from numpy
935+
raise IndexError(
936+
r"only integers, slices (`:`), ellipsis (`...`), numpy.newaxis "
937+
r"(`None`) and integer or boolean arrays are valid indices"
938+
)
939+
931940
else:
932941
# TODO: I think we can avoid densifying when masking a
933942
# boolean SparseArray with another. Need to look at the

pandas/core/arrays/string_arrow.py

+12
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,13 @@ def __getitem__(
316316
elif isinstance(item, tuple):
317317
item = unpack_tuple_and_ellipses(item)
318318

319+
if is_scalar(item) and not is_integer(item):
320+
# e.g. "foo" or 2.5
321+
# exception message copied from numpy
322+
raise IndexError(
323+
r"only integers, slices (`:`), ellipsis (`...`), numpy.newaxis "
324+
r"(`None`) and integer or boolean arrays are valid indices"
325+
)
319326
# We are not an array indexer, so maybe e.g. a slice or integer
320327
# indexer. We dispatch to pyarrow.
321328
value = self._data[item]
@@ -386,6 +393,11 @@ def _cmp_method(self, other, op):
386393
# TODO(ARROW-9429): Add a .to_numpy() to ChunkedArray
387394
return BooleanArray._from_sequence(result.to_pandas().values)
388395

396+
def insert(self, loc: int, item):
397+
if not isinstance(item, str) and item is not libmissing.NA:
398+
raise TypeError("Scalar must be NA or str")
399+
return super().insert(loc, item)
400+
389401
def __setitem__(self, key: int | slice | np.ndarray, value: Any) -> None:
390402
"""Set one or more values inplace.
391403

pandas/tests/extension/base/getitem.py

+27
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,33 @@ def test_getitem_scalar(self, data):
120120
result = pd.Series(data)[0]
121121
assert isinstance(result, data.dtype.type)
122122

123+
def test_getitem_invalid(self, data):
124+
# TODO: box over scalar, [scalar], (scalar,)?
125+
126+
msg = (
127+
r"only integers, slices \(`:`\), ellipsis \(`...`\), numpy.newaxis "
128+
r"\(`None`\) and integer or boolean arrays are valid indices"
129+
)
130+
with pytest.raises(IndexError, match=msg):
131+
data["foo"]
132+
with pytest.raises(IndexError, match=msg):
133+
data[2.5]
134+
135+
ub = len(data)
136+
msg = "|".join(
137+
[
138+
"list index out of range", # json
139+
"index out of bounds", # pyarrow
140+
"Out of bounds access", # Sparse
141+
f"index {ub+1} is out of bounds for axis 0 with size {ub}",
142+
f"index -{ub+1} is out of bounds for axis 0 with size {ub}",
143+
]
144+
)
145+
with pytest.raises(IndexError, match=msg):
146+
data[ub + 1]
147+
with pytest.raises(IndexError, match=msg):
148+
data[-ub - 1]
149+
123150
def test_getitem_scalar_na(self, data_missing, na_cmp, na_value):
124151
result = data_missing[0]
125152
assert na_cmp(result, na_value)

pandas/tests/extension/base/setitem.py

+8
Original file line numberDiff line numberDiff line change
@@ -367,3 +367,11 @@ def test_delitem_series(self, data):
367367
expected = ser[taker]
368368
del ser[1]
369369
self.assert_series_equal(ser, expected)
370+
371+
def test_setitem_invalid(self, data, invalid_scalar):
372+
msg = "" # messages vary by subclass, so we do not test it
373+
with pytest.raises((ValueError, TypeError), match=msg):
374+
data[0] = invalid_scalar
375+
376+
with pytest.raises((ValueError, TypeError), match=msg):
377+
data[:] = invalid_scalar

pandas/tests/extension/json/array.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@
3232
from pandas._typing import type_t
3333

3434
from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
35-
from pandas.core.dtypes.common import pandas_dtype
35+
from pandas.core.dtypes.common import (
36+
is_list_like,
37+
pandas_dtype,
38+
)
3639

3740
import pandas as pd
3841
from pandas.api.extensions import (
@@ -97,6 +100,13 @@ def __getitem__(self, item):
97100
elif isinstance(item, slice):
98101
# slice
99102
return type(self)(self.data[item])
103+
elif not is_list_like(item):
104+
# e.g. "foo" or 2.5
105+
# exception message copied from numpy
106+
raise IndexError(
107+
r"only integers, slices (`:`), ellipsis (`...`), numpy.newaxis "
108+
r"(`None`) and integer or boolean arrays are valid indices"
109+
)
100110
else:
101111
item = pd.api.indexers.check_array_indexer(self, item)
102112
if is_bool_dtype(item.dtype):

pandas/tests/extension/test_numpy.py

+5
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,11 @@ def test_concat(self, data, in_frame):
363363

364364

365365
class TestSetitem(BaseNumPyTests, base.BaseSetitemTests):
366+
@skip_nested
367+
def test_setitem_invalid(self, data, invalid_scalar):
368+
# object dtype can hold anything, so doesn't raise
369+
super().test_setitem_invalid(data, invalid_scalar)
370+
366371
@skip_nested
367372
def test_setitem_sequence_broadcasts(self, data, box_in_series):
368373
# ValueError: cannot set using a list-like indexer with a different

pandas/tests/extension/test_string.py

-7
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,6 @@ def test_value_counts(self, all_data, dropna):
160160
def test_value_counts_with_normalize(self, data):
161161
pass
162162

163-
def test_insert_invalid(self, data, invalid_scalar, request):
164-
if data.dtype.storage == "pyarrow":
165-
mark = pytest.mark.xfail(reason="casts invalid_scalar to string")
166-
request.node.add_marker(mark)
167-
168-
super().test_insert_invalid(data, invalid_scalar)
169-
170163

171164
class TestCasting(base.BaseCastingTests):
172165
pass

pandas/tests/indexes/common.py

+27
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,33 @@ def test_insert_base(self, index):
403403
# test 0th element
404404
assert index[0:4].equals(result.insert(0, index[0]))
405405

406+
def test_insert_out_of_bounds(self, index):
407+
# TypeError/IndexError matches what np.insert raises in these cases
408+
409+
if len(index) > 0:
410+
err = TypeError
411+
else:
412+
err = IndexError
413+
if len(index) == 0:
414+
# 0 vs 0.5 in error message varies with numpy version
415+
msg = "index (0|0.5) is out of bounds for axis 0 with size 0"
416+
else:
417+
msg = "slice indices must be integers or None or have an __index__ method"
418+
with pytest.raises(err, match=msg):
419+
index.insert(0.5, "foo")
420+
421+
msg = "|".join(
422+
[
423+
r"index -?\d+ is out of bounds for axis 0 with size \d+",
424+
"loc must be an integer between",
425+
]
426+
)
427+
with pytest.raises(IndexError, match=msg):
428+
index.insert(len(index) + 1, 1)
429+
430+
with pytest.raises(IndexError, match=msg):
431+
index.insert(-len(index) - 1, 1)
432+
406433
def test_delete_base(self, index):
407434
if not len(index):
408435
return

0 commit comments

Comments
 (0)