diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b29512938..23c60e403 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,6 +35,9 @@ jobs: - name: Run mypy on 'tests' (using the local stubs) and on the local stubs run: poetry run poe mypy + - name: Run ty on 'pandas-stubs' (using the local stubs) and on the local stubs + run: poetry run poe ty + - name: Run pyright on 'tests' (using the local stubs) and on the local stubs run: poetry run poe pyright diff --git a/pandas-stubs/_typing.pyi b/pandas-stubs/_typing.pyi index f4747598d..c404fc746 100644 --- a/pandas-stubs/_typing.pyi +++ b/pandas-stubs/_typing.pyi @@ -19,6 +19,7 @@ from typing import ( SupportsIndex, TypedDict, TypeVar, + Union, overload, ) @@ -463,6 +464,10 @@ VoidDtypeArg: TypeAlias = ( | Literal["V", "void", "void0"] ) +# DtypeArg specifies all allowable dtypes in a functions its dtype argument +DtypeArg: TypeAlias = Dtype | Mapping[Hashable, Dtype] +DtypeObj: TypeAlias = np.dtype[np.generic] | ExtensionDtype + AstypeArg: TypeAlias = ( BooleanDtypeArg | IntDtypeArg @@ -479,10 +484,6 @@ AstypeArg: TypeAlias = ( | DtypeObj ) -# DtypeArg specifies all allowable dtypes in a functions its dtype argument -DtypeArg: TypeAlias = Dtype | Mapping[Hashable, Dtype] -DtypeObj: TypeAlias = np.dtype[np.generic] | ExtensionDtype - # converters ConvertersArg: TypeAlias = Mapping[Hashable, Callable[[Dtype], Dtype]] @@ -513,7 +514,8 @@ IndexKeyFunc: TypeAlias = Callable[[Index], Index | AnyArrayLike] | None # types of `func` kwarg for DataFrame.aggregate and Series.aggregate # More specific than what is in pandas -AggFuncTypeBase: TypeAlias = Callable | str | np.ufunc +# following Union is here to make it ty compliant https://github.com/astral-sh/ty/issues/591 +AggFuncTypeBase: TypeAlias = Union[Callable, str, np.ufunc] # noqa: UP007 AggFuncTypeDictSeries: TypeAlias = Mapping[HashableT, AggFuncTypeBase] AggFuncTypeDictFrame: TypeAlias = Mapping[ HashableT, AggFuncTypeBase | list[AggFuncTypeBase] diff --git a/pandas-stubs/core/arrays/datetimes.pyi b/pandas-stubs/core/arrays/datetimes.pyi index 27f4ba789..baf4e5be3 100644 --- a/pandas-stubs/core/arrays/datetimes.pyi +++ b/pandas-stubs/core/arrays/datetimes.pyi @@ -1,4 +1,4 @@ -from datetime import tzinfo +from datetime import tzinfo as _tzinfo import numpy as np from pandas.core.arrays.datetimelike import ( @@ -28,7 +28,7 @@ class DatetimeArray(DatetimeLikeArrayMixin, TimelikeOps, DatelikeOps): @tz.setter def tz(self, value) -> None: ... @property - def tzinfo(self) -> tzinfo | None: ... + def tzinfo(self) -> _tzinfo | None: ... @property def is_normalized(self): ... def __array__(self, dtype=...) -> np.ndarray: ... diff --git a/pandas-stubs/core/generic.pyi b/pandas-stubs/core/generic.pyi index 9d521e743..cac7dfa86 100644 --- a/pandas-stubs/core/generic.pyi +++ b/pandas-stubs/core/generic.pyi @@ -69,8 +69,8 @@ class NDFrame(indexing.IndexingMixin): def set_flags( self, *, - copy: bool = ..., - allows_duplicate_labels: bool | None = ..., + copy: _bool = ..., + allows_duplicate_labels: _bool | None = ..., ) -> Self: ... @property def attrs(self) -> dict[Hashable | None, Any]: ... diff --git a/pandas-stubs/core/groupby/groupby.pyi b/pandas-stubs/core/groupby/groupby.pyi index 36a60ce7d..c7af2d4fa 100644 --- a/pandas-stubs/core/groupby/groupby.pyi +++ b/pandas-stubs/core/groupby/groupby.pyi @@ -75,8 +75,6 @@ from pandas._typing import ( from pandas.plotting import PlotAccessor -_GroupByT = TypeVar("_GroupByT", bound=GroupBy) - _KeysArgType: TypeAlias = ( Hashable | list[Hashable] @@ -91,67 +89,6 @@ _ResamplerGroupBy: TypeAlias = ( | TimedeltaIndexResamplerGroupby[NDFrameT] ) -# GroupByPlot does not really inherit from PlotAccessor but it delegates -# to it using __call__ and __getattr__. We lie here to avoid repeating the -# whole stub of PlotAccessor -@final -class GroupByPlot(PlotAccessor, Generic[_GroupByT]): - def __init__(self, groupby: _GroupByT) -> None: ... - # The following methods are inherited from the fake parent class PlotAccessor - # def __call__(self, *args, **kwargs): ... - # def __getattr__(self, name: str): ... - -class BaseGroupBy(SelectionMixin[NDFrameT], GroupByIndexingMixin): - axis: AxisInt - grouper: ops.BaseGrouper - keys: _KeysArgType | None - level: IndexLabel | None - group_keys: bool - @final - def __len__(self) -> int: ... - @final - def __repr__(self) -> str: ... # noqa: PYI029 __repr__ here is final - @final - @property - def groups(self) -> dict[Hashable, Index]: ... - @final - @property - def ngroups(self) -> int: ... - @final - @property - def indices(self) -> dict[Hashable, Index | npt.NDArray[np.int_] | list[int]]: ... - @overload - def pipe( - self, - func: Callable[Concatenate[Self, P], T], - *args: P.args, - **kwargs: P.kwargs, - ) -> T: ... - @overload - def pipe( - self, - func: tuple[Callable[..., T], str], - *args: Any, - **kwargs: Any, - ) -> T: ... - @final - def get_group(self, name, obj: NDFrameT | None = ...) -> NDFrameT: ... - @final - def __iter__(self) -> Iterator[tuple[Hashable, NDFrameT]]: ... - @overload - def __getitem__(self: BaseGroupBy[DataFrame], key: Scalar) -> generic.SeriesGroupBy: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] - @overload - def __getitem__( - self: BaseGroupBy[DataFrame], key: Iterable[Hashable] - ) -> generic.DataFrameGroupBy: ... - @overload - def __getitem__( - self: BaseGroupBy[Series[S1]], - idx: list[str] | Index | Series[S1] | MaskType | tuple[Hashable | slice, ...], - ) -> generic.SeriesGroupBy: ... - @overload - def __getitem__(self: BaseGroupBy[Series[S1]], idx: Scalar) -> S1: ... - class GroupBy(BaseGroupBy[NDFrameT]): as_index: bool sort: bool @@ -393,3 +330,66 @@ class GroupBy(BaseGroupBy[NDFrameT]): weights: Sequence | Series | None = ..., random_state: RandomState | None = ..., ) -> NDFrameT: ... + +_GroupByT = TypeVar("_GroupByT", bound=GroupBy) + +# GroupByPlot does not really inherit from PlotAccessor but it delegates +# to it using __call__ and __getattr__. We lie here to avoid repeating the +# whole stub of PlotAccessor +@final +class GroupByPlot(PlotAccessor, Generic[_GroupByT]): + def __init__(self, groupby: _GroupByT) -> None: ... + # The following methods are inherited from the fake parent class PlotAccessor + # def __call__(self, *args, **kwargs): ... + # def __getattr__(self, name: str): ... + +class BaseGroupBy(SelectionMixin[NDFrameT], GroupByIndexingMixin): + axis: AxisInt + grouper: ops.BaseGrouper + keys: _KeysArgType | None + level: IndexLabel | None + group_keys: bool + @final + def __len__(self) -> int: ... + @final + def __repr__(self) -> str: ... # noqa: PYI029 __repr__ here is final + @final + @property + def groups(self) -> dict[Hashable, Index]: ... + @final + @property + def ngroups(self) -> int: ... + @final + @property + def indices(self) -> dict[Hashable, Index | npt.NDArray[np.int_] | list[int]]: ... + @overload + def pipe( + self, + func: Callable[Concatenate[Self, P], T], + *args: P.args, + **kwargs: P.kwargs, + ) -> T: ... + @overload + def pipe( + self, + func: tuple[Callable[..., T], str], + *args: Any, + **kwargs: Any, + ) -> T: ... + @final + def get_group(self, name, obj: NDFrameT | None = ...) -> NDFrameT: ... + @final + def __iter__(self) -> Iterator[tuple[Hashable, NDFrameT]]: ... + @overload + def __getitem__(self: BaseGroupBy[DataFrame], key: Scalar) -> generic.SeriesGroupBy: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] + @overload + def __getitem__( + self: BaseGroupBy[DataFrame], key: Iterable[Hashable] + ) -> generic.DataFrameGroupBy: ... + @overload + def __getitem__( + self: BaseGroupBy[Series[S1]], + idx: list[str] | Index | Series[S1] | MaskType | tuple[Hashable | slice, ...], + ) -> generic.SeriesGroupBy: ... + @overload + def __getitem__(self: BaseGroupBy[Series[S1]], idx: Scalar) -> S1: ... diff --git a/pandas-stubs/core/indexes/accessors.pyi b/pandas-stubs/core/indexes/accessors.pyi index c861e97e4..b5db84dba 100644 --- a/pandas-stubs/core/indexes/accessors.pyi +++ b/pandas-stubs/core/indexes/accessors.pyi @@ -1,7 +1,7 @@ import datetime as dt from datetime import ( timedelta, - tzinfo, + tzinfo as _tzinfo, ) from typing import ( Generic, @@ -119,7 +119,7 @@ class _FreqProperty(Generic[_DTFreqReturnType]): class _TZProperty: @property - def tz(self) -> tzinfo | None: ... + def tz(self) -> _tzinfo | None: ... class _DatetimeObjectOps( _FreqProperty[_DTFreqReturnType], _TZProperty, Generic[_DTFreqReturnType] @@ -416,7 +416,7 @@ class DatetimeIndexProperties( @property def is_normalized(self) -> bool: ... @property - def tzinfo(self) -> tzinfo | None: ... + def tzinfo(self) -> _tzinfo | None: ... def to_pydatetime(self) -> npt.NDArray[np.object_]: ... def std( self, axis: int | None = ..., ddof: int = ..., skipna: bool = ... diff --git a/pandas-stubs/core/indexes/base.pyi b/pandas-stubs/core/indexes/base.pyi index b2a714b56..ba5ab8467 100644 --- a/pandas-stubs/core/indexes/base.pyi +++ b/pandas-stubs/core/indexes/base.pyi @@ -272,10 +272,10 @@ class Index(IndexOpsMixin[S1]): Self, MultiIndex, np_ndarray_bool, - Index[list[str]], + Index[list[_str]], Index[int], Index[bytes], - Index[str], + Index[_str], Index[type[object]], ]: ... def is_(self, other) -> bool: ... @@ -478,7 +478,7 @@ class Index(IndexOpsMixin[S1]): UnknownIndex: TypeAlias = Index[Any] def ensure_index_from_sequences( - sequences: Sequence[Sequence[Dtype]], names: list[str] = ... + sequences: Sequence[Sequence[Dtype]], names: list[_str] = ... ) -> Index: ... def ensure_index(index_like: Sequence | Index, copy: bool = ...) -> Index: ... def maybe_extract_name(name, obj, cls) -> Label: ... diff --git a/pandas-stubs/core/indexes/datetimes.pyi b/pandas-stubs/core/indexes/datetimes.pyi index 352d38c81..4aa810c21 100644 --- a/pandas-stubs/core/indexes/datetimes.pyi +++ b/pandas-stubs/core/indexes/datetimes.pyi @@ -5,7 +5,7 @@ from collections.abc import ( from datetime import ( datetime, timedelta, - tzinfo, + tzinfo as _tzinfo, ) from typing import overload @@ -85,7 +85,7 @@ class DatetimeIndex(DatetimeTimedeltaMixin[Timestamp], DatetimeIndexProperties): def to_julian_date(self) -> Index[float]: ... def isocalendar(self) -> DataFrame: ... @property - def tzinfo(self) -> tzinfo | None: ... + def tzinfo(self) -> _tzinfo | None: ... @property def dtype(self) -> np.dtype | DatetimeTZDtype: ... def shift(self, periods: int = ..., freq=...) -> Self: ... diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 9552d4076..8ea4612b5 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -158,7 +158,7 @@ from pandas._typing import ( StrDtypeArg, StrLike, Suffixes, - T, + T as _T, TimeAmbiguous, TimedeltaDtypeArg, TimestampDtypeArg, @@ -179,8 +179,8 @@ from pandas.core.dtypes.dtypes import CategoricalDtype from pandas.plotting import PlotAccessor -_bool = bool -_str = str +_bool: TypeAlias = bool +_str: TypeAlias = str class _iLocIndexerSeries(_iLocIndexer, Generic[S1]): # get item @@ -213,7 +213,7 @@ class _LocIndexerSeries(_LocIndexer, Generic[S1]): idx: ( MaskType | Index - | SequenceNotStr[float | str | Timestamp] + | SequenceNotStr[float | _str | Timestamp] | slice | _IndexSliceTuple | Sequence[_IndexSliceTuple] @@ -231,7 +231,7 @@ class _LocIndexerSeries(_LocIndexer, Generic[S1]): @overload def __setitem__( self, - idx: str, + idx: _str, value: S1 | None, ) -> None: ... @overload @@ -269,21 +269,21 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __new__( cls, - data: Sequence[list[str]], + data: Sequence[list[_str]], index: Axes | None = ..., dtype: Dtype = ..., name: Hashable = ..., copy: bool = ..., - ) -> Series[list[str]]: ... + ) -> Series[list[_str]]: ... @overload def __new__( cls, - data: Sequence[str], + data: Sequence[_str], index: Axes | None = ..., dtype: Dtype = ..., name: Hashable = ..., copy: bool = ..., - ) -> Series[str]: ... + ) -> Series[_str]: ... @overload def __new__( cls, @@ -478,7 +478,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def get(self, key: Hashable, default: S1) -> S1: ... @overload - def get(self, key: Hashable, default: T) -> S1 | T: ... + def get(self, key: Hashable, default: _T) -> S1 | _T: ... def repeat( self, repeats: int | list[int], axis: AxisIndex | None = ... ) -> Series[S1]: ... @@ -519,7 +519,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def to_string( self, - buf: FilePath | WriteBuffer[str], + buf: FilePath | WriteBuffer[_str], na_rep: _str = ..., float_format: FloatFormatType = ..., header: _bool = ..., @@ -547,7 +547,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def to_json( self, - path_or_buf: FilePath | WriteBuffer[str], + path_or_buf: FilePath | WriteBuffer[_str], *, orient: Literal["records"], date_format: Literal["epoch", "iso"] | None = ..., @@ -581,7 +581,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def to_json( self, - path_or_buf: FilePath | WriteBuffer[str] | WriteBuffer[bytes], + path_or_buf: FilePath | WriteBuffer[_str] | WriteBuffer[bytes], orient: JsonSeriesOrient | None = ..., date_format: Literal["epoch", "iso"] | None = ..., double_precision: int = ..., @@ -802,7 +802,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def diff(self: Series[type], periods: int = ...) -> Never: ... @overload - def diff(self: Series[str], periods: int = ...) -> Never: ... + def diff(self: Series[_str], periods: int = ...) -> Never: ... @overload def diff(self, periods: int = ...) -> Series[float]: ... def autocorr(self, lag: int = ...) -> float: ... @@ -1129,7 +1129,7 @@ class Series(IndexOpsMixin[S1], NDFrame): def info( self, verbose: bool | None = ..., - buf: WriteBuffer[str] | None = ..., + buf: WriteBuffer[_str] | None = ..., memory_usage: bool | Literal["deep"] | None = ..., show_counts: bool | None = ..., ) -> None: ... @@ -1177,10 +1177,10 @@ class Series(IndexOpsMixin[S1], NDFrame): Self, DataFrame, Series[bool], - Series[list[str]], + Series[list[_str]], Series[int], Series[bytes], - Series[str], + Series[_str], Series[type[object]], ]: ... @property diff --git a/pandas-stubs/core/strings.pyi b/pandas-stubs/core/strings.pyi index 16eae157d..74588eae8 100644 --- a/pandas-stubs/core/strings.pyi +++ b/pandas-stubs/core/strings.pyi @@ -8,6 +8,7 @@ from typing import ( Any, Generic, Literal, + TypeAlias, TypeVar, overload, ) @@ -46,12 +47,14 @@ _T_STR = TypeVar("_T_STR", bound=Series[str] | Index[str]) # Used for the result of str.partition _T_OBJECT = TypeVar("_T_OBJECT", bound=Series[type[object]] | Index[type[object]]) +_slice: TypeAlias = slice + class StringMethods( NoNewAttributesMixin, Generic[T, _T_EXPANDING, _T_BOOL, _T_LIST_STR, _T_INT, _T_BYTES, _T_STR, _T_OBJECT], ): def __init__(self, data: T) -> None: ... - def __getitem__(self, key: slice | int) -> _T_STR: ... + def __getitem__(self, key: _slice | int) -> _T_STR: ... def __iter__(self) -> _T_STR: ... @overload def cat( diff --git a/pandas-stubs/io/formats/style_render.pyi b/pandas-stubs/io/formats/style_render.pyi index a46ee60b1..15f4bcd30 100644 --- a/pandas-stubs/io/formats/style_render.pyi +++ b/pandas-stubs/io/formats/style_render.pyi @@ -8,7 +8,11 @@ from typing import ( TypedDict, ) -import jinja2 +from jinja2.environment import ( + Environment, + Template, +) +from jinja2.loaders import PackageLoader from pandas import Index from pandas.core.indexing import _IndexSlice from typing_extensions import ( @@ -46,12 +50,12 @@ CSSStyles: TypeAlias = list[CSSDict] Subset: TypeAlias = _IndexSlice | slice | tuple[slice, ...] | list[HashableT] | Index class StylerRenderer: - loader: jinja2.loaders.PackageLoader - env: jinja2.environment.Environment - template_html: jinja2.environment.Template - template_html_table: jinja2.environment.Template - template_html_style: jinja2.environment.Template - template_latex: jinja2.environment.Template + loader: PackageLoader + env: Environment + template_html: Template + template_html_table: Template + template_html_style: Template + template_latex: Template def format( self, formatter: ExtFormatter | None = ..., diff --git a/pandas-stubs/plotting/_core.pyi b/pandas-stubs/plotting/_core.pyi index d46bfc293..df3237fa2 100644 --- a/pandas-stubs/plotting/_core.pyi +++ b/pandas-stubs/plotting/_core.pyi @@ -18,7 +18,7 @@ import numpy as np import pandas as pd from pandas import Series from pandas.core.frame import DataFrame -from scipy.stats.kde import gaussian_kde +from scipy.stats import gaussian_kde from typing_extensions import TypeAlias from pandas._typing import ( diff --git a/pyproject.toml b/pyproject.toml index 9779b2325..4b879f2f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ packages = [{ "include" = "pandas-stubs" }] python = ">=3.10" types-pytz = ">= 2022.1.1" numpy = ">= 1.23.5" +ty = "^0.0.1a8" [tool.poetry.group.dev.dependencies] mypy = "1.16.0" @@ -103,6 +104,10 @@ script = "scripts.test:mypy_src(mypy_nightly)" help = "Run mypy on 'tests' using the installed stubs" script = "scripts.test:test(dist=True, type_checker='mypy')" +[tool.poe.tasks.ty] +help = "Run ty on pandas-stubs" +script = "scripts.test.run:ty" + [tool.poe.tasks.pyright] help = "Run pyright on 'tests' (using the local stubs) and on the local stubs" script = "scripts.test.run:pyright_src" @@ -234,3 +239,6 @@ filterwarnings = [ # Next line needed to avoid poetry complaint [tool.setuptools_scm] + +[tool.ty.rules] +unresolved-import = "ignore" diff --git a/scripts/test/__init__.py b/scripts/test/__init__.py index 5ae28dc6d..6363a3d83 100644 --- a/scripts/test/__init__.py +++ b/scripts/test/__init__.py @@ -5,7 +5,13 @@ from scripts._job import run_job from scripts.test import _step -_SRC_STEPS = [_step.mypy_src, _step.pyright_src, _step.pytest, _step.style] +_SRC_STEPS = [ + _step.mypy_src, + _step.ty_src, + _step.pyright_src, + _step.pytest, + _step.style, +] _DIST_STEPS = [ _step.build_dist, _step.install_dist, diff --git a/scripts/test/_step.py b/scripts/test/_step.py index 5bd077174..127ae9b60 100644 --- a/scripts/test/_step.py +++ b/scripts/test/_step.py @@ -5,6 +5,10 @@ name="Run mypy on 'tests' (using the local stubs) and on the local stubs", run=run.mypy_src, ) +ty_src = Step( + name="Run ty on 'pandas-stubs' (using the local stubs) and on the local stubs", + run=run.ty, +) pyright_src = Step( name="Run pyright on 'tests' (using the local stubs) and on the local stubs", run=run.pyright_src, diff --git a/scripts/test/run.py b/scripts/test/run.py index 24ca194c5..a8a9a8758 100644 --- a/scripts/test/run.py +++ b/scripts/test/run.py @@ -151,3 +151,8 @@ def released_mypy(): "warn_unused_ignores = false", "warn_unused_ignores = true" ) ) + + +def ty(): + cmd = ["ty", "check", "pandas-stubs"] + subprocess.run(cmd, check=True)