From 1f7a03bb4476d79087c04a9b159b60c6a311dbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Tue, 9 Aug 2022 12:38:19 -0400 Subject: [PATCH 01/10] TYP: avoid inherit_names for DatetimeIndexOpsMixin --- pandas/core/arrays/datetimelike.py | 32 ++++++++++----- pandas/core/arrays/timedeltas.py | 7 +++- pandas/core/indexes/datetimelike.py | 63 ++++++++++++++++++++++------- pandas/core/indexes/datetimes.py | 1 - pandas/core/indexes/period.py | 6 +-- 5 files changed, 80 insertions(+), 29 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 2c070499308a7..8f274afe82177 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -552,7 +552,12 @@ def _concat_same_type( if obj.freq is not None and all(x.freq == obj.freq for x in to_concat): pairs = zip(to_concat[:-1], to_concat[1:]) - if all(pair[0][-1] + obj.freq == pair[1][0] for pair in pairs): + # error: No overload variant of "__radd__" of "BaseOffset" matches + # argument type "NaTType" + if all( + pair[0][-1] + obj.freq == pair[1][0] # type: ignore[operator] + for pair in pairs + ): new_freq = obj.freq new_obj._freq = new_freq @@ -925,7 +930,7 @@ def _maybe_mask_results( # Frequency Properties/Methods @property - def freq(self): + def freq(self) -> BaseOffset | None: """ Return the frequency object if it is set, otherwise None. """ @@ -1219,7 +1224,10 @@ def _sub_period(self, other: Period) -> npt.NDArray[np.object_]: new_i8_data = checked_add_with_arr( self.asi8, -other.ordinal, arr_mask=self._isnan ) - new_data = np.array([self.freq.base * x for x in new_i8_data]) + # error: Item "None" of "Optional[BaseOffset]" has no attribute "base" + new_data = np.array( + [self.freq.base * x for x in new_i8_data] # type: ignore[union-attr] + ) if self._hasna: new_data[self._isnan] = NaT @@ -1417,8 +1425,10 @@ def _time_shift( if self.freq is None: raise NullFrequencyError("Cannot shift with no freq") - start = self[0] + periods * self.freq - end = self[-1] + periods * self.freq + # error: No overload variant of "__radd__" of "BaseOffset" matches + # argument type "NaTType" + start = self[0] + periods * self.freq # type: ignore[operator] + end = self[-1] + periods * self.freq # type: ignore[operator] # Note: in the DatetimeTZ case, _generate_range will infer the # appropriate timezone from `start` and `end`, so tz does not need @@ -1446,8 +1456,9 @@ def __add__(self, other): # as is_integer returns True for these if not is_period_dtype(self.dtype): raise integer_op_not_supported(self) + # error: Item "None" of "Optional[BaseOffset]" has no attribute "n" result = cast("PeriodArray", self)._addsub_int_array_or_scalar( - other * self.freq.n, operator.add + other * self.freq.n, operator.add # type: ignore[union-attr] ) # array-like others @@ -1463,8 +1474,9 @@ def __add__(self, other): elif is_integer_dtype(other_dtype): if not is_period_dtype(self.dtype): raise integer_op_not_supported(self) + # error: Item "None" of "Optional[BaseOffset]" has no attribute "n" result = cast("PeriodArray", self)._addsub_int_array_or_scalar( - other * self.freq.n, operator.add + other * self.freq.n, operator.add # type: ignore[union-attr] ) else: # Includes Categorical, other ExtensionArrays @@ -1504,8 +1516,9 @@ def __sub__(self, other): # as is_integer returns True for these if not is_period_dtype(self.dtype): raise integer_op_not_supported(self) + # error: Item "None" of "Optional[BaseOffset]" has no attribute "n" result = cast("PeriodArray", self)._addsub_int_array_or_scalar( - other * self.freq.n, operator.sub + other * self.freq.n, operator.sub # type: ignore[union-attr] ) elif isinstance(other, Period): @@ -1527,8 +1540,9 @@ def __sub__(self, other): elif is_integer_dtype(other_dtype): if not is_period_dtype(self.dtype): raise integer_op_not_supported(self) + # error: Item "None" of "Optional[BaseOffset]" has no attribute "n" result = cast("PeriodArray", self)._addsub_int_array_or_scalar( - other * self.freq.n, operator.sub + other * self.freq.n, operator.sub # type: ignore[union-attr] ) else: # Includes ExtensionArrays, float_dtype diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 4011f29855949..d5174976a2e98 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -446,7 +446,8 @@ def __truediv__(self, other): freq = None if self.freq is not None: # Tick division is not implemented, so operate on Timedelta - freq = self.freq.delta / other + # error: "BaseOffset" has no attribute "delta" + freq = self.freq.delta / other # type: ignore[attr-defined] freq = to_offset(freq) return type(self)._simple_new(result, dtype=result.dtype, freq=freq) @@ -926,7 +927,9 @@ def sequence_to_td64ns( data = np.array(data, copy=copy) assert data.dtype == "m8[ns]", data - return data, inferred_freq + # error: Incompatible return value type (got "Tuple[Any, Optional[BaseOffset]]", + # expected "Tuple[ndarray[Any, Any], Optional[Tick]]") + return data, inferred_freq # type: ignore[return-value] def ints_to_td64ns(data, unit="ns"): diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 84955d5137383..382abc888474c 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -30,6 +30,7 @@ parsing, to_offset, ) +from pandas._typing import npt from pandas.compat.numpy import function as nv from pandas.util._decorators import ( Appender, @@ -59,10 +60,7 @@ Index, _index_shared_docs, ) -from pandas.core.indexes.extension import ( - NDArrayBackedExtensionIndex, - inherit_names, -) +from pandas.core.indexes.extension import NDArrayBackedExtensionIndex from pandas.core.indexes.range import RangeIndex from pandas.core.tools.timedeltas import to_timedelta @@ -75,12 +73,6 @@ _TDT = TypeVar("_TDT", bound="DatetimeTimedeltaMixin") -@inherit_names( - ["inferred_freq", "_resolution_obj", "resolution"], - DatetimeLikeArrayMixin, - cache=True, -) -@inherit_names(["mean", "asi8", "freq", "freqstr"], DatetimeLikeArrayMixin) class DatetimeIndexOpsMixin(NDArrayBackedExtensionIndex): """ Common ops mixin to support a unified interface datetimelike Index. @@ -89,9 +81,51 @@ class DatetimeIndexOpsMixin(NDArrayBackedExtensionIndex): _is_numeric_dtype = False _can_hold_strings = False _data: DatetimeArray | TimedeltaArray | PeriodArray - freq: BaseOffset | None - freqstr: str | None - _resolution_obj: Resolution + + # ------------------------------------------------------------------------ + + @doc(DatetimeLikeArrayMixin.mean) + def mean(self, *, skipna: bool = True, axis: int | None = 0): + return self._data.mean(skipna=skipna, axis=axis) + + # error: Decorated property not supported + @property # type: ignore[misc] + @doc(DatetimeLikeArrayMixin.asi8) + def asi8(self) -> npt.NDArray[np.int64]: + return self._data.asi8 + + # error: Decorated property not supported + @property # type: ignore[misc] + @doc(DatetimeLikeArrayMixin.freq) + def freq(self) -> BaseOffset | None: + return self._data.freq + + @freq.setter + def freq(self, value) -> None: + # error: Property "freq" defined in "PeriodArray" is read-only + self._data.freq = value # type: ignore[misc] + + # error: Decorated property not supported + @property # type: ignore[misc] + @doc(DatetimeLikeArrayMixin.freqstr) + def freqstr(self) -> str | None: + return self._data.freqstr + + # error: Decorated property not supported + @cache_readonly # type: ignore[misc] + @doc(DatetimeLikeArrayMixin.inferred_freq) + def inferred_freq(self) -> str | None: + return self._data.inferred_freq + + @cache_readonly + def _resolution_obj(self) -> Resolution | None: + return self._data._resolution_obj + + # error: Decorated property not supported + @cache_readonly # type: ignore[misc] + @doc(DatetimeLikeArrayMixin.resolution) + def resolution(self) -> str: + return self._data.resolution # ------------------------------------------------------------------------ @@ -215,7 +249,8 @@ def _summary(self, name=None) -> str: def _can_partial_date_slice(self, reso: Resolution) -> bool: # e.g. test_getitem_setitem_periodindex # History of conversation GH#3452, GH#3931, GH#2369, GH#14826 - return reso > self._resolution_obj + # error: Unsupported left operand type for > ("Resolution") + return reso > self._resolution_obj # type: ignore[operator] # NB: for DTI/PI, not TDI def _parsed_string_to_bounds(self, reso: Resolution, parsed): diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 2625d8c683a0c..edadea182334a 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -261,7 +261,6 @@ def _engine_type(self) -> type[libindex.DatetimeEngine]: return libindex.DatetimeEngine _data: DatetimeArray - inferred_freq: str | None tz: tzinfo | None # -------------------------------------------------------------------- diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index fedcba7aa9644..469a01f46c947 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -167,8 +167,7 @@ def _engine_type(self) -> type[libindex.PeriodEngine]: return libindex.PeriodEngine @cache_readonly - # Signature of "_resolution_obj" incompatible with supertype "DatetimeIndexOpsMixin" - def _resolution_obj(self) -> Resolution: # type: ignore[override] + def _resolution_obj(self) -> Resolution: # for compat with DatetimeIndex return self.dtype._resolution_obj @@ -393,7 +392,8 @@ def is_full(self) -> bool: if not self.is_monotonic_increasing: raise ValueError("Index is not monotonic") values = self.asi8 - return ((values[1:] - values[:-1]) < 2).all() + # error: Incompatible return value type (got "bool_", expected "bool") + return ((values[1:] - values[:-1]) < 2).all() # type: ignore[return-value] @property def inferred_type(self) -> str: From dafc87deec1642b2de02b1320c1774e1d8dcd1fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Fri, 12 Aug 2022 12:12:13 -0400 Subject: [PATCH 02/10] address some of the comments --- pandas/core/arrays/timedeltas.py | 26 ++++++++++++++++++++------ pandas/core/indexes/period.py | 3 +-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index d5174976a2e98..49cc91d1f3ea1 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -36,6 +36,7 @@ npt, ) from pandas.compat.numpy import function as nv +from pandas.util._decorators import doc from pandas.util._validators import validate_endpoints from pandas.core.dtypes.astype import astype_td64_unit_conversion @@ -143,6 +144,23 @@ def _box_func(self, x: np.timedelta64) -> Timedelta | NaTType: return NaT return Timedelta._from_value_and_reso(y, reso=self._reso) + # error: Decorated property not supported + @property # type: ignore[misc] + @doc(dtl.DatetimeLikeArrayMixin.freq) + def freq(self) -> Tick: + # error: Incompatible return value type (got "Optional[BaseOffset]", + # expected "Tick") + return self._freq # type: ignore[return-value] + + @freq.setter + def freq(self, value) -> None: + # python doesn't support super().freq = value (any mypy has some + # issue with the workaround) + # error: overloaded function has no attribute "fset" + super(TimedeltaArray, TimedeltaArray).freq.fset( # type: ignore[attr-defined] + self, value + ) + @property # error: Return type "dtype" of "dtype" incompatible with return type # "ExtensionDtype" in supertype "ExtensionArray" @@ -445,9 +463,7 @@ def __truediv__(self, other): result = self._ndarray / other freq = None if self.freq is not None: - # Tick division is not implemented, so operate on Timedelta - # error: "BaseOffset" has no attribute "delta" - freq = self.freq.delta / other # type: ignore[attr-defined] + freq = self.freq.delta / other freq = to_offset(freq) return type(self)._simple_new(result, dtype=result.dtype, freq=freq) @@ -927,9 +943,7 @@ def sequence_to_td64ns( data = np.array(data, copy=copy) assert data.dtype == "m8[ns]", data - # error: Incompatible return value type (got "Tuple[Any, Optional[BaseOffset]]", - # expected "Tuple[ndarray[Any, Any], Optional[Tick]]") - return data, inferred_freq # type: ignore[return-value] + return data, inferred_freq def ints_to_td64ns(data, unit="ns"): diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 469a01f46c947..e2741aa185051 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -392,8 +392,7 @@ def is_full(self) -> bool: if not self.is_monotonic_increasing: raise ValueError("Index is not monotonic") values = self.asi8 - # error: Incompatible return value type (got "bool_", expected "bool") - return ((values[1:] - values[:-1]) < 2).all() # type: ignore[return-value] + return ((values[1:] - values[:-1]) < 2).all().item() @property def inferred_type(self) -> str: From d3a443074d7318518303a10afdade5ce8005c2c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Sat, 13 Aug 2022 14:39:20 -0400 Subject: [PATCH 03/10] move inferred_freq --- pandas/core/indexes/datetimelike.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 382abc888474c..df60ad28614eb 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -111,12 +111,6 @@ def freq(self, value) -> None: def freqstr(self) -> str | None: return self._data.freqstr - # error: Decorated property not supported - @cache_readonly # type: ignore[misc] - @doc(DatetimeLikeArrayMixin.inferred_freq) - def inferred_freq(self) -> str | None: - return self._data.inferred_freq - @cache_readonly def _resolution_obj(self) -> Resolution | None: return self._data._resolution_obj @@ -438,6 +432,12 @@ def values(self) -> np.ndarray: # NB: For Datetime64TZ this is lossy return self._data._ndarray + # error: Decorated property not supported + @cache_readonly # type: ignore[misc] + @doc(DatetimeLikeArrayMixin.inferred_freq) + def inferred_freq(self) -> str | None: + return self._data.inferred_freq + # -------------------------------------------------------------------- # Set Operation Methods From 40b8d11d6c700ef7469b724f39dd592286430827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Mon, 15 Aug 2022 22:10:06 -0400 Subject: [PATCH 04/10] move freq-setter in DatetimeTimedeltaMixin --- pandas/core/arrays/timedeltas.py | 1 + pandas/core/indexes/datetimelike.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 49cc91d1f3ea1..874976436b663 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -463,6 +463,7 @@ def __truediv__(self, other): result = self._ndarray / other freq = None if self.freq is not None: + # Tick division is not implemented, so operate on Timedelta freq = self.freq.delta / other freq = to_offset(freq) return type(self)._simple_new(result, dtype=result.dtype, freq=freq) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index df60ad28614eb..75497d03cc47b 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -100,11 +100,6 @@ def asi8(self) -> npt.NDArray[np.int64]: def freq(self) -> BaseOffset | None: return self._data.freq - @freq.setter - def freq(self, value) -> None: - # error: Property "freq" defined in "PeriodArray" is read-only - self._data.freq = value # type: ignore[misc] - # error: Decorated property not supported @property # type: ignore[misc] @doc(DatetimeLikeArrayMixin.freqstr) @@ -432,6 +427,17 @@ def values(self) -> np.ndarray: # NB: For Datetime64TZ this is lossy return self._data._ndarray + # error: Decorated property not supported + @property # type: ignore[misc] + @doc(DatetimeLikeArrayMixin.freq) + def freq(self) -> BaseOffset | None: + # needed to define the setter (same as in DatetimeIndexOpsMixin) + return self._data.freq + + @freq.setter + def freq(self, value) -> None: + self._data.freq = value + # error: Decorated property not supported @cache_readonly # type: ignore[misc] @doc(DatetimeLikeArrayMixin.inferred_freq) From 05c7e0fdafa643c078ad35ccb7c3f9954291ecf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Sat, 20 Aug 2022 09:42:43 -0400 Subject: [PATCH 05/10] fix dev test --- pandas/tests/indexes/period/test_freq_attr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/indexes/period/test_freq_attr.py b/pandas/tests/indexes/period/test_freq_attr.py index e1ecffa4982bd..71115929121bc 100644 --- a/pandas/tests/indexes/period/test_freq_attr.py +++ b/pandas/tests/indexes/period/test_freq_attr.py @@ -20,7 +20,7 @@ def test_freq_setter_deprecated(self): # warning for setter msg = ( - "property 'freq' of 'PeriodArray' object has no setter" + "property 'freq' of 'PeriodIndex' object has no setter" if PY311 else "can't set attribute" ) From b00e4400b92b103f9d14fe3532d3868e2583d082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Sat, 3 Sep 2022 09:47:04 -0400 Subject: [PATCH 06/10] make _resolution_obj abstract --- pandas/core/indexes/datetimelike.py | 16 ++++++++++------ pandas/core/indexes/datetimes.py | 6 +++++- pandas/core/indexes/timedeltas.py | 7 +++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 7b03a9e9c4955..37d94ed93f285 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -3,6 +3,10 @@ """ from __future__ import annotations +from abc import ( + ABC, + abstractmethod, +) from datetime import datetime import inspect from typing import ( @@ -73,7 +77,7 @@ _TDT = TypeVar("_TDT", bound="DatetimeTimedeltaMixin") -class DatetimeIndexOpsMixin(NDArrayBackedExtensionIndex): +class DatetimeIndexOpsMixin(NDArrayBackedExtensionIndex, ABC): """ Common ops mixin to support a unified interface datetimelike Index. """ @@ -106,9 +110,10 @@ def freq(self) -> BaseOffset | None: def freqstr(self) -> str | None: return self._data.freqstr + @abstractmethod @cache_readonly - def _resolution_obj(self) -> Resolution | None: - return self._data._resolution_obj + def _resolution_obj(self) -> Resolution: + ... # error: Decorated property not supported @cache_readonly # type: ignore[misc] @@ -238,8 +243,7 @@ def _summary(self, name=None) -> str: def _can_partial_date_slice(self, reso: Resolution) -> bool: # e.g. test_getitem_setitem_periodindex # History of conversation GH#3452, GH#3931, GH#2369, GH#14826 - # error: Unsupported left operand type for > ("Resolution") - return reso > self._resolution_obj # type: ignore[operator] + return reso > self._resolution_obj # NB: for DTI/PI, not TDI def _parsed_string_to_bounds(self, reso: Resolution, parsed): @@ -397,7 +401,7 @@ def _maybe_cast_listlike_indexer(self, keyarr): return Index(res, dtype=res.dtype) -class DatetimeTimedeltaMixin(DatetimeIndexOpsMixin): +class DatetimeTimedeltaMixin(DatetimeIndexOpsMixin, ABC): """ Mixin class for methods shared by DatetimeIndex and TimedeltaIndex, but not PeriodIndex diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index f6e56422c38df..d6c5d81a8fae1 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -121,7 +121,7 @@ def _new_DatetimeIndex(cls, d): DatetimeArray, wrap=True, ) -@inherit_names(["is_normalized", "_resolution_obj"], DatetimeArray, cache=True) +@inherit_names(["is_normalized"], DatetimeArray, cache=True) @inherit_names( [ "tz", @@ -307,6 +307,10 @@ def isocalendar(self) -> DataFrame: df = self._data.isocalendar() return df.set_index(self) + @cache_readonly + def _resolution_obj(self) -> Resolution: + return self._data._resolution_obj + # -------------------------------------------------------------------- # Constructors diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 12a8f2c0d5a9d..c7bcdac9c6739 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -112,6 +112,13 @@ def _engine_type(self) -> type[libindex.TimedeltaEngine]: # Use base class method instead of DatetimeTimedeltaMixin._get_string_slice _get_string_slice = Index._get_string_slice + # error: Return type "None" of "_resolution_obj" incompatible with return type + # "Resolution" in supertype "DatetimeIndexOpsMixin" + @property + def _resolution_obj(self) -> None: # type: ignore[override] + # not used but need to implement it because it is an abstract method + return None + # ------------------------------------------------------------------- # Constructors From f2af92953c17938344fe1153a46e3f5287c633ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Sat, 3 Sep 2022 12:05:33 -0400 Subject: [PATCH 07/10] use property in abstract class --- pandas/core/indexes/datetimelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 37d94ed93f285..6ae2126eef3b9 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -111,7 +111,7 @@ def freqstr(self) -> str | None: return self._data.freqstr @abstractmethod - @cache_readonly + @property def _resolution_obj(self) -> Resolution: ... From 4723a9bf9a384993d0c84b70ee059d903b553c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Sat, 3 Sep 2022 12:16:50 -0400 Subject: [PATCH 08/10] fix order --- pandas/core/indexes/datetimelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 6ae2126eef3b9..ab5fc8f06f5ab 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -110,8 +110,8 @@ def freq(self) -> BaseOffset | None: def freqstr(self) -> str | None: return self._data.freqstr + @cache_readonly @abstractmethod - @property def _resolution_obj(self) -> Resolution: ... From 0f0fb06b07e02cf6d62341e11f9925d321d812bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Tue, 6 Sep 2022 19:27:48 -0400 Subject: [PATCH 09/10] address non-cast comments --- pandas/_libs/tslibs/offsets.pyi | 3 +++ pandas/core/arrays/datetimelike.py | 13 +++---------- pandas/core/arrays/timedeltas.py | 6 +++--- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyi b/pandas/_libs/tslibs/offsets.pyi index c3d550c7a5ba9..34c5c661f6ec9 100644 --- a/pandas/_libs/tslibs/offsets.pyi +++ b/pandas/_libs/tslibs/offsets.pyi @@ -13,6 +13,7 @@ from typing import ( import numpy as np +from pandas._libs.tslibs.nattype import NaTType from pandas._typing import npt from .timedeltas import Timedelta @@ -49,6 +50,8 @@ class BaseOffset: @overload def __radd__(self, other: npt.NDArray[np.object_]) -> npt.NDArray[np.object_]: ... @overload + def __radd__(self, other: NaTType) -> NaTType: ... + @overload def __radd__(self: _BaseOffsetT, other: BaseOffset) -> _BaseOffsetT: ... @overload def __radd__(self, other: _DatetimeT) -> _DatetimeT: ... diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 5b46ac9bf6476..95e2c15c4862a 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -552,12 +552,7 @@ def _concat_same_type( if obj.freq is not None and all(x.freq == obj.freq for x in to_concat): pairs = zip(to_concat[:-1], to_concat[1:]) - # error: No overload variant of "__radd__" of "BaseOffset" matches - # argument type "NaTType" - if all( - pair[0][-1] + obj.freq == pair[1][0] # type: ignore[operator] - for pair in pairs - ): + if all(pair[0][-1] + obj.freq == pair[1][0] for pair in pairs): new_freq = obj.freq new_obj._freq = new_freq @@ -1435,10 +1430,8 @@ def _time_shift( if self.freq is None: raise NullFrequencyError("Cannot shift with no freq") - # error: No overload variant of "__radd__" of "BaseOffset" matches - # argument type "NaTType" - start = self[0] + periods * self.freq # type: ignore[operator] - end = self[-1] + periods * self.freq # type: ignore[operator] + start = self[0] + periods * self.freq + end = self[-1] + periods * self.freq # Note: in the DatetimeTZ case, _generate_range will infer the # appropriate timezone from `start` and `end`, so tz does not need diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 8fdf86b9af707..a76491e04b93c 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -147,9 +147,9 @@ def _box_func(self, x: np.timedelta64) -> Timedelta | NaTType: # error: Decorated property not supported @property # type: ignore[misc] @doc(dtl.DatetimeLikeArrayMixin.freq) - def freq(self) -> Tick: - # error: Incompatible return value type (got "Optional[BaseOffset]", - # expected "Tick") + def freq(self) -> Tick | None: + # error: Incompatible return value type (got "Optional[BaseOffset]", expected + # "Optional[Tick]") return self._freq # type: ignore[return-value] @freq.setter From 2ed057dcf3eae03dfe7862a52c7ca7ad22a26c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Tue, 6 Sep 2022 19:43:21 -0400 Subject: [PATCH 10/10] add casts --- pandas/core/arrays/datetimelike.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 95e2c15c4862a..c946fc2ac6e13 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1220,9 +1220,8 @@ def _sub_period(self, other: Period) -> npt.NDArray[np.object_]: new_i8_data = checked_add_with_arr( self.asi8, -other.ordinal, arr_mask=self._isnan ) - # error: Item "None" of "Optional[BaseOffset]" has no attribute "base" new_data = np.array( - [self.freq.base * x for x in new_i8_data] # type: ignore[union-attr] + [cast("PeriodArray", self).freq.base * x for x in new_i8_data] ) if self._hasna: @@ -1459,9 +1458,9 @@ def __add__(self, other): # as is_integer returns True for these if not is_period_dtype(self.dtype): raise integer_op_not_supported(self) - # error: Item "None" of "Optional[BaseOffset]" has no attribute "n" - result = cast("PeriodArray", self)._addsub_int_array_or_scalar( - other * self.freq.n, operator.add # type: ignore[union-attr] + self_periodarray = cast("PeriodArray", self) + result = self_periodarray._addsub_int_array_or_scalar( + other * self_periodarray.freq.n, operator.add ) # array-like others