From d39172179d03674128178d7f3daaf978e2183406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Sun, 27 Feb 2022 17:01:16 -0500 Subject: [PATCH] TYP: offsets.pyi (#45331) --- pandas/_libs/tslibs/ccalendar.pyi | 2 + pandas/_libs/tslibs/offsets.pyi | 256 ++++++++++++++++++ pandas/_libs/tslibs/period.pyi | 7 +- pandas/_libs/tslibs/period.pyx | 2 +- pandas/_libs/tslibs/timedeltas.pyi | 19 +- pandas/_libs/tslibs/timestamps.pyi | 9 +- pandas/core/arrays/datetimelike.py | 5 +- pandas/core/arrays/period.py | 7 +- pandas/core/arrays/timedeltas.py | 40 ++- pandas/core/indexes/period.py | 9 +- pandas/core/ops/array_ops.py | 4 +- pandas/core/window/rolling.py | 10 +- pandas/plotting/_matplotlib/converter.py | 6 +- pandas/plotting/_matplotlib/timeseries.py | 17 +- pandas/tests/tseries/offsets/common.py | 3 +- .../tseries/offsets/test_business_day.py | 7 +- .../tseries/offsets/test_business_hour.py | 4 +- .../tseries/offsets/test_business_month.py | 8 +- .../tseries/offsets/test_business_quarter.py | 8 +- .../tseries/offsets/test_business_year.py | 10 +- .../offsets/test_custom_business_hour.py | 4 +- .../offsets/test_custom_business_month.py | 7 +- pandas/tests/tseries/offsets/test_month.py | 12 +- pandas/tests/tseries/offsets/test_quarter.py | 4 +- pandas/tests/tseries/offsets/test_week.py | 9 +- pandas/tests/tseries/offsets/test_year.py | 8 +- pandas/tests/tslibs/test_period_asfreq.py | 3 +- pandas/tseries/frequencies.py | 3 +- 28 files changed, 413 insertions(+), 70 deletions(-) create mode 100644 pandas/_libs/tslibs/offsets.pyi diff --git a/pandas/_libs/tslibs/ccalendar.pyi b/pandas/_libs/tslibs/ccalendar.pyi index 993f18a61d74a..5d5b935ffa54b 100644 --- a/pandas/_libs/tslibs/ccalendar.pyi +++ b/pandas/_libs/tslibs/ccalendar.pyi @@ -8,5 +8,7 @@ def get_firstbday(year: int, month: int) -> int: ... def get_lastbday(year: int, month: int) -> int: ... def get_day_of_year(year: int, month: int, day: int) -> int: ... def get_iso_calendar(year: int, month: int, day: int) -> tuple[int, int, int]: ... +def is_leapyear(year: int) -> bool: ... def get_week_of_year(year: int, month: int, day: int) -> int: ... def get_days_in_month(year: int, month: int) -> int: ... +def dayofweek(y: int, m: int, d: int) -> int: ... diff --git a/pandas/_libs/tslibs/offsets.pyi b/pandas/_libs/tslibs/offsets.pyi new file mode 100644 index 0000000000000..4cc301018e8f8 --- /dev/null +++ b/pandas/_libs/tslibs/offsets.pyi @@ -0,0 +1,256 @@ +from __future__ import annotations + +from datetime import ( + datetime, + timedelta, +) +from typing import ( + TYPE_CHECKING, + Any, + Collection, + Literal, + Tuple, + TypeVar, + Union, + overload, +) + +import numpy as np + +from pandas._typing import npt + +from .timedeltas import Timedelta + +if TYPE_CHECKING: + from pandas.core.indexes.datetimes import DatetimeIndex +_BaseOffsetT = TypeVar("_BaseOffsetT", bound="BaseOffset") +_DatetimeT = TypeVar("_DatetimeT", bound=datetime) +_TimedeltaT = TypeVar("_TimedeltaT", bound=timedelta) + +_relativedelta_kwds: set[str] +prefix_mapping: dict[str, type] + +class ApplyTypeError(TypeError): ... + +class BaseOffset: + n: int + def __init__(self, n: int = ..., normalize: bool = ...) -> None: ... + def __eq__(self, other) -> bool: ... + def __ne__(self, other) -> bool: ... + def __hash__(self) -> int: ... + @property + def kwds(self) -> dict: ... + @property + def base(self) -> BaseOffset: ... + @overload + def __add__(self, other: npt.NDArray[np.object_]) -> npt.NDArray[np.object_]: ... + @overload + def __add__(self: _BaseOffsetT, other: BaseOffset) -> _BaseOffsetT: ... + @overload + def __add__(self, other: _DatetimeT) -> _DatetimeT: ... + @overload + def __add__(self, other: _TimedeltaT) -> _TimedeltaT: ... + @overload + def __radd__(self, other: npt.NDArray[np.object_]) -> npt.NDArray[np.object_]: ... + @overload + def __radd__(self: _BaseOffsetT, other: BaseOffset) -> _BaseOffsetT: ... + @overload + def __radd__(self, other: _DatetimeT) -> _DatetimeT: ... + @overload + def __radd__(self, other: _TimedeltaT) -> _TimedeltaT: ... + def __sub__(self: _BaseOffsetT, other: BaseOffset) -> _BaseOffsetT: ... + @overload + def __rsub__(self, other: npt.NDArray[np.object_]) -> npt.NDArray[np.object_]: ... + @overload + def __rsub__(self: _BaseOffsetT, other: BaseOffset) -> _BaseOffsetT: ... + @overload + def __rsub__(self, other: _DatetimeT) -> _DatetimeT: ... + @overload + def __rsub__(self, other: _TimedeltaT) -> _TimedeltaT: ... + def __call__(self, other): ... + @overload + def __mul__(self, other: np.ndarray) -> np.ndarray: ... + @overload + def __mul__(self: _BaseOffsetT, other: int) -> _BaseOffsetT: ... + @overload + def __rmul__(self, other: np.ndarray) -> np.ndarray: ... + @overload + def __rmul__(self: _BaseOffsetT, other: int) -> _BaseOffsetT: ... + def __neg__(self: _BaseOffsetT) -> _BaseOffsetT: ... + def copy(self: _BaseOffsetT) -> _BaseOffsetT: ... + def __repr__(self) -> str: ... + @property + def name(self) -> str: ... + @property + def rule_code(self) -> str: ... + def freqstr(self) -> str: ... + def apply_index(self, dtindex: "DatetimeIndex") -> "DatetimeIndex": ... + def _apply_array(self, dtarr) -> None: ... + def rollback(self, dt: datetime) -> datetime: ... + def rollforward(self, dt: datetime) -> datetime: ... + def is_on_offset(self, dt: datetime) -> bool: ... + def __setstate__(self, state) -> None: ... + def __getstate__(self): ... + @property + def nanos(self) -> int: ... + def onOffset(self, dt: datetime) -> bool: ... + def isAnchored(self) -> bool: ... + def is_anchored(self) -> bool: ... + +def _get_offset(name: str) -> BaseOffset: ... + +class SingleConstructorOffset(BaseOffset): + @classmethod + def _from_name(cls, suffix=...): ... + def __reduce__(self): ... + +@overload +def to_offset(freq: None) -> None: ... +@overload +def to_offset(freq: timedelta | BaseOffset | str) -> BaseOffset: ... + +class Tick(SingleConstructorOffset): + def __init__(self, n: int = ..., normalize: bool = ...) -> None: ... + @property + def delta(self) -> Timedelta: ... + @property + def nanos(self) -> int: ... + +def delta_to_tick(delta: timedelta) -> Tick: ... + +class Day(Tick): ... +class Hour(Tick): ... +class Minute(Tick): ... +class Second(Tick): ... +class Milli(Tick): ... +class Micro(Tick): ... +class Nano(Tick): ... + +class RelativeDeltaOffset(BaseOffset): + def __init__(self, n: int = ..., normalize: bool = ..., **kwds: Any) -> None: ... + +class BusinessMixin(SingleConstructorOffset): + def __init__( + self, n: int = ..., normalize: bool = ..., offset: timedelta = ... + ): ... + +class BusinessDay(BusinessMixin): ... + +class BusinessHour(BusinessMixin): + def __init__( + self, + n: int = ..., + normalize: bool = ..., + start: str | Collection[str] = ..., + end: str | Collection[str] = ..., + offset: timedelta = ..., + ): ... + +class WeekOfMonthMixin(SingleConstructorOffset): ... + +class YearOffset(SingleConstructorOffset): + def __init__( + self, n: int = ..., normalize: bool = ..., month: int | None = ... + ): ... + +class BYearEnd(YearOffset): ... +class BYearBegin(YearOffset): ... +class YearEnd(YearOffset): ... +class YearBegin(YearOffset): ... + +class QuarterOffset(SingleConstructorOffset): + def __init__( + self, n: int = ..., normalize: bool = ..., startingMonth: int | None = ... + ) -> None: ... + +class BQuarterEnd(QuarterOffset): ... +class BQuarterBegin(QuarterOffset): ... +class QuarterEnd(QuarterOffset): ... +class QuarterBegin(QuarterOffset): ... +class MonthOffset(SingleConstructorOffset): ... +class MonthEnd(MonthOffset): ... +class MonthBegin(MonthOffset): ... +class BusinessMonthEnd(MonthOffset): ... +class BusinessMonthBegin(MonthOffset): ... + +class SemiMonthOffset(SingleConstructorOffset): + def __init__( + self, n: int = ..., normalize: bool = ..., day_of_month: int | None = ... + ) -> None: ... + +class SemiMonthEnd(SemiMonthOffset): ... +class SemiMonthBegin(SemiMonthOffset): ... + +class Week(SingleConstructorOffset): + def __init__( + self, n: int = ..., normalize: bool = ..., weekday: int | None = ... + ) -> None: ... + +class WeekOfMonth(WeekOfMonthMixin): ... +class LastWeekOfMonth(WeekOfMonthMixin): ... + +class FY5253Mixin(SingleConstructorOffset): + def __init__( + self, + n: int = ..., + normalize: bool = ..., + weekday: int = ..., + startingMonth: int = ..., + variation: str = ..., + ) -> None: ... + +class FY5253(FY5253Mixin): ... +class FY5253Quarter(FY5253Mixin): ... +class Easter(SingleConstructorOffset): ... + +class _CustomBusinessMonth(BusinessMixin): + def __init__( + self, + n: int = ..., + normalize: bool = ..., + offset: timedelta = ..., + holidays: None | list = ..., + ): ... + +class CustomBusinessDay(BusinessDay): + def __init__( + self, + n: int = ..., + normalize: bool = ..., + offset: timedelta = ..., + weekmask: str = ..., + ): ... + +class CustomBusinessHour(BusinessHour): + def __init__( + self, + n: int = ..., + normalize: bool = ..., + start: str = ..., + end: str = ..., + offset: timedelta = ..., + holidays: None | list = ..., + ): ... + +class CustomBusinessMonthEnd(_CustomBusinessMonth): ... +class CustomBusinessMonthBegin(_CustomBusinessMonth): ... +class DateOffset(RelativeDeltaOffset): ... + +BDay = BusinessDay +BMonthEnd = BusinessMonthEnd +BMonthBegin = BusinessMonthBegin +CBMonthEnd = CustomBusinessMonthEnd +CBMonthBegin = CustomBusinessMonthBegin +CDay = CustomBusinessDay + +def roll_qtrday( + other: datetime, n: int, month: int, day_opt: str, modby: int +) -> int: ... + +INVALID_FREQ_ERR_MSG: Literal["Invalid frequency: {0}"] + +def shift_months( + dtindex: npt.NDArray[np.int64], months: int, day_opt: str | None = ... +) -> npt.NDArray[np.int64]: ... + +_offset_map: dict[str, BaseOffset] diff --git a/pandas/_libs/tslibs/period.pyi b/pandas/_libs/tslibs/period.pyi index 2f60df0ad888e..7d6e31f339e29 100644 --- a/pandas/_libs/tslibs/period.pyi +++ b/pandas/_libs/tslibs/period.pyi @@ -1,3 +1,4 @@ +from datetime import timedelta from typing import Literal import numpy as np @@ -33,7 +34,7 @@ def get_period_field_arr( ) -> npt.NDArray[np.int64]: ... def from_ordinals( values: npt.NDArray[np.int64], # const int64_t[:] - freq: Frequency, + freq: timedelta | BaseOffset | str, ) -> npt.NDArray[np.int64]: ... def extract_ordinals( values: npt.NDArray[np.object_], @@ -59,7 +60,7 @@ class Period: def __new__( # type: ignore[misc] cls, value=..., - freq: int | str | None = ..., + freq: int | str | BaseOffset | None = ..., ordinal: int | None = ..., year: int | None = ..., month: int | None = ..., @@ -82,7 +83,7 @@ class Period: how: str = ..., tz: Timezone | None = ..., ) -> Timestamp: ... - def asfreq(self, freq: str, how: str = ...) -> Period: ... + def asfreq(self, freq: str | BaseOffset, how: str = ...) -> Period: ... @property def freqstr(self) -> str: ... @property diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 8c331b13f9735..40a83afee8eab 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1795,7 +1795,7 @@ cdef class _Period(PeriodMixin): Parameters ---------- - freq : str + freq : str, BaseOffset The desired frequency. how : {'E', 'S', 'end', 'start'}, default 'end' Start or end of the timespan. diff --git a/pandas/_libs/tslibs/timedeltas.pyi b/pandas/_libs/tslibs/timedeltas.pyi index d8369f0cc90f9..7bb0fccd2780b 100644 --- a/pandas/_libs/tslibs/timedeltas.pyi +++ b/pandas/_libs/tslibs/timedeltas.pyi @@ -67,10 +67,25 @@ class Timedelta(timedelta): def __abs__(self) -> timedelta: ... def __mul__(self, other: float) -> timedelta: ... def __rmul__(self, other: float) -> timedelta: ... - @overload + # error: Signature of "__floordiv__" incompatible with supertype "timedelta" + @overload # type: ignore[override] def __floordiv__(self, other: timedelta) -> int: ... @overload - def __floordiv__(self, other: int) -> timedelta: ... + def __floordiv__(self, other: int | float) -> timedelta: ... + @overload + def __floordiv__( + self, other: npt.NDArray[np.timedelta64] + ) -> npt.NDArray[np.intp]: ... + @overload + def __floordiv__( + self, other: npt.NDArray[np.number] + ) -> npt.NDArray[np.timedelta64] | Timedelta: ... + @overload + def __rfloordiv__(self, other: timedelta | str) -> int: ... + @overload + def __rfloordiv__(self, other: None | NaTType) -> NaTType: ... + @overload + def __rfloordiv__(self, other: np.ndarray) -> npt.NDArray[np.timedelta64]: ... @overload def __truediv__(self, other: timedelta) -> float: ... @overload diff --git a/pandas/_libs/tslibs/timestamps.pyi b/pandas/_libs/tslibs/timestamps.pyi index ecddd83322bbf..6b808355eceaf 100644 --- a/pandas/_libs/tslibs/timestamps.pyi +++ b/pandas/_libs/tslibs/timestamps.pyi @@ -18,6 +18,7 @@ from pandas._libs.tslibs import ( BaseOffset, NaTType, Period, + Tick, Timedelta, ) @@ -139,14 +140,14 @@ class Timestamp(datetime): @overload # type: ignore[override] def __add__(self, other: np.ndarray) -> np.ndarray: ... @overload - # TODO: other can also be Tick (but it cannot be resolved) - def __add__(self: _DatetimeT, other: timedelta | np.timedelta64) -> _DatetimeT: ... + def __add__( + self: _DatetimeT, other: timedelta | np.timedelta64 | Tick + ) -> _DatetimeT: ... def __radd__(self: _DatetimeT, other: timedelta) -> _DatetimeT: ... @overload # type: ignore def __sub__(self, other: datetime) -> timedelta: ... @overload - # TODO: other can also be Tick (but it cannot be resolved) - def __sub__(self, other: timedelta | np.timedelta64) -> datetime: ... + def __sub__(self, other: timedelta | np.timedelta64 | Tick) -> datetime: ... def __hash__(self) -> int: ... def weekday(self) -> int: ... def isoweekday(self) -> int: ... diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 483878706db75..a0d9fab764811 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -130,6 +130,7 @@ from pandas.core.arrays import ( DatetimeArray, + PeriodArray, TimedeltaArray, ) @@ -1325,7 +1326,7 @@ def __add__(self, other): elif is_integer_dtype(other_dtype): if not is_period_dtype(self.dtype): raise integer_op_not_supported(self) - result = self._addsub_int_array(other, operator.add) + result = cast("PeriodArray", self)._addsub_int_array(other, operator.add) else: # Includes Categorical, other ExtensionArrays # For PeriodDtype, if self is a TimedeltaArray and other is a @@ -1385,7 +1386,7 @@ def __sub__(self, other): elif is_integer_dtype(other_dtype): if not is_period_dtype(self.dtype): raise integer_op_not_supported(self) - result = self._addsub_int_array(other, operator.sub) + result = cast("PeriodArray", self)._addsub_int_array(other, operator.sub) else: # Includes ExtensionArrays, float_dtype return NotImplemented diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 88f6c5e5024c8..c0d913794c5f9 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -606,7 +606,8 @@ def asfreq(self, freq=None, how: str = "E") -> PeriodArray: freq = Period._maybe_convert_freq(freq) - base1 = self.freq._period_dtype_code + # error: "BaseOffset" has no attribute "_period_dtype_code" + base1 = self.freq._period_dtype_code # type: ignore[attr-defined] base2 = freq._period_dtype_code asi8 = self.asi8 @@ -1049,7 +1050,9 @@ def period_array( if is_integer_dtype(arrdata.dtype): arr = arrdata.astype(np.int64, copy=False) - ordinals = libperiod.from_ordinals(arr, freq) + # error: Argument 2 to "from_ordinals" has incompatible type "Union[str, + # Tick, None]"; expected "Union[timedelta, BaseOffset, str]" + ordinals = libperiod.from_ordinals(arr, freq) # type: ignore[arg-type] return PeriodArray(ordinals, dtype=dtype) data = ensure_object(arrdata) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 2d17536c07a6e..255b6e732ed9a 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -1,7 +1,10 @@ from __future__ import annotations from datetime import timedelta -from typing import TYPE_CHECKING +from typing import ( + TYPE_CHECKING, + cast, +) import numpy as np @@ -535,7 +538,9 @@ def __truediv__(self, other): if isinstance(other, self._recognized_scalars): other = Timedelta(other) - if other is NaT: + # mypy assumes that __new__ returns an instance of the class + # github.com/python/mypy/issues/1020 + if cast("Timedelta | NaTType", other) is NaT: # specifically timedelta64-NaT result = np.empty(self.shape, dtype=np.float64) result.fill(np.nan) @@ -569,8 +574,8 @@ def __truediv__(self, other): # on NaT srav = self.ravel() orav = other.ravel() - result = [srav[n] / orav[n] for n in range(len(srav))] - result = np.array(result).reshape(self.shape) + result_list = [srav[n] / orav[n] for n in range(len(srav))] + result = np.array(result_list).reshape(self.shape) # We need to do dtype inference in order to keep DataFrame ops # behavior consistent with Series behavior @@ -584,7 +589,10 @@ def __truediv__(self, other): # GH#39750 this occurs when result is all-NaT, in which case # we want to interpret these NaTs as td64. # We construct an all-td64NaT result. - result = self * np.nan + # error: Incompatible types in assignment (expression has type + # "TimedeltaArray", variable has type "ndarray[Any, + # dtype[floating[_64Bit]]]") + result = self * np.nan # type: ignore[assignment] return result @@ -597,7 +605,9 @@ def __rtruediv__(self, other): # X / timedelta is defined only for timedelta-like X if isinstance(other, self._recognized_scalars): other = Timedelta(other) - if other is NaT: + # mypy assumes that __new__ returns an instance of the class + # github.com/python/mypy/issues/1020 + if cast("Timedelta | NaTType", other) is NaT: # specifically timedelta64-NaT result = np.empty(self.shape, dtype=np.float64) result.fill(np.nan) @@ -626,8 +636,8 @@ def __rtruediv__(self, other): # Note: unlike in __truediv__, we do not _need_ to do type # inference on the result. It does not raise, a numeric array # is returned. GH#23829 - result = [other[n] / self[n] for n in range(len(self))] - return np.array(result) + result_list = [other[n] / self[n] for n in range(len(self))] + return np.array(result_list) else: raise TypeError( @@ -640,15 +650,16 @@ def __floordiv__(self, other): if is_scalar(other): if isinstance(other, self._recognized_scalars): other = Timedelta(other) - if other is NaT: + # mypy assumes that __new__ returns an instance of the class + # github.com/python/mypy/issues/1020 + if cast("Timedelta | NaTType", other) is NaT: # treat this specifically as timedelta-NaT result = np.empty(self.shape, dtype=np.float64) result.fill(np.nan) return result # dispatch to Timedelta implementation - result = other.__rfloordiv__(self._ndarray) - return result + return other.__rfloordiv__(self._ndarray) # at this point we should only have numeric scalars; anything # else will raise @@ -716,15 +727,16 @@ def __rfloordiv__(self, other): if is_scalar(other): if isinstance(other, self._recognized_scalars): other = Timedelta(other) - if other is NaT: + # mypy assumes that __new__ returns an instance of the class + # github.com/python/mypy/issues/1020 + if cast("Timedelta | NaTType", other) is NaT: # treat this specifically as timedelta-NaT result = np.empty(self.shape, dtype=np.float64) result.fill(np.nan) return result # dispatch to Timedelta implementation - result = other.__floordiv__(self._ndarray) - return result + return other.__floordiv__(self._ndarray) raise TypeError( f"Cannot divide {type(other).__name__} by {type(self).__name__}" diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 006f53ba06c71..e6f4b352aea69 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -321,7 +321,9 @@ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool: freq = dtype.freq own_freq = self.freq return ( - freq._period_dtype_code == own_freq._period_dtype_code + freq._period_dtype_code + # error: "BaseOffset" has no attribute "_period_dtype_code" + == own_freq._period_dtype_code # type: ignore[attr-defined] and freq.n == own_freq.n ) @@ -461,7 +463,10 @@ def get_loc(self, key, method=None, tolerance=None): kfreq = key.freq if not ( sfreq.n == kfreq.n - and sfreq._period_dtype_code == kfreq._period_dtype_code + # error: "BaseOffset" has no attribute "_period_dtype_code" + and sfreq._period_dtype_code # type: ignore[attr-defined] + # error: "BaseOffset" has no attribute "_period_dtype_code" + == kfreq._period_dtype_code # type: ignore[attr-defined] ): # GH#42247 For the subset of DateOffsets that can be Period freqs, # checking these two attributes is sufficient to check equality, diff --git a/pandas/core/ops/array_ops.py b/pandas/core/ops/array_ops.py index 84b8ce6bdcb31..2caaadbc05cff 100644 --- a/pandas/core/ops/array_ops.py +++ b/pandas/core/ops/array_ops.py @@ -219,7 +219,9 @@ def arithmetic_op(left: ArrayLike, right: Any, op): # (https://github.com/pandas-dev/pandas/issues/41165) _bool_arith_check(op, left, right) - res_values = _na_arithmetic_op(left, right, op) + # error: Argument 1 to "_na_arithmetic_op" has incompatible type + # "Union[ExtensionArray, ndarray[Any, Any]]"; expected "ndarray[Any, Any]" + res_values = _na_arithmetic_op(left, right, op) # type: ignore[arg-type] return res_values diff --git a/pandas/core/window/rolling.py b/pandas/core/window/rolling.py index 0f7e624967e9b..02f0b521a0953 100644 --- a/pandas/core/window/rolling.py +++ b/pandas/core/window/rolling.py @@ -1672,9 +1672,15 @@ def _validate(self): "compatible with a datetimelike index" ) from err if isinstance(self._on, PeriodIndex): - self._win_freq_i8 = freq.nanos / (self._on.freq.nanos / self._on.freq.n) + # error: Incompatible types in assignment (expression has type "float", + # variable has type "None") + self._win_freq_i8 = freq.nanos / ( # type: ignore[assignment] + self._on.freq.nanos / self._on.freq.n + ) else: - self._win_freq_i8 = freq.nanos + # error: Incompatible types in assignment (expression has type "int", + # variable has type "None") + self._win_freq_i8 = freq.nanos # type: ignore[assignment] # min_periods must be an integer if self.min_periods is None: diff --git a/pandas/plotting/_matplotlib/converter.py b/pandas/plotting/_matplotlib/converter.py index 8216374732aff..6c40770b4fac5 100644 --- a/pandas/plotting/_matplotlib/converter.py +++ b/pandas/plotting/_matplotlib/converter.py @@ -531,7 +531,8 @@ def has_level_label(label_flags: np.ndarray, vmin: float) -> bool: def _daily_finder(vmin, vmax, freq: BaseOffset): - dtype_code = freq._period_dtype_code + # error: "BaseOffset" has no attribute "_period_dtype_code" + dtype_code = freq._period_dtype_code # type: ignore[attr-defined] periodsperday = -1 @@ -895,7 +896,8 @@ def _annual_finder(vmin, vmax, freq): def get_finder(freq: BaseOffset): - dtype_code = freq._period_dtype_code + # error: "BaseOffset" has no attribute "_period_dtype_code" + dtype_code = freq._period_dtype_code # type: ignore[attr-defined] fgroup = (dtype_code // 1000) * 1000 fgroup = FreqGroup(fgroup) diff --git a/pandas/plotting/_matplotlib/timeseries.py b/pandas/plotting/_matplotlib/timeseries.py index 3cd312b06020d..303266ae410de 100644 --- a/pandas/plotting/_matplotlib/timeseries.py +++ b/pandas/plotting/_matplotlib/timeseries.py @@ -223,18 +223,19 @@ def use_dynamic_x(ax: Axes, data: DataFrame | Series) -> bool: if freq is None: return False - freq = _get_period_alias(freq) + freq_str = _get_period_alias(freq) - if freq is None: + if freq_str is None: return False # FIXME: hack this for 0.10.1, creating more technical debt...sigh if isinstance(data.index, ABCDatetimeIndex): - base = to_offset(freq)._period_dtype_code + # error: "BaseOffset" has no attribute "_period_dtype_code" + base = to_offset(freq_str)._period_dtype_code # type: ignore[attr-defined] x = data.index if base <= FreqGroup.FR_DAY.value: return x[:1].is_normalized - return Period(x[0], freq).to_timestamp().tz_localize(x.tz) == x[0] + return Period(x[0], freq_str).to_timestamp().tz_localize(x.tz) == x[0] return True @@ -256,7 +257,7 @@ def maybe_convert_index(ax: Axes, data): # tsplot converts automatically, but don't want to convert index # over and over for DataFrames if isinstance(data.index, (ABCDatetimeIndex, ABCPeriodIndex)): - freq = data.index.freq + freq: str | BaseOffset | None = data.index.freq if freq is None: # We only get here for DatetimeIndex @@ -270,12 +271,12 @@ def maybe_convert_index(ax: Axes, data): if freq is None: raise ValueError("Could not get frequency alias for plotting") - freq = _get_period_alias(freq) + freq_str = _get_period_alias(freq) if isinstance(data.index, ABCDatetimeIndex): - data = data.tz_localize(None).to_period(freq=freq) + data = data.tz_localize(None).to_period(freq=freq_str) elif isinstance(data.index, ABCPeriodIndex): - data.index = data.index.asfreq(freq=freq) + data.index = data.index.asfreq(freq=freq_str) return data diff --git a/pandas/tests/tseries/offsets/common.py b/pandas/tests/tseries/offsets/common.py index d8e98bb0c6876..c12130544e6b2 100644 --- a/pandas/tests/tseries/offsets/common.py +++ b/pandas/tests/tseries/offsets/common.py @@ -14,6 +14,7 @@ ) from pandas._libs.tslibs.offsets import ( FY5253, + BaseOffset, BusinessHour, CustomBusinessHour, DateOffset, @@ -59,7 +60,7 @@ class WeekDay: class Base: - _offset: type[DateOffset] | None = None + _offset: type[BaseOffset] | None = None d = Timestamp(datetime(2008, 1, 2)) timezones = [ diff --git a/pandas/tests/tseries/offsets/test_business_day.py b/pandas/tests/tseries/offsets/test_business_day.py index 58d3985913994..f23772dd0bd7c 100644 --- a/pandas/tests/tseries/offsets/test_business_day.py +++ b/pandas/tests/tseries/offsets/test_business_day.py @@ -1,6 +1,8 @@ """ Tests for offsets.BDay """ +from __future__ import annotations + from datetime import ( date, datetime, @@ -26,13 +28,12 @@ assert_is_on_offset, assert_offset_equal, ) -from pandas.tests.tseries.offsets.test_offsets import _ApplyCases from pandas.tseries import offsets as offsets class TestBusinessDay(Base): - _offset = BDay + _offset: type[BDay] = BDay def setup_method(self): self.d = datetime(2008, 1, 1) @@ -136,7 +137,7 @@ def test_is_on_offset(self): for offset, d, expected in tests: assert_is_on_offset(offset, d, expected) - apply_cases: _ApplyCases = [ + apply_cases: list[tuple[int, dict[datetime, datetime]]] = [ ( 1, { diff --git a/pandas/tests/tseries/offsets/test_business_hour.py b/pandas/tests/tseries/offsets/test_business_hour.py index 314308c7e06f0..392991009a449 100644 --- a/pandas/tests/tseries/offsets/test_business_hour.py +++ b/pandas/tests/tseries/offsets/test_business_hour.py @@ -1,6 +1,8 @@ """ Tests for offsets.BusinessHour """ +from __future__ import annotations + from datetime import ( datetime, time as dt_time, @@ -30,7 +32,7 @@ class TestBusinessHour(Base): - _offset = BusinessHour + _offset: type[BusinessHour] = BusinessHour def setup_method(self): self.d = datetime(2014, 7, 1, 10, 00) diff --git a/pandas/tests/tseries/offsets/test_business_month.py b/pandas/tests/tseries/offsets/test_business_month.py index bb2049fd35489..db8aaab98a379 100644 --- a/pandas/tests/tseries/offsets/test_business_month.py +++ b/pandas/tests/tseries/offsets/test_business_month.py @@ -3,10 +3,14 @@ - BMonthBegin - BMonthEnd """ +from __future__ import annotations + from datetime import datetime import pytest +from pandas._libs.tslibs.offsets import MonthOffset + import pandas as pd from pandas.tests.tseries.offsets.common import ( Base, @@ -44,7 +48,7 @@ def test_apply_index(cls, n): class TestBMonthBegin(Base): - _offset = BMonthBegin + _offset: type[MonthOffset] = BMonthBegin def test_offsets_compare_equal(self): # root cause of #456 @@ -132,7 +136,7 @@ def test_is_on_offset(self, case): class TestBMonthEnd(Base): - _offset = BMonthEnd + _offset: type[MonthOffset] = BMonthEnd def test_normalize(self): dt = datetime(2007, 1, 1, 3) diff --git a/pandas/tests/tseries/offsets/test_business_quarter.py b/pandas/tests/tseries/offsets/test_business_quarter.py index b928b47d30a0d..5c50e080f71a8 100644 --- a/pandas/tests/tseries/offsets/test_business_quarter.py +++ b/pandas/tests/tseries/offsets/test_business_quarter.py @@ -3,10 +3,14 @@ - BQuarterBegin - BQuarterEnd """ +from __future__ import annotations + from datetime import datetime import pytest +from pandas._libs.tslibs.offsets import QuarterOffset + from pandas.tests.tseries.offsets.common import ( Base, assert_is_on_offset, @@ -44,7 +48,7 @@ def test_on_offset(offset): class TestBQuarterBegin(Base): - _offset = BQuarterBegin + _offset: type[QuarterOffset] = BQuarterBegin def test_repr(self): expected = "" @@ -169,7 +173,7 @@ def test_offset(self, case): class TestBQuarterEnd(Base): - _offset = BQuarterEnd + _offset: type[QuarterOffset] = BQuarterEnd def test_repr(self): expected = "" diff --git a/pandas/tests/tseries/offsets/test_business_year.py b/pandas/tests/tseries/offsets/test_business_year.py index d531a586c5db2..21778acd78bfd 100644 --- a/pandas/tests/tseries/offsets/test_business_year.py +++ b/pandas/tests/tseries/offsets/test_business_year.py @@ -3,10 +3,14 @@ - BYearBegin - BYearEnd """ +from __future__ import annotations + from datetime import datetime import pytest +from pandas._libs.tslibs.offsets import YearOffset + from pandas.tests.tseries.offsets.common import ( Base, assert_is_on_offset, @@ -20,7 +24,7 @@ class TestBYearBegin(Base): - _offset = BYearBegin + _offset: type[YearOffset] = BYearBegin def test_misspecified(self): msg = "Month must go from 1 to 12" @@ -93,7 +97,7 @@ def test_offset(self, case): class TestBYearEnd(Base): - _offset = BYearEnd + _offset: type[YearOffset] = BYearEnd offset_cases = [] offset_cases.append( @@ -166,7 +170,7 @@ def test_is_on_offset(self, case): class TestBYearEndLagged(Base): - _offset = BYearEnd + _offset: type[YearOffset] = BYearEnd def test_bad_month_fail(self): msg = "Month must go from 1 to 12" diff --git a/pandas/tests/tseries/offsets/test_custom_business_hour.py b/pandas/tests/tseries/offsets/test_custom_business_hour.py index 3fc20df2d930b..9a2d24f1d0307 100644 --- a/pandas/tests/tseries/offsets/test_custom_business_hour.py +++ b/pandas/tests/tseries/offsets/test_custom_business_hour.py @@ -1,6 +1,8 @@ """ Tests for offsets.CustomBusinessHour """ +from __future__ import annotations + from datetime import datetime import numpy as np @@ -23,7 +25,7 @@ class TestCustomBusinessHour(Base): - _offset = CustomBusinessHour + _offset: type[CustomBusinessHour] = CustomBusinessHour holidays = ["2014-06-27", datetime(2014, 6, 30), np.datetime64("2014-07-02")] def setup_method(self): diff --git a/pandas/tests/tseries/offsets/test_custom_business_month.py b/pandas/tests/tseries/offsets/test_custom_business_month.py index 935213229a65a..c0f9b3cb5be87 100644 --- a/pandas/tests/tseries/offsets/test_custom_business_month.py +++ b/pandas/tests/tseries/offsets/test_custom_business_month.py @@ -4,6 +4,8 @@ - CustomBusinessMonthBegin - CustomBusinessMonthEnd """ +from __future__ import annotations + from datetime import ( date, datetime, @@ -17,6 +19,7 @@ CBMonthBegin, CBMonthEnd, CDay, + _CustomBusinessMonth, ) from pandas import ( @@ -66,7 +69,7 @@ def test_copy(self): class TestCustomBusinessMonthBegin(CustomBusinessMonthBase, Base): - _offset = CBMonthBegin + _offset: type[_CustomBusinessMonth] = CBMonthBegin def test_different_normalize_equals(self): # GH#21404 changed __eq__ to return False when `normalize` does not match @@ -256,7 +259,7 @@ def test_apply_with_extra_offset(self, case): class TestCustomBusinessMonthEnd(CustomBusinessMonthBase, Base): - _offset = CBMonthEnd + _offset: type[_CustomBusinessMonth] = CBMonthEnd def test_different_normalize_equals(self): # GH#21404 changed __eq__ to return False when `normalize` does not match diff --git a/pandas/tests/tseries/offsets/test_month.py b/pandas/tests/tseries/offsets/test_month.py index 00b9d7e186a59..305b57cea17b1 100644 --- a/pandas/tests/tseries/offsets/test_month.py +++ b/pandas/tests/tseries/offsets/test_month.py @@ -5,6 +5,8 @@ - MonthBegin - MonthEnd """ +from __future__ import annotations + from datetime import datetime import pytest @@ -13,8 +15,10 @@ from pandas._libs.tslibs.offsets import ( MonthBegin, MonthEnd, + MonthOffset, SemiMonthBegin, SemiMonthEnd, + SemiMonthOffset, ) from pandas import ( @@ -31,7 +35,7 @@ class TestSemiMonthEnd(Base): - _offset = SemiMonthEnd + _offset: type[SemiMonthOffset] = SemiMonthEnd offset1 = _offset() offset2 = _offset(2) @@ -295,7 +299,7 @@ def test_vectorized_offset_addition(self, klass): class TestSemiMonthBegin(Base): - _offset = SemiMonthBegin + _offset: type[SemiMonthOffset] = SemiMonthBegin offset1 = _offset() offset2 = _offset(2) @@ -535,7 +539,7 @@ def test_vectorized_offset_addition(self, klass): class TestMonthBegin(Base): - _offset = MonthBegin + _offset: type[MonthOffset] = MonthBegin offset_cases = [] # NOTE: I'm not entirely happy with the logic here for Begin -ss @@ -600,7 +604,7 @@ def test_offset(self, case): class TestMonthEnd(Base): - _offset = MonthEnd + _offset: type[MonthOffset] = MonthEnd def test_day_of_month(self): dt = datetime(2007, 1, 1) diff --git a/pandas/tests/tseries/offsets/test_quarter.py b/pandas/tests/tseries/offsets/test_quarter.py index e076fb9f4d53b..9063b71cdece4 100644 --- a/pandas/tests/tseries/offsets/test_quarter.py +++ b/pandas/tests/tseries/offsets/test_quarter.py @@ -3,6 +3,8 @@ - QuarterBegin - QuarterEnd """ +from __future__ import annotations + from datetime import datetime import pytest @@ -152,7 +154,7 @@ def test_offset(self, case): class TestQuarterEnd(Base): - _offset = QuarterEnd + _offset: type[QuarterEnd] = QuarterEnd def test_repr(self): expected = "" diff --git a/pandas/tests/tseries/offsets/test_week.py b/pandas/tests/tseries/offsets/test_week.py index be574fd963eff..fcf11899d8a56 100644 --- a/pandas/tests/tseries/offsets/test_week.py +++ b/pandas/tests/tseries/offsets/test_week.py @@ -4,6 +4,8 @@ - WeekOfMonth - LastWeekOfMonth """ +from __future__ import annotations + from datetime import ( datetime, timedelta, @@ -17,6 +19,7 @@ LastWeekOfMonth, Week, WeekOfMonth, + WeekOfMonthMixin, ) from pandas.tests.tseries.offsets.common import ( @@ -28,7 +31,7 @@ class TestWeek(Base): - _offset = Week + _offset: type[Week] = Week d = Timestamp(datetime(2008, 1, 2)) offset1 = _offset() offset2 = _offset(2) @@ -151,7 +154,7 @@ def test_week_add_invalid(self): class TestWeekOfMonth(Base): - _offset = WeekOfMonth + _offset: type[WeekOfMonthMixin] = WeekOfMonth offset1 = _offset() offset2 = _offset(2) @@ -267,7 +270,7 @@ def test_is_on_offset_nanoseconds(self, n, week, date, tz): class TestLastWeekOfMonth(Base): - _offset = LastWeekOfMonth + _offset: type[WeekOfMonthMixin] = LastWeekOfMonth offset1 = _offset() offset2 = _offset(2) diff --git a/pandas/tests/tseries/offsets/test_year.py b/pandas/tests/tseries/offsets/test_year.py index 85994adb6f19d..55c3057b23cb0 100644 --- a/pandas/tests/tseries/offsets/test_year.py +++ b/pandas/tests/tseries/offsets/test_year.py @@ -3,10 +3,14 @@ - YearBegin - YearEnd """ +from __future__ import annotations + from datetime import datetime import pytest +from pandas._libs.tslibs.offsets import YearOffset + from pandas.tests.tseries.offsets.common import ( Base, assert_is_on_offset, @@ -20,7 +24,7 @@ class TestYearBegin(Base): - _offset = YearBegin + _offset: type[YearOffset] = YearBegin def test_misspecified(self): with pytest.raises(ValueError, match="Month must go from 1 to 12"): @@ -174,7 +178,7 @@ def test_is_on_offset(self, case): class TestYearEnd(Base): - _offset = YearEnd + _offset: type[YearOffset] = YearEnd def test_misspecified(self): with pytest.raises(ValueError, match="Month must go from 1 to 12"): diff --git a/pandas/tests/tslibs/test_period_asfreq.py b/pandas/tests/tslibs/test_period_asfreq.py index 84c3d07470c38..7c9047b3e7c60 100644 --- a/pandas/tests/tslibs/test_period_asfreq.py +++ b/pandas/tests/tslibs/test_period_asfreq.py @@ -16,7 +16,8 @@ def get_freq_code(freqstr: str) -> int: off = to_offset(freqstr) - code = off._period_dtype_code + # error: "BaseOffset" has no attribute "_period_dtype_code" + code = off._period_dtype_code # type: ignore[attr-defined] return code diff --git a/pandas/tseries/frequencies.py b/pandas/tseries/frequencies.py index 3807189f7681b..1088b3b1a79ea 100644 --- a/pandas/tseries/frequencies.py +++ b/pandas/tseries/frequencies.py @@ -21,6 +21,7 @@ month_position_check, ) from pandas._libs.tslibs.offsets import ( # noqa:F401 + BaseOffset, DateOffset, Day, _get_offset, @@ -103,7 +104,7 @@ def get_period_alias(offset_str: str) -> str | None: return _offset_to_period_map.get(offset_str, None) -def get_offset(name: str) -> DateOffset: +def get_offset(name: str) -> BaseOffset: """ Return DateOffset object associated with rule name.