From 0a91b98d5a63a183a56886eac859da397652f8cd Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Tue, 19 Sep 2023 21:58:24 -0700 Subject: [PATCH 1/8] Convert `Variable` to use `Self` for typing I wanted to do this separately, as it's the only one that adds some casts. And given the ratio of impact-to-potential-merge-conflicts, I didn't want to slow the other PR down, even if it seems to be OK. --- xarray/core/indexes.py | 65 +++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index 9972896d6df..7240ce882ad 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -4,7 +4,7 @@ import copy from collections import defaultdict from collections.abc import Hashable, Iterable, Iterator, Mapping, Sequence -from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, cast import numpy as np import pandas as pd @@ -24,7 +24,7 @@ ) if TYPE_CHECKING: - from xarray.core.types import ErrorOptions, JoinOptions, T_Index + from xarray.core.types import ErrorOptions, JoinOptions, Self, T_Index from xarray.core.variable import Variable @@ -60,11 +60,11 @@ class Index: @classmethod def from_variables( - cls: type[T_Index], + cls, variables: Mapping[Any, Variable], *, options: Mapping[str, Any], - ) -> T_Index: + ) -> Self: """Create a new index object from one or more coordinate variables. This factory method must be implemented in all subclasses of Index. @@ -88,11 +88,11 @@ def from_variables( @classmethod def concat( - cls: type[T_Index], + cls, indexes: Sequence[T_Index], dim: Hashable, positions: Iterable[Iterable[int]] | None = None, - ) -> T_Index: + ) -> Self: """Create a new index by concatenating one or more indexes of the same type. @@ -120,9 +120,7 @@ def concat( raise NotImplementedError() @classmethod - def stack( - cls: type[T_Index], variables: Mapping[Any, Variable], dim: Hashable - ) -> T_Index: + def stack(cls, variables: Mapping[Any, Variable], dim: Hashable) -> Self: """Create a new index by stacking coordinate variables into a single new dimension. @@ -208,8 +206,8 @@ def to_pandas_index(self) -> pd.Index: raise TypeError(f"{self!r} cannot be cast to a pandas.Index object") def isel( - self: T_Index, indexers: Mapping[Any, int | slice | np.ndarray | Variable] - ) -> T_Index | None: + self, indexers: Mapping[Any, int | slice | np.ndarray | Variable] + ) -> Self | None: """Maybe returns a new index from the current index itself indexed by positional indexers. @@ -264,7 +262,7 @@ def sel(self, labels: dict[Any, Any]) -> IndexSelResult: """ raise NotImplementedError(f"{self!r} doesn't support label-based selection") - def join(self: T_Index, other: T_Index, how: JoinOptions = "inner") -> T_Index: + def join(self, other: T_Index, how: JoinOptions = "inner") -> Self: """Return a new index from the combination of this index with another index of the same type. @@ -286,7 +284,7 @@ def join(self: T_Index, other: T_Index, how: JoinOptions = "inner") -> T_Index: f"{self!r} doesn't support alignment with inner/outer join method" ) - def reindex_like(self: T_Index, other: T_Index) -> dict[Hashable, Any]: + def reindex_like(self, other: T_Index) -> dict[Hashable, Any]: """Query the index with another index of the same type. Implementation is optional but required in order to support alignment. @@ -304,7 +302,7 @@ def reindex_like(self: T_Index, other: T_Index) -> dict[Hashable, Any]: """ raise NotImplementedError(f"{self!r} doesn't support re-indexing labels") - def equals(self: T_Index, other: T_Index) -> bool: + def equals(self, other: T_Index) -> bool: """Compare this index with another index of the same type. Implementation is optional but required in order to support alignment. @@ -321,7 +319,7 @@ def equals(self: T_Index, other: T_Index) -> bool: """ raise NotImplementedError() - def roll(self: T_Index, shifts: Mapping[Any, int]) -> T_Index | None: + def roll(self, shifts: Mapping[Any, int]) -> Self | None: """Roll this index by an offset along one or more dimensions. This method can be re-implemented in subclasses of Index, e.g., when the @@ -347,10 +345,10 @@ def roll(self: T_Index, shifts: Mapping[Any, int]) -> T_Index | None: return None def rename( - self: T_Index, + self, name_dict: Mapping[Any, Hashable], dims_dict: Mapping[Any, Hashable], - ) -> T_Index: + ) -> Self: """Maybe update the index with new coordinate and dimension names. This method should be re-implemented in subclasses of Index if it has @@ -377,7 +375,7 @@ def rename( """ return self - def copy(self: T_Index, deep: bool = True) -> T_Index: + def copy(self, deep: bool = True) -> Self: """Return a (deep) copy of this index. Implementation in subclasses of Index is optional. The base class @@ -396,15 +394,13 @@ def copy(self: T_Index, deep: bool = True) -> T_Index: """ return self._copy(deep=deep) - def __copy__(self: T_Index) -> T_Index: + def __copy__(self) -> Self: return self.copy(deep=False) def __deepcopy__(self, memo: dict[int, Any] | None = None) -> Index: return self._copy(deep=True, memo=memo) - def _copy( - self: T_Index, deep: bool = True, memo: dict[int, Any] | None = None - ) -> T_Index: + def _copy(self, deep: bool = True, memo: dict[int, Any] | None = None) -> Self: cls = self.__class__ copied = cls.__new__(cls) if deep: @@ -414,7 +410,7 @@ def _copy( copied.__dict__.update(self.__dict__) return copied - def __getitem__(self: T_Index, indexer: Any) -> T_Index: + def __getitem__(self, indexer: Any) -> Self: raise NotImplementedError() def _repr_inline_(self, max_width): @@ -674,10 +670,11 @@ def _concat_indexes(indexes, dim, positions=None) -> pd.Index: @classmethod def concat( cls, - indexes: Sequence[PandasIndex], + # Currently only compatible with PandasIndex, which fails the Liskov substitution principle + indexes: Sequence[Self], # type: ignore[override] dim: Hashable, positions: Iterable[Iterable[int]] | None = None, - ) -> PandasIndex: + ) -> Self: new_pd_index = cls._concat_indexes(indexes, dim, positions) if not indexes: @@ -800,7 +797,12 @@ def equals(self, other: Index): return False return self.index.equals(other.index) and self.dim == other.dim - def join(self: PandasIndex, other: PandasIndex, how: str = "inner") -> PandasIndex: + def join( + self, + # Currently only compatible with PandasIndex, which fails the Liskov substitution principle + other: Self, # type: ignore[override] + how: str = "inner", + ) -> Self: if how == "outer": index = self.index.union(other.index) else: @@ -810,8 +812,12 @@ def join(self: PandasIndex, other: PandasIndex, how: str = "inner") -> PandasInd coord_dtype = np.result_type(self.coord_dtype, other.coord_dtype) return type(self)(index, self.dim, coord_dtype=coord_dtype) - def reindex_like( - self, other: PandasIndex, method=None, tolerance=None + # Currently only compatible with PandasIndex, which fails the Liskov substitution principle + def reindex_like( # type: ignore[override] + self, + other: Self, + method=None, + tolerance=None, ) -> dict[Hashable, Any]: if not self.index.is_unique: raise ValueError( @@ -1602,7 +1608,7 @@ def to_pandas_indexes(self) -> Indexes[pd.Index]: return Indexes(indexes, self._variables, index_type=pd.Index) def copy_indexes( - self, deep: bool = True, memo: dict[int, Any] | None = None + self, deep: bool = True, memo: dict[int, T_PandasOrXarrayIndex] | None = None ) -> tuple[dict[Hashable, T_PandasOrXarrayIndex], dict[Hashable, Variable]]: """Return a new dictionary with copies of indexes, preserving unique indexes. @@ -1619,6 +1625,7 @@ def copy_indexes( new_indexes = {} new_index_vars = {} + idx: T_PandasOrXarrayIndex for idx, coords in self.group_by_index(): if isinstance(idx, pd.Index): convert_new_idx = True From 91b4b5b673408231343955a5dab69f028e35350a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 05:01:05 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray/core/indexes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index 7240ce882ad..e5b00b1f539 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -4,7 +4,7 @@ import copy from collections import defaultdict from collections.abc import Hashable, Iterable, Iterator, Mapping, Sequence -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast import numpy as np import pandas as pd From a4ebad02d675c04629e21bc776b90007b3937eb8 Mon Sep 17 00:00:00 2001 From: Maximilian Roos <5635139+max-sixty@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:28:44 -0700 Subject: [PATCH 3/8] Update xarray/core/indexes.py Co-authored-by: Michael Niklas --- xarray/core/indexes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index e5b00b1f539..116051257f4 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -302,7 +302,7 @@ def reindex_like(self, other: T_Index) -> dict[Hashable, Any]: """ raise NotImplementedError(f"{self!r} doesn't support re-indexing labels") - def equals(self, other: T_Index) -> bool: + def equals(self, other: Self) -> bool: """Compare this index with another index of the same type. Implementation is optional but required in order to support alignment. From f87fbb75444ea6716468a93f1d1a8097b4b1ffd0 Mon Sep 17 00:00:00 2001 From: Maximilian Roos <5635139+max-sixty@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:29:02 -0700 Subject: [PATCH 4/8] Update xarray/core/indexes.py Co-authored-by: Michael Niklas --- xarray/core/indexes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index 116051257f4..1e3b96d6e09 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -262,7 +262,7 @@ def sel(self, labels: dict[Any, Any]) -> IndexSelResult: """ raise NotImplementedError(f"{self!r} doesn't support label-based selection") - def join(self, other: T_Index, how: JoinOptions = "inner") -> Self: + def join(self, other: Self, how: JoinOptions = "inner") -> Self: """Return a new index from the combination of this index with another index of the same type. From 92b565e380fe14575717fa2f80b4b26e3989b814 Mon Sep 17 00:00:00 2001 From: Maximilian Roos <5635139+max-sixty@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:43:18 -0700 Subject: [PATCH 5/8] Update xarray/core/indexes.py Co-authored-by: Michael Niklas --- xarray/core/indexes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index 1e3b96d6e09..a6454eb5a8f 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -284,7 +284,7 @@ def join(self, other: Self, how: JoinOptions = "inner") -> Self: f"{self!r} doesn't support alignment with inner/outer join method" ) - def reindex_like(self, other: T_Index) -> dict[Hashable, Any]: + def reindex_like(self, other: Self) -> dict[Hashable, Any]: """Query the index with another index of the same type. Implementation is optional but required in order to support alignment. From b37578e2a75ad4ba803da9691c1c9344c24ba281 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Wed, 20 Sep 2023 10:46:06 -0700 Subject: [PATCH 6/8] --- xarray/core/indexes.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index a6454eb5a8f..90a0af4f174 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -24,7 +24,7 @@ ) if TYPE_CHECKING: - from xarray.core.types import ErrorOptions, JoinOptions, Self, T_Index + from xarray.core.types import ErrorOptions, JoinOptions, Self from xarray.core.variable import Variable @@ -89,7 +89,7 @@ def from_variables( @classmethod def concat( cls, - indexes: Sequence[T_Index], + indexes: Sequence[Self], dim: Hashable, positions: Iterable[Iterable[int]] | None = None, ) -> Self: @@ -670,8 +670,7 @@ def _concat_indexes(indexes, dim, positions=None) -> pd.Index: @classmethod def concat( cls, - # Currently only compatible with PandasIndex, which fails the Liskov substitution principle - indexes: Sequence[Self], # type: ignore[override] + indexes: Sequence[Self], dim: Hashable, positions: Iterable[Iterable[int]] | None = None, ) -> Self: @@ -799,8 +798,7 @@ def equals(self, other: Index): def join( self, - # Currently only compatible with PandasIndex, which fails the Liskov substitution principle - other: Self, # type: ignore[override] + other: Self, how: str = "inner", ) -> Self: if how == "outer": @@ -812,8 +810,7 @@ def join( coord_dtype = np.result_type(self.coord_dtype, other.coord_dtype) return type(self)(index, self.dim, coord_dtype=coord_dtype) - # Currently only compatible with PandasIndex, which fails the Liskov substitution principle - def reindex_like( # type: ignore[override] + def reindex_like( self, other: Self, method=None, @@ -969,12 +966,12 @@ def from_variables( return obj @classmethod - def concat( # type: ignore[override] + def concat( cls, - indexes: Sequence[PandasMultiIndex], + indexes: Sequence[Self], dim: Hashable, positions: Iterable[Iterable[int]] | None = None, - ) -> PandasMultiIndex: + ) -> Self: new_pd_index = cls._concat_indexes(indexes, dim, positions) if not indexes: From 469f8b8beca5430f94e5bda42d81ba090cdbf6d4 Mon Sep 17 00:00:00 2001 From: Maximilian Roos <5635139+max-sixty@users.noreply.github.com> Date: Wed, 20 Sep 2023 13:26:32 -0700 Subject: [PATCH 7/8] Update xarray/core/indexes.py Co-authored-by: Michael Niklas --- xarray/core/indexes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index 90a0af4f174..9a03079e9c2 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -814,7 +814,7 @@ def reindex_like( self, other: Self, method=None, - tolerance=None, + tolerance=None ) -> dict[Hashable, Any]: if not self.index.is_unique: raise ValueError( From 4864ed366328513bf4e78831516fee3319c143ee Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:27:06 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray/core/indexes.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index 9a03079e9c2..1697762f7ae 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -811,10 +811,7 @@ def join( return type(self)(index, self.dim, coord_dtype=coord_dtype) def reindex_like( - self, - other: Self, - method=None, - tolerance=None + self, other: Self, method=None, tolerance=None ) -> dict[Hashable, Any]: if not self.index.is_unique: raise ValueError(