diff --git a/pandas-stubs/core/base.pyi b/pandas-stubs/core/base.pyi index c116a717e..4b6e2e05d 100644 --- a/pandas-stubs/core/base.pyi +++ b/pandas-stubs/core/base.pyi @@ -20,6 +20,7 @@ from pandas import ( from pandas.core.arraylike import OpsMixin from pandas.core.arrays import ExtensionArray from pandas.core.arrays.categorical import Categorical +from pandas.core.indexes.accessors import ArrayDescriptor from typing_extensions import Self from pandas._typing import ( @@ -69,8 +70,7 @@ class IndexOpsMixin(OpsMixin, Generic[S1, GenericT_co]): def nbytes(self) -> int: ... @property def size(self) -> int: ... - @property - def array(self) -> ExtensionArray: ... + array = ArrayDescriptor() @overload def to_numpy( self, diff --git a/pandas-stubs/core/indexes/accessors.pyi b/pandas-stubs/core/indexes/accessors.pyi index a9a5f8a54..0cc058af7 100644 --- a/pandas-stubs/core/indexes/accessors.pyi +++ b/pandas-stubs/core/indexes/accessors.pyi @@ -1,42 +1,44 @@ -import datetime as dt from datetime import ( + date, + time, timedelta, tzinfo as _tzinfo, ) from typing import ( - Any, Generic, Literal, TypeVar, overload, + type_check_only, ) import numpy as np -from pandas import ( - DatetimeIndex, - Index, - PeriodIndex, - Timedelta, - TimedeltaIndex, - Timestamp, -) from pandas.core.accessor import PandasDelegate -from pandas.core.arrays import ( - DatetimeArray, - PeriodArray, +from pandas.core.arrays.base import ExtensionArray +from pandas.core.arrays.categorical import Categorical +from pandas.core.arrays.datetimes import DatetimeArray +from pandas.core.arrays.interval import IntervalArray +from pandas.core.arrays.period import PeriodArray +from pandas.core.arrays.timedeltas import TimedeltaArray +from pandas.core.base import ( + IndexOpsMixin, + NoNewAttributesMixin, ) -from pandas.core.base import NoNewAttributesMixin from pandas.core.frame import DataFrame -from pandas.core.series import ( - Series, -) +from pandas.core.indexes.base import Index +from pandas.core.indexes.datetimes import DatetimeIndex +from pandas.core.indexes.period import PeriodIndex +from pandas.core.indexes.timedeltas import TimedeltaIndex +from pandas.core.series import Series from typing_extensions import Never +from pandas._libs.interval import Interval from pandas._libs.tslibs import BaseOffset from pandas._libs.tslibs.offsets import DateOffset from pandas._libs.tslibs.period import Period +from pandas._libs.tslibs.timedeltas import Timedelta +from pandas._libs.tslibs.timestamps import Timestamp from pandas._typing import ( - S1, TimeAmbiguous, TimeNonexistent, TimestampConvention, @@ -46,6 +48,8 @@ from pandas._typing import ( np_ndarray_bool, ) +from pandas.core.dtypes.dtypes import CategoricalDtype + class Properties(PandasDelegate, NoNewAttributesMixin): ... _DTFieldOpsReturnType = TypeVar("_DTFieldOpsReturnType", bound=Series[int] | Index[int]) @@ -129,10 +133,10 @@ class _DatetimeObjectOps( ): ... _DTOtherOpsDateReturnType = TypeVar( - "_DTOtherOpsDateReturnType", bound=Series[dt.date] | np_1darray[np.object_] + "_DTOtherOpsDateReturnType", bound=Series[date] | np_1darray[np.object_] ) _DTOtherOpsTimeReturnType = TypeVar( - "_DTOtherOpsTimeReturnType", bound=Series[dt.time] | np_1darray[np.object_] + "_DTOtherOpsTimeReturnType", bound=Series[time] | np_1darray[np.object_] ) class _DatetimeOtherOps(Generic[_DTOtherOpsDateReturnType, _DTOtherOpsTimeReturnType]): @@ -380,8 +384,8 @@ class CombinedDatetimelikeProperties( Series[int], Series[bool], Series, - Series[dt.date], - Series[dt.time], + Series[date], + Series[time], str, Series[Timestamp], Series[str], @@ -390,13 +394,15 @@ class CombinedDatetimelikeProperties( _TimedeltaPropertiesNoRounding[Series[int], Series[float]], _PeriodProperties, ): ... + +@type_check_only class TimestampProperties( DatetimeProperties[ Series[int], Series[bool], Series[Timestamp], - Series[dt.date], - Series[dt.time], + Series[date], + Series[time], str, Series[Timestamp], Series[str], @@ -434,51 +440,47 @@ class TimedeltaIndexProperties( _DatetimeRoundingMethods[TimedeltaIndex], ): ... -class _dtDescriptor(CombinedDatetimelikeProperties, Generic[S1]): - @overload - def __get__(self, instance: Series[Never], owner: Any) -> Never: ... +@type_check_only +class DtDescriptor: @overload - def __get__(self, instance: Series[Period], owner: Any) -> PeriodProperties: ... + def __get__(self, instance: Series[Never], owner: type[Series]) -> Properties: ... @overload def __get__( - self, instance: Series[Timestamp], owner: Any + self, instance: Series[Timestamp], owner: type[Series] ) -> TimestampProperties: ... @overload def __get__( - self, instance: Series[Timedelta], owner: Any + self, instance: Series[Timedelta], owner: type[Series] ) -> TimedeltaProperties: ... @overload def __get__( - self, instance: Series[S1], owner: Any - ) -> CombinedDatetimelikeProperties: ... - def round( - self, - freq: str | BaseOffset | None, - ambiguous: Literal["raise", "infer", "NaT"] | bool | np_ndarray_bool = ..., - nonexistent: ( - Literal["shift_forward", "shift_backward", "NaT", "raise"] - | timedelta - | Timedelta - ) = ..., - ) -> Series[S1]: ... - def floor( - self, - freq: str | BaseOffset | None, - ambiguous: Literal["raise", "infer", "NaT"] | bool | np_ndarray_bool = ..., - nonexistent: ( - Literal["shift_forward", "shift_backward", "NaT", "raise"] - | timedelta - | Timedelta - ) = ..., - ) -> Series[S1]: ... - def ceil( - self, - freq: str | BaseOffset | None, - ambiguous: Literal["raise", "infer", "NaT"] | bool | np_ndarray_bool = ..., - nonexistent: ( - Literal["shift_forward", "shift_backward", "NaT", "raise"] - | timedelta - | Timedelta - ) = ..., - ) -> Series[S1]: ... - def as_unit(self, unit: TimeUnit) -> Series[S1]: ... + self, instance: Series[Period], owner: type[Series] + ) -> PeriodProperties: ... + +@type_check_only +class ArrayDescriptor: + @overload + def __get__( + self, instance: IndexOpsMixin[Never], owner: type[IndexOpsMixin] + ) -> ExtensionArray: ... + @overload + def __get__( + self, instance: IndexOpsMixin[CategoricalDtype], owner: type[IndexOpsMixin] + ) -> Categorical: ... + @overload + def __get__( + self, instance: IndexOpsMixin[Interval], owner: type[IndexOpsMixin] + ) -> IntervalArray: ... + @overload + def __get__( + self, instance: IndexOpsMixin[Timestamp], owner: type[IndexOpsMixin] + ) -> DatetimeArray: ... + @overload + def __get__( + self, instance: IndexOpsMixin[Timedelta], owner: type[IndexOpsMixin] + ) -> TimedeltaArray: ... + # should be NumpyExtensionArray + @overload + def __get__( + self, instance: IndexOpsMixin, owner: type[IndexOpsMixin] + ) -> ExtensionArray: ... diff --git a/pandas-stubs/core/indexes/base.pyi b/pandas-stubs/core/indexes/base.pyi index ba3e6812c..32f2f6297 100644 --- a/pandas-stubs/core/indexes/base.pyi +++ b/pandas-stubs/core/indexes/base.pyi @@ -36,7 +36,6 @@ from pandas import ( Series, TimedeltaIndex, ) -from pandas.core.arrays import ExtensionArray from pandas.core.base import ( IndexOpsMixin, NumListLike, @@ -326,7 +325,7 @@ class Index(IndexOpsMixin[S1]): def to_flat_index(self): ... def to_series( self, index: Index | None = None, name: Hashable | None = None - ) -> Series: ... + ) -> Series[S1]: ... def to_frame(self, index: bool = True, name=...) -> DataFrame: ... @property def name(self) -> Hashable | None: ... @@ -415,8 +414,6 @@ class Index(IndexOpsMixin[S1]): ): ... @property def values(self) -> np_1darray: ... - @property - def array(self) -> ExtensionArray: ... def memory_usage(self, deep: bool = False): ... def where(self, cond, other: Scalar | ArrayLike | None = None): ... def __contains__(self, key) -> bool: ... diff --git a/pandas-stubs/core/indexes/datetimes.pyi b/pandas-stubs/core/indexes/datetimes.pyi index 8f8912990..12af992f7 100644 --- a/pandas-stubs/core/indexes/datetimes.pyi +++ b/pandas-stubs/core/indexes/datetimes.pyi @@ -20,7 +20,6 @@ from pandas import ( TimedeltaIndex, Timestamp, ) -from pandas.core.arrays import DatetimeArray from pandas.core.indexes.accessors import DatetimeIndexProperties from pandas.core.indexes.datetimelike import DatetimeTimedeltaMixin from pandas.core.series import Series @@ -60,10 +59,6 @@ class DatetimeIndex( ) -> Self: ... def __reduce__(self): ... - # Override the array property to return DatetimeArray instead of ExtensionArray - @property - def array(self) -> DatetimeArray: ... - # various ignores needed for mypy, as we do want to restrict what can be used in # arithmetic for these types def __add__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 352c1e776..04b7a712f 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -27,6 +27,7 @@ from typing import ( Literal, NoReturn, Protocol, + TypeVar, final, overload, type_check_only, @@ -55,11 +56,10 @@ from pandas.core.api import ( Int32Dtype as Int32Dtype, Int64Dtype as Int64Dtype, ) -from pandas.core.arrays import TimedeltaArray -from pandas.core.arrays.base import ExtensionArray +from pandas.core.arrays.boolean import BooleanDtype from pandas.core.arrays.categorical import CategoricalAccessor from pandas.core.arrays.datetimes import DatetimeArray -from pandas.core.arrays.interval import IntervalArray +from pandas.core.arrays.timedeltas import TimedeltaArray from pandas.core.base import ( IndexOpsMixin, NumListLike, @@ -70,7 +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 _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 @@ -210,10 +210,16 @@ from pandas.core.dtypes.dtypes import CategoricalDtype from pandas.plotting import PlotAccessor +_T_INTERVAL_NP = TypeVar("_T_INTERVAL_NP", bound=np.bytes_ | np.str_) + @type_check_only class _SupportsAdd(Protocol[_T_co]): def __add__(self, value: Self, /) -> _T_co: ... +@type_check_only +class SupportsSelfSub(Protocol[_T_co]): + def __sub__(self, x: Self, /) -> _T_co: ... + @type_check_only class _SupportsMul(Protocol[_T_co]): def __mul__(self, value: Self, /) -> _T_co: ... @@ -300,7 +306,7 @@ class Series(IndexOpsMixin[S1], NDFrame): copy: bool = ..., ) -> Series[float]: ... @overload - def __new__( # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] + def __new__( cls, data: Sequence[Never], index: AxesData | None = ..., @@ -398,7 +404,7 @@ class Series(IndexOpsMixin[S1], NDFrame): dtype: Literal["Interval"] = ..., name: Hashable = ..., copy: bool = ..., - ) -> IntervalSeries[_OrderableT]: ... + ) -> Series[Interval[_OrderableT]]: ... @overload def __new__( # type: ignore[overload-overlap] cls, @@ -485,8 +491,6 @@ class Series(IndexOpsMixin[S1], NDFrame): def name(self, value: Hashable | None) -> None: ... @property def values(self) -> ArrayLike: ... - @property - def array(self) -> ExtensionArray: ... def ravel(self, order: _str = ...) -> np.ndarray: ... def __len__(self) -> int: ... def view(self, dtype=...) -> Series[S1]: ... @@ -843,23 +847,21 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: Series[S1], min_periods: int | None = None, ddof: int = 1 ) -> float: ... @overload - def diff(self: Series[_bool], periods: int = ...) -> Series[type[object]]: ... # type: ignore[overload-overlap] - @overload - def diff(self: Series[complex], periods: int = ...) -> Series[complex]: ... # type: ignore[overload-overlap] - @overload - def diff(self: Series[bytes], periods: int = ...) -> Never: ... - @overload - def diff(self: Series[type], periods: int = ...) -> Never: ... - @overload - def diff(self: Series[_str], periods: int = ...) -> Never: ... + def diff( # type: ignore[overload-overlap] + self: Series[Never] | Series[int], periods: int = ... + ) -> Series[float]: ... @overload - def diff(self: Series[Timestamp], periods: int = ...) -> Series[Timedelta]: ... # type: ignore[overload-overlap] + def diff(self: Series[_bool], periods: int = ...) -> Series: ... @overload - def diff(self: Series[Timedelta], periods: int = ...) -> Series[Timedelta]: ... # type: ignore[overload-overlap] + def diff( + self: Series[BooleanDtype], periods: int = ... + ) -> Series[BooleanDtype]: ... @overload - def diff(self: Series[Period], periods: int = ...) -> Series[BaseOffset]: ... # type: ignore[overload-overlap] + def diff(self: Series[Interval], periods: int = ...) -> Never: ... @overload - def diff(self, periods: int = ...) -> Series[float]: ... + def diff( + self: SupportsGetItem[Scalar, SupportsSelfSub[S1_CO]], periods: int = ... + ) -> Series[S1_CO]: ... def autocorr(self, lag: int = 1) -> float: ... @overload def dot(self, other: Series[S1]) -> Scalar: ... @@ -1242,7 +1244,7 @@ class Series(IndexOpsMixin[S1], NDFrame): Series[_str], Series, ]: ... - dt = _dtDescriptor() + dt = DtDescriptor() @property def plot(self) -> PlotAccessor: ... sparse = ... @@ -4246,9 +4248,6 @@ class Series(IndexOpsMixin[S1], NDFrame): def __xor__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... @final def __invert__(self) -> Series[bool]: ... - # properties - # @property - # def array(self) -> _npndarray @property def at(self) -> _AtIndexer: ... @property @@ -4698,6 +4697,22 @@ class Series(IndexOpsMixin[S1], NDFrame): **kwargs, ) -> np_1darray[np.bytes_]: ... @overload + def to_numpy( + self: Series[Interval], + dtype: type[np.object_] | None = None, + copy: bool = False, + na_value: Scalar = ..., + **kwargs, + ) -> np_1darray[np.object_]: ... + @overload + def to_numpy( + self: Series[Interval], + dtype: type[_T_INTERVAL_NP], + copy: bool = False, + na_value: Scalar = ..., + **kwargs, + ) -> np_1darray[_T_INTERVAL_NP]: ... + @overload def to_numpy( # pyright: ignore[reportIncompatibleMethodOverride] self, dtype: DTypeLike | None = None, @@ -4790,10 +4805,3 @@ class _SeriesSubclassBase(Series[S1], Generic[S1, GenericT_co]): na_value: Scalar = ..., **kwargs, ) -> np_1darray: ... - -class IntervalSeries( - _SeriesSubclassBase[Interval[_OrderableT], np.object_], Generic[_OrderableT] -): - @property - def array(self) -> IntervalArray: ... - def diff(self, periods: int = ...) -> Never: ... # pyrefly: ignore diff --git a/pyproject.toml b/pyproject.toml index 4fbbc4e77..32aafb808 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,16 +39,16 @@ numpy = ">= 1.23.5" mypy = "1.18.2" pandas = "2.3.2" pyarrow = ">=10.0.1" -pytest = ">=7.1.2" +pytest = ">=8.4.2" pyright = ">=1.1.405" ty = ">=0.0.1a21" -pyrefly = ">=0.33.1" +pyrefly = ">=0.34.0" poethepoet = ">=0.16.5" loguru = ">=0.6.0" typing-extensions = ">=4.4.0" matplotlib = ">=3.10.1" pre-commit = ">=2.19.0" -black = ">=25.1.0" +black = ">=25.9.0" isort = ">=6.0.1" openpyxl = ">=3.0.10" tables = { version = ">=3.10.1", python = "<4" } diff --git a/tests/indexes/test_indexes.py b/tests/indexes/test_indexes.py index eb6a790b4..1a21d9149 100644 --- a/tests/indexes/test_indexes.py +++ b/tests/indexes/test_indexes.py @@ -12,8 +12,11 @@ import numpy as np from numpy import typing as npt import pandas as pd -from pandas.core.arrays import DatetimeArray +from pandas.core.arrays.base import ExtensionArray from pandas.core.arrays.categorical import Categorical +from pandas.core.arrays.datetimes import DatetimeArray +from pandas.core.arrays.interval import IntervalArray +from pandas.core.arrays.timedeltas import TimedeltaArray from pandas.core.indexes.base import Index from typing_extensions import ( Never, @@ -1519,12 +1522,84 @@ def test_period_index_asof_locs() -> None: ) -def test_datetime_index_array_property() -> None: - """Test that DatetimeIndex.array returns DatetimeArray instead of ExtensionArray.""" +def test_array_property() -> None: + """Test that Index.array and semantic Index.array return ExtensionArray and its subclasses""" + # casting due to pandas-dev/pandas-stubs#1383 + check( + assert_type( + cast("Index[pd.CategoricalDtype]", Index([1], dtype="category")).array, + pd.Categorical, + ), + pd.Categorical, + int, + ) + check( + assert_type(pd.interval_range(0, 1).array, IntervalArray), + IntervalArray, + pd.Interval, + ) + # Test with pd.to_datetime().array - this is the main issue reported arr = pd.to_datetime(["2020-01-01", "2020-01-02"]).array - check(assert_type(arr, DatetimeArray), DatetimeArray) + check(assert_type(arr, DatetimeArray), DatetimeArray, pd.Timestamp) # Test with DatetimeIndex constructor directly dt_index = pd.DatetimeIndex(["2020-01-01", "2020-01-02"]) - check(assert_type(dt_index.array, DatetimeArray), DatetimeArray) + check(assert_type(dt_index.array, DatetimeArray), DatetimeArray, pd.Timestamp) + + check( + assert_type(pd.to_timedelta(["1s"]).array, TimedeltaArray), + TimedeltaArray, + pd.Timedelta, + ) + check(assert_type(Index([1]).array, ExtensionArray), ExtensionArray, np.integer) + + +def test_to_series() -> None: + """Test that Index.to_series return typed Series""" + check( + assert_type(pd.interval_range(0, 1).to_series(), "pd.Series[pd.Interval[int]]"), + pd.Series, + pd.Interval, + ) + check( + assert_type( + pd.date_range(start="2022-06-01", periods=10).to_series(), + "pd.Series[pd.Timestamp]", + ), + pd.Series, + pd.Timestamp, + ) + + check( + assert_type( + pd.timedelta_range(start="1 day", periods=10).to_series(), + "pd.Series[pd.Timedelta]", + ), + pd.Series, + pd.Timedelta, + ) + check( + assert_type( + pd.period_range(start="2022-06-01", periods=10).to_series(), + "pd.Series[pd.Period]", + ), + pd.Series, + pd.Period, + ) + + check( + assert_type(Index([True]).to_series(), "pd.Series[bool]"), pd.Series, np.bool_ + ) + check(assert_type(Index([1]).to_series(), "pd.Series[int]"), pd.Series, np.integer) + check( + assert_type(Index([1.0]).to_series(), "pd.Series[float]"), + pd.Series, + np.floating, + ) + check( + assert_type(Index([1j]).to_series(), "pd.Series[complex]"), + pd.Series, + np.complexfloating, + ) + check(assert_type(Index(["1"]).to_series(), "pd.Series[str]"), pd.Series, str) diff --git a/tests/scalars/__init__.py b/tests/scalars/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_scalars.py b/tests/scalars/test_scalars.py similarity index 99% rename from tests/test_scalars.py rename to tests/scalars/test_scalars.py index 3eb8f7cc4..54fcb08c5 100644 --- a/tests/test_scalars.py +++ b/tests/scalars/test_scalars.py @@ -287,6 +287,16 @@ def test_interval_math() -> None: pd.Interval, ) + if TYPE_CHECKING_INVALID_USAGE: + _i = interval_i - pd.Interval(1, 2) # type: ignore[type-var] # pyright: ignore[reportOperatorIssue] + _f = interval_f - pd.Interval(1.0, 2.0) # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _ts = interval_ts - pd.Interval( # type: ignore[operator] + pd.Timestamp(2025, 9, 29), pd.Timestamp(2025, 9, 30), closed="both" + ) # pyright: ignore[reportOperatorIssue] + _td = interval_td - pd.Interval( # type: ignore[operator] + pd.Timedelta(1, "ns"), pd.Timedelta(2, "ns") + ) # pyright: ignore[reportOperatorIssue] + def test_interval_cmp(): interval_i = pd.Interval(0, 1, closed="left") diff --git a/tests/series/test_properties.py b/tests/series/test_properties.py new file mode 100644 index 000000000..72cf432a6 --- /dev/null +++ b/tests/series/test_properties.py @@ -0,0 +1,86 @@ +from typing import ( + TYPE_CHECKING, + cast, +) + +import numpy as np +import pandas as pd +from pandas.core.arrays import DatetimeArray +from pandas.core.arrays.base import ExtensionArray +from pandas.core.arrays.interval import IntervalArray +from pandas.core.arrays.timedeltas import TimedeltaArray +from pandas.core.indexes.accessors import ( + DatetimeProperties, + PeriodProperties, + Properties, + TimedeltaProperties, +) +from typing_extensions import assert_type + +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) + +if TYPE_CHECKING: + from pandas.core.indexes.accessors import TimestampProperties # noqa: F401 + + +def test_dt_property() -> None: + """Test the Series.dt property""" + check( + assert_type(pd.Series([pd.Timestamp(2025, 9, 28)]).dt, "TimestampProperties"), + DatetimeProperties, + ) + check( + assert_type(pd.Series([pd.Timedelta(1, "s")]).dt, TimedeltaProperties), + TimedeltaProperties, + ) + check( + assert_type( + pd.period_range(start="2022-06-01", periods=10).to_series().dt, + PeriodProperties, + ), + PeriodProperties, + ) + + if TYPE_CHECKING_INVALID_USAGE: + s = pd.DataFrame({"a": [1]})["a"] + # python/mypy#19952: mypy believes Properties and its subclasses have a + # conflict and gives Any for s.dt + assert_type(s.dt, Properties) # type: ignore[assert-type] + _1 = pd.Series([1]).dt # type: ignore[arg-type] # pyright: ignore[reportAttributeAccessIssue] + + +def test_array_property() -> None: + """Test that Series.array returns ExtensionArray and its subclasses""" + # casting due to pandas-dev/pandas-stubs#1383 + check( + assert_type( + cast( + "pd.Series[pd.CategoricalDtype]", pd.Series([1], dtype="category") + ).array, + pd.Categorical, + ), + pd.Categorical, + int, + ) + check( + assert_type(pd.Series(pd.interval_range(0, 1)).array, IntervalArray), + IntervalArray, + pd.Interval, + ) + check( + assert_type(pd.Series([pd.Timestamp(2025, 9, 28)]).array, DatetimeArray), + DatetimeArray, + pd.Timestamp, + ) + check( + assert_type(pd.Series([pd.Timedelta(1, "s")]).array, TimedeltaArray), + TimedeltaArray, + pd.Timedelta, + ) + check(assert_type(pd.Series([1]).array, ExtensionArray), ExtensionArray, np.integer) + # python/mypy#19952: mypy believes ExtensionArray and its subclasses have a + # conflict and gives Any for s.array + check(assert_type(pd.Series([1, "s"]).array, ExtensionArray), ExtensionArray) # type: ignore[assert-type] diff --git a/tests/series/test_series.py b/tests/series/test_series.py index 6e6c70233..12c75f50d 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -21,6 +21,7 @@ TypedDict, TypeVar, Union, + cast, ) import numpy as np @@ -40,7 +41,6 @@ Never, Self, TypeAlias, - assert_never, assert_type, ) import xarray as xr @@ -3500,47 +3500,51 @@ def test_diff() -> None: BaseOffset, index_to_check_for_type=-1, ) - # bool -> object + # bool -> Any check( assert_type( - pd.Series([True, True, False, False, True]).diff(), - "pd.Series[type[object]]", + pd.Series([True, True, False, False, True]).diff(), "pd.Series[Any]" ), pd.Series, - object, + bool, + index_to_check_for_type=-1, ) - # object -> object + # nullable bool -> nullable bool + # casting due to pandas-dev/pandas-stubs#1395 check( - assert_type(s.astype(object).diff(), "pd.Series[type[object]]"), + assert_type( + cast( + "pd.Series[pd.BooleanDtype]", + pd.Series([True, True, False, False, True], dtype="boolean").diff(), + ), + "pd.Series[pd.BooleanDtype]", + ), pd.Series, - object, + np.bool_, + index_to_check_for_type=-1, ) + # Any -> float + s_o = s.astype(object) + assert_type(s_o, "pd.Series[Any]") + check(assert_type(s_o.diff(), "pd.Series[float]"), pd.Series, float) # complex -> complex check( assert_type(s.astype(complex).diff(), "pd.Series[complex]"), pd.Series, complex ) - if TYPE_CHECKING_INVALID_USAGE: - # interval -> TypeError: IntervalArray has no 'diff' method. Convert to a suitable dtype prior to calling 'diff'. - assert_never(pd.Series([pd.Interval(0, 2), pd.Interval(1, 4)]).diff()) - -def test_diff_never1() -> None: - s = pd.Series([1, 1, 2, 3, 5, 8]) if TYPE_CHECKING_INVALID_USAGE: # bytes -> numpy.core._exceptions._UFuncNoLoopError: ufunc 'subtract' did not contain a loop with signature matching types (dtype('S21'), dtype('S21')) -> None - assert_never(s.astype(bytes).diff()) + pd.Series([1, 1, 2, 3, 5, 8]).astype(bytes).diff() # type: ignore[misc] # pyright: ignore[reportAttributeAccessIssue] - -def test_diff_never2() -> None: - if TYPE_CHECKING_INVALID_USAGE: # dtype -> TypeError: unsupported operand type(s) for -: 'type' and 'type' - assert_never(pd.Series([str, int, bool]).diff()) + pd.Series([str, int, bool]).diff() # type: ignore[misc] # pyright: ignore[reportAttributeAccessIssue] - -def test_diff_never3() -> None: - if TYPE_CHECKING_INVALID_USAGE: # str -> TypeError: unsupported operand type(s) for -: 'str' and 'str' - assert_never(pd.Series(["a", "b"]).diff()) + pd.Series(["a", "b"]).diff() # type: ignore[misc] # pyright: ignore[reportAttributeAccessIssue] + + def _diff_invalid0(): # pyright: ignore[reportUnusedFunction] + # interval -> TypeError: IntervalArray has no 'diff' method. Convert to a suitable dtype prior to calling 'diff'. + assert_type(pd.Series([pd.Interval(0, 2), pd.Interval(1, 4)]).diff(), Never) def test_operator_constistency() -> None: diff --git a/tests/test_extension.py b/tests/test_extension.py index a8e34c171..a12f264d2 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -24,7 +24,9 @@ def test_tolist() -> None: s = pd.Series(data) data1 = [1, 2, 3] s1 = pd.Series(data1) - check(assert_type(s.array.tolist(), list), list) + # python/mypy#19952: mypy believes ExtensionArray and its subclasses have a + # conflict and gives Any for s.array + check(assert_type(s.array.tolist(), list), list) # type: ignore[assert-type] check(assert_type(s1.array.tolist(), list), list) check(assert_type(pd.array([1, 2, 3]).tolist(), list), list) diff --git a/tests/test_timefuncs.py b/tests/test_timefuncs.py index 1e2ac6995..2fdf6a138 100644 --- a/tests/test_timefuncs.py +++ b/tests/test_timefuncs.py @@ -2,9 +2,7 @@ import datetime as dt from typing import ( - TYPE_CHECKING, Optional, - cast, ) from dateutil.relativedelta import ( @@ -49,11 +47,6 @@ Day, ) -if TYPE_CHECKING: - from pandas.core.series import ( - IntervalSeries, - ) - if not PD_LTE_23: from pandas.errors import Pandas4Warning # type: ignore[attr-defined] # pyright: ignore # isort: skip else: @@ -355,10 +348,6 @@ def test_series_dt_accessors() -> None: i0 = pd.date_range(start="2022-06-01", periods=10) check(assert_type(i0, pd.DatetimeIndex), pd.DatetimeIndex, pd.Timestamp) - check( - assert_type(i0.to_series(), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp - ) - s0 = pd.Series(i0) check(assert_type(s0.dt.date, "pd.Series[dt.date]"), pd.Series, dt.date) @@ -531,8 +520,6 @@ def test_series_dt_accessors() -> None: check(assert_type(i1, pd.PeriodIndex), pd.PeriodIndex) - check(assert_type(i1.to_series(), pd.Series), pd.Series, pd.Period) - s1 = pd.Series(i1) check(assert_type(s1.dt.qyear, "pd.Series[int]"), pd.Series, np.integer) @@ -946,7 +933,7 @@ def test_series_types_to_numpy() -> None: ts_s = pd.to_datetime(pd.Series(["2020-01-01", "2020-01-02"])) p_s = pd.Series(pd.period_range("2012-1-1", periods=10, freq="D")) o_s = pd.Series([pd.DateOffset(days=1), pd.DateOffset(days=2)]) - i_s = cast("IntervalSeries", pd.interval_range(1, 2).to_series()) + i_s = pd.interval_range(1, 2).to_series() # default dtype check( @@ -1069,6 +1056,11 @@ def test_series_types_to_numpy() -> None: np_1darray, dtype=np.bytes_, ) + check( + assert_type(i_s.to_numpy(dtype=np.str_), np_1darray[np.str_]), + np_1darray, + dtype=np.str_, + ) def test_index_types_to_numpy() -> None: