Skip to content

Commit f98696a

Browse files
authored
CI/TYP: run stubtest (pandas-dev#47817)
* CI/TYP: run stubtest * avoid try/except * hard fail on the CI * Timedelta.__new__(unit: str|None = ...) * add *args to hashtable.*Vector.__init__
1 parent 6e1a040 commit f98696a

File tree

7 files changed

+125
-25
lines changed

7 files changed

+125
-25
lines changed

.pre-commit-config.yaml

+13-3
Original file line numberDiff line numberDiff line change
@@ -85,32 +85,42 @@ repos:
8585
- repo: local
8686
hooks:
8787
- id: pyright
88+
# note: assumes python env is setup and activated
8889
name: pyright
8990
entry: pyright
90-
# note: assumes python env is setup and activated
9191
language: node
9292
pass_filenames: false
9393
types: [python]
9494
stages: [manual]
9595
additional_dependencies: &pyright_dependencies
9696
- pyright@1.1.262
9797
- id: pyright_reportGeneralTypeIssues
98+
# note: assumes python env is setup and activated
9899
name: pyright reportGeneralTypeIssues
99100
entry: pyright --skipunannotated -p pyright_reportGeneralTypeIssues.json
100-
# note: assumes python env is setup and activated
101101
language: node
102102
pass_filenames: false
103103
types: [python]
104104
stages: [manual]
105105
additional_dependencies: *pyright_dependencies
106106
- id: mypy
107+
# note: assumes python env is setup and activated
107108
name: mypy
108109
entry: mypy
109-
# note: assumes python env is setup and activated
110110
language: system
111111
pass_filenames: false
112112
types: [python]
113113
stages: [manual]
114+
- id: stubtest
115+
# note: assumes python env is setup and activated
116+
# note: requires pandas dev to be installed
117+
name: mypy (stubtest)
118+
entry: python
119+
language: system
120+
pass_filenames: false
121+
types: [pyi]
122+
args: [scripts/run_stubtest.py]
123+
stages: [manual]
114124
- id: flake8-rst
115125
name: flake8-rst
116126
description: Run flake8 on code snippets in docstrings or RST files

pandas/_libs/hashtable.pyi

+14-14
Original file line numberDiff line numberDiff line change
@@ -39,72 +39,72 @@ class Int64Factorizer(Factorizer):
3939
) -> npt.NDArray[np.intp]: ...
4040

4141
class Int64Vector:
42-
def __init__(self): ...
42+
def __init__(self, *args): ...
4343
def __len__(self) -> int: ...
4444
def to_array(self) -> npt.NDArray[np.int64]: ...
4545

4646
class Int32Vector:
47-
def __init__(self): ...
47+
def __init__(self, *args): ...
4848
def __len__(self) -> int: ...
4949
def to_array(self) -> npt.NDArray[np.int32]: ...
5050

5151
class Int16Vector:
52-
def __init__(self): ...
52+
def __init__(self, *args): ...
5353
def __len__(self) -> int: ...
5454
def to_array(self) -> npt.NDArray[np.int16]: ...
5555

5656
class Int8Vector:
57-
def __init__(self): ...
57+
def __init__(self, *args): ...
5858
def __len__(self) -> int: ...
5959
def to_array(self) -> npt.NDArray[np.int8]: ...
6060

6161
class UInt64Vector:
62-
def __init__(self): ...
62+
def __init__(self, *args): ...
6363
def __len__(self) -> int: ...
6464
def to_array(self) -> npt.NDArray[np.uint64]: ...
6565

6666
class UInt32Vector:
67-
def __init__(self): ...
67+
def __init__(self, *args): ...
6868
def __len__(self) -> int: ...
6969
def to_array(self) -> npt.NDArray[np.uint32]: ...
7070

7171
class UInt16Vector:
72-
def __init__(self): ...
72+
def __init__(self, *args): ...
7373
def __len__(self) -> int: ...
7474
def to_array(self) -> npt.NDArray[np.uint16]: ...
7575

7676
class UInt8Vector:
77-
def __init__(self): ...
77+
def __init__(self, *args): ...
7878
def __len__(self) -> int: ...
7979
def to_array(self) -> npt.NDArray[np.uint8]: ...
8080

8181
class Float64Vector:
82-
def __init__(self): ...
82+
def __init__(self, *args): ...
8383
def __len__(self) -> int: ...
8484
def to_array(self) -> npt.NDArray[np.float64]: ...
8585

8686
class Float32Vector:
87-
def __init__(self): ...
87+
def __init__(self, *args): ...
8888
def __len__(self) -> int: ...
8989
def to_array(self) -> npt.NDArray[np.float32]: ...
9090

9191
class Complex128Vector:
92-
def __init__(self): ...
92+
def __init__(self, *args): ...
9393
def __len__(self) -> int: ...
9494
def to_array(self) -> npt.NDArray[np.complex128]: ...
9595

9696
class Complex64Vector:
97-
def __init__(self): ...
97+
def __init__(self, *args): ...
9898
def __len__(self) -> int: ...
9999
def to_array(self) -> npt.NDArray[np.complex64]: ...
100100

101101
class StringVector:
102-
def __init__(self): ...
102+
def __init__(self, *args): ...
103103
def __len__(self) -> int: ...
104104
def to_array(self) -> npt.NDArray[np.object_]: ...
105105

106106
class ObjectVector:
107-
def __init__(self): ...
107+
def __init__(self, *args): ...
108108
def __len__(self) -> int: ...
109109
def to_array(self) -> npt.NDArray[np.object_]: ...
110110

pandas/_libs/tslibs/timedeltas.pyi

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class Timedelta(timedelta):
8585
def __new__(
8686
cls: type[_S],
8787
value=...,
88-
unit: str = ...,
88+
unit: str | None = ...,
8989
**kwargs: float | np.integer | np.floating,
9090
) -> _S: ...
9191
# GH 46171

pandas/core/arrays/arrow/array.py

+1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ class ArrowExtensionArray(OpsMixin, ExtensionArray):
163163
"""
164164

165165
_data: pa.ChunkedArray
166+
_dtype: ArrowDtype
166167

167168
def __init__(self, values: pa.Array | pa.ChunkedArray) -> None:
168169
if pa_version_under1p01:

pandas/core/arrays/string_arrow.py

+4
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ class ArrowStringArray(ArrowExtensionArray, BaseStringArray, ObjectStringArrayMi
108108
Length: 4, dtype: string
109109
"""
110110

111+
# error: Incompatible types in assignment (expression has type "StringDtype",
112+
# base class "ArrowExtensionArray" defined the type as "ArrowDtype")
113+
_dtype: StringDtype # type: ignore[assignment]
114+
111115
def __init__(self, values) -> None:
112116
super().__init__(values)
113117
# TODO: Migrate to ArrowDtype instead

pandas/core/window/rolling.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def __init__(
144144
self._win_type = win_type
145145
self.axis = obj._get_axis_number(axis) if axis is not None else None
146146
self.method = method
147-
self._win_freq_i8 = None
147+
self._win_freq_i8: int | None = None
148148
if self.on is None:
149149
if self.axis == 0:
150150
self._on = self.obj.index
@@ -1838,15 +1838,13 @@ def _validate(self):
18381838
"compatible with a datetimelike index"
18391839
) from err
18401840
if isinstance(self._on, PeriodIndex):
1841-
# error: Incompatible types in assignment (expression has type "float",
1842-
# variable has type "None")
1841+
# error: Incompatible types in assignment (expression has type
1842+
# "float", variable has type "Optional[int]")
18431843
self._win_freq_i8 = freq.nanos / ( # type: ignore[assignment]
18441844
self._on.freq.nanos / self._on.freq.n
18451845
)
18461846
else:
1847-
# error: Incompatible types in assignment (expression has type "int",
1848-
# variable has type "None")
1849-
self._win_freq_i8 = freq.nanos # type: ignore[assignment]
1847+
self._win_freq_i8 = freq.nanos
18501848

18511849
# min_periods must be an integer
18521850
if self.min_periods is None:
@@ -2867,7 +2865,9 @@ def _get_window_indexer(self) -> GroupbyIndexer:
28672865
window = self.window
28682866
elif self._win_freq_i8 is not None:
28692867
rolling_indexer = VariableWindowIndexer
2870-
window = self._win_freq_i8
2868+
# error: Incompatible types in assignment (expression has type
2869+
# "int", variable has type "BaseIndexer")
2870+
window = self._win_freq_i8 # type: ignore[assignment]
28712871
else:
28722872
rolling_indexer = FixedWindowIndexer
28732873
window = self.window

scripts/run_stubtest.py

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import os
2+
from pathlib import Path
3+
import sys
4+
import tempfile
5+
import warnings
6+
7+
from mypy import stubtest
8+
9+
import pandas as pd
10+
11+
# fail early if pandas is not installed
12+
if "dev" not in getattr(pd, "__version__", ""):
13+
# fail on the CI, soft fail during local development
14+
warnings.warn("You need to install the development version of pandas")
15+
if pd.compat.is_ci_environment():
16+
sys.exit(1)
17+
else:
18+
sys.exit(0)
19+
20+
21+
_ALLOWLIST = [ # should be empty
22+
# TODO (child classes implement these methods)
23+
"pandas._libs.hashtable.HashTable.__contains__",
24+
"pandas._libs.hashtable.HashTable.__len__",
25+
"pandas._libs.hashtable.HashTable.factorize",
26+
"pandas._libs.hashtable.HashTable.get_item",
27+
"pandas._libs.hashtable.HashTable.get_labels",
28+
"pandas._libs.hashtable.HashTable.get_state",
29+
"pandas._libs.hashtable.HashTable.lookup",
30+
"pandas._libs.hashtable.HashTable.map_locations",
31+
"pandas._libs.hashtable.HashTable.set_item",
32+
"pandas._libs.hashtable.HashTable.sizeof",
33+
"pandas._libs.hashtable.HashTable.unique",
34+
# stubtest might be too sensitive
35+
"pandas._libs.lib.NoDefault",
36+
"pandas._libs.lib._NoDefault.no_default",
37+
# internal type alias (should probably be private)
38+
"pandas._libs.lib.ndarray_obj_2d",
39+
# workaround for mypy (cache_readonly = property)
40+
"pandas._libs.properties.cache_readonly.__get__",
41+
"pandas._libs.properties.cache_readonly.deleter",
42+
"pandas._libs.properties.cache_readonly.getter",
43+
"pandas._libs.properties.cache_readonly.setter",
44+
# TODO (child classes implement these methods)
45+
"pandas._libs.sparse.SparseIndex.__init__",
46+
"pandas._libs.sparse.SparseIndex.equals",
47+
"pandas._libs.sparse.SparseIndex.indices",
48+
"pandas._libs.sparse.SparseIndex.intersect",
49+
"pandas._libs.sparse.SparseIndex.lookup",
50+
"pandas._libs.sparse.SparseIndex.lookup_array",
51+
"pandas._libs.sparse.SparseIndex.make_union",
52+
"pandas._libs.sparse.SparseIndex.nbytes",
53+
"pandas._libs.sparse.SparseIndex.ngaps",
54+
"pandas._libs.sparse.SparseIndex.to_block_index",
55+
"pandas._libs.sparse.SparseIndex.to_int_index",
56+
# TODO (decorator changes argument names)
57+
"pandas._libs.tslibs.offsets.BaseOffset._apply_array",
58+
"pandas._libs.tslibs.offsets.BusinessHour.rollback",
59+
"pandas._libs.tslibs.offsets.BusinessHour.rollforward ",
60+
# type alias
61+
"pandas._libs.tslibs.timedeltas.UnitChoices",
62+
]
63+
64+
if __name__ == "__main__":
65+
# find pyi files
66+
root = Path.cwd()
67+
pyi_modules = [
68+
str(pyi.relative_to(root).with_suffix("")).replace(os.sep, ".")
69+
for pyi in root.glob("pandas/**/*.pyi")
70+
]
71+
72+
# create allowlist
73+
with tempfile.NamedTemporaryFile(mode="w+t") as allow:
74+
allow.write("\n".join(_ALLOWLIST))
75+
allow.flush()
76+
77+
args = pyi_modules + [
78+
"--ignore-missing-stub",
79+
"--concise",
80+
"--mypy-config-file",
81+
"pyproject.toml",
82+
"--allowlist",
83+
allow.name,
84+
]
85+
sys.exit(stubtest.test_stubs(stubtest.parse_options(args)))

0 commit comments

Comments
 (0)