Skip to content

Commit 447ef57

Browse files
authored
TYP: Subset of "Improved the type stubs in the _libs directory to help with type checking" (#44251)
1 parent 01f4b7b commit 447ef57

File tree

15 files changed

+105
-60
lines changed

15 files changed

+105
-60
lines changed

pandas/_libs/interval.pyx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -516,9 +516,9 @@ def intervals_to_interval_bounds(ndarray intervals, bint validate_closed=True):
516516
517517
Returns
518518
-------
519-
tuple of tuples
520-
left : (ndarray, object, array)
521-
right : (ndarray, object, array)
519+
tuple of
520+
left : ndarray
521+
right : ndarray
522522
closed: str
523523
"""
524524
cdef:

pandas/_libs/missing.pyi

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import numpy as np
2+
from numpy import typing as npt
3+
4+
class NAType: ...
5+
6+
NA: NAType
7+
8+
def is_matching_na(
9+
left: object, right: object, nan_matches_none: bool = ...
10+
) -> bool: ...
11+
def isposinf_scalar(val: object) -> bool: ...
12+
def isneginf_scalar(val: object) -> bool: ...
13+
def checknull(val: object, inf_as_na: bool = ...) -> bool: ...
14+
def isnaobj(arr: np.ndarray, inf_as_na: bool = ...) -> npt.NDArray[np.bool_]: ...
15+
def is_numeric_na(values: np.ndarray) -> npt.NDArray[np.bool_]: ...

pandas/_libs/tslibs/dtypes.pyi

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,33 @@ class PeriodDtypeBase:
1818
def resolution(self) -> Resolution: ...
1919

2020
class FreqGroup(Enum):
21-
FR_ANN: int = ...
22-
FR_QTR: int = ...
23-
FR_MTH: int = ...
24-
FR_WK: int = ...
25-
FR_BUS: int = ...
26-
FR_DAY: int = ...
27-
FR_HR: int = ...
28-
FR_MIN: int = ...
29-
FR_SEC: int = ...
30-
FR_MS: int = ...
31-
FR_US: int = ...
32-
FR_NS: int = ...
33-
FR_UND: int = ...
21+
FR_ANN: int
22+
FR_QTR: int
23+
FR_MTH: int
24+
FR_WK: int
25+
FR_BUS: int
26+
FR_DAY: int
27+
FR_HR: int
28+
FR_MIN: int
29+
FR_SEC: int
30+
FR_MS: int
31+
FR_US: int
32+
FR_NS: int
33+
FR_UND: int
3434
@staticmethod
3535
def get_freq_group(code: int) -> FreqGroup: ...
3636

3737
class Resolution(Enum):
38-
RESO_NS: int = ...
39-
RESO_US: int = ...
40-
RESO_MS: int = ...
41-
RESO_SEC: int = ...
42-
RESO_MIN: int = ...
43-
RESO_HR: int = ...
44-
RESO_DAY: int = ...
45-
RESO_MTH: int = ...
46-
RESO_QTR: int = ...
47-
RESO_YR: int = ...
38+
RESO_NS: int
39+
RESO_US: int
40+
RESO_MS: int
41+
RESO_SEC: int
42+
RESO_MIN: int
43+
RESO_HR: int
44+
RESO_DAY: int
45+
RESO_MTH: int
46+
RESO_QTR: int
47+
RESO_YR: int
4848
def __lt__(self, other: Resolution) -> bool: ...
4949
def __ge__(self, other: Resolution) -> bool: ...
5050
@property

pandas/_libs/tslibs/nattype.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ NaT: NaTType
1212
iNaT: int
1313
nat_strings: set[str]
1414

15+
def is_null_datetimelike(val: object, inat_is_null: bool = ...) -> bool: ...
16+
1517
class NaTType(datetime):
1618
value: np.int64
1719
def asm8(self) -> np.datetime64: ...

pandas/_libs/tslibs/np_datetime.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class OutOfBoundsDatetime(ValueError): ...

pandas/_libs/tslibs/offsets.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3573,7 +3573,7 @@ cpdef to_offset(freq):
35733573
35743574
Parameters
35753575
----------
3576-
freq : str, tuple, datetime.timedelta, DateOffset or None
3576+
freq : str, datetime.timedelta, BaseOffset or None
35773577
35783578
Returns
35793579
-------
@@ -3586,7 +3586,7 @@ cpdef to_offset(freq):
35863586
35873587
See Also
35883588
--------
3589-
DateOffset : Standard kind of date increment used for a date range.
3589+
BaseOffset : Standard kind of date increment used for a date range.
35903590
35913591
Examples
35923592
--------

pandas/_libs/tslibs/timestamps.pyi

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@ import numpy as np
1717

1818
from pandas._libs.tslibs import (
1919
BaseOffset,
20-
NaT,
2120
NaTType,
2221
Period,
2322
Timedelta,
2423
)
2524

2625
_S = TypeVar("_S")
2726

28-
def integer_op_not_supported(obj) -> None: ...
27+
def integer_op_not_supported(obj) -> TypeError: ...
2928

3029
class Timestamp(datetime):
3130
min: ClassVar[Timestamp]

pandas/core/arrays/string_.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,9 @@ def _cmp_method(self, other, op):
512512

513513
# ------------------------------------------------------------------------
514514
# String methods interface
515-
_str_na_value = StringDtype.na_value
515+
# error: Incompatible types in assignment (expression has type "NAType",
516+
# base class "PandasArray" defined the type as "float")
517+
_str_na_value = StringDtype.na_value # type: ignore[assignment]
516518

517519
def _str_map(
518520
self, f, na_value=None, dtype: Dtype | None = None, convert: bool = True

pandas/core/dtypes/cast.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,10 @@ def maybe_upcast(
912912
# We get a copy in all cases _except_ (values.dtype == new_dtype and not copy)
913913
upcast_values = values.astype(new_dtype, copy=copy)
914914

915+
# error: Incompatible return value type (got "Tuple[ndarray[Any, dtype[Any]],
916+
# Union[Union[str, int, float, bool] Union[Period, Timestamp, Timedelta, Any]]]",
917+
# expected "Tuple[NumpyArrayT, Union[Union[str, int, float, bool], Union[Period,
918+
# Timestamp, Timedelta, Any]]]")
915919
return upcast_values, fill_value # type: ignore[return-value]
916920

917921

pandas/core/dtypes/dtypes.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -876,15 +876,15 @@ def freq(self):
876876

877877
@classmethod
878878
def _parse_dtype_strict(cls, freq: str_type) -> BaseOffset:
879-
if isinstance(freq, str):
879+
if isinstance(freq, str): # note: freq is already of type str!
880880
if freq.startswith("period[") or freq.startswith("Period["):
881881
m = cls._match.search(freq)
882882
if m is not None:
883883
freq = m.group("freq")
884884

885-
freq = to_offset(freq)
886-
if freq is not None:
887-
return freq
885+
freq_offset = to_offset(freq)
886+
if freq_offset is not None:
887+
return freq_offset
888888

889889
raise ValueError("could not construct PeriodDtype")
890890

pandas/core/dtypes/missing.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,10 @@ def _isna_array(values: ArrayLike, inf_as_na: bool = False):
241241
if inf_as_na and is_categorical_dtype(dtype):
242242
result = libmissing.isnaobj(values.to_numpy(), inf_as_na=inf_as_na)
243243
else:
244-
result = values.isna()
244+
# error: Incompatible types in assignment (expression has type
245+
# "Union[ndarray[Any, Any], ExtensionArraySupportsAnyAll]", variable has
246+
# type "ndarray[Any, dtype[bool_]]")
247+
result = values.isna() # type: ignore[assignment]
245248
elif is_string_or_object_np_dtype(values.dtype):
246249
result = _isna_string_dtype(values, inf_as_na=inf_as_na)
247250
elif needs_i8_conversion(dtype):

pandas/core/ops/mask_ops.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313

1414
def kleene_or(
15-
left: bool | np.ndarray,
16-
right: bool | np.ndarray,
15+
left: bool | np.ndarray | libmissing.NAType,
16+
right: bool | np.ndarray | libmissing.NAType,
1717
left_mask: np.ndarray | None,
1818
right_mask: np.ndarray | None,
1919
):
@@ -37,12 +37,13 @@ def kleene_or(
3737
The result of the logical or, and the new mask.
3838
"""
3939
# To reduce the number of cases, we ensure that `left` & `left_mask`
40-
# always come from an array, not a scalar. This is safe, since because
40+
# always come from an array, not a scalar. This is safe, since
4141
# A | B == B | A
4242
if left_mask is None:
4343
return kleene_or(right, left, right_mask, left_mask)
4444

45-
assert isinstance(left, np.ndarray)
45+
if not isinstance(left, np.ndarray):
46+
raise TypeError("Either `left` or `right` need to be a np.ndarray.")
4647

4748
raise_for_nan(right, method="or")
4849

@@ -73,8 +74,8 @@ def kleene_or(
7374

7475

7576
def kleene_xor(
76-
left: bool | np.ndarray,
77-
right: bool | np.ndarray,
77+
left: bool | np.ndarray | libmissing.NAType,
78+
right: bool | np.ndarray | libmissing.NAType,
7879
left_mask: np.ndarray | None,
7980
right_mask: np.ndarray | None,
8081
):
@@ -99,16 +100,20 @@ def kleene_xor(
99100
result, mask: ndarray[bool]
100101
The result of the logical xor, and the new mask.
101102
"""
103+
# To reduce the number of cases, we ensure that `left` & `left_mask`
104+
# always come from an array, not a scalar. This is safe, since
105+
# A ^ B == B ^ A
102106
if left_mask is None:
103107
return kleene_xor(right, left, right_mask, left_mask)
104108

109+
if not isinstance(left, np.ndarray):
110+
raise TypeError("Either `left` or `right` need to be a np.ndarray.")
111+
105112
raise_for_nan(right, method="xor")
106113
if right is libmissing.NA:
107114
result = np.zeros_like(left)
108115
else:
109-
# error: Incompatible types in assignment (expression has type
110-
# "Union[bool, Any]", variable has type "ndarray")
111-
result = left ^ right # type: ignore[assignment]
116+
result = left ^ right
112117

113118
if right_mask is None:
114119
if right is libmissing.NA:
@@ -146,12 +151,13 @@ def kleene_and(
146151
The result of the logical xor, and the new mask.
147152
"""
148153
# To reduce the number of cases, we ensure that `left` & `left_mask`
149-
# always come from an array, not a scalar. This is safe, since because
150-
# A | B == B | A
154+
# always come from an array, not a scalar. This is safe, since
155+
# A & B == B & A
151156
if left_mask is None:
152157
return kleene_and(right, left, right_mask, left_mask)
153158

154-
assert isinstance(left, np.ndarray)
159+
if not isinstance(left, np.ndarray):
160+
raise TypeError("Either `left` or `right` need to be a np.ndarray.")
155161
raise_for_nan(right, method="and")
156162

157163
if right is libmissing.NA:

pandas/core/resample.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2012,30 +2012,30 @@ def _adjust_dates_anchored(
20122012
if closed == "right":
20132013
if foffset > 0:
20142014
# roll back
2015-
fresult = first.value - foffset
2015+
fresult_int = first.value - foffset
20162016
else:
2017-
fresult = first.value - freq.nanos
2017+
fresult_int = first.value - freq.nanos
20182018

20192019
if loffset > 0:
20202020
# roll forward
2021-
lresult = last.value + (freq.nanos - loffset)
2021+
lresult_int = last.value + (freq.nanos - loffset)
20222022
else:
20232023
# already the end of the road
2024-
lresult = last.value
2024+
lresult_int = last.value
20252025
else: # closed == 'left'
20262026
if foffset > 0:
2027-
fresult = first.value - foffset
2027+
fresult_int = first.value - foffset
20282028
else:
20292029
# start of the road
2030-
fresult = first.value
2030+
fresult_int = first.value
20312031

20322032
if loffset > 0:
20332033
# roll forward
2034-
lresult = last.value + (freq.nanos - loffset)
2034+
lresult_int = last.value + (freq.nanos - loffset)
20352035
else:
2036-
lresult = last.value + freq.nanos
2037-
fresult = Timestamp(fresult)
2038-
lresult = Timestamp(lresult)
2036+
lresult_int = last.value + freq.nanos
2037+
fresult = Timestamp(fresult_int)
2038+
lresult = Timestamp(lresult_int)
20392039
if first_tzinfo is not None:
20402040
fresult = fresult.tz_localize("UTC").tz_convert(first_tzinfo)
20412041
if last_tzinfo is not None:

pandas/core/strings/object_array.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def rep(x, r):
193193
return result
194194

195195
def _str_match(
196-
self, pat: str, case: bool = True, flags: int = 0, na: Scalar = None
196+
self, pat: str, case: bool = True, flags: int = 0, na: Scalar | None = None
197197
):
198198
if not case:
199199
flags |= re.IGNORECASE
@@ -208,7 +208,7 @@ def _str_fullmatch(
208208
pat: str | re.Pattern,
209209
case: bool = True,
210210
flags: int = 0,
211-
na: Scalar = None,
211+
na: Scalar | None = None,
212212
):
213213
if not case:
214214
flags |= re.IGNORECASE

pandas/tests/arrays/boolean/test_logical.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
import pandas as pd
77
import pandas._testing as tm
88
from pandas.arrays import BooleanArray
9+
from pandas.core.ops.mask_ops import (
10+
kleene_and,
11+
kleene_or,
12+
kleene_xor,
13+
)
914
from pandas.tests.extension.base import BaseOpsUtil
1015

1116

@@ -239,3 +244,11 @@ def test_no_masked_assumptions(self, other, all_logical_operators):
239244
result = getattr(a, all_logical_operators)(other)
240245
expected = getattr(b, all_logical_operators)(other)
241246
tm.assert_extension_array_equal(result, expected)
247+
248+
249+
@pytest.mark.parametrize("operation", [kleene_or, kleene_xor, kleene_and])
250+
def test_error_both_scalar(operation):
251+
msg = r"Either `left` or `right` need to be a np\.ndarray."
252+
with pytest.raises(TypeError, match=msg):
253+
# masks need to be non-None, otherwise it ends up in an infinite recursion
254+
operation(True, True, np.zeros(1), np.zeros(1))

0 commit comments

Comments
 (0)