From 6e29a8666d989711195021a34a285540d8e5042a Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 15 Oct 2025 15:58:24 -0700 Subject: [PATCH 1/5] TYP: unit str->TimeUnit --- pandas/core/arrays/datetimes.py | 3 ++- pandas/core/arrays/timedeltas.py | 10 ++++------ pandas/core/indexes/datetimelike.py | 5 +++-- pandas/core/indexes/datetimes.py | 5 +++-- pandas/core/indexes/timedeltas.py | 9 ++++++--- pandas/core/resample.py | 5 +++-- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index d17ffbbfa5b4d..0e2c5c18066f3 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -88,6 +88,7 @@ IntervalClosedType, TimeAmbiguous, TimeNonexistent, + TimeUnit, npt, ) @@ -413,7 +414,7 @@ def _generate_range( nonexistent: TimeNonexistent = "raise", inclusive: IntervalClosedType = "both", *, - unit: str | None = None, + unit: TimeUnit = "ns", ) -> Self: periods = dtl.validate_periods(periods) if freq is None and any(x is None for x in [periods, start, end]): diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 1942212cd97b8..35374658e22b4 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -73,6 +73,7 @@ DtypeObj, NpDtype, npt, + TimeUnit, ) from pandas import DataFrame @@ -275,7 +276,7 @@ def _from_sequence_not_strict( @classmethod def _generate_range( - cls, start, end, periods, freq, closed=None, *, unit: str | None = None + cls, start, end, periods, freq, closed=None, *, unit: TimeUnit ) -> Self: periods = dtl.validate_periods(periods) if freq is None and any(x is None for x in [periods, start, end]): @@ -293,11 +294,8 @@ def _generate_range( if end is not None: end = Timedelta(end).as_unit("ns") - if unit is not None: - if unit not in ["s", "ms", "us", "ns"]: - raise ValueError("'unit' must be one of 's', 'ms', 'us', 'ns'") - else: - unit = "ns" + if unit not in ["s", "ms", "us", "ns"]: + raise ValueError("'unit' must be one of 's', 'ms', 'us', 'ns'") if start is not None and unit is not None: start = start.as_unit(unit, round_ok=False) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 7e6461f0fab5e..58529c5597b6e 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -75,6 +75,7 @@ from pandas._typing import ( Axis, JoinHow, + TimeUnit, npt, ) @@ -434,10 +435,10 @@ class DatetimeTimedeltaMixin(DatetimeIndexOpsMixin, ABC): _is_unique = Index.is_unique @property - def unit(self) -> str: + def unit(self) -> TimeUnit: return self._data.unit - def as_unit(self, unit: str) -> Self: + def as_unit(self, unit: TimeUnit) -> Self: """ Convert to a dtype with the given unit resolution. diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index f64152278a648..6451e55f7fc4d 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -66,6 +66,7 @@ TimeAmbiguous, TimeNonexistent, npt, + TimeUnit, ) from pandas.core.api import ( @@ -852,7 +853,7 @@ def date_range( name: Hashable | None = None, inclusive: IntervalClosedType = "both", *, - unit: str | None = None, + unit: TimeUnit = "ns", **kwargs, ) -> DatetimeIndex: """ @@ -893,7 +894,7 @@ def date_range( Include boundaries; Whether to set each bound as closed or open. .. versionadded:: 1.4.0 - unit : str, default None + unit : {'s', 'ms', 'us', 'ns'}, default 'ns' Specify the desired resolution of the result. .. versionadded:: 2.0.0 diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 84b5d9d262740..be8d1e465ed34 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -33,7 +33,10 @@ if TYPE_CHECKING: from pandas._libs import NaTType - from pandas._typing import DtypeObj + from pandas._typing import ( + DtypeObj, + TimeUnit, + ) @inherit_names( @@ -249,7 +252,7 @@ def timedelta_range( name=None, closed=None, *, - unit: str | None = None, + unit: TimeUnit = "ns", ) -> TimedeltaIndex: """ Return a fixed frequency TimedeltaIndex with day as the default. @@ -269,7 +272,7 @@ def timedelta_range( closed : str, default None Make the interval closed with respect to the given frequency to the 'left', 'right', or both sides (None). - unit : str, default None + unit : {'s', 'ms', 'us', 'ns'}, default 'ns' Specify the desired resolution of the result. .. versionadded:: 2.0.0 diff --git a/pandas/core/resample.py b/pandas/core/resample.py index 44304bcc7f388..0e61780e894aa 100644 --- a/pandas/core/resample.py +++ b/pandas/core/resample.py @@ -99,6 +99,7 @@ TimedeltaConvertibleTypes, TimeGrouperOrigin, TimestampConvertibleTypes, + TimeUnit, npt, ) @@ -2835,7 +2836,7 @@ def _get_timestamp_range_edges( first: Timestamp, last: Timestamp, freq: BaseOffset, - unit: str, + unit: TimeUnit, closed: Literal["right", "left"] = "left", origin: TimeGrouperOrigin = "start_day", offset: Timedelta | None = None, @@ -2985,7 +2986,7 @@ def _adjust_dates_anchored( closed: Literal["right", "left"] = "right", origin: TimeGrouperOrigin = "start_day", offset: Timedelta | None = None, - unit: str = "ns", + unit: TimeUnit = "ns", ) -> tuple[Timestamp, Timestamp]: # First and last offsets should be calculated from the start day to fix an # error cause by resampling across multiple days when a one day period is From 044610feb2df7f7db0196635954b7f05be4bc011 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 15 Oct 2025 18:36:36 -0700 Subject: [PATCH 2/5] mypy fixup --- pandas/core/arrays/datetimelike.py | 4 +++- pandas/core/resample.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 7f5661f224348..641d9f79423af 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -157,6 +157,8 @@ Sequence, ) + from pandas._typing import TimeUnit + from pandas import Index from pandas.core.arrays import ( DatetimeArray, @@ -2114,7 +2116,7 @@ def _creso(self) -> int: return get_unit_from_dtype(self._ndarray.dtype) @cache_readonly - def unit(self) -> str: + def unit(self) -> TimeUnit: """ The precision unit of the datetime data. diff --git a/pandas/core/resample.py b/pandas/core/resample.py index 0e61780e894aa..c85f6b36f0947 100644 --- a/pandas/core/resample.py +++ b/pandas/core/resample.py @@ -3095,7 +3095,7 @@ def asfreq( new_obj.index = _asfreq_compat(obj.index, freq) else: - unit = None + unit: TimeUnit = "ns" if isinstance(obj.index, DatetimeIndex): # TODO: should we disallow non-DatetimeIndex? unit = obj.index.unit From f768a2fb9d4032f1fafc90a9dd5e90ee5e8699d6 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 16 Oct 2025 08:29:24 -0700 Subject: [PATCH 3/5] mypy fixup --- pandas/core/arrays/datetimelike.py | 2 +- pandas/core/arrays/datetimes.py | 2 +- pandas/core/arrays/timedeltas.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 641d9f79423af..6a8a22261cbb2 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -2144,7 +2144,7 @@ def unit(self) -> TimeUnit: # "ExtensionDtype"; expected "Union[DatetimeTZDtype, dtype[Any]]" return dtype_to_unit(self.dtype) # type: ignore[arg-type] - def as_unit(self, unit: str, round_ok: bool = True) -> Self: + def as_unit(self, unit: TimeUnit, round_ok: bool = True) -> Self: """ Convert to a dtype with the given unit resolution. diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 0e2c5c18066f3..d23398aae1156 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -535,7 +535,7 @@ def _unbox_scalar(self, value) -> np.datetime64: raise ValueError("'value' should be a Timestamp.") self._check_compatible_with(value) if value is NaT: - return np.datetime64(value._value, self.unit) # type: ignore[call-overload] + return np.datetime64(value._value, self.unit) else: return value.as_unit(self.unit, round_ok=False).asm8 diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 35374658e22b4..2b942041edf89 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -325,7 +325,7 @@ def _unbox_scalar(self, value) -> np.timedelta64: raise ValueError("'value' should be a Timedelta.") self._check_compatible_with(value) if value is NaT: - return np.timedelta64(value._value, self.unit) # type: ignore[call-overload] + return np.timedelta64(value._value, self.unit) else: return value.as_unit(self.unit, round_ok=False).asm8 From 4ada17ee8c355b08f7593550a73168851ef730ab Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 16 Oct 2025 09:58:12 -0700 Subject: [PATCH 4/5] mypy fixup --- pandas/core/arrays/datetimelike.py | 6 +++--- pandas/core/arrays/datetimes.py | 5 ++++- pandas/core/arrays/period.py | 4 +++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 6a8a22261cbb2..a384548623ae7 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -2140,9 +2140,9 @@ def unit(self) -> TimeUnit: >>> idx.as_unit("s").unit 's' """ - # error: Argument 1 to "dtype_to_unit" has incompatible type - # "ExtensionDtype"; expected "Union[DatetimeTZDtype, dtype[Any]]" - return dtype_to_unit(self.dtype) # type: ignore[arg-type] + # error: Incompatible return value type (got "str", expected + # "Literal['s', 'ms', 'us', 'ns']") [return-value] + return dtype_to_unit(self.dtype) # type: ignore[return-value] def as_unit(self, unit: TimeUnit, round_ok: bool = True) -> Self: """ diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index d23398aae1156..64d0347aa815e 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -395,7 +395,10 @@ def _from_sequence_not_strict( result = cls._simple_new(subarr, freq=inferred_freq, dtype=data_dtype) if unit is not None and unit != result.unit: # If unit was specified in user-passed dtype, cast to it here - result = result.as_unit(unit) + # error: Argument 1 to "as_unit" of "TimelikeOps" has + # incompatible type "str"; expected "Literal['s', 'ms', 'us', 'ns']" + # [arg-type] + result = result.as_unit(unit) # type: ignore[arg-type] validate_kwds = {"ambiguous": ambiguous} result._maybe_pin_freq(freq, validate_kwds) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index adc09afab485b..90388336ba83d 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -962,7 +962,9 @@ def astype(self, dtype, copy: bool = True): # GH#45038 match PeriodIndex behavior. tz = getattr(dtype, "tz", None) unit = dtl.dtype_to_unit(dtype) - return self.to_timestamp().tz_localize(tz).as_unit(unit) + # error: Argument 1 to "as_unit" of "TimelikeOps" has incompatible + # type "str"; expected "Literal['s', 'ms', 'us', 'ns']" [arg-type] + return self.to_timestamp().tz_localize(tz).as_unit(unit) # type: ignore[arg-type] return super().astype(dtype, copy=copy) From d974a1e0039ba8bfbbe8201e29172e027a165331 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 16 Oct 2025 10:21:10 -0700 Subject: [PATCH 5/5] mypy fixup --- pandas/core/arrays/datetimelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index a384548623ae7..97113d98a2aab 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -2142,7 +2142,7 @@ def unit(self) -> TimeUnit: """ # error: Incompatible return value type (got "str", expected # "Literal['s', 'ms', 'us', 'ns']") [return-value] - return dtype_to_unit(self.dtype) # type: ignore[return-value] + return dtype_to_unit(self.dtype) # type: ignore[return-value,arg-type] def as_unit(self, unit: TimeUnit, round_ok: bool = True) -> Self: """