Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions pandas-stubs/_libs/tslibs/period.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ from pandas import (
)
from pandas.core.series import (
OffsetSeries,
PeriodSeries,
)
from typing_extensions import TypeAlias

Expand Down Expand Up @@ -85,7 +84,7 @@ class Period(PeriodMixin):
@overload
def __sub__(self, other: PeriodIndex) -> Index: ...
@overload
def __sub__(self, other: Series[Timedelta]) -> PeriodSeries: ...
def __sub__(self, other: Series[Timedelta]) -> Series[Period]: ...
@overload
def __sub__(self, other: TimedeltaIndex) -> PeriodIndex: ...
@overload
Expand All @@ -95,15 +94,15 @@ class Period(PeriodMixin):
@overload
def __add__(self, other: Index) -> PeriodIndex: ...
@overload
def __add__(self, other: OffsetSeries | Series[Timedelta]) -> PeriodSeries: ...
def __add__(self, other: OffsetSeries | Series[Timedelta]) -> Series[Period]: ...
# ignore[misc] here because we know all other comparisons
# are False, so we use Literal[False]
@overload
def __eq__(self, other: Period) -> bool: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload]
@overload
def __eq__(self, other: Index) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
@overload
def __eq__(self, other: PeriodSeries) -> Series[bool]: ... # type: ignore[overload-overlap]
def __eq__(self, other: Series[Period]) -> Series[bool]: ... # type: ignore[overload-overlap]
@overload
def __eq__(self, other: np_ndarray[ShapeT, np.object_]) -> np_ndarray[ShapeT, np.bool]: ... # type: ignore[overload-overlap]
@overload
Expand All @@ -113,7 +112,7 @@ class Period(PeriodMixin):
@overload
def __ge__(self, other: PeriodIndex) -> np_1darray[np.bool]: ...
@overload
def __ge__(self, other: PeriodSeries) -> Series[bool]: ...
def __ge__(self, other: Series[Period]) -> Series[bool]: ...
@overload
def __ge__(
self, other: np_ndarray[ShapeT, np.object_]
Expand All @@ -123,7 +122,7 @@ class Period(PeriodMixin):
@overload
def __gt__(self, other: PeriodIndex) -> np_1darray[np.bool]: ...
@overload
def __gt__(self, other: PeriodSeries) -> Series[bool]: ...
def __gt__(self, other: Series[Period]) -> Series[bool]: ...
@overload
def __gt__(
self, other: np_ndarray[ShapeT, np.object_]
Expand All @@ -133,7 +132,7 @@ class Period(PeriodMixin):
@overload
def __le__(self, other: PeriodIndex) -> np_1darray[np.bool]: ...
@overload
def __le__(self, other: PeriodSeries) -> Series[bool]: ...
def __le__(self, other: Series[Period]) -> Series[bool]: ...
@overload
def __le__(
self, other: np_ndarray[ShapeT, np.object_]
Expand All @@ -143,7 +142,7 @@ class Period(PeriodMixin):
@overload
def __lt__(self, other: PeriodIndex) -> np_1darray[np.bool]: ...
@overload
def __lt__(self, other: PeriodSeries) -> Series[bool]: ...
def __lt__(self, other: Series[Period]) -> Series[bool]: ...
@overload
def __lt__(
self, other: np_ndarray[ShapeT, np.object_]
Expand All @@ -155,7 +154,7 @@ class Period(PeriodMixin):
@overload
def __ne__(self, other: Index) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
@overload
def __ne__(self, other: PeriodSeries) -> Series[bool]: ... # type: ignore[overload-overlap]
def __ne__(self, other: Series[Period]) -> Series[bool]: ... # type: ignore[overload-overlap]
@overload
def __ne__(self, other: np_ndarray[ShapeT, np.object_]) -> np_ndarray[ShapeT, np.bool]: ... # type: ignore[overload-overlap]
@overload
Expand Down
10 changes: 6 additions & 4 deletions pandas-stubs/core/indexes/accessors.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ from pandas.core.arrays import (
from pandas.core.base import NoNewAttributesMixin
from pandas.core.frame import DataFrame
from pandas.core.series import (
PeriodSeries,
Series,
)
from typing_extensions import Never

from pandas._libs.tslibs import BaseOffset
from pandas._libs.tslibs.offsets import DateOffset
from pandas._libs.tslibs.period import Period
from pandas._typing import (
S1,
TimeAmbiguous,
Expand Down Expand Up @@ -208,7 +208,7 @@ _DTNormalizeReturnType = TypeVar(
)
_DTStrKindReturnType = TypeVar("_DTStrKindReturnType", bound=Series[str] | Index)
_DTToPeriodReturnType = TypeVar(
"_DTToPeriodReturnType", bound=PeriodSeries | PeriodIndex
"_DTToPeriodReturnType", bound=Series[Period] | PeriodIndex
)

class _DatetimeLikeNoTZMethods(
Expand Down Expand Up @@ -385,7 +385,7 @@ class CombinedDatetimelikeProperties(
str,
Series[Timestamp],
Series[str],
PeriodSeries,
Series[Period],
],
_TimedeltaPropertiesNoRounding[Series[int], Series[float]],
_PeriodProperties,
Expand All @@ -400,7 +400,7 @@ class TimestampProperties(
str,
Series[Timestamp],
Series[str],
PeriodSeries,
Series[Period],
]
): ...

Expand Down Expand Up @@ -438,6 +438,8 @@ class _dtDescriptor(CombinedDatetimelikeProperties, Generic[S1]):
@overload
def __get__(self, instance: Series[Never], owner: Any) -> Never: ...
@overload
def __get__(self, instance: Series[Period], owner: Any) -> PeriodProperties: ...
@overload
def __get__(
self, instance: Series[Timestamp], owner: Any
) -> TimestampProperties: ...
Expand Down
44 changes: 28 additions & 16 deletions pandas-stubs/core/series.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,7 @@ from pandas.core.generic import NDFrame
from pandas.core.groupby.generic import SeriesGroupBy
from pandas.core.groupby.groupby import BaseGroupBy
from pandas.core.indexers import BaseIndexer
from pandas.core.indexes.accessors import (
PeriodProperties,
_dtDescriptor,
)
from pandas.core.indexes.accessors import _dtDescriptor
from pandas.core.indexes.category import CategoricalIndex
from pandas.core.indexes.datetimes import DatetimeIndex
from pandas.core.indexes.interval import IntervalIndex
Expand Down Expand Up @@ -363,7 +360,7 @@ class Series(IndexOpsMixin[S1], NDFrame):
dtype: PeriodDtype = ...,
name: Hashable = ...,
copy: bool = ...,
) -> PeriodSeries: ...
) -> Series[Period]: ...
@overload
def __new__(
cls,
Expand Down Expand Up @@ -851,6 +848,8 @@ class Series(IndexOpsMixin[S1], NDFrame):
@overload
def diff(self: Series[Timedelta], periods: int = ...) -> Series[Timedelta]: ... # type: ignore[overload-overlap]
@overload
def diff(self: Series[Period], periods: int = ...) -> OffsetSeries: ... # type: ignore[overload-overlap]
@overload
Comment on lines +836 to +852
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could try the following idea to simplify, if you want.

class SupportsSelfSub(Protocol[_T_co]):
    def __sub__(self, x: Self, /) -> _T_co: ...

And

def diff(self: SupportsGetItem[Scalar, SupportsSelfSub[S1_CO]], period: int = ...) -> Series[S1_CO]: ...

I've first seen the idea in #1362 and it worked again in #1374

def diff(self, periods: int = ...) -> Series[float]: ...
def autocorr(self, lag: int = 1) -> float: ...
@overload
Expand Down Expand Up @@ -1695,7 +1694,7 @@ class Series(IndexOpsMixin[S1], NDFrame):
),
) -> Series[Timedelta]: ...
@overload
def __add__(self: Series[Timedelta], other: Period) -> PeriodSeries: ...
def __add__(self: Series[Timedelta], other: Period) -> Series[Period]: ...
@overload
def __add__(self: Series[bool], other: bool | Sequence[bool]) -> Series[bool]: ...
@overload
Expand Down Expand Up @@ -1822,7 +1821,7 @@ class Series(IndexOpsMixin[S1], NDFrame):
level: Level | None = None,
fill_value: float | None = None,
axis: int = 0,
) -> PeriodSeries: ...
) -> Series[Period]: ...
@overload
def add(
self: Series[bool],
Expand Down Expand Up @@ -1957,7 +1956,7 @@ class Series(IndexOpsMixin[S1], NDFrame):
),
) -> Series[Timedelta]: ...
@overload
def __radd__(self: Series[Timedelta], other: Period) -> PeriodSeries: ...
def __radd__(self: Series[Timedelta], other: Period) -> Series[Period]: ...
@overload
def __radd__(self: Series[bool], other: bool | Sequence[bool]) -> Series[bool]: ...
@overload
Expand Down Expand Up @@ -2084,7 +2083,7 @@ class Series(IndexOpsMixin[S1], NDFrame):
level: Level | None = None,
fill_value: float | None = None,
axis: int = 0,
) -> PeriodSeries: ...
) -> Series[Period]: ...
@overload
def radd(
self: Series[bool],
Expand Down Expand Up @@ -3079,7 +3078,7 @@ class Series(IndexOpsMixin[S1], NDFrame):
other: complex | NumListLike | Index[T_COMPLEX] | Series[T_COMPLEX],
) -> Series: ...
@overload
def __sub__(self, other: Index[Never] | Series[Never]) -> Series: ...
def __sub__(self, other: Index[Never] | Series[Never]) -> Series: ... # type: ignore[overload-overlap]
@overload
def __sub__(
self: Series[bool],
Expand Down Expand Up @@ -3201,6 +3200,8 @@ class Series(IndexOpsMixin[S1], NDFrame):
),
) -> Series[Timedelta]: ...
@overload
def __sub__(self: Series[Period], other: Series[Period]) -> OffsetSeries: ...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TLDR:

  • Use -> Series[Offset]
  • Please also add def __rsub__(self: Series[Period], other: Series[Period]) -> OffsetSeries: ... and the corresponding sub and rsub.
  • Would be great if tests/series/period/test_sub.py can be added

This one is the cause of #1386 (comment). I'll try to give my reasoning.

Note that self: Series[Period] implies it also accepts self: Series[Any]. The same applies to other: Series[Period]. mypy checks if the existing rules are "compatible" with the current result. Existing rules must have given Series[...], whereas the current result is OffsetSeries. mypy thinks this is _in_compatible, hence gives Any.

That pyright does not give Any, could be a pyright bug. See microsoft/pyright#10924

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely changing the return type to Series[BaseOffset] fixes that mypy problem. But there is a problem with test_timefuncs.py and PeriodProperties and pyright that I am trying to figure out.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def __sub__(self: Series[Period], other: Series[Period]) -> OffsetSeries: ...
def __sub__(self: Series[Period], other: Series[Period]) -> Series[BaseOffset]: ...

@overload
def sub(
self: Series[Never],
other: complex | NumListLike | Index[T_COMPLEX] | Series[T_COMPLEX],
Expand Down Expand Up @@ -4623,6 +4624,22 @@ class Series(IndexOpsMixin[S1], NDFrame):
**kwargs,
) -> np_1darray[GenericT]: ...
@overload
def to_numpy(
self: Series[Period],
dtype: None = None,
copy: bool = False,
na_value: Scalar = ...,
**kwargs,
) -> np_1darray[np.object_]: ...
@overload
def to_numpy(
self: Series[Period],
dtype: type[np.int64],
copy: bool = False,
na_value: Scalar = ...,
**kwargs,
) -> np_1darray[np.int64]: ...
@overload
def to_numpy( # pyright: ignore[reportIncompatibleMethodOverride]
self,
dtype: DTypeLike | None = None,
Expand Down Expand Up @@ -4716,15 +4733,10 @@ class _SeriesSubclassBase(Series[S1], Generic[S1, GenericT_co]):
**kwargs,
) -> np_1darray: ...

class PeriodSeries(_SeriesSubclassBase[Period, np.object_]):
@property
def dt(self) -> PeriodProperties: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
def __sub__(self, other: PeriodSeries) -> OffsetSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
def diff(self, periods: int = ...) -> OffsetSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]

class OffsetSeries(_SeriesSubclassBase[BaseOffset, np.object_]):
@overload # type: ignore[override]
def __radd__(self, other: Period) -> PeriodSeries: ...
def __radd__(self, other: Period) -> Series[Period]: ...
@overload
def __radd__( # pyright: ignore[reportIncompatibleMethodOverride]
self, other: BaseOffset
Expand Down
12 changes: 5 additions & 7 deletions tests/test_scalars.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,8 @@
if TYPE_CHECKING:
from pandas.core.series import (
OffsetSeries,
PeriodSeries,
)
else:
PeriodSeries: TypeAlias = pd.Series
OffsetSeries: TypeAlias = pd.Series

if not PD_LTE_23:
Expand Down Expand Up @@ -1861,7 +1859,7 @@ def test_period_add_subtract() -> None:
as_td_series = pd.Series(pd.timedelta_range(scale, scale, freq="D"))
check(assert_type(as_td_series, "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta)
as_period_series = pd.Series(as_period_index)
check(assert_type(as_period_series, PeriodSeries), pd.Series, pd.Period)
check(assert_type(as_period_series, "pd.Series[pd.Period]"), pd.Series, pd.Period)
as_timedelta_idx = pd.timedelta_range(scale, scale, freq="D")
as_nat = pd.NaT

Expand All @@ -1876,20 +1874,20 @@ def test_period_add_subtract() -> None:
# https://github.com/pandas-dev/pandas/issues/50162
check(assert_type(p + offset_index, pd.PeriodIndex), pd.Index)

check(assert_type(p + as_td_series, PeriodSeries), pd.Series, pd.Period)
check(assert_type(p + as_td_series, "pd.Series[pd.Period]"), pd.Series, pd.Period)
check(assert_type(p + as_timedelta_idx, pd.PeriodIndex), pd.PeriodIndex)
check(assert_type(p + as_nat, NaTType), NaTType)
offset_series = as_period_series - as_period_series
check(assert_type(offset_series, OffsetSeries), pd.Series)
check(assert_type(p + offset_series, PeriodSeries), pd.Series, pd.Period)
check(assert_type(p + offset_series, "pd.Series[pd.Period]"), pd.Series, pd.Period)
check(assert_type(p - as_pd_td, pd.Period), pd.Period)
check(assert_type(p - as_dt_td, pd.Period), pd.Period)
check(assert_type(p - as_np_td, pd.Period), pd.Period)
check(assert_type(p - as_np_i64, pd.Period), pd.Period)
check(assert_type(p - as_int, pd.Period), pd.Period)
check(assert_type(offset_index, pd.Index), pd.Index)
check(assert_type(p - as_period, BaseOffset), Day)
check(assert_type(p - as_td_series, PeriodSeries), pd.Series, pd.Period)
check(assert_type(p - as_td_series, "pd.Series[pd.Period]"), pd.Series, pd.Period)
check(assert_type(p - as_timedelta_idx, pd.PeriodIndex), pd.PeriodIndex)
check(assert_type(p - as_nat, NaTType), NaTType)
check(assert_type(p - p.freq, pd.Period), pd.Period)
Expand All @@ -1912,7 +1910,7 @@ def test_period_add_subtract() -> None:
check(assert_type(as_int + p, pd.Period), pd.Period)
check(assert_type(p.__radd__(as_int), pd.Period), pd.Period)

check(assert_type(as_td_series + p, PeriodSeries), pd.Series, pd.Period)
check(assert_type(as_td_series + p, "pd.Series[pd.Period]"), pd.Series, pd.Period)

check(assert_type(as_timedelta_idx + p, pd.PeriodIndex), pd.PeriodIndex)

Expand Down
5 changes: 3 additions & 2 deletions tests/test_timefuncs.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
from pandas.core.series import ( # noqa: F401
IntervalSeries,
OffsetSeries,
PeriodSeries,
)

if not PD_LTE_23:
Expand Down Expand Up @@ -392,7 +391,9 @@ def test_series_dt_accessors() -> None:
check(assert_type(s0.dt.tz, Optional[dt.tzinfo]), type(None))
check(assert_type(s0.dt.freq, Optional[str]), str)
check(assert_type(s0.dt.isocalendar(), pd.DataFrame), pd.DataFrame)
check(assert_type(s0.dt.to_period("D"), "PeriodSeries"), pd.Series, pd.Period)
check(
assert_type(s0.dt.to_period("D"), "pd.Series[pd.Period]"), pd.Series, pd.Period
)

with pytest_warns_bounded(
FutureWarning,
Expand Down
Loading