Skip to content

Commit 7238827

Browse files
authored
type indexing.pyi (#1554)
* type indexing.pyi * remove indexingmixin * extra test case * move test location, add some `TYPE_CHECKING_INVALID_USAGE`
1 parent 4a101db commit 7238827

File tree

6 files changed

+71
-63
lines changed

6 files changed

+71
-63
lines changed

pandas-stubs/core/frame.pyi

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -323,42 +323,21 @@ class _LocIndexerFrame(_LocIndexer, Generic[_T]):
323323
) -> None: ...
324324

325325
class _iAtIndexerFrame(_iAtIndexer):
326-
def __getitem__(self, key: tuple[int, int]) -> Scalar: ...
327-
def __setitem__(
326+
def __getitem__(self, key: tuple[int, int]) -> Scalar: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
327+
def __setitem__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
328328
self,
329329
key: tuple[int, int],
330330
value: Scalar | NAType | NaTType | None,
331331
) -> None: ...
332332

333333
class _AtIndexerFrame(_AtIndexer):
334-
def __getitem__(
335-
self,
336-
key: tuple[
337-
int
338-
| StrLike
339-
| Timestamp
340-
| tuple[Scalar, ...]
341-
| Callable[[DataFrame], ScalarT],
342-
int | StrLike | tuple[Scalar, ...],
343-
],
334+
def __getitem__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
335+
self, key: tuple[Hashable, Hashable]
344336
) -> Scalar: ...
345-
def __setitem__(
337+
def __setitem__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
346338
self,
347-
key: (
348-
MaskType | StrLike | _IndexSliceTuple | list[ScalarT] | IndexingInt | slice
349-
),
350-
value: (
351-
Scalar
352-
| NAType
353-
| NaTType
354-
| ArrayLike
355-
| IndexOpsMixin
356-
| DataFrame
357-
| Sequence[Scalar]
358-
| Sequence[Sequence[Scalar]]
359-
| Mapping[Hashable, Scalar | NAType | NaTType]
360-
| None
361-
),
339+
key: tuple[Hashable, Hashable],
340+
value: Scalar | NAType | NaTType | None,
362341
) -> None: ...
363342

364343
# With mypy 1.14.1 and python 3.12, the second overload needs a type-ignore statement

pandas-stubs/core/generic.pyi

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ from typing import (
2222
)
2323

2424
from pandas import Index
25-
import pandas.core.indexing as indexing
2625
from pandas.core.resample import DatetimeIndexResampler
2726
from pandas.core.series import Series
2827
import sqlalchemy.engine
@@ -63,7 +62,7 @@ from pandas._typing import (
6362
from pandas.io.pytables import HDFStore
6463
from pandas.io.sql import SQLTable
6564

66-
class NDFrame(indexing.IndexingMixin):
65+
class NDFrame:
6766
__hash__: ClassVar[None] # type: ignore[assignment] # pyright: ignore[reportIncompatibleMethodOverride]
6867

6968
@final

pandas-stubs/core/indexing.pyi

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1-
from collections.abc import Sequence
1+
from collections.abc import (
2+
Hashable,
3+
Sequence,
4+
)
25
from typing import (
36
TypeAlias,
47
TypeVar,
58
)
69

710
from pandas.core.base import IndexOpsMixin
11+
from typing_extensions import Self
812

913
from pandas._libs.indexing import _NDFrameIndexerBase
14+
from pandas._libs.missing import NAType
15+
from pandas._libs.tslibs.nattype import NaTType
1016
from pandas._typing import (
17+
Axis,
18+
AxisInt,
1119
MaskType,
1220
Scalar,
1321
)
@@ -27,31 +35,21 @@ class _IndexSlice:
2735

2836
IndexSlice: _IndexSlice
2937

30-
class IndexingMixin:
31-
@property
32-
def iloc(self) -> _iLocIndexer: ...
33-
@property
34-
def loc(self) -> _LocIndexer: ...
35-
@property
36-
def at(self) -> _AtIndexer: ...
37-
@property
38-
def iat(self) -> _iAtIndexer: ...
39-
4038
class _NDFrameIndexer(_NDFrameIndexerBase):
41-
axis = ...
42-
def __call__(self, axis=...): ...
43-
def __getitem__(self, key): ...
44-
def __setitem__(self, key, value) -> None: ...
45-
46-
class _LocationIndexer(_NDFrameIndexer):
47-
def __getitem__(self, key): ...
48-
49-
class _LocIndexer(_LocationIndexer): ...
50-
class _iLocIndexer(_LocationIndexer): ...
51-
52-
class _ScalarAccessIndexer(_NDFrameIndexerBase):
53-
def __getitem__(self, key): ...
54-
def __setitem__(self, key, value) -> None: ...
55-
56-
class _AtIndexer(_ScalarAccessIndexer): ...
57-
class _iAtIndexer(_ScalarAccessIndexer): ...
39+
axis: AxisInt | None = None
40+
def __call__(self, axis: Axis | None = None) -> Self: ...
41+
42+
class _LocIndexer(_NDFrameIndexer): ...
43+
class _iLocIndexer(_NDFrameIndexer): ...
44+
45+
class _AtIndexer(_NDFrameIndexerBase):
46+
def __getitem__(self, key: Hashable) -> Scalar: ...
47+
def __setitem__(
48+
self, key: Hashable, value: Scalar | NAType | NaTType | None
49+
) -> None: ...
50+
51+
class _iAtIndexer(_NDFrameIndexerBase):
52+
def __getitem__(self, key: int) -> Scalar: ...
53+
def __setitem__(
54+
self, key: int, value: Scalar | NAType | NaTType | None
55+
) -> None: ...

pyproject.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,6 @@ ignore = [
205205
"PYI042", # https://docs.astral.sh/ruff/rules/snake-case-type-alias/
206206
"ERA001", "PLR0402", "PLC0105"
207207
]
208-
"indexing.pyi" = [
209-
# TODO: remove when indexing.pyi is fully typed
210-
"ANN001", "ANN201", "ANN204", "ANN206",
211-
]
212208
"*computation*" = [
213209
# TODO: remove when computations are fully typed
214210
"ANN001", "ANN201", "ANN204", "ANN206",

tests/frame/test_indexing.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626

2727
from pandas._typing import Scalar
2828

29-
from tests import check
29+
from tests import (
30+
TYPE_CHECKING_INVALID_USAGE,
31+
check,
32+
)
3033

3134

3235
def test_types_getitem() -> None:
@@ -592,3 +595,28 @@ def test_frame_ndarray_assignmment() -> None:
592595

593596
df_b = pd.DataFrame({"a": [0.0] * 10, "b": [1.0] * 10})
594597
df_b.iloc[:, :] = np.array([[-1.0, np.inf]] * 10)
598+
599+
600+
def test_frame_at() -> None:
601+
df = pd.DataFrame(data={"col1": [1.6, 2], "col2": [3, 4]})
602+
603+
check(assert_type(df.at[0, "col1"], Scalar), float)
604+
df.at[0, "col1"] = 999
605+
df.at[0, "col1"] = float("nan")
606+
607+
mi = pd.MultiIndex.from_arrays([[2, 3], [4, 5]])
608+
df = pd.DataFrame(data={"col1": [1.6, 2], "col2": [3, 4]}, index=mi)
609+
610+
check(assert_type(df.at[(2, 4), "col1"], Scalar), float)
611+
df.at[(2, 4), "col1"] = 999
612+
df.at[(2, 4), "col1"] = float("nan")
613+
614+
615+
def test_frame_iat() -> None:
616+
df = pd.DataFrame(data={"col1": [1, 2], "col2": [3, 4]})
617+
618+
check(assert_type(df.iat[0, 0], Scalar), np.integer)
619+
df.iat[0, 0] = 999
620+
df.iat[0, 0] = float("nan")
621+
if TYPE_CHECKING_INVALID_USAGE:
622+
df.iat[(0,), 0] = 999 # type: ignore[index] # pyright: ignore[reportArgumentType]

tests/series/test_indexing.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from tests import (
1010
PD_LTE_23,
11+
TYPE_CHECKING_INVALID_USAGE,
1112
check,
1213
pytest_warns_bounded,
1314
)
@@ -31,17 +32,24 @@ def test_types_iloc_iat() -> None:
3132
s2 = pd.Series(data=[1, 2])
3233
s.loc["row1"]
3334
s.iat[0]
35+
s.iat[0] = 999
3436
s2.loc[0]
3537
s2.iat[0]
38+
s2.iat[0] = None
39+
40+
if TYPE_CHECKING_INVALID_USAGE:
41+
s.iat[0, 0] # type: ignore[index] # pyright: ignore[reportArgumentType]
3642

3743

3844
def test_types_loc_at() -> None:
3945
s = pd.Series(data={"row1": 1, "row2": 2})
4046
s2 = pd.Series(data=[1, 2])
4147
s.loc["row1"]
4248
s.at["row1"]
49+
s.at["row1"] = 9
4350
s2.loc[1]
4451
s2.at[1]
52+
s2.at[1] = 99
4553

4654

4755
def test_types_getitem() -> None:

0 commit comments

Comments
 (0)