Skip to content

Commit e4fe194

Browse files
headtr1ckjhammanmathausespencerkclarkpre-commit-ci[bot]
authored
Add parse_dims func (#7051)
* add parse_dims func * add some more tests for tuples * add parse_ordered_dims * fix typing issue * remove double ellipsis typehints * fix gen aggrs * remove more double ellipsis typehints * fix doctests: supress urllib3 warning (#7326) * Enable `origin` and `offset` arguments in `resample` (#7284) * Initial work toward enabling origin and offset arguments in resample * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix _convert_offset_to_timedelta * Reduce number of tests * Address initial review comments * Add more typing information * Make cftime import lazy * Fix module_available import and test * Remove old origin argument * Add type annotations for resample_cftime.py * Add None as a possibility for closed and label * Add what's new entry * Add missing type annotation * Delete added line * Fix typing errors * Add comment and test for as_timedelta stub * Remove old code * [test-upstream] Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Deepak Cherian <[email protected]> * Fix PR number in what’s new (#7331) * [pre-commit.ci] pre-commit autoupdate (#7330) updates: - [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](pre-commit/pre-commit-hooks@v4.3.0...v4.4.0) - [github.com/PyCQA/autoflake: v1.7.7 → v2.0.0](PyCQA/autoflake@v1.7.7...v2.0.0) - [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](PyCQA/flake8@5.0.4...6.0.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Mathias Hauser <[email protected]> * add comment explaining type: ignore * fix doctest win/linux issue once again Co-authored-by: Joe Hamman <[email protected]> Co-authored-by: Mathias Hauser <[email protected]> Co-authored-by: Spencer Clark <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Deepak Cherian <[email protected]>
1 parent 675a3ff commit e4fe194

11 files changed

+383
-142
lines changed

xarray/core/_aggregations.py

+112-112
Large diffs are not rendered by default.

xarray/core/dataarray.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -3614,7 +3614,7 @@ def combine_first(self: T_DataArray, other: T_DataArray) -> T_DataArray:
36143614
def reduce(
36153615
self: T_DataArray,
36163616
func: Callable[..., Any],
3617-
dim: Dims | ellipsis = None,
3617+
dim: Dims = None,
36183618
*,
36193619
axis: int | Sequence[int] | None = None,
36203620
keep_attrs: bool | None = None,
@@ -4601,7 +4601,7 @@ def imag(self: T_DataArray) -> T_DataArray:
46014601
def dot(
46024602
self: T_DataArray,
46034603
other: T_DataArray,
4604-
dims: Dims | ellipsis = None,
4604+
dims: Dims = None,
46054605
) -> T_DataArray:
46064606
"""Perform dot product of two DataArrays along their shared dims.
46074607
@@ -5605,7 +5605,7 @@ def idxmax(
56055605
# https://github.com/python/mypy/issues/12846 is resolved
56065606
def argmin(
56075607
self,
5608-
dim: Dims | ellipsis = None,
5608+
dim: Dims = None,
56095609
axis: int | None = None,
56105610
keep_attrs: bool | None = None,
56115611
skipna: bool | None = None,
@@ -5707,7 +5707,7 @@ def argmin(
57075707
# https://github.com/python/mypy/issues/12846 is resolved
57085708
def argmax(
57095709
self,
5710-
dim: Dims | ellipsis = None,
5710+
dim: Dims = None,
57115711
axis: int | None = None,
57125712
keep_attrs: bool | None = None,
57135713
skipna: bool | None = None,

xarray/core/dataset.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5798,7 +5798,7 @@ def combine_first(self: T_Dataset, other: T_Dataset) -> T_Dataset:
57985798
def reduce(
57995799
self: T_Dataset,
58005800
func: Callable,
5801-
dim: Dims | ellipsis = None,
5801+
dim: Dims = None,
58025802
*,
58035803
keep_attrs: bool | None = None,
58045804
keepdims: bool = False,

xarray/core/groupby.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ def map(
493493
def reduce(
494494
self,
495495
func: Callable[..., Any],
496-
dim: Dims | ellipsis = None,
496+
dim: Dims = None,
497497
*,
498498
axis: int | Sequence[int] | None = None,
499499
keep_attrs: bool | None = None,
@@ -652,7 +652,7 @@ def _maybe_unstack(self, obj):
652652

653653
def _flox_reduce(
654654
self,
655-
dim: Dims | ellipsis,
655+
dim: Dims,
656656
keep_attrs: bool | None = None,
657657
**kwargs: Any,
658658
):
@@ -1143,7 +1143,7 @@ def _combine(self, applied, shortcut=False):
11431143
def reduce(
11441144
self,
11451145
func: Callable[..., Any],
1146-
dim: Dims | ellipsis = None,
1146+
dim: Dims = None,
11471147
*,
11481148
axis: int | Sequence[int] | None = None,
11491149
keep_attrs: bool | None = None,
@@ -1296,7 +1296,7 @@ def _combine(self, applied):
12961296
def reduce(
12971297
self,
12981298
func: Callable[..., Any],
1299-
dim: Dims | ellipsis = None,
1299+
dim: Dims = None,
13001300
*,
13011301
axis: int | Sequence[int] | None = None,
13021302
keep_attrs: bool | None = None,

xarray/core/resample.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def __init__(
4949

5050
def _flox_reduce(
5151
self,
52-
dim: Dims | ellipsis,
52+
dim: Dims,
5353
keep_attrs: bool | None = None,
5454
**kwargs,
5555
) -> T_Xarray:
@@ -368,7 +368,7 @@ def apply(self, func, args=(), shortcut=None, **kwargs):
368368
def reduce(
369369
self,
370370
func: Callable[..., Any],
371-
dim: Dims | ellipsis = None,
371+
dim: Dims = None,
372372
*,
373373
axis: int | Sequence[int] | None = None,
374374
keep_attrs: bool | None = None,

xarray/core/types.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ def dtype(self) -> np.dtype:
113113
VarCompatible = Union["Variable", "ScalarOrArray"]
114114
GroupByIncompatible = Union["Variable", "GroupBy"]
115115

116-
Dims = Union[str, Iterable[Hashable], None]
116+
Dims = Union[str, Iterable[Hashable], "ellipsis", None]
117+
OrderedDims = Union[str, Sequence[Union[Hashable, "ellipsis"]], "ellipsis", None]
117118

118119
ErrorOptions = Literal["raise", "ignore"]
119120
ErrorOptionsWithWarn = Literal["raise", "warn", "ignore"]

xarray/core/utils.py

+161-5
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,11 @@
5757
Hashable,
5858
Iterable,
5959
Iterator,
60+
Literal,
6061
Mapping,
6162
MutableMapping,
6263
MutableSet,
64+
Sequence,
6365
TypeVar,
6466
cast,
6567
overload,
@@ -69,7 +71,7 @@
6971
import pandas as pd
7072

7173
if TYPE_CHECKING:
72-
from .types import ErrorOptionsWithWarn
74+
from .types import Dims, ErrorOptionsWithWarn, OrderedDims
7375

7476
K = TypeVar("K")
7577
V = TypeVar("V")
@@ -887,15 +889,17 @@ def drop_dims_from_indexers(
887889

888890

889891
def drop_missing_dims(
890-
supplied_dims: Collection, dims: Collection, missing_dims: ErrorOptionsWithWarn
891-
) -> Collection:
892+
supplied_dims: Iterable[Hashable],
893+
dims: Iterable[Hashable],
894+
missing_dims: ErrorOptionsWithWarn,
895+
) -> Iterable[Hashable]:
892896
"""Depending on the setting of missing_dims, drop any dimensions from supplied_dims that
893897
are not present in dims.
894898
895899
Parameters
896900
----------
897-
supplied_dims : dict
898-
dims : sequence
901+
supplied_dims : Iterable of Hashable
902+
dims : Iterable of Hashable
899903
missing_dims : {"raise", "warn", "ignore"}
900904
"""
901905

@@ -928,6 +932,158 @@ def drop_missing_dims(
928932
)
929933

930934

935+
T_None = TypeVar("T_None", None, "ellipsis")
936+
937+
938+
@overload
939+
def parse_dims(
940+
dim: str | Iterable[Hashable] | T_None,
941+
all_dims: tuple[Hashable, ...],
942+
*,
943+
check_exists: bool = True,
944+
replace_none: Literal[True] = True,
945+
) -> tuple[Hashable, ...]:
946+
...
947+
948+
949+
@overload
950+
def parse_dims(
951+
dim: str | Iterable[Hashable] | T_None,
952+
all_dims: tuple[Hashable, ...],
953+
*,
954+
check_exists: bool = True,
955+
replace_none: Literal[False],
956+
) -> tuple[Hashable, ...] | T_None:
957+
...
958+
959+
960+
def parse_dims(
961+
dim: Dims,
962+
all_dims: tuple[Hashable, ...],
963+
*,
964+
check_exists: bool = True,
965+
replace_none: bool = True,
966+
) -> tuple[Hashable, ...] | None | ellipsis:
967+
"""Parse one or more dimensions.
968+
969+
A single dimension must be always a str, multiple dimensions
970+
can be Hashables. This supports e.g. using a tuple as a dimension.
971+
If you supply e.g. a set of dimensions the order cannot be
972+
conserved, but for sequences it will be.
973+
974+
Parameters
975+
----------
976+
dim : str, Iterable of Hashable, "..." or None
977+
Dimension(s) to parse.
978+
all_dims : tuple of Hashable
979+
All possible dimensions.
980+
check_exists: bool, default: True
981+
if True, check if dim is a subset of all_dims.
982+
replace_none : bool, default: True
983+
If True, return all_dims if dim is None or "...".
984+
985+
Returns
986+
-------
987+
parsed_dims : tuple of Hashable
988+
Input dimensions as a tuple.
989+
"""
990+
if dim is None or dim is ...:
991+
if replace_none:
992+
return all_dims
993+
return dim
994+
if isinstance(dim, str):
995+
dim = (dim,)
996+
if check_exists:
997+
_check_dims(set(dim), set(all_dims))
998+
return tuple(dim)
999+
1000+
1001+
@overload
1002+
def parse_ordered_dims(
1003+
dim: str | Sequence[Hashable | ellipsis] | T_None,
1004+
all_dims: tuple[Hashable, ...],
1005+
*,
1006+
check_exists: bool = True,
1007+
replace_none: Literal[True] = True,
1008+
) -> tuple[Hashable, ...]:
1009+
...
1010+
1011+
1012+
@overload
1013+
def parse_ordered_dims(
1014+
dim: str | Sequence[Hashable | ellipsis] | T_None,
1015+
all_dims: tuple[Hashable, ...],
1016+
*,
1017+
check_exists: bool = True,
1018+
replace_none: Literal[False],
1019+
) -> tuple[Hashable, ...] | T_None:
1020+
...
1021+
1022+
1023+
def parse_ordered_dims(
1024+
dim: OrderedDims,
1025+
all_dims: tuple[Hashable, ...],
1026+
*,
1027+
check_exists: bool = True,
1028+
replace_none: bool = True,
1029+
) -> tuple[Hashable, ...] | None | ellipsis:
1030+
"""Parse one or more dimensions.
1031+
1032+
A single dimension must be always a str, multiple dimensions
1033+
can be Hashables. This supports e.g. using a tuple as a dimension.
1034+
An ellipsis ("...") in a sequence of dimensions will be
1035+
replaced with all remaining dimensions. This only makes sense when
1036+
the input is a sequence and not e.g. a set.
1037+
1038+
Parameters
1039+
----------
1040+
dim : str, Sequence of Hashable or "...", "..." or None
1041+
Dimension(s) to parse. If "..." appears in a Sequence
1042+
it always gets replaced with all remaining dims
1043+
all_dims : tuple of Hashable
1044+
All possible dimensions.
1045+
check_exists: bool, default: True
1046+
if True, check if dim is a subset of all_dims.
1047+
replace_none : bool, default: True
1048+
If True, return all_dims if dim is None.
1049+
1050+
Returns
1051+
-------
1052+
parsed_dims : tuple of Hashable
1053+
Input dimensions as a tuple.
1054+
"""
1055+
if dim is not None and dim is not ... and not isinstance(dim, str) and ... in dim:
1056+
dims_set: set[Hashable | ellipsis] = set(dim)
1057+
all_dims_set = set(all_dims)
1058+
if check_exists:
1059+
_check_dims(dims_set, all_dims_set)
1060+
if len(all_dims_set) != len(all_dims):
1061+
raise ValueError("Cannot use ellipsis with repeated dims")
1062+
dims = tuple(dim)
1063+
if dims.count(...) > 1:
1064+
raise ValueError("More than one ellipsis supplied")
1065+
other_dims = tuple(d for d in all_dims if d not in dims_set)
1066+
idx = dims.index(...)
1067+
return dims[:idx] + other_dims + dims[idx + 1 :]
1068+
else:
1069+
# mypy cannot resolve that the sequence cannot contain "..."
1070+
return parse_dims( # type: ignore[call-overload]
1071+
dim=dim,
1072+
all_dims=all_dims,
1073+
check_exists=check_exists,
1074+
replace_none=replace_none,
1075+
)
1076+
1077+
1078+
def _check_dims(dim: set[Hashable | ellipsis], all_dims: set[Hashable]) -> None:
1079+
wrong_dims = dim - all_dims
1080+
if wrong_dims and wrong_dims != {...}:
1081+
wrong_dims_str = ", ".join(f"'{d!s}'" for d in wrong_dims)
1082+
raise ValueError(
1083+
f"Dimension(s) {wrong_dims_str} do not exist. Expected one or more of {all_dims}"
1084+
)
1085+
1086+
9311087
_Accessor = TypeVar("_Accessor")
9321088

9331089

xarray/core/variable.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1889,7 +1889,7 @@ def clip(self, min=None, max=None):
18891889
def reduce(
18901890
self,
18911891
func: Callable[..., Any],
1892-
dim: Dims | ellipsis = None,
1892+
dim: Dims = None,
18931893
axis: int | Sequence[int] | None = None,
18941894
keep_attrs: bool | None = None,
18951895
keepdims: bool = False,
@@ -2663,7 +2663,7 @@ def _to_numeric(self, offset=None, datetime_unit=None, dtype=float):
26632663
def _unravel_argminmax(
26642664
self,
26652665
argminmax: str,
2666-
dim: Dims | ellipsis,
2666+
dim: Dims,
26672667
axis: int | None,
26682668
keep_attrs: bool | None,
26692669
skipna: bool | None,
@@ -2732,7 +2732,7 @@ def _unravel_argminmax(
27322732

27332733
def argmin(
27342734
self,
2735-
dim: Dims | ellipsis = None,
2735+
dim: Dims = None,
27362736
axis: int | None = None,
27372737
keep_attrs: bool | None = None,
27382738
skipna: bool | None = None,
@@ -2777,7 +2777,7 @@ def argmin(
27772777

27782778
def argmax(
27792779
self,
2780-
dim: Dims | ellipsis = None,
2780+
dim: Dims = None,
27812781
axis: int | None = None,
27822782
keep_attrs: bool | None = None,
27832783
skipna: bool | None = None,

xarray/core/weighted.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def _check_dim(self, dim: Dims):
207207
def _reduce(
208208
da: DataArray,
209209
weights: DataArray,
210-
dim: Dims | ellipsis = None,
210+
dim: Dims = None,
211211
skipna: bool | None = None,
212212
) -> DataArray:
213213
"""reduce using dot; equivalent to (da * weights).sum(dim, skipna)

0 commit comments

Comments
 (0)