From d410f2efd08acf4c3c7357db25de496195f60930 Mon Sep 17 00:00:00 2001 From: Irv Lustig Date: Sat, 4 Jan 2025 10:04:13 -0500 Subject: [PATCH] upgrade mypy and pyright. close 8 issues (#1087) * update mypy and allow timestamp slicing * fix for getitem with pythong 3.12 and mypy * allow apply to return an offset * allow loc setitem to accept a slice * allow indexing with .loc using Timestamp * define __bool__ for Series and DataFrame to be NoReturn * support Path div * change TypeVar to use Union where possible * support dict keys in constructor to Series * remove path overloads. add assert False for unreachable code --- pandas-stubs/_libs/tslibs/timestamps.pyi | 5 +- pandas-stubs/_typing.pyi | 5 +- pandas-stubs/core/dtypes/concat.pyi | 2 +- pandas-stubs/core/frame.pyi | 51 ++++++++--- pandas-stubs/core/indexes/accessors.pyi | 44 +++++----- pandas-stubs/core/series.pyi | 24 ++++-- pandas-stubs/core/strings.pyi | 6 +- pyproject.toml | 4 +- tests/test_frame.py | 62 ++++++++------ tests/test_series.py | 104 +++++++++++++++++++++++ 10 files changed, 236 insertions(+), 71 deletions(-) diff --git a/pandas-stubs/_libs/tslibs/timestamps.pyi b/pandas-stubs/_libs/tslibs/timestamps.pyi index 504b801cc..d99661b17 100644 --- a/pandas-stubs/_libs/tslibs/timestamps.pyi +++ b/pandas-stubs/_libs/tslibs/timestamps.pyi @@ -11,6 +11,7 @@ from time import struct_time from typing import ( ClassVar, Literal, + SupportsIndex, overload, ) @@ -48,7 +49,7 @@ _Nonexistent: TypeAlias = ( Literal["raise", "NaT", "shift_backward", "shift_forward"] | Timedelta | timedelta ) -class Timestamp(datetime): +class Timestamp(datetime, SupportsIndex): min: ClassVar[Timestamp] # pyright: ignore[reportIncompatibleVariableOverride] max: ClassVar[Timestamp] # pyright: ignore[reportIncompatibleVariableOverride] @@ -309,3 +310,5 @@ class Timestamp(datetime): @property def unit(self) -> TimeUnit: ... def as_unit(self, unit: TimeUnit, round_ok: bool = ...) -> Self: ... + # To support slicing + def __index__(self) -> int: ... diff --git a/pandas-stubs/_typing.pyi b/pandas-stubs/_typing.pyi index 56b83e176..eb63ee310 100644 --- a/pandas-stubs/_typing.pyi +++ b/pandas-stubs/_typing.pyi @@ -57,7 +57,7 @@ Incomplete: TypeAlias = Any ArrayLike: TypeAlias = ExtensionArray | np.ndarray[Any, Any] AnyArrayLike: TypeAlias = Index[Any] | Series[Any] | np.ndarray[Any, Any] PythonScalar: TypeAlias = str | bool | complex -DatetimeLikeScalar = TypeVar("DatetimeLikeScalar", Period, Timestamp, Timedelta) +DatetimeLikeScalar = TypeVar("DatetimeLikeScalar", bound=Period | Timestamp | Timedelta) PandasScalar: TypeAlias = bytes | datetime.date | datetime.datetime | datetime.timedelta IntStrT = TypeVar("IntStrT", int, str) # Scalar: TypeAlias = PythonScalar | PandasScalar @@ -490,7 +490,8 @@ AxisColumn: TypeAlias = Literal["columns", 1] Axis: TypeAlias = AxisIndex | AxisColumn DtypeNp = TypeVar("DtypeNp", bound=np.dtype[np.generic]) KeysArgType: TypeAlias = Any -ListLike = TypeVar("ListLike", Sequence, np.ndarray, Series, Index) +ListLike: TypeAlias = Sequence | np.ndarray | Series | Index +ListLikeT = TypeVar("ListLikeT", bound=ListLike) ListLikeExceptSeriesAndStr: TypeAlias = ( MutableSequence[Any] | np.ndarray[Any, Any] | tuple[Any, ...] | Index[Any] ) diff --git a/pandas-stubs/core/dtypes/concat.pyi b/pandas-stubs/core/dtypes/concat.pyi index 4573758a6..a253220a7 100644 --- a/pandas-stubs/core/dtypes/concat.pyi +++ b/pandas-stubs/core/dtypes/concat.pyi @@ -6,7 +6,7 @@ from pandas import ( Series, ) -_CatT = TypeVar("_CatT", Categorical, CategoricalIndex, Series) +_CatT = TypeVar("_CatT", bound=Categorical | CategoricalIndex | Series) def union_categoricals( to_union: list[_CatT], sort_categories: bool = ..., ignore_order: bool = ... diff --git a/pandas-stubs/core/frame.pyi b/pandas-stubs/core/frame.pyi index 10f127b19..826ba3ad4 100644 --- a/pandas-stubs/core/frame.pyi +++ b/pandas-stubs/core/frame.pyi @@ -9,10 +9,12 @@ from collections.abc import ( ) import datetime as dt from re import Pattern +import sys from typing import ( Any, ClassVar, Literal, + NoReturn, overload, ) @@ -112,6 +114,7 @@ from pandas._typing import ( ReplaceMethod, Scalar, ScalarT, + SequenceNotStr, SeriesByT, SortKind, StataDateFormat, @@ -193,7 +196,11 @@ class _LocIndexerFrame(_LocIndexer): def __getitem__( # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] self, idx: tuple[ - int | StrLike | tuple[Scalar, ...] | Callable[[DataFrame], ScalarT], + int + | StrLike + | Timestamp + | tuple[Scalar, ...] + | Callable[[DataFrame], ScalarT], int | StrLike | tuple[Scalar, ...], ], ) -> Scalar: ... @@ -206,6 +213,7 @@ class _LocIndexerFrame(_LocIndexer): IndexType | MaskType | _IndexSliceTuple + | SequenceNotStr[float | str | Timestamp] | Callable[ [DataFrame], ScalarT | list[HashableT] | IndexType | MaskType ], @@ -219,7 +227,9 @@ class _LocIndexerFrame(_LocIndexer): @overload def __setitem__( self, - idx: MaskType | StrLike | _IndexSliceTuple | list[ScalarT] | IndexingInt, + idx: ( + MaskType | StrLike | _IndexSliceTuple | list[ScalarT] | IndexingInt | slice + ), value: Scalar | NAType | NaTType | ArrayLike | Series | DataFrame | list | None, ) -> None: ... @overload @@ -229,8 +239,32 @@ class _LocIndexerFrame(_LocIndexer): value: Scalar | NAType | NaTType | ArrayLike | Series | list | None, ) -> None: ... -class DataFrame(NDFrame, OpsMixin): - __hash__: ClassVar[None] # type: ignore[assignment] +# With mypy 1.14.1 and python 3.12, the second overload needs a type-ignore statement +if sys.version_info >= (3, 12): + class _GetItemHack: + @overload + def __getitem__(self, key: Scalar | tuple[Hashable, ...]) -> Series: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] + @overload + def __getitem__( # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] + self, key: Iterable[Hashable] | slice + ) -> DataFrame: ... + @overload + def __getitem__(self, key: Hashable) -> Series: ... + +else: + class _GetItemHack: + @overload + def __getitem__(self, key: Scalar | tuple[Hashable, ...]) -> Series: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] + @overload + def __getitem__( # pyright: ignore[reportOverlappingOverload] + self, key: Iterable[Hashable] | slice + ) -> DataFrame: ... + @overload + def __getitem__(self, key: Hashable) -> Series: ... + +class DataFrame(NDFrame, OpsMixin, _GetItemHack): + + __hash__: ClassVar[None] # type: ignore[assignment] # pyright: ignore[reportIncompatibleMethodOverride] @overload def __new__( @@ -607,14 +641,6 @@ class DataFrame(NDFrame, OpsMixin): @property def T(self) -> DataFrame: ... def __getattr__(self, name: str) -> Series: ... - @overload - def __getitem__(self, key: Scalar | tuple[Hashable, ...]) -> Series: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] - @overload - def __getitem__( # pyright: ignore[reportOverlappingOverload] - self, key: Iterable[Hashable] | slice - ) -> DataFrame: ... - @overload - def __getitem__(self, key: Hashable) -> Series: ... def isetitem( self, loc: int | Sequence[int], value: Scalar | ArrayLike | list[Any] ) -> None: ... @@ -2453,6 +2479,7 @@ class DataFrame(NDFrame, OpsMixin): ) -> Self: ... def __truediv__(self, other: float | DataFrame | Series | Sequence) -> Self: ... def __rtruediv__(self, other: float | DataFrame | Series | Sequence) -> Self: ... + def __bool__(self) -> NoReturn: ... class _PandasNamedTuple(tuple[Any, ...]): def __getattr__(self, field: str) -> Scalar: ... diff --git a/pandas-stubs/core/indexes/accessors.pyi b/pandas-stubs/core/indexes/accessors.pyi index 437b8b195..cc9d60b7f 100644 --- a/pandas-stubs/core/indexes/accessors.pyi +++ b/pandas-stubs/core/indexes/accessors.pyi @@ -42,7 +42,7 @@ from pandas._typing import ( class Properties(PandasDelegate, NoNewAttributesMixin): ... -_DTFieldOpsReturnType = TypeVar("_DTFieldOpsReturnType", Series[int], Index[int]) +_DTFieldOpsReturnType = TypeVar("_DTFieldOpsReturnType", bound=Series[int] | Index[int]) class _DayLikeFieldOps(Generic[_DTFieldOpsReturnType]): @property @@ -84,7 +84,9 @@ class _DatetimeFieldOps( _DayLikeFieldOps[_DTFieldOpsReturnType], _MiniSeconds[_DTFieldOpsReturnType] ): ... -_DTBoolOpsReturnType = TypeVar("_DTBoolOpsReturnType", Series[bool], np_ndarray_bool) +_DTBoolOpsReturnType = TypeVar( + "_DTBoolOpsReturnType", bound=Series[bool] | np_ndarray_bool +) class _IsLeapYearProperty(Generic[_DTBoolOpsReturnType]): @property @@ -106,7 +108,7 @@ class _DatetimeBoolOps( @property def is_year_end(self) -> _DTBoolOpsReturnType: ... -_DTFreqReturnType = TypeVar("_DTFreqReturnType", str, BaseOffset) +_DTFreqReturnType = TypeVar("_DTFreqReturnType", bound=str | BaseOffset) class _FreqProperty(Generic[_DTFreqReturnType]): @property @@ -121,10 +123,10 @@ class _DatetimeObjectOps( ): ... _DTOtherOpsDateReturnType = TypeVar( - "_DTOtherOpsDateReturnType", Series[dt.date], np.ndarray + "_DTOtherOpsDateReturnType", bound=Series[dt.date] | np.ndarray ) _DTOtherOpsTimeReturnType = TypeVar( - "_DTOtherOpsTimeReturnType", Series[dt.time], np.ndarray + "_DTOtherOpsTimeReturnType", bound=Series[dt.time] | np.ndarray ) class _DatetimeOtherOps(Generic[_DTOtherOpsDateReturnType, _DTOtherOpsTimeReturnType]): @@ -157,11 +159,7 @@ class _DatetimeLikeOps( _DTTimestampTimedeltaReturnType = TypeVar( "_DTTimestampTimedeltaReturnType", - Series, - TimestampSeries, - TimedeltaSeries, - DatetimeIndex, - TimedeltaIndex, + bound=Series | TimestampSeries | TimedeltaSeries | DatetimeIndex | TimedeltaIndex, ) class _DatetimeRoundingMethods(Generic[_DTTimestampTimedeltaReturnType]): @@ -199,8 +197,10 @@ class _DatetimeRoundingMethods(Generic[_DTTimestampTimedeltaReturnType]): _DTNormalizeReturnType = TypeVar( "_DTNormalizeReturnType", TimestampSeries, DatetimeIndex ) -_DTStrKindReturnType = TypeVar("_DTStrKindReturnType", Series[str], Index) -_DTToPeriodReturnType = TypeVar("_DTToPeriodReturnType", PeriodSeries, PeriodIndex) +_DTStrKindReturnType = TypeVar("_DTStrKindReturnType", bound=Series[str] | Index) +_DTToPeriodReturnType = TypeVar( + "_DTToPeriodReturnType", bound=PeriodSeries | PeriodIndex +) class _DatetimeLikeNoTZMethods( _DatetimeRoundingMethods[_DTTimestampTimedeltaReturnType], @@ -289,9 +289,11 @@ class DatetimeProperties( def as_unit(self, unit: TimeUnit) -> _DTTimestampTimedeltaReturnType: ... _TDNoRoundingMethodReturnType = TypeVar( - "_TDNoRoundingMethodReturnType", Series[int], Index + "_TDNoRoundingMethodReturnType", bound=Series[int] | Index +) +_TDTotalSecondsReturnType = TypeVar( + "_TDTotalSecondsReturnType", bound=Series[float] | Index ) -_TDTotalSecondsReturnType = TypeVar("_TDTotalSecondsReturnType", Series[float], Index) class _TimedeltaPropertiesNoRounding( Generic[_TDNoRoundingMethodReturnType, _TDTotalSecondsReturnType] @@ -318,11 +320,15 @@ class TimedeltaProperties( def unit(self) -> TimeUnit: ... def as_unit(self, unit: TimeUnit) -> TimedeltaSeries: ... -_PeriodDTReturnTypes = TypeVar("_PeriodDTReturnTypes", TimestampSeries, DatetimeIndex) -_PeriodIntReturnTypes = TypeVar("_PeriodIntReturnTypes", Series[int], Index[int]) -_PeriodStrReturnTypes = TypeVar("_PeriodStrReturnTypes", Series[str], Index) -_PeriodDTAReturnTypes = TypeVar("_PeriodDTAReturnTypes", DatetimeArray, DatetimeIndex) -_PeriodPAReturnTypes = TypeVar("_PeriodPAReturnTypes", PeriodArray, PeriodIndex) +_PeriodDTReturnTypes = TypeVar( + "_PeriodDTReturnTypes", bound=TimestampSeries | DatetimeIndex +) +_PeriodIntReturnTypes = TypeVar("_PeriodIntReturnTypes", bound=Series[int] | Index[int]) +_PeriodStrReturnTypes = TypeVar("_PeriodStrReturnTypes", bound=Series[str] | Index) +_PeriodDTAReturnTypes = TypeVar( + "_PeriodDTAReturnTypes", bound=DatetimeArray | DatetimeIndex +) +_PeriodPAReturnTypes = TypeVar("_PeriodPAReturnTypes", bound=PeriodArray | PeriodIndex) class _PeriodProperties( Generic[ diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index b91d3842b..718da55d1 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1,3 +1,4 @@ +from collections import dict_keys # type: ignore[attr-defined] from collections.abc import ( Callable, Hashable, @@ -13,11 +14,13 @@ from datetime import ( time, timedelta, ) +from pathlib import Path from typing import ( Any, ClassVar, Generic, Literal, + NoReturn, overload, ) @@ -139,6 +142,7 @@ from pandas._typing import ( ReplaceMethod, Scalar, ScalarT, + SequenceNotStr, SeriesByT, SortKind, StrDtypeArg, @@ -195,8 +199,7 @@ class _LocIndexerSeries(_LocIndexer, Generic[S1]): idx: ( MaskType | Index - | Sequence[float] - | list[str] + | SequenceNotStr[float | str | Timestamp] | slice | _IndexSliceTuple | Sequence[_IndexSliceTuple] @@ -208,7 +211,7 @@ class _LocIndexerSeries(_LocIndexer, Generic[S1]): @overload def __setitem__( self, - idx: Index | MaskType, + idx: Index | MaskType | slice, value: S1 | ArrayLike | Series[S1] | None, ) -> None: ... @overload @@ -352,7 +355,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __new__( cls, - data: S1 | _ListLike[S1] | dict[HashableT1, S1], + data: S1 | _ListLike[S1] | dict[HashableT1, S1] | dict_keys[S1, Any], index: Axes | None = ..., *, dtype: Dtype = ..., @@ -1030,6 +1033,14 @@ class Series(IndexOpsMixin[S1], NDFrame): **kwds, ) -> Series: ... @overload + def apply( + self, + func: Callable[..., BaseOffset], + convertDType: _bool = ..., + args: tuple = ..., + **kwds, + ) -> OffsetSeries: ... + @overload def apply( self, func: Callable[..., Series], @@ -1640,7 +1651,7 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: int | np_ndarray_anyint | Series[int] ) -> Series[int]: ... def __rsub__(self, other: num | _ListLike | Series[S1]) -> Series: ... - def __rtruediv__(self, other: num | _ListLike | Series[S1]) -> Series: ... + def __rtruediv__(self, other: num | _ListLike | Series[S1] | Path) -> Series: ... # ignore needed for mypy as we want different results based on the arguments @overload # type: ignore[override] def __rxor__( # pyright: ignore[reportOverlappingOverload] @@ -1666,7 +1677,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> TimedeltaSeries: ... @overload def __sub__(self, other: num | _ListLike | Series) -> Series: ... - def __truediv__(self, other: num | _ListLike | Series[S1]) -> Series: ... + def __truediv__(self, other: num | _ListLike | Series[S1] | Path) -> Series: ... # ignore needed for mypy as we want different results based on the arguments @overload # type: ignore[override] def __xor__( # pyright: ignore[reportOverlappingOverload] @@ -2144,6 +2155,7 @@ class Series(IndexOpsMixin[S1], NDFrame): level: Level | None = ..., drop_level: _bool = ..., ) -> Self: ... + def __bool__(self) -> NoReturn: ... class TimestampSeries(Series[Timestamp]): @property diff --git a/pandas-stubs/core/strings.pyi b/pandas-stubs/core/strings.pyi index b952ced0d..c12851705 100644 --- a/pandas-stubs/core/strings.pyi +++ b/pandas-stubs/core/strings.pyi @@ -28,11 +28,11 @@ from pandas._typing import ( ) # The _TS type is what is used for the result of str.split with expand=True -_TS = TypeVar("_TS", DataFrame, MultiIndex) +_TS = TypeVar("_TS", bound=DataFrame | MultiIndex) # The _TS2 type is what is used for the result of str.split with expand=False -_TS2 = TypeVar("_TS2", Series[list[str]], Index[list[str]]) +_TS2 = TypeVar("_TS2", bound=Series[list[str]] | Index[list[str]]) # The _TM type is what is used for the result of str.match -_TM = TypeVar("_TM", Series[bool], np_ndarray_bool) +_TM = TypeVar("_TM", bound=Series[bool] | np_ndarray_bool) class StringMethods(NoNewAttributesMixin, Generic[T, _TS, _TM, _TS2]): def __init__(self, data: T) -> None: ... diff --git a/pyproject.toml b/pyproject.toml index 11a3dd93b..c233df4c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,11 +33,11 @@ types-pytz = ">= 2022.1.1" numpy = ">= 1.23.5" [tool.poetry.group.dev.dependencies] -mypy = "1.13.0" +mypy = "1.14.1" pandas = "2.2.3" pyarrow = ">=10.0.1" pytest = ">=7.1.2" -pyright = ">= 1.1.390" +pyright = ">= 1.1.391" poethepoet = ">=0.16.5" loguru = ">=0.6.0" typing-extensions = ">=4.4.0" diff --git a/tests/test_frame.py b/tests/test_frame.py index 5ce029cfd..3ea1a6cef 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -7,7 +7,6 @@ Iterator, Mapping, MutableMapping, - Sequence, ) import csv import datetime @@ -39,7 +38,6 @@ from pandas.core.series import Series import pytest from typing_extensions import ( - Never, TypeAlias, assert_never, assert_type, @@ -2409,14 +2407,12 @@ def test_indexslice_getitem(): .set_index(["x", "y"]) ) ind = pd.Index([2, 3]) - # This next test is written this way to support both mypy 1.13 and newer - # versions of mypy and pyright that treat slice as a Generic due to - # a change in typeshed. - # Once pyright 1.1.390 and mypy 1.14 are released, the test can be - # reverted to the standard form. - # check(assert_type(pd.IndexSlice[ind, :], tuple["pd.Index[int]", slice]), tuple) - tmp = cast(tuple["pd.Index[int]", slice], pd.IndexSlice[ind, :]) # type: ignore[redundant-cast] - check(assert_type(tmp, tuple["pd.Index[int]", slice]), tuple) + check( + assert_type( + pd.IndexSlice[ind, :], tuple["pd.Index[int]", "slice[None, None, None]"] + ), + tuple, + ) check(assert_type(df.loc[pd.IndexSlice[ind, :]], pd.DataFrame), pd.DataFrame) check(assert_type(df.loc[pd.IndexSlice[1:2]], pd.DataFrame), pd.DataFrame) check( @@ -3765,22 +3761,38 @@ def test_info() -> None: check(assert_type(df.info(show_counts=None), None), type(None)) -def test_series_typed_dict() -> None: - """Test that no error is raised when constructing a series from a typed dict.""" +def test_frame_single_slice() -> None: + # GH 572 + df = pd.DataFrame([1, 2, 3]) + check(assert_type(df.loc[:], pd.DataFrame), pd.DataFrame) - class MyDict(TypedDict): - a: str - b: str + df.loc[:] = 1 + df - my_dict = MyDict(a="", b="") - sr = pd.Series(my_dict) - check(assert_type(sr, pd.Series), pd.Series) +def test_frame_index_timestamp() -> None: + # GH 620 + dt1 = pd.to_datetime("2023-05-01") + dt2 = pd.to_datetime("2023-05-02") + s = pd.Series([1, 2], index=[dt1, dt2]) + df = pd.DataFrame(s) + # Next result is Series or DataFrame because the index could be a MultiIndex + check(assert_type(df.loc[dt1, :], pd.Series | pd.DataFrame), pd.Series) + check(assert_type(df.loc[[dt1], :], pd.DataFrame), pd.DataFrame) + df2 = pd.DataFrame({"x": s}) + check(assert_type(df2.loc[dt1, "x"], Scalar), np.integer) + check(assert_type(df2.loc[[dt1], "x"], pd.Series), pd.Series, np.integer) -def test_series_empty_dtype() -> None: - """Test for the creation of a Series from an empty list GH571 to map to a Series[Any].""" - new_tab: Sequence[Never] = [] # need to be typehinted to please mypy - check(assert_type(pd.Series(new_tab), "pd.Series[Any]"), pd.Series) - check(assert_type(pd.Series([]), "pd.Series[Any]"), pd.Series) - # ensure that an empty string does not get matched to Sequence[Never] - check(assert_type(pd.Series(""), "pd.Series[str]"), pd.Series) + +def test_frame_bool_fails() -> None: + # GH 663 + + df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) + + try: + # We want the type checker to tell us the next line is invalid + # mypy doesn't seem to figure that out, but pyright does + if df == "foo": # pyright: ignore[reportGeneralTypeIssues] + # Next line is unreachable. + s = df["a"] + except ValueError: + pass diff --git a/tests/test_series.py b/tests/test_series.py index 87055fb74..d11ab74db 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -16,6 +16,7 @@ Any, Generic, Literal, + TypedDict, TypeVar, Union, cast, @@ -33,6 +34,7 @@ from pandas.core.window import ExponentialMovingWindow import pytest from typing_extensions import ( + Never, Self, TypeAlias, assert_never, @@ -3435,3 +3437,105 @@ def test_series_unique_timedelta() -> None: """Test type return of Series.unique on Series[timedeta64[ns]].""" sr = pd.Series([pd.Timedelta("1 days"), pd.Timedelta("3 days")]) check(assert_type(sr.unique(), TimedeltaArray), TimedeltaArray) + + +def test_slice_timestamp() -> None: + dti = pd.date_range("1/1/2025", "2/28/2025") + + s = pd.Series([i for i in range(len(dti))], index=dti) + + # For `s1`, see discussion in GH 397. Needs mypy fix. + # s1 = s.loc["2025-01-15":"2025-01-20"] + + # GH 397 + check( + assert_type( + s.loc[pd.Timestamp("2025-01-15") : pd.Timestamp("2025-01-20")], + "pd.Series[int]", + ), + pd.Series, + np.integer, + ) + + +def test_apply_dateoffset() -> None: + # GH 454 + months = [1, 2, 3] + s = pd.Series(months) + check( + assert_type(s.apply(lambda x: pd.DateOffset(months=x)), "OffsetSeries"), + pd.Series, + pd.DateOffset, + ) + + +def test_series_single_slice() -> None: + # GH 572 + s = pd.Series([1, 2, 3]) + check(assert_type(s.loc[:], "pd.Series[int]"), pd.Series, np.integer) + + s.loc[:] = 1 + s + + +def test_series_typed_dict() -> None: + """Test that no error is raised when constructing a series from a typed dict.""" + + class MyDict(TypedDict): + a: str + b: str + + my_dict = MyDict(a="", b="") + sr = pd.Series(my_dict) + check(assert_type(sr, pd.Series), pd.Series) + + +def test_series_empty_dtype() -> None: + """Test for the creation of a Series from an empty list GH571 to map to a Series[Any].""" + new_tab: Sequence[Never] = [] # need to be typehinted to please mypy + check(assert_type(pd.Series(new_tab), "pd.Series[Any]"), pd.Series) + check(assert_type(pd.Series([]), "pd.Series[Any]"), pd.Series) + # ensure that an empty string does not get matched to Sequence[Never] + check(assert_type(pd.Series(""), "pd.Series[str]"), pd.Series) + + +def test_series_index_timestamp() -> None: + # GH 620 + dt1 = pd.to_datetime("2023-05-01") + dt2 = pd.to_datetime("2023-05-02") + s = pd.Series([1, 2], index=[dt1, dt2]) + check(assert_type(s[dt1], int), np.integer) + check(assert_type(s.loc[[dt1]], "pd.Series[int]"), pd.Series, np.integer) + + +def test_series_bool_fails() -> None: + # GH 663 + s = pd.Series([1, 2, 3]) + + try: + # We want the type checker to tell us the next line is invalid + # mypy doesn't seem to figure that out, but pyright does + if s == "foo": # pyright: ignore[reportGeneralTypeIssues] + # Next line is unreachable. + a = s[0] + assert False + except ValueError: + pass + + +def test_path_div() -> None: + # GH 682 + folder = Path.cwd() + files = pd.Series(["a.png", "b.png"]) + check(assert_type(folder / files, pd.Series), pd.Series, Path) + + folders = pd.Series([folder, folder]) + check(assert_type(folders / Path("a.png"), pd.Series), pd.Series, Path) + + +def test_series_dict() -> None: + # GH 812 + check( + assert_type(pd.Series({"a": 1, "b": 2}.keys()), "pd.Series[str]"), + pd.Series, + str, + )