From 309fdf46561e8caa9035a12d93c07e045e23af47 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Thu, 2 Oct 2025 10:38:58 +0200 Subject: [PATCH 1/3] feat(array): #624 accumulate --- pandas-stubs/core/arrays/base.pyi | 9 +++- pandas-stubs/core/indexes/accessors.pyi | 8 ++++ pyproject.toml | 2 +- tests/arrays/__init__.py | 0 tests/arrays/test_arrays.py | 39 +++++++++++++++++ tests/series/test_properties.py | 57 ++++++++++++++++--------- 6 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 tests/arrays/__init__.py create mode 100644 tests/arrays/test_arrays.py diff --git a/pandas-stubs/core/arrays/base.pyi b/pandas-stubs/core/arrays/base.pyi index 0d2c54ff2..1172b87ed 100644 --- a/pandas-stubs/core/arrays/base.pyi +++ b/pandas-stubs/core/arrays/base.pyi @@ -1,6 +1,7 @@ from collections.abc import Iterator from typing import ( Any, + Literal, overload, ) @@ -68,7 +69,13 @@ class ExtensionArray: def _reduce( self, name: str, *, skipna: bool = ..., keepdims: bool = ..., **kwargs ) -> object: ... - def _accumulate(self, name: str, *, skipna: bool = ..., **kwargs) -> Self: ... + def _accumulate( + self, + name: Literal["cummin", "cummax", "cumsum", "cumprod"], + *, + skipna: bool = True, + **kwargs, + ) -> Self: ... class ExtensionOpsMixin: @classmethod diff --git a/pandas-stubs/core/indexes/accessors.pyi b/pandas-stubs/core/indexes/accessors.pyi index 0cc058af7..34d3a5c8e 100644 --- a/pandas-stubs/core/indexes/accessors.pyi +++ b/pandas-stubs/core/indexes/accessors.pyi @@ -17,6 +17,10 @@ from pandas.core.accessor import PandasDelegate 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.integer import ( + Int64Dtype, + IntegerArray, +) from pandas.core.arrays.interval import IntervalArray from pandas.core.arrays.period import PeriodArray from pandas.core.arrays.timedeltas import TimedeltaArray @@ -464,6 +468,10 @@ class ArrayDescriptor: self, instance: IndexOpsMixin[Never], owner: type[IndexOpsMixin] ) -> ExtensionArray: ... @overload + def __get__( + self, instance: IndexOpsMixin[Int64Dtype], owner: type[IndexOpsMixin] + ) -> IntegerArray: ... + @overload def __get__( self, instance: IndexOpsMixin[CategoricalDtype], owner: type[IndexOpsMixin] ) -> Categorical: ... diff --git a/pyproject.toml b/pyproject.toml index a4b6430a1..f98dd1195 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ mypy = "1.18.2" pandas = "2.3.2" pyarrow = ">=10.0.1" pytest = ">=8.4.2" -pyright = ">=1.1.405" +pyright = ">=1.1.406" ty = ">=0.0.1a21" pyrefly = ">=0.35.0" poethepoet = ">=0.16.5" diff --git a/tests/arrays/__init__.py b/tests/arrays/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/arrays/test_arrays.py b/tests/arrays/test_arrays.py new file mode 100644 index 000000000..99855e7ad --- /dev/null +++ b/tests/arrays/test_arrays.py @@ -0,0 +1,39 @@ +from typing import cast + +from pandas.core.arrays.integer import ( + Int64Dtype, + IntegerArray, +) +from pandas.core.series import Series +from typing_extensions import assert_type + +from pandas._libs.missing import NA + +from tests import check + + +def test_cumul_int64dtype(): + # cast will be removed if pandas-dev/pandas-stubs#1395 is resolved + arr = cast("Series[Int64Dtype]", Series([1, NA, 2], dtype="Int64")).array + + check(assert_type(arr._accumulate("cummin"), IntegerArray), IntegerArray) + check(assert_type(arr._accumulate("cummax"), IntegerArray), IntegerArray) + check(assert_type(arr._accumulate("cumsum"), IntegerArray), IntegerArray) + check(assert_type(arr._accumulate("cumprod"), IntegerArray), IntegerArray) + + check( + assert_type(arr._accumulate("cummin", skipna=False), IntegerArray), + IntegerArray, + ) + check( + assert_type(arr._accumulate("cummax", skipna=False), IntegerArray), + IntegerArray, + ) + check( + assert_type(arr._accumulate("cumsum", skipna=False), IntegerArray), + IntegerArray, + ) + check( + assert_type(arr._accumulate("cumprod", skipna=False), IntegerArray), + IntegerArray, + ) diff --git a/tests/series/test_properties.py b/tests/series/test_properties.py index 2b76e50db..5894a4bbc 100644 --- a/tests/series/test_properties.py +++ b/tests/series/test_properties.py @@ -1,21 +1,35 @@ 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.categorical import Categorical +from pandas.core.arrays.integer import ( + Int64Dtype, + IntegerArray, +) from pandas.core.arrays.interval import IntervalArray from pandas.core.arrays.timedeltas import TimedeltaArray +from pandas.core.frame import DataFrame from pandas.core.indexes.accessors import ( DatetimeProperties, PeriodProperties, Properties, TimedeltaProperties, ) +from pandas.core.indexes.interval import interval_range +from pandas.core.indexes.period import period_range +from pandas.core.series import Series from typing_extensions import assert_type +from pandas._libs.interval import Interval +from pandas._libs.missing import NA +from pandas._libs.tslibs.timedeltas import Timedelta +from pandas._libs.tslibs.timestamps import Timestamp + from tests import ( TYPE_CHECKING_INVALID_USAGE, check, @@ -25,58 +39,61 @@ from pandas.core.indexes.accessors import TimestampProperties # noqa: F401 -def test_dt_property() -> None: +def test_property_dt() -> None: """Test the Series.dt property""" check( - assert_type(pd.Series([pd.Timestamp(2025, 9, 28)]).dt, "TimestampProperties"), + assert_type(Series([Timestamp(2025, 9, 28)]).dt, "TimestampProperties"), DatetimeProperties, ) check( - assert_type(pd.Series([pd.Timedelta(1, "s")]).dt, TimedeltaProperties), + assert_type(Series([Timedelta(1, "s")]).dt, TimedeltaProperties), TimedeltaProperties, ) check( assert_type( - pd.period_range(start="2022-06-01", periods=10).to_series().dt, + period_range(start="2022-06-01", periods=10).to_series().dt, PeriodProperties, ), PeriodProperties, ) if TYPE_CHECKING_INVALID_USAGE: - s = pd.DataFrame({"a": [1]})["a"] + s = 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] + _1 = Series([1]).dt # type: ignore[arg-type] # pyright: ignore[reportAttributeAccessIssue] -def test_array_property() -> None: +def test_property_array() -> None: """Test that Series.array returns ExtensionArray and its subclasses""" + check( + assert_type(Series([1], dtype="category").array, Categorical), Categorical, int + ) + # cast will be removed if pandas-dev/pandas-stubs#1395 is resolved check( assert_type( - pd.Series([1], dtype="category").array, - pd.Categorical, + cast("Series[Int64Dtype]", Series([1, NA], dtype="Int64")).array, + IntegerArray, ), - pd.Categorical, - int, + IntegerArray, ) check( - assert_type(pd.Series(pd.interval_range(0, 1)).array, IntervalArray), + assert_type(Series(interval_range(0, 1)).array, IntervalArray), IntervalArray, - pd.Interval, + Interval, ) check( - assert_type(pd.Series([pd.Timestamp(2025, 9, 28)]).array, DatetimeArray), + assert_type(Series([Timestamp(2025, 9, 28)]).array, DatetimeArray), DatetimeArray, - pd.Timestamp, + Timestamp, ) check( - assert_type(pd.Series([pd.Timedelta(1, "s")]).array, TimedeltaArray), + assert_type(Series([Timedelta(1, "s")]).array, TimedeltaArray), TimedeltaArray, - pd.Timedelta, + Timedelta, ) - check(assert_type(pd.Series([1]).array, ExtensionArray), ExtensionArray, np.integer) + check(assert_type(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] + check(assert_type(Series([1, "s"]).array, ExtensionArray), ExtensionArray) # type: ignore[assert-type] From cdcb176391bdf350b7ce9b1d785ce70bf47355de Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Thu, 2 Oct 2025 15:40:03 +0200 Subject: [PATCH 2/3] use pd.array directly --- pandas-stubs/core/construction.pyi | 11 +++++++++ tests/arrays/test_arrays.py | 37 ++++-------------------------- tests/arrays/test_cumul.py | 33 ++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 32 deletions(-) create mode 100644 tests/arrays/test_cumul.py diff --git a/pandas-stubs/core/construction.pyi b/pandas-stubs/core/construction.pyi index e70207c19..2372c1edc 100644 --- a/pandas-stubs/core/construction.pyi +++ b/pandas-stubs/core/construction.pyi @@ -1,10 +1,21 @@ from collections.abc import Sequence +from typing import overload import numpy as np from pandas.core.arrays.base import ExtensionArray +from pandas.core.arrays.integer import IntegerArray + +from pandas._libs.missing import NAType from pandas.core.dtypes.dtypes import ExtensionDtype +@overload +def array( + data: Sequence[int] | Sequence[int | NAType], + dtype: str | np.dtype | ExtensionDtype | None = None, + copy: bool = True, +) -> IntegerArray: ... +@overload def array( data: Sequence[object], dtype: str | np.dtype | ExtensionDtype | None = None, diff --git a/tests/arrays/test_arrays.py b/tests/arrays/test_arrays.py index 99855e7ad..fd4cca834 100644 --- a/tests/arrays/test_arrays.py +++ b/tests/arrays/test_arrays.py @@ -1,10 +1,5 @@ -from typing import cast - -from pandas.core.arrays.integer import ( - Int64Dtype, - IntegerArray, -) -from pandas.core.series import Series +from pandas.core.arrays.integer import IntegerArray +from pandas.core.construction import array from typing_extensions import assert_type from pandas._libs.missing import NA @@ -12,28 +7,6 @@ from tests import check -def test_cumul_int64dtype(): - # cast will be removed if pandas-dev/pandas-stubs#1395 is resolved - arr = cast("Series[Int64Dtype]", Series([1, NA, 2], dtype="Int64")).array - - check(assert_type(arr._accumulate("cummin"), IntegerArray), IntegerArray) - check(assert_type(arr._accumulate("cummax"), IntegerArray), IntegerArray) - check(assert_type(arr._accumulate("cumsum"), IntegerArray), IntegerArray) - check(assert_type(arr._accumulate("cumprod"), IntegerArray), IntegerArray) - - check( - assert_type(arr._accumulate("cummin", skipna=False), IntegerArray), - IntegerArray, - ) - check( - assert_type(arr._accumulate("cummax", skipna=False), IntegerArray), - IntegerArray, - ) - check( - assert_type(arr._accumulate("cumsum", skipna=False), IntegerArray), - IntegerArray, - ) - check( - assert_type(arr._accumulate("cumprod", skipna=False), IntegerArray), - IntegerArray, - ) +def test_construction() -> None: + check(assert_type(array([1]), IntegerArray), IntegerArray) + check(assert_type(array([1, NA]), IntegerArray), IntegerArray) diff --git a/tests/arrays/test_cumul.py b/tests/arrays/test_cumul.py new file mode 100644 index 000000000..54a2af323 --- /dev/null +++ b/tests/arrays/test_cumul.py @@ -0,0 +1,33 @@ +from pandas.core.arrays.integer import IntegerArray +from pandas.core.construction import array +from typing_extensions import assert_type + +from pandas._libs.missing import NA + +from tests import check + + +def test_cumul_int64dtype() -> None: + arr = array([1, NA, 2]) + + check(assert_type(arr._accumulate("cummin"), IntegerArray), IntegerArray) + check(assert_type(arr._accumulate("cummax"), IntegerArray), IntegerArray) + check(assert_type(arr._accumulate("cumsum"), IntegerArray), IntegerArray) + check(assert_type(arr._accumulate("cumprod"), IntegerArray), IntegerArray) + + check( + assert_type(arr._accumulate("cummin", skipna=False), IntegerArray), + IntegerArray, + ) + check( + assert_type(arr._accumulate("cummax", skipna=False), IntegerArray), + IntegerArray, + ) + check( + assert_type(arr._accumulate("cumsum", skipna=False), IntegerArray), + IntegerArray, + ) + check( + assert_type(arr._accumulate("cumprod", skipna=False), IntegerArray), + IntegerArray, + ) From 3a6710b6158ff5a615c64208db99e4eec6554c48 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Fri, 3 Oct 2025 16:21:38 +0200 Subject: [PATCH 3/3] fix(comment): remove Series[Int64Dtype] --- .pre-commit-config.yaml | 2 +- pandas-stubs/core/indexes/accessors.pyi | 8 -------- tests/series/test_properties.py | 14 -------------- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a94f5c741..7829f3207 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: hooks: - id: isort - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.2 + rev: v0.13.3 hooks: - id: ruff-check args: [ diff --git a/pandas-stubs/core/indexes/accessors.pyi b/pandas-stubs/core/indexes/accessors.pyi index 34d3a5c8e..0cc058af7 100644 --- a/pandas-stubs/core/indexes/accessors.pyi +++ b/pandas-stubs/core/indexes/accessors.pyi @@ -17,10 +17,6 @@ from pandas.core.accessor import PandasDelegate 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.integer import ( - Int64Dtype, - IntegerArray, -) from pandas.core.arrays.interval import IntervalArray from pandas.core.arrays.period import PeriodArray from pandas.core.arrays.timedeltas import TimedeltaArray @@ -468,10 +464,6 @@ class ArrayDescriptor: self, instance: IndexOpsMixin[Never], owner: type[IndexOpsMixin] ) -> ExtensionArray: ... @overload - def __get__( - self, instance: IndexOpsMixin[Int64Dtype], owner: type[IndexOpsMixin] - ) -> IntegerArray: ... - @overload def __get__( self, instance: IndexOpsMixin[CategoricalDtype], owner: type[IndexOpsMixin] ) -> Categorical: ... diff --git a/tests/series/test_properties.py b/tests/series/test_properties.py index 5894a4bbc..ecba403d5 100644 --- a/tests/series/test_properties.py +++ b/tests/series/test_properties.py @@ -1,16 +1,11 @@ from typing import ( TYPE_CHECKING, - cast, ) import numpy as np from pandas.core.arrays import DatetimeArray from pandas.core.arrays.base import ExtensionArray from pandas.core.arrays.categorical import Categorical -from pandas.core.arrays.integer import ( - Int64Dtype, - IntegerArray, -) from pandas.core.arrays.interval import IntervalArray from pandas.core.arrays.timedeltas import TimedeltaArray from pandas.core.frame import DataFrame @@ -26,7 +21,6 @@ from typing_extensions import assert_type from pandas._libs.interval import Interval -from pandas._libs.missing import NA from pandas._libs.tslibs.timedeltas import Timedelta from pandas._libs.tslibs.timestamps import Timestamp @@ -70,14 +64,6 @@ def test_property_array() -> None: check( assert_type(Series([1], dtype="category").array, Categorical), Categorical, int ) - # cast will be removed if pandas-dev/pandas-stubs#1395 is resolved - check( - assert_type( - cast("Series[Int64Dtype]", Series([1, NA], dtype="Int64")).array, - IntegerArray, - ), - IntegerArray, - ) check( assert_type(Series(interval_range(0, 1)).array, IntervalArray), IntervalArray,