From d17c78a3d91c1c3fa98fe83745849ca4f2eb16bc Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Thu, 29 May 2025 21:58:52 -0600 Subject: [PATCH 01/14] Making decoding arrays lazy too --- xarray/coding/strings.py | 9 ++++++--- xarray/coding/variables.py | 17 +++++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/xarray/coding/strings.py b/xarray/coding/strings.py index 4ca6a3f0a46..a2295c218a6 100644 --- a/xarray/coding/strings.py +++ b/xarray/coding/strings.py @@ -250,14 +250,17 @@ def __repr__(self): return f"{type(self).__name__}({self.array!r})" def _vindex_get(self, key): - return _numpy_char_to_bytes(self.array.vindex[key]) + return type(self)(self.array.vindex[key]) def _oindex_get(self, key): - return _numpy_char_to_bytes(self.array.oindex[key]) + return type(self)(self.array.oindex[key]) def __getitem__(self, key): # require slicing the last dimension completely key = type(key)(indexing.expanded_indexer(key.tuple, self.array.ndim)) if key.tuple[-1] != slice(None): raise IndexError("too many indices") - return _numpy_char_to_bytes(self.array[key]) + return type(self)(self.array[key]) + + def get_duck_array(self): + return _numpy_char_to_bytes(self.array.get_duck_array()) diff --git a/xarray/coding/variables.py b/xarray/coding/variables.py index 662fec4b2c4..1a91115d9c5 100644 --- a/xarray/coding/variables.py +++ b/xarray/coding/variables.py @@ -58,13 +58,16 @@ def dtype(self) -> np.dtype: return np.dtype(self.array.dtype.kind + str(self.array.dtype.itemsize)) def _oindex_get(self, key): - return np.asarray(self.array.oindex[key], dtype=self.dtype) + return type(self)(self.array.oindex[key]) def _vindex_get(self, key): - return np.asarray(self.array.vindex[key], dtype=self.dtype) + return type(self)(self.array.vindex[key]) def __getitem__(self, key) -> np.ndarray: - return np.asarray(self.array[key], dtype=self.dtype) + return type(self)(self.array[key]) + + def get_duck_array(self): + return duck_array_ops.astype(self.array.get_duck_array(), dtype=self.dtype) class BoolTypeArray(indexing.ExplicitlyIndexedNDArrayMixin): @@ -96,14 +99,16 @@ def dtype(self) -> np.dtype: return np.dtype("bool") def _oindex_get(self, key): - return np.asarray(self.array.oindex[key], dtype=self.dtype) + return type(self)(self.array.oindex[key]) def _vindex_get(self, key): - return np.asarray(self.array.vindex[key], dtype=self.dtype) + return type(self)(self.array.vindex[key]) def __getitem__(self, key) -> np.ndarray: - return np.asarray(self.array[key], dtype=self.dtype) + return type(self)(self.array[key]) + def get_duck_array(self): + return duck_array_ops.astype(self.array.get_duck_array(), dtype=self.dtype) def _apply_mask( data: np.ndarray, From 930f24d2e8b9cc7f3e7efa5b493c30ada16c5c7c Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Thu, 29 May 2025 22:19:32 -0600 Subject: [PATCH 02/14] Add IndexingAdapter mixin class --- xarray/core/indexing.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/xarray/core/indexing.py b/xarray/core/indexing.py index e14543e646f..8b52f96ad84 100644 --- a/xarray/core/indexing.py +++ b/xarray/core/indexing.py @@ -520,8 +520,7 @@ class ExplicitlyIndexedNDArrayMixin(NDArrayMixin, ExplicitlyIndexed): __slots__ = () def get_duck_array(self): - key = BasicIndexer((slice(None),) * self.ndim) - return self[key] + raise NotImplementedError def _oindex_get(self, indexer: OuterIndexer): raise NotImplementedError( @@ -559,6 +558,17 @@ def vindex(self) -> IndexCallable: return IndexCallable(self._vindex_get, self._vindex_set) +class IndexingAdapter: + """Marker class for indexing adapters. + These classes translate between Xarray's indexing semantics and the underlying array's + indexing semantics. + """ + + def get_duck_array(self): + key = BasicIndexer((slice(None),) * self.ndim) + return self[key] + + class ImplicitToExplicitIndexingAdapter(NDArrayMixin): """Wrap an array, converting tuples into the indicated explicit indexer.""" @@ -1527,7 +1537,7 @@ def is_fancy_indexer(indexer: Any) -> bool: return True -class NumpyIndexingAdapter(ExplicitlyIndexedNDArrayMixin): +class NumpyIndexingAdapter(IndexingAdapter, ExplicitlyIndexedNDArrayMixin): """Wrap a NumPy array to use explicit indexing.""" __slots__ = ("array",) @@ -1606,7 +1616,7 @@ def __init__(self, array): self.array = array -class ArrayApiIndexingAdapter(ExplicitlyIndexedNDArrayMixin): +class ArrayApiIndexingAdapter(IndexingAdapter, ExplicitlyIndexedNDArrayMixin): """Wrap an array API array to use explicit indexing.""" __slots__ = ("array",) @@ -1671,7 +1681,7 @@ def _assert_not_chunked_indexer(idxr: tuple[Any, ...]) -> None: ) -class DaskIndexingAdapter(ExplicitlyIndexedNDArrayMixin): +class DaskIndexingAdapter(IndexingAdapter, ExplicitlyIndexedNDArrayMixin): """Wrap a dask array to support explicit indexing.""" __slots__ = ("array",) @@ -1747,7 +1757,7 @@ def transpose(self, order): return self.array.transpose(order) -class PandasIndexingAdapter(ExplicitlyIndexedNDArrayMixin): +class PandasIndexingAdapter(IndexingAdapter, ExplicitlyIndexedNDArrayMixin): """Wrap a pandas.Index to preserve dtypes and handle explicit indexing.""" __slots__ = ("_dtype", "array") @@ -2068,7 +2078,9 @@ def copy(self, deep: bool = True) -> Self: return type(self)(array, self._dtype, self.level) -class CoordinateTransformIndexingAdapter(ExplicitlyIndexedNDArrayMixin): +class CoordinateTransformIndexingAdapter( + IndexingAdapter, ExplicitlyIndexedNDArrayMixin +): """Wrap a CoordinateTransform as a lazy coordinate array. Supports explicit indexing (both outer and vectorized). From afa9fb312fb329d513e76eefc4d0216cf26ef54e Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Tue, 17 Jun 2025 16:26:46 -0600 Subject: [PATCH 03/14] Cleanup backends some more --- xarray/backends/memory.py | 8 +++++++- xarray/backends/scipy_.py | 2 ++ xarray/conventions.py | 17 +++++++++++++++-- xarray/core/indexes.py | 2 +- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/xarray/backends/memory.py b/xarray/backends/memory.py index aba767ab731..22cb47d85f2 100644 --- a/xarray/backends/memory.py +++ b/xarray/backends/memory.py @@ -5,6 +5,7 @@ import numpy as np from xarray.backends.common import AbstractWritableDataStore +from xarray.core import indexing from xarray.core.variable import Variable @@ -24,7 +25,12 @@ def get_attrs(self): return self._attributes def get_variables(self): - return self._variables + res = {} + for k, v in self._variables.items(): + v = v.copy(deep=True) + res[k] = v + v._data = indexing.LazilyIndexedArray(v._data) + return res def get_dimensions(self): return {d: s for v in self._variables.values() for d, s in v.dims.items()} diff --git a/xarray/backends/scipy_.py b/xarray/backends/scipy_.py index 93d0e40a6e1..ae00855ac7d 100644 --- a/xarray/backends/scipy_.py +++ b/xarray/backends/scipy_.py @@ -190,6 +190,8 @@ def ds(self): def open_store_variable(self, name, var): return Variable( var.dimensions, + # this backend always loads in to memory, so we don't wrap + # with LazilyIndexedArray ScipyArrayWrapper(name, self), _decode_attrs(var._attributes), ) diff --git a/xarray/conventions.py b/xarray/conventions.py index c9cd2a5dcdc..f64d9b175e0 100644 --- a/xarray/conventions.py +++ b/xarray/conventions.py @@ -18,7 +18,7 @@ ) from xarray.core.utils import emit_user_level_warning from xarray.core.variable import IndexVariable, Variable -from xarray.namedarray.utils import is_duck_dask_array +from xarray.namedarray.utils import is_duck_array CF_RELATED_DATA = ( "bounds", @@ -248,7 +248,20 @@ def decode_cf_variable( encoding.setdefault("dtype", original_dtype) - if not is_duck_dask_array(data): + if ( + # we don't need to lazily index duck arrays + not is_duck_array(data) + # These arrays already support lazy indexing + # OR for IndexingAdapters, it makes no sense to wrap them + and not isinstance(data, indexing.ExplicitlyIndexedNDArrayMixin) + ): + # this path applies to bare BackendArray objects. + # It is not hit for any internal Xarray backend + emit_user_level_warning( + "The backend you are using has not explicitly supported lazy indexing with Xarray. " + "Please ask the backend developers to support lazy loading by wrapping with LazilyIndexedArray. " + "See https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html#how-to-support-lazy-loading for more." + ) data = indexing.LazilyIndexedArray(data) return Variable(dimensions, data, attributes, encoding=encoding, fastpath=True) diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index ed71865060a..26ba5f93103 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -717,7 +717,7 @@ def from_variables( # preserve wrapped pd.Index (if any) # accessing `.data` can load data from disk, so we only access if needed - data = var._data.array if hasattr(var._data, "array") else var.data + data = var._data if isinstance(var._data, PandasIndexingAdapter) else var.data # multi-index level variable: get level index if isinstance(var._data, PandasMultiIndexingAdapter): level = var._data.level From 11fec41fe16283fd94370d1b129695f1945f3aea Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Tue, 17 Jun 2025 16:26:54 -0600 Subject: [PATCH 04/14] Revert "Add IndexingAdapter mixin class" This reverts commit 930f24d2e8b9cc7f3e7efa5b493c30ada16c5c7c. --- xarray/core/indexing.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/xarray/core/indexing.py b/xarray/core/indexing.py index 8b52f96ad84..e14543e646f 100644 --- a/xarray/core/indexing.py +++ b/xarray/core/indexing.py @@ -520,7 +520,8 @@ class ExplicitlyIndexedNDArrayMixin(NDArrayMixin, ExplicitlyIndexed): __slots__ = () def get_duck_array(self): - raise NotImplementedError + key = BasicIndexer((slice(None),) * self.ndim) + return self[key] def _oindex_get(self, indexer: OuterIndexer): raise NotImplementedError( @@ -558,17 +559,6 @@ def vindex(self) -> IndexCallable: return IndexCallable(self._vindex_get, self._vindex_set) -class IndexingAdapter: - """Marker class for indexing adapters. - These classes translate between Xarray's indexing semantics and the underlying array's - indexing semantics. - """ - - def get_duck_array(self): - key = BasicIndexer((slice(None),) * self.ndim) - return self[key] - - class ImplicitToExplicitIndexingAdapter(NDArrayMixin): """Wrap an array, converting tuples into the indicated explicit indexer.""" @@ -1537,7 +1527,7 @@ def is_fancy_indexer(indexer: Any) -> bool: return True -class NumpyIndexingAdapter(IndexingAdapter, ExplicitlyIndexedNDArrayMixin): +class NumpyIndexingAdapter(ExplicitlyIndexedNDArrayMixin): """Wrap a NumPy array to use explicit indexing.""" __slots__ = ("array",) @@ -1616,7 +1606,7 @@ def __init__(self, array): self.array = array -class ArrayApiIndexingAdapter(IndexingAdapter, ExplicitlyIndexedNDArrayMixin): +class ArrayApiIndexingAdapter(ExplicitlyIndexedNDArrayMixin): """Wrap an array API array to use explicit indexing.""" __slots__ = ("array",) @@ -1681,7 +1671,7 @@ def _assert_not_chunked_indexer(idxr: tuple[Any, ...]) -> None: ) -class DaskIndexingAdapter(IndexingAdapter, ExplicitlyIndexedNDArrayMixin): +class DaskIndexingAdapter(ExplicitlyIndexedNDArrayMixin): """Wrap a dask array to support explicit indexing.""" __slots__ = ("array",) @@ -1757,7 +1747,7 @@ def transpose(self, order): return self.array.transpose(order) -class PandasIndexingAdapter(IndexingAdapter, ExplicitlyIndexedNDArrayMixin): +class PandasIndexingAdapter(ExplicitlyIndexedNDArrayMixin): """Wrap a pandas.Index to preserve dtypes and handle explicit indexing.""" __slots__ = ("_dtype", "array") @@ -2078,9 +2068,7 @@ def copy(self, deep: bool = True) -> Self: return type(self)(array, self._dtype, self.level) -class CoordinateTransformIndexingAdapter( - IndexingAdapter, ExplicitlyIndexedNDArrayMixin -): +class CoordinateTransformIndexingAdapter(ExplicitlyIndexedNDArrayMixin): """Wrap a CoordinateTransform as a lazy coordinate array. Supports explicit indexing (both outer and vectorized). From 1e4037678d6eb9a8811fc767577e28dca2adee66 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Tue, 17 Jun 2025 16:41:25 -0600 Subject: [PATCH 05/14] Fix scipy backend --- xarray/backends/scipy_.py | 4 +--- xarray/coding/variables.py | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/xarray/backends/scipy_.py b/xarray/backends/scipy_.py index ae00855ac7d..16fb4528f55 100644 --- a/xarray/backends/scipy_.py +++ b/xarray/backends/scipy_.py @@ -190,9 +190,7 @@ def ds(self): def open_store_variable(self, name, var): return Variable( var.dimensions, - # this backend always loads in to memory, so we don't wrap - # with LazilyIndexedArray - ScipyArrayWrapper(name, self), + indexing.LazilyIndexedArray(ScipyArrayWrapper(name, self)), _decode_attrs(var._attributes), ) diff --git a/xarray/coding/variables.py b/xarray/coding/variables.py index 1a91115d9c5..f76e011ef1e 100644 --- a/xarray/coding/variables.py +++ b/xarray/coding/variables.py @@ -110,6 +110,7 @@ def __getitem__(self, key) -> np.ndarray: def get_duck_array(self): return duck_array_ops.astype(self.array.get_duck_array(), dtype=self.dtype) + def _apply_mask( data: np.ndarray, encoded_fill_values: list, From 13ff262ea63244ca8a77cb2d6f7d290589041de6 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Tue, 17 Jun 2025 16:45:27 -0600 Subject: [PATCH 06/14] Add scipy-only CI job xref #8909 --- .github/workflows/ci.yaml | 3 +++ ci/requirements/bare-min-and-scipy.yml | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 ci/requirements/bare-min-and-scipy.yml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b884b246f47..76f4f0d5e7e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -54,6 +54,9 @@ jobs: - env: "bare-minimum" python-version: "3.10" os: ubuntu-latest + - env: "bare-min-and-scipy" + python-version: "3.10" + os: ubuntu-latest - env: "min-all-deps" python-version: "3.10" os: ubuntu-latest diff --git a/ci/requirements/bare-min-and-scipy.yml b/ci/requirements/bare-min-and-scipy.yml new file mode 100644 index 00000000000..5e5522faaea --- /dev/null +++ b/ci/requirements/bare-min-and-scipy.yml @@ -0,0 +1,18 @@ +name: xarray-tests +channels: + - conda-forge + - nodefaults +dependencies: + - python=3.10 + - coveralls + - pip + - pytest + - pytest-cov + - pytest-env + - pytest-mypy-plugins + - pytest-timeout + - pytest-xdist + - numpy=1.24 + - packaging=23.1 + - pandas=2.1 + - scipy=1.11 From 1af56054f4eb863c4e89396267ca494cba57d97b Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Tue, 17 Jun 2025 18:52:52 -0600 Subject: [PATCH 07/14] Pin array-api-strict --- ci/requirements/all-but-dask.yml | 2 +- ci/requirements/all-but-numba.yml | 2 +- ci/requirements/environment-3.14.yml | 2 +- ci/requirements/environment-windows-3.14.yml | 2 +- ci/requirements/environment-windows.yml | 2 +- ci/requirements/environment.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ci/requirements/all-but-dask.yml b/ci/requirements/all-but-dask.yml index ca4943bddb1..5f5db4a0f18 100644 --- a/ci/requirements/all-but-dask.yml +++ b/ci/requirements/all-but-dask.yml @@ -4,7 +4,7 @@ channels: - nodefaults dependencies: - aiobotocore - - array-api-strict + - array-api-strict<2.4 - boto3 - bottleneck - cartopy diff --git a/ci/requirements/all-but-numba.yml b/ci/requirements/all-but-numba.yml index fa7ad81f198..7c492aec704 100644 --- a/ci/requirements/all-but-numba.yml +++ b/ci/requirements/all-but-numba.yml @@ -6,7 +6,7 @@ dependencies: # Pin a "very new numpy" (updated Sept 24, 2024) - numpy>=2.1.1 - aiobotocore - - array-api-strict + - array-api-strict<2.4 - boto3 - bottleneck - cartopy diff --git a/ci/requirements/environment-3.14.yml b/ci/requirements/environment-3.14.yml index 1e6ee7ff5f9..06c4df82663 100644 --- a/ci/requirements/environment-3.14.yml +++ b/ci/requirements/environment-3.14.yml @@ -4,7 +4,7 @@ channels: - nodefaults dependencies: - aiobotocore - - array-api-strict + - array-api-strict<2.4 - boto3 - bottleneck - cartopy diff --git a/ci/requirements/environment-windows-3.14.yml b/ci/requirements/environment-windows-3.14.yml index 4eb2049f2e6..dd48add6b73 100644 --- a/ci/requirements/environment-windows-3.14.yml +++ b/ci/requirements/environment-windows-3.14.yml @@ -2,7 +2,7 @@ name: xarray-tests channels: - conda-forge dependencies: - - array-api-strict + - array-api-strict<2.4 - boto3 - bottleneck - cartopy diff --git a/ci/requirements/environment-windows.yml b/ci/requirements/environment-windows.yml index 45cbebd38db..3213ef687d3 100644 --- a/ci/requirements/environment-windows.yml +++ b/ci/requirements/environment-windows.yml @@ -2,7 +2,7 @@ name: xarray-tests channels: - conda-forge dependencies: - - array-api-strict + - array-api-strict<2.4 - boto3 - bottleneck - cartopy diff --git a/ci/requirements/environment.yml b/ci/requirements/environment.yml index a9499694e15..fc54b6600fe 100644 --- a/ci/requirements/environment.yml +++ b/ci/requirements/environment.yml @@ -4,7 +4,7 @@ channels: - nodefaults dependencies: - aiobotocore - - array-api-strict + - array-api-strict<2.4 - boto3 - bottleneck - cartopy From f7ac28ac91afdbb1b4515ece82d8f37e9708d9d5 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Tue, 17 Jun 2025 18:55:59 -0600 Subject: [PATCH 08/14] Fix doctest --- xarray/coding/strings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/coding/strings.py b/xarray/coding/strings.py index a2295c218a6..ea2f58274b6 100644 --- a/xarray/coding/strings.py +++ b/xarray/coding/strings.py @@ -221,7 +221,7 @@ class StackedBytesArray(indexing.ExplicitlyIndexedNDArrayMixin): values, when accessed, are automatically stacked along the last dimension. >>> indexer = indexing.BasicIndexer((slice(None),)) - >>> StackedBytesArray(np.array(["a", "b", "c"], dtype="S1"))[indexer] + >>> np.array(StackedBytesArray(np.array(["a", "b", "c"], dtype="S1"))[indexer]) array(b'abc', dtype='|S3') """ From 3a277474573d084a56e5d7230b185226b8ec0d1d Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Tue, 17 Jun 2025 19:09:27 -0600 Subject: [PATCH 09/14] Add test for #8909 Closes #8909, #8921 --- xarray/tests/test_backends.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 68ff9233080..6a42e135587 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -1427,6 +1427,25 @@ def test_string_object_warning(self) -> None: with self.roundtrip(original) as actual: assert_identical(original, actual) + @pytest.mark.parametrize( + "indexer", + ( + {"y": [1]}, + {"y": slice(2)}, + {"y": 1}, + {"x": [1], "y": [1]}, + {"x": ("x0", [0, 1]), "y": ("x0", [0, 1])}, + ), + ) + def test_indexing_roundtrip(self, indexer) -> None: + # regression test for GH8909 + ds = xr.Dataset() + ds["A"] = xr.DataArray([[1, "a"], [2, "b"]], dims=["x", "y"]) + with self.roundtrip(ds) as ds2: + expected = ds2.sel(indexer) + with self.roundtrip(expected) as actual: + assert_identical(actual, expected) + class NetCDFBase(CFEncodedBase): """Tests for all netCDF3 and netCDF4 backends.""" From 00e05ddf72389c39a9d7b20d3a0a0e4722a09b83 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Tue, 17 Jun 2025 19:12:16 -0600 Subject: [PATCH 10/14] Remove user warning --- xarray/conventions.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/xarray/conventions.py b/xarray/conventions.py index f64d9b175e0..5ae40ea57d8 100644 --- a/xarray/conventions.py +++ b/xarray/conventions.py @@ -257,11 +257,6 @@ def decode_cf_variable( ): # this path applies to bare BackendArray objects. # It is not hit for any internal Xarray backend - emit_user_level_warning( - "The backend you are using has not explicitly supported lazy indexing with Xarray. " - "Please ask the backend developers to support lazy loading by wrapping with LazilyIndexedArray. " - "See https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html#how-to-support-lazy-loading for more." - ) data = indexing.LazilyIndexedArray(data) return Variable(dimensions, data, attributes, encoding=encoding, fastpath=True) From f0d8668bf3b6d3dc5a923fa915b6806ca9885fd4 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Tue, 17 Jun 2025 19:20:07 -0600 Subject: [PATCH 11/14] fix types --- xarray/coding/variables.py | 5 +++-- xarray/core/indexes.py | 2 +- xarray/tests/test_dtypes.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/xarray/coding/variables.py b/xarray/coding/variables.py index f76e011ef1e..3b7be898ccf 100644 --- a/xarray/coding/variables.py +++ b/xarray/coding/variables.py @@ -21,6 +21,7 @@ ) from xarray.coding.times import CFDatetimeCoder, CFTimedeltaCoder from xarray.core import dtypes, duck_array_ops, indexing +from xarray.core.types import Self from xarray.core.variable import Variable if TYPE_CHECKING: @@ -63,7 +64,7 @@ def _oindex_get(self, key): def _vindex_get(self, key): return type(self)(self.array.vindex[key]) - def __getitem__(self, key) -> np.ndarray: + def __getitem__(self, key) -> Self: return type(self)(self.array[key]) def get_duck_array(self): @@ -104,7 +105,7 @@ def _oindex_get(self, key): def _vindex_get(self, key): return type(self)(self.array.vindex[key]) - def __getitem__(self, key) -> np.ndarray: + def __getitem__(self, key) -> Self: return type(self)(self.array[key]) def get_duck_array(self): diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index 26ba5f93103..552e743e1f1 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -717,7 +717,7 @@ def from_variables( # preserve wrapped pd.Index (if any) # accessing `.data` can load data from disk, so we only access if needed - data = var._data if isinstance(var._data, PandasIndexingAdapter) else var.data + data = var._data if isinstance(var._data, PandasIndexingAdapter) else var.data # type: ignore[redundant-expr] # multi-index level variable: get level index if isinstance(var._data, PandasMultiIndexingAdapter): level = var._data.level diff --git a/xarray/tests/test_dtypes.py b/xarray/tests/test_dtypes.py index 0ccda1d8074..c2950a833c2 100644 --- a/xarray/tests/test_dtypes.py +++ b/xarray/tests/test_dtypes.py @@ -15,7 +15,7 @@ class DummyArrayAPINamespace: int32 = None # type: ignore[unused-ignore,var-annotated] float64 = None # type: ignore[unused-ignore,var-annotated] - array_api_strict = DummyArrayAPINamespace + array_api_strict = DummyArrayAPINamespace # type: ignore[assignment] @pytest.mark.parametrize( From 0ed11f5e965c4cc29b26edaeb5ca65ed9473957b Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Tue, 17 Jun 2025 19:22:15 -0600 Subject: [PATCH 12/14] Add whats-new --- doc/whats-new.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 618fc72763d..ceb79a3e173 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -26,6 +26,8 @@ Bug fixes ~~~~~~~~~ - Fix Pydap test_cmp_local_file for numpy 2.3.0 changes, 1. do always return arrays for all versions and 2. skip astype(str) for numpy >= 2.3.0 for expected data. (:pull:`10421`) By `Kai Mühlbauer `_. +- Fix the SciPy backend for netCDF3 files . (:issue:`8909`, :pull:`10376`) + By `Deepak Cherian `_. Documentation From 432a1e8ebacb4c22f900cb62c6b94a6757073a9e Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Tue, 17 Jun 2025 21:41:20 -0600 Subject: [PATCH 13/14] fix types --- xarray/tests/test_dtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_dtypes.py b/xarray/tests/test_dtypes.py index c2950a833c2..0ccda1d8074 100644 --- a/xarray/tests/test_dtypes.py +++ b/xarray/tests/test_dtypes.py @@ -15,7 +15,7 @@ class DummyArrayAPINamespace: int32 = None # type: ignore[unused-ignore,var-annotated] float64 = None # type: ignore[unused-ignore,var-annotated] - array_api_strict = DummyArrayAPINamespace # type: ignore[assignment] + array_api_strict = DummyArrayAPINamespace @pytest.mark.parametrize( From 73bd320b53df643564aaab86728b2062c2b76bd3 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Wed, 18 Jun 2025 11:04:07 -0600 Subject: [PATCH 14/14] Fix docs build --- xarray/coding/common.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xarray/coding/common.py b/xarray/coding/common.py index 1b455009668..0e8d7e1955e 100644 --- a/xarray/coding/common.py +++ b/xarray/coding/common.py @@ -63,6 +63,10 @@ def __init__(self, array, func: Callable, dtype: np.typing.DTypeLike): def dtype(self) -> np.dtype: return np.dtype(self._dtype) + def transpose(self, order): + # For elementwise functions, we can compose transpose and function application + return type(self)(self.array.transpose(order), self.func, self.dtype) + def _oindex_get(self, key): return type(self)(self.array.oindex[key], self.func, self.dtype)