Skip to content

Commit 6a03ab7

Browse files
authored
TYP: Add typing for pd.to_timedelta, fix Timedelta operator return types (#46343)
1 parent 17b55aa commit 6a03ab7

File tree

6 files changed

+145
-26
lines changed

6 files changed

+145
-26
lines changed

pandas/_libs/tslibs/timedeltas.pyi

+63-15
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,65 @@
11
from datetime import timedelta
22
from typing import (
33
ClassVar,
4+
Literal,
45
Type,
56
TypeVar,
67
overload,
78
)
89

910
import numpy as np
10-
import numpy.typing as npt
1111

1212
from pandas._libs.tslibs import (
1313
NaTType,
1414
Tick,
1515
)
16+
from pandas._typing import npt
1617

18+
# This should be kept consistent with the keys in the dict timedelta_abbrevs
19+
# in pandas/_libs/tslibs/timedeltas.pyx
20+
UnitChoices = Literal[
21+
"Y",
22+
"y",
23+
"M",
24+
"W",
25+
"w",
26+
"D",
27+
"d",
28+
"days",
29+
"day",
30+
"hours",
31+
"hour",
32+
"hr",
33+
"h",
34+
"m",
35+
"minute",
36+
"min",
37+
"minutes",
38+
"t",
39+
"s",
40+
"seconds",
41+
"sec",
42+
"second",
43+
"ms",
44+
"milliseconds",
45+
"millisecond",
46+
"milli",
47+
"millis",
48+
"l",
49+
"us",
50+
"microseconds",
51+
"microsecond",
52+
"µs",
53+
"micro",
54+
"micros",
55+
"u",
56+
"ns",
57+
"nanoseconds",
58+
"nano",
59+
"nanos",
60+
"nanosecond",
61+
"n",
62+
]
1763
_S = TypeVar("_S", bound=timedelta)
1864

1965
def ints_to_pytimedelta(
@@ -25,7 +71,7 @@ def array_to_timedelta64(
2571
unit: str | None = ...,
2672
errors: str = ...,
2773
) -> np.ndarray: ... # np.ndarray[m8ns]
28-
def parse_timedelta_unit(unit: str | None) -> str: ...
74+
def parse_timedelta_unit(unit: str | None) -> UnitChoices: ...
2975
def delta_to_nanoseconds(delta: np.timedelta64 | timedelta | Tick) -> int: ...
3076

3177
class Timedelta(timedelta):
@@ -59,20 +105,20 @@ class Timedelta(timedelta):
59105
def ceil(self: _S, freq: str) -> _S: ...
60106
@property
61107
def resolution_string(self) -> str: ...
62-
def __add__(self, other: timedelta) -> timedelta: ...
63-
def __radd__(self, other: timedelta) -> timedelta: ...
64-
def __sub__(self, other: timedelta) -> timedelta: ...
65-
def __rsub__(self, other: timedelta) -> timedelta: ...
66-
def __neg__(self) -> timedelta: ...
67-
def __pos__(self) -> timedelta: ...
68-
def __abs__(self) -> timedelta: ...
69-
def __mul__(self, other: float) -> timedelta: ...
70-
def __rmul__(self, other: float) -> timedelta: ...
108+
def __add__(self, other: timedelta) -> Timedelta: ...
109+
def __radd__(self, other: timedelta) -> Timedelta: ...
110+
def __sub__(self, other: timedelta) -> Timedelta: ...
111+
def __rsub__(self, other: timedelta) -> Timedelta: ...
112+
def __neg__(self) -> Timedelta: ...
113+
def __pos__(self) -> Timedelta: ...
114+
def __abs__(self) -> Timedelta: ...
115+
def __mul__(self, other: float) -> Timedelta: ...
116+
def __rmul__(self, other: float) -> Timedelta: ...
71117
# error: Signature of "__floordiv__" incompatible with supertype "timedelta"
72118
@overload # type: ignore[override]
73119
def __floordiv__(self, other: timedelta) -> int: ...
74120
@overload
75-
def __floordiv__(self, other: int | float) -> timedelta: ...
121+
def __floordiv__(self, other: int | float) -> Timedelta: ...
76122
@overload
77123
def __floordiv__(
78124
self, other: npt.NDArray[np.timedelta64]
@@ -90,11 +136,13 @@ class Timedelta(timedelta):
90136
@overload
91137
def __truediv__(self, other: timedelta) -> float: ...
92138
@overload
93-
def __truediv__(self, other: float) -> timedelta: ...
94-
def __mod__(self, other: timedelta) -> timedelta: ...
95-
def __divmod__(self, other: timedelta) -> tuple[int, timedelta]: ...
139+
def __truediv__(self, other: float) -> Timedelta: ...
140+
def __mod__(self, other: timedelta) -> Timedelta: ...
141+
def __divmod__(self, other: timedelta) -> tuple[int, Timedelta]: ...
96142
def __le__(self, other: timedelta) -> bool: ...
97143
def __lt__(self, other: timedelta) -> bool: ...
98144
def __ge__(self, other: timedelta) -> bool: ...
99145
def __gt__(self, other: timedelta) -> bool: ...
100146
def __hash__(self) -> int: ...
147+
def isoformat(self) -> str: ...
148+
def to_numpy(self) -> np.timedelta64: ...

pandas/_libs/tslibs/timedeltas.pyx

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Components = collections.namedtuple(
8282
],
8383
)
8484

85+
# This should be kept consistent with UnitChoices in pandas/_libs/tslibs/timedeltas.pyi
8586
cdef dict timedelta_abbrevs = {
8687
"Y": "Y",
8788
"y": "Y",

pandas/_typing.py

+1
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,4 @@ def closed(self) -> bool:
313313
# datetime and NaTType
314314

315315
DatetimeNaTType = Union[datetime, "NaTType"]
316+
DateTimeErrorChoices = Literal["ignore", "raise", "coerce"]

pandas/core/tools/datetimes.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from pandas._typing import (
4040
AnyArrayLike,
4141
ArrayLike,
42+
DateTimeErrorChoices,
4243
Timezone,
4344
)
4445
from pandas.util._exceptions import find_stack_level
@@ -79,6 +80,7 @@
7980

8081
if TYPE_CHECKING:
8182
from pandas._libs.tslibs.nattype import NaTType
83+
from pandas._libs.tslibs.timedeltas import UnitChoices
8284

8385
from pandas import (
8486
DataFrame,
@@ -657,7 +659,7 @@ def _adjust_to_origin(arg, origin, unit):
657659
@overload
658660
def to_datetime(
659661
arg: DatetimeScalar,
660-
errors: str = ...,
662+
errors: DateTimeErrorChoices = ...,
661663
dayfirst: bool = ...,
662664
yearfirst: bool = ...,
663665
utc: bool | None = ...,
@@ -674,7 +676,7 @@ def to_datetime(
674676
@overload
675677
def to_datetime(
676678
arg: Series | DictConvertible,
677-
errors: str = ...,
679+
errors: DateTimeErrorChoices = ...,
678680
dayfirst: bool = ...,
679681
yearfirst: bool = ...,
680682
utc: bool | None = ...,
@@ -691,7 +693,7 @@ def to_datetime(
691693
@overload
692694
def to_datetime(
693695
arg: list | tuple | Index | ArrayLike,
694-
errors: str = ...,
696+
errors: DateTimeErrorChoices = ...,
695697
dayfirst: bool = ...,
696698
yearfirst: bool = ...,
697699
utc: bool | None = ...,
@@ -707,7 +709,7 @@ def to_datetime(
707709

708710
def to_datetime(
709711
arg: DatetimeScalarOrArrayConvertible | DictConvertible,
710-
errors: str = "raise",
712+
errors: DateTimeErrorChoices = "raise",
711713
dayfirst: bool = False,
712714
yearfirst: bool = False,
713715
utc: bool | None = None,
@@ -1148,7 +1150,7 @@ def to_datetime(
11481150
}
11491151

11501152

1151-
def _assemble_from_unit_mappings(arg, errors, tz):
1153+
def _assemble_from_unit_mappings(arg, errors: DateTimeErrorChoices, tz):
11521154
"""
11531155
assemble the unit specified fields from the arg (DataFrame)
11541156
Return a Series for actual parsing
@@ -1228,7 +1230,8 @@ def coerce(values):
12281230
except (TypeError, ValueError) as err:
12291231
raise ValueError(f"cannot assemble the datetimes: {err}") from err
12301232

1231-
for u in ["h", "m", "s", "ms", "us", "ns"]:
1233+
units: list[UnitChoices] = ["h", "m", "s", "ms", "us", "ns"]
1234+
for u in units:
12321235
value = unit_rev.get(u)
12331236
if value is not None and value in arg:
12341237
try:

pandas/core/tools/timedeltas.py

+66-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
"""
44
from __future__ import annotations
55

6+
from datetime import timedelta
7+
from typing import (
8+
TYPE_CHECKING,
9+
overload,
10+
)
11+
612
import numpy as np
713

814
from pandas._libs import lib
@@ -23,8 +29,61 @@
2329

2430
from pandas.core.arrays.timedeltas import sequence_to_td64ns
2531

26-
27-
def to_timedelta(arg, unit=None, errors="raise"):
32+
if TYPE_CHECKING:
33+
from pandas._libs.tslibs.timedeltas import UnitChoices
34+
from pandas._typing import (
35+
ArrayLike,
36+
DateTimeErrorChoices,
37+
)
38+
39+
from pandas import (
40+
Index,
41+
Series,
42+
TimedeltaIndex,
43+
)
44+
45+
46+
@overload
47+
def to_timedelta(
48+
arg: str | int | float | timedelta,
49+
unit: UnitChoices | None = ...,
50+
errors: DateTimeErrorChoices = ...,
51+
) -> Timedelta:
52+
...
53+
54+
55+
@overload
56+
def to_timedelta(
57+
arg: Series,
58+
unit: UnitChoices | None = ...,
59+
errors: DateTimeErrorChoices = ...,
60+
) -> Series:
61+
...
62+
63+
64+
@overload
65+
def to_timedelta(
66+
arg: list | tuple | range | ArrayLike | Index,
67+
unit: UnitChoices | None = ...,
68+
errors: DateTimeErrorChoices = ...,
69+
) -> TimedeltaIndex:
70+
...
71+
72+
73+
def to_timedelta(
74+
arg: str
75+
| int
76+
| float
77+
| timedelta
78+
| list
79+
| tuple
80+
| range
81+
| ArrayLike
82+
| Index
83+
| Series,
84+
unit: UnitChoices | None = None,
85+
errors: DateTimeErrorChoices = "raise",
86+
) -> Timedelta | TimedeltaIndex | Series:
2887
"""
2988
Convert argument to timedelta.
3089
@@ -133,7 +192,11 @@ def to_timedelta(arg, unit=None, errors="raise"):
133192
return _convert_listlike(arg, unit=unit, errors=errors, name=arg.name)
134193
elif isinstance(arg, np.ndarray) and arg.ndim == 0:
135194
# extract array scalar and process below
136-
arg = lib.item_from_zerodim(arg)
195+
# error: Incompatible types in assignment (expression has type "object",
196+
# variable has type "Union[str, int, float, timedelta, List[Any],
197+
# Tuple[Any, ...], Union[Union[ExtensionArray, ndarray[Any, Any]], Index,
198+
# Series]]") [assignment]
199+
arg = lib.item_from_zerodim(arg) # type: ignore[assignment]
137200
elif is_list_like(arg) and getattr(arg, "ndim", 1) == 1:
138201
return _convert_listlike(arg, unit=unit, errors=errors)
139202
elif getattr(arg, "ndim", 1) > 1:

pandas/io/sql.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
import numpy as np
2626

2727
import pandas._libs.lib as lib
28-
from pandas._typing import DtypeArg
28+
from pandas._typing import (
29+
DateTimeErrorChoices,
30+
DtypeArg,
31+
)
2932
from pandas.compat._optional import import_optional_dependency
3033
from pandas.errors import AbstractMethodError
3134
from pandas.util._exceptions import find_stack_level
@@ -86,7 +89,7 @@ def _handle_date_column(
8689
# read_sql like functions.
8790
# Format can take on custom to_datetime argument values such as
8891
# {"errors": "coerce"} or {"dayfirst": True}
89-
error = format.pop("errors", None) or "ignore"
92+
error: DateTimeErrorChoices = format.pop("errors", None) or "ignore"
9093
return to_datetime(col, errors=error, **format)
9194
else:
9295
# Allow passing of formatting string for integers

0 commit comments

Comments
 (0)