From 62e2f31109bf62e6c33eceb98fcde11a9a68336e Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Tue, 11 Nov 2025 13:26:02 -0500 Subject: [PATCH 01/11] Ensure base Variable class is used when assigning --- xarray/core/coordinates.py | 2 +- xarray/structure/merge.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/xarray/core/coordinates.py b/xarray/core/coordinates.py index ab5bf7408f3..9aa64a57ff2 100644 --- a/xarray/core/coordinates.py +++ b/xarray/core/coordinates.py @@ -1265,7 +1265,7 @@ def create_coords_with_default_indexes( variables.update(idx_vars) all_variables.update(idx_vars) else: - variables[name] = variable + variables[name] = variable.to_base_variable() new_coords = Coordinates._construct_direct(coords=variables, indexes=indexes) diff --git a/xarray/structure/merge.py b/xarray/structure/merge.py index e5f3c0959bd..d398a37c8ef 100644 --- a/xarray/structure/merge.py +++ b/xarray/structure/merge.py @@ -22,7 +22,12 @@ emit_user_level_warning, equivalent, ) -from xarray.core.variable import Variable, as_variable, calculate_dimensions +from xarray.core.variable import ( + IndexVariable, + Variable, + as_variable, + calculate_dimensions, +) from xarray.structure.alignment import deep_align from xarray.util.deprecation_helpers import ( _COMPAT_DEFAULT, @@ -1206,7 +1211,11 @@ def dataset_update_method(dataset: Dataset, other: CoercibleMapping) -> _MergeRe if c not in value.dims and c in dataset.coords ] if coord_names: - other[key] = value.drop_vars(coord_names) + value = value.drop_vars(coord_names) + if isinstance(value.variable, IndexVariable): + variable = value.variable.to_base_variable() + value = value._replace(variable=variable) + other[key] = value return merge_core( [dataset, other], From 93281b737bf855f6e23718d847609409cd75cb65 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Tue, 11 Nov 2025 13:28:01 -0500 Subject: [PATCH 02/11] Add tests --- xarray/tests/test_dataarray.py | 5 +++++ xarray/tests/test_dataset.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index 5eec7b8a2fd..bc4f33fd8e1 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -1702,6 +1702,11 @@ def should_add_coord_to_array(self, name, var, dims): assert_identical(actual, expected, check_default_indexes=False) assert "x_bnds" not in actual.dims + def test_assign_coords_uses_base_variable_class(self) -> None: + a = DataArray([0, 1, 2], dims=["x"], coords={"x": [0, 1, 2]}) + a = a.assign_coords(foo=a.x) + assert not isinstance(a["foo"].variable, IndexVariable) + def test_coords_alignment(self) -> None: lhs = DataArray([1, 2, 3], [("x", [0, 1, 2])]) rhs = DataArray([2, 3, 4], [("x", [1, 2, 3])]) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index e677430dfbf..d823953297b 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -4779,6 +4779,11 @@ def test_setitem_using_list_errors(self, var_list, data, error_regex) -> None: with pytest.raises(ValueError, match=error_regex): actual[var_list] = data + def test_setitem_uses_base_variable_class_even_for_index_variables(self) -> None: + ds = Dataset(coords={"x": [1, 2, 3]}) + ds["y"] = ds["x"] + assert not isinstance(ds["y"].variable, IndexVariable) + def test_assign(self) -> None: ds = Dataset() actual = ds.assign(x=[0, 1, 2], y=2) From 3370345457b799e554effbe8c34b88d757add610 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Tue, 11 Nov 2025 13:34:10 -0500 Subject: [PATCH 03/11] Update what's new --- doc/whats-new.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 6dcbdd1062b..e81535a101b 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -44,6 +44,9 @@ Bug Fixes - Fix error handling issue in ``decode_cf_variables`` when decoding fails - the exception is now re-raised correctly, with a note added about the variable name that caused the error (:issue:`10873`, :pull:`10886`). By `Jonas L. Bertelsen `_ +- When assigning an indexed coordinate to a data variable or coordinate, coerce it from + ``IndexVariable`` to ``Variable`` (:issue:`9859`, :issue:`10829`, :pull:`10909`) + By `Julia Signell `_ Performance ~~~~~~~~~~~ From 7062b3b9e48726ff3707b8cf71097d0f7b9b47fd Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Wed, 12 Nov 2025 16:54:27 -0500 Subject: [PATCH 04/11] Improve internal_invariants assertions --- xarray/testing/assertions.py | 45 +++++++++++++++++++--------------- xarray/tests/test_dataarray.py | 2 +- xarray/tests/test_dataset.py | 8 +++--- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/xarray/testing/assertions.py b/xarray/testing/assertions.py index 39e6da6a83c..25690d3f425 100644 --- a/xarray/testing/assertions.py +++ b/xarray/testing/assertions.py @@ -3,6 +3,7 @@ import functools import warnings from collections.abc import Hashable +from typing import Any import numpy as np import pandas as pd @@ -401,11 +402,25 @@ def _assert_indexes_invariants_checks( ) -def _assert_variable_invariants(var: Variable, name: Hashable = None): +def _assert_variable_invariants( + var: Variable | Any, + name: Hashable = None, + check_default_indexes: bool = True, + is_index: bool = False, +): if name is None: name_or_empty: tuple = () else: name_or_empty = (name,) + + assert isinstance(var, Variable), {name: type(var)} + if name: + if var.dims == name_or_empty: + if check_default_indexes: + assert isinstance(var, IndexVariable), {name: type(var)} + elif not is_index: + assert not isinstance(var, IndexVariable), {name: type(var)} + assert isinstance(var._dims, tuple), name_or_empty + (var._dims,) assert len(var._dims) == len(var._data.shape), name_or_empty + ( var._dims, @@ -418,25 +433,20 @@ def _assert_variable_invariants(var: Variable, name: Hashable = None): def _assert_dataarray_invariants(da: DataArray, check_default_indexes: bool): - assert isinstance(da._variable, Variable), da._variable - _assert_variable_invariants(da._variable) + _assert_variable_invariants(da._variable, name=da.name, check_default_indexes=False) assert isinstance(da._coords, dict), da._coords - assert all(isinstance(v, Variable) for v in da._coords.values()), da._coords if check_default_indexes: assert all(set(v.dims) <= set(da.dims) for v in da._coords.values()), ( da.dims, {k: v.dims for k, v in da._coords.items()}, ) - assert all( - isinstance(v, IndexVariable) - for (k, v) in da._coords.items() - if v.dims == (k,) - ), {k: type(v) for k, v in da._coords.items()} for k, v in da._coords.items(): - _assert_variable_invariants(v, k) + _assert_variable_invariants( + v, k, check_default_indexes=check_default_indexes, is_index=k in da._indexes + ) if da._indexes is not None: _assert_indexes_invariants_checks( @@ -446,9 +456,11 @@ def _assert_dataarray_invariants(da: DataArray, check_default_indexes: bool): def _assert_dataset_invariants(ds: Dataset, check_default_indexes: bool): assert isinstance(ds._variables, dict), type(ds._variables) - assert all(isinstance(v, Variable) for v in ds._variables.values()), ds._variables + for k, v in ds._variables.items(): - _assert_variable_invariants(v, k) + _assert_variable_invariants( + v, k, check_default_indexes=check_default_indexes, is_index=k in ds._indexes + ) assert isinstance(ds._coord_names, set), ds._coord_names assert ds._coord_names <= ds._variables.keys(), ( @@ -466,13 +478,6 @@ def _assert_dataset_invariants(ds: Dataset, check_default_indexes: bool): ds._dims[k] == v.sizes[k] for v in ds._variables.values() for k in v.sizes ), (ds._dims, {k: v.sizes for k, v in ds._variables.items()}) - if check_default_indexes: - assert all( - isinstance(v, IndexVariable) - for (k, v) in ds._variables.items() - if v.dims == (k,) - ), {k: type(v) for k, v in ds._variables.items() if v.dims == (k,)} - if ds._indexes is not None: _assert_indexes_invariants_checks( ds._indexes, ds._variables, ds._dims, check_default=check_default_indexes @@ -492,7 +497,7 @@ def _assert_internal_invariants( private APIs. """ if isinstance(xarray_obj, Variable): - _assert_variable_invariants(xarray_obj) + _assert_variable_invariants(xarray_obj, check_default_indexes=False) elif isinstance(xarray_obj, DataArray): _assert_dataarray_invariants( xarray_obj, check_default_indexes=check_default_indexes diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index bc4f33fd8e1..65668ae873c 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -1705,7 +1705,7 @@ def should_add_coord_to_array(self, name, var, dims): def test_assign_coords_uses_base_variable_class(self) -> None: a = DataArray([0, 1, 2], dims=["x"], coords={"x": [0, 1, 2]}) a = a.assign_coords(foo=a.x) - assert not isinstance(a["foo"].variable, IndexVariable) + _assert_internal_invariants(a, check_default_indexes=True) def test_coords_alignment(self) -> None: lhs = DataArray([1, 2, 3], [("x", [0, 1, 2])]) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index d823953297b..a9cfc6959d2 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -4311,9 +4311,11 @@ def test_to_stacked_array_preserves_dtype(self) -> None: # coordinate created from variables names should be of string dtype data = np.array(["a", "a", "a", "b"], dtype=" None: @@ -4782,7 +4784,7 @@ def test_setitem_using_list_errors(self, var_list, data, error_regex) -> None: def test_setitem_uses_base_variable_class_even_for_index_variables(self) -> None: ds = Dataset(coords={"x": [1, 2, 3]}) ds["y"] = ds["x"] - assert not isinstance(ds["y"].variable, IndexVariable) + _assert_internal_invariants(ds, check_default_indexes=True) def test_assign(self) -> None: ds = Dataset() From 6132204364aaf8d814114fe20d61a6c3c05be76c Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 13 Nov 2025 14:50:53 -0500 Subject: [PATCH 05/11] Add typing Co-authored-by: Illviljan <14371165+Illviljan@users.noreply.github.com> --- xarray/testing/assertions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/testing/assertions.py b/xarray/testing/assertions.py index 25690d3f425..bfa159f5371 100644 --- a/xarray/testing/assertions.py +++ b/xarray/testing/assertions.py @@ -407,7 +407,7 @@ def _assert_variable_invariants( name: Hashable = None, check_default_indexes: bool = True, is_index: bool = False, -): +) -> None: if name is None: name_or_empty: tuple = () else: From 93882c5c37f827b360928aaa76a267e1852aabdc Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Fri, 28 Nov 2025 09:44:54 -0500 Subject: [PATCH 06/11] Move `IndexVariable` check --- xarray/testing/assertions.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/xarray/testing/assertions.py b/xarray/testing/assertions.py index bfa159f5371..25471c38583 100644 --- a/xarray/testing/assertions.py +++ b/xarray/testing/assertions.py @@ -364,6 +364,13 @@ def _assert_indexes_invariants_checks( } assert indexes.keys() <= index_vars, (set(indexes), index_vars) + non_index_dim_vars = ( + v + for k, v in possible_coord_variables.items() + if v.dims in (k, (k,)) and k not in indexes.keys() + ) + assert not any(isinstance(var, IndexVariable) for var in non_index_dim_vars) + # check pandas index wrappers vs. coordinate data adapters for k, index in indexes.items(): if isinstance(index, PandasIndex): @@ -405,8 +412,6 @@ def _assert_indexes_invariants_checks( def _assert_variable_invariants( var: Variable | Any, name: Hashable = None, - check_default_indexes: bool = True, - is_index: bool = False, ) -> None: if name is None: name_or_empty: tuple = () @@ -414,12 +419,6 @@ def _assert_variable_invariants( name_or_empty = (name,) assert isinstance(var, Variable), {name: type(var)} - if name: - if var.dims == name_or_empty: - if check_default_indexes: - assert isinstance(var, IndexVariable), {name: type(var)} - elif not is_index: - assert not isinstance(var, IndexVariable), {name: type(var)} assert isinstance(var._dims, tuple), name_or_empty + (var._dims,) assert len(var._dims) == len(var._data.shape), name_or_empty + ( @@ -433,7 +432,7 @@ def _assert_variable_invariants( def _assert_dataarray_invariants(da: DataArray, check_default_indexes: bool): - _assert_variable_invariants(da._variable, name=da.name, check_default_indexes=False) + _assert_variable_invariants(da._variable) assert isinstance(da._coords, dict), da._coords @@ -444,9 +443,7 @@ def _assert_dataarray_invariants(da: DataArray, check_default_indexes: bool): ) for k, v in da._coords.items(): - _assert_variable_invariants( - v, k, check_default_indexes=check_default_indexes, is_index=k in da._indexes - ) + _assert_variable_invariants(v, k) if da._indexes is not None: _assert_indexes_invariants_checks( @@ -458,9 +455,7 @@ def _assert_dataset_invariants(ds: Dataset, check_default_indexes: bool): assert isinstance(ds._variables, dict), type(ds._variables) for k, v in ds._variables.items(): - _assert_variable_invariants( - v, k, check_default_indexes=check_default_indexes, is_index=k in ds._indexes - ) + _assert_variable_invariants(v, k) assert isinstance(ds._coord_names, set), ds._coord_names assert ds._coord_names <= ds._variables.keys(), ( @@ -497,7 +492,7 @@ def _assert_internal_invariants( private APIs. """ if isinstance(xarray_obj, Variable): - _assert_variable_invariants(xarray_obj, check_default_indexes=False) + _assert_variable_invariants(xarray_obj) elif isinstance(xarray_obj, DataArray): _assert_dataarray_invariants( xarray_obj, check_default_indexes=check_default_indexes From a6dde1934c12b91b4d8ace6e1e61d3e2d9f5f731 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Fri, 28 Nov 2025 15:06:15 -0500 Subject: [PATCH 07/11] Ensure that the old tests are still covered --- xarray/testing/assertions.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/xarray/testing/assertions.py b/xarray/testing/assertions.py index 25471c38583..d603c6d9179 100644 --- a/xarray/testing/assertions.py +++ b/xarray/testing/assertions.py @@ -363,13 +363,17 @@ def _assert_indexes_invariants_checks( if isinstance(v, IndexVariable) } assert indexes.keys() <= index_vars, (set(indexes), index_vars) + assert all( + k in index_vars + for k, v in possible_coord_variables.items() + if v.dims == (k,) + ), {k: type(v) for k, v in possible_coord_variables.items()} - non_index_dim_vars = ( - v + assert not any( + isinstance(v, IndexVariable) for k, v in possible_coord_variables.items() - if v.dims in (k, (k,)) and k not in indexes.keys() - ) - assert not any(isinstance(var, IndexVariable) for var in non_index_dim_vars) + if v.dims == (k,) and k not in indexes.keys() + ), {k: type(v) for k, v in possible_coord_variables.items()} # check pandas index wrappers vs. coordinate data adapters for k, index in indexes.items(): @@ -445,10 +449,10 @@ def _assert_dataarray_invariants(da: DataArray, check_default_indexes: bool): for k, v in da._coords.items(): _assert_variable_invariants(v, k) - if da._indexes is not None: - _assert_indexes_invariants_checks( - da._indexes, da._coords, da.dims, check_default=check_default_indexes - ) + assert da._indexes is not None + _assert_indexes_invariants_checks( + da._indexes, da._coords, da.dims, check_default=check_default_indexes + ) def _assert_dataset_invariants(ds: Dataset, check_default_indexes: bool): @@ -473,10 +477,10 @@ def _assert_dataset_invariants(ds: Dataset, check_default_indexes: bool): ds._dims[k] == v.sizes[k] for v in ds._variables.values() for k in v.sizes ), (ds._dims, {k: v.sizes for k, v in ds._variables.items()}) - if ds._indexes is not None: - _assert_indexes_invariants_checks( - ds._indexes, ds._variables, ds._dims, check_default=check_default_indexes - ) + assert ds._indexes is not None + _assert_indexes_invariants_checks( + ds._indexes, ds._variables, ds._dims, check_default=check_default_indexes + ) assert isinstance(ds._encoding, type(None) | dict) assert isinstance(ds._attrs, type(None) | dict) From 84c25d3fd3a91118547de927bcad1919e6f19745 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Fri, 28 Nov 2025 15:07:02 -0500 Subject: [PATCH 08/11] Make type checking explicit in new tests Co-authored-by: Benoit Bovy --- xarray/tests/test_dataset.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index a9cfc6959d2..b9e0d1a095d 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -4784,7 +4784,14 @@ def test_setitem_using_list_errors(self, var_list, data, error_regex) -> None: def test_setitem_uses_base_variable_class_even_for_index_variables(self) -> None: ds = Dataset(coords={"x": [1, 2, 3]}) ds["y"] = ds["x"] - _assert_internal_invariants(ds, check_default_indexes=True) + + # explicit check + assert isintance(ds["x"].variable, IndexVariable) + assert not isinstance(ds["y"].variable, IndexVariable) + + # test internal invariant checks when comparing the datasets + expected = Dataset(coords={"x": [1, 2, 3], "y": ("x", [1, 2, 3])}) + assert_identical(ds, expected) def test_assign(self) -> None: ds = Dataset() From e530907e356eaec597b1fe23fcfedba5a137f465 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Fri, 28 Nov 2025 15:18:18 -0500 Subject: [PATCH 09/11] Make the new tests more explicit --- xarray/tests/test_dataarray.py | 13 +++++++++++-- xarray/tests/test_dataset.py | 8 ++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index 65668ae873c..6b453d13646 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -1703,9 +1703,18 @@ def should_add_coord_to_array(self, name, var, dims): assert "x_bnds" not in actual.dims def test_assign_coords_uses_base_variable_class(self) -> None: - a = DataArray([0, 1, 2], dims=["x"], coords={"x": [0, 1, 2]}) + a = DataArray([0, 1, 3], dims=["x"], coords={"x": [0, 1, 2]}) a = a.assign_coords(foo=a.x) - _assert_internal_invariants(a, check_default_indexes=True) + + # explicit check + assert isinstance(a["x"].variable, IndexVariable) + assert not isinstance(a["foo"].variable, IndexVariable) + + # test internal invariant checks when comparing the datasets + expected = DataArray( + [0, 1, 3], dims=["x"], coords={"x": [0, 1, 2], "foo": ("x", [0, 1, 2])} + ) + assert_identical(a, expected) def test_coords_alignment(self) -> None: lhs = DataArray([1, 2, 3], [("x", [0, 1, 2])]) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index b9e0d1a095d..6fa6a2146a5 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -4784,13 +4784,13 @@ def test_setitem_using_list_errors(self, var_list, data, error_regex) -> None: def test_setitem_uses_base_variable_class_even_for_index_variables(self) -> None: ds = Dataset(coords={"x": [1, 2, 3]}) ds["y"] = ds["x"] - + # explicit check - assert isintance(ds["x"].variable, IndexVariable) + assert isinstance(ds["x"].variable, IndexVariable) assert not isinstance(ds["y"].variable, IndexVariable) - + # test internal invariant checks when comparing the datasets - expected = Dataset(coords={"x": [1, 2, 3], "y": ("x", [1, 2, 3])}) + expected = Dataset(data_vars={"y": ("x", [1, 2, 3])}, coords={"x": [1, 2, 3]}) assert_identical(ds, expected) def test_assign(self) -> None: From c2ac14ab8fe40df89ba8fe013eaac3d4abd12465 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Tue, 2 Dec 2025 09:51:23 -0500 Subject: [PATCH 10/11] Check all variables Co-authored-by: Benoit Bovy --- xarray/testing/assertions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/testing/assertions.py b/xarray/testing/assertions.py index d603c6d9179..7a5c6523bdb 100644 --- a/xarray/testing/assertions.py +++ b/xarray/testing/assertions.py @@ -372,7 +372,7 @@ def _assert_indexes_invariants_checks( assert not any( isinstance(v, IndexVariable) for k, v in possible_coord_variables.items() - if v.dims == (k,) and k not in indexes.keys() + if k not in indexes.keys() ), {k: type(v) for k, v in possible_coord_variables.items()} # check pandas index wrappers vs. coordinate data adapters From 5fdaa13370f50eeadd3f3acca16ae10a81ae66cd Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Tue, 2 Dec 2025 09:54:13 -0500 Subject: [PATCH 11/11] Fix what's new --- doc/whats-new.rst | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 730c153136f..d2f951dabe1 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -33,22 +33,10 @@ Deprecations Bug Fixes ~~~~~~~~~ -- Fix h5netcdf backend for format=None, use same rule as netcdf4 backend (:pull:`10859`). - By `Kai Mühlbauer `_ -- ``netcdf4`` and ``pydap`` backends now use stricter URL detection to avoid incorrectly claiming - remote URLs. The ``pydap`` backend now only claims URLs with explicit DAP protocol indicators - (``dap2://`` or ``dap4://`` schemes, or ``/dap2/`` or ``/dap4/`` in the URL path). This prevents - both backends from claiming remote Zarr stores and other non-DAP URLs without an explicit - ``engine=`` argument. (:pull:`10804`). By `Ian Hunt-Isaak `_. -- Fix indexing with empty arrays for scipy & h5netcdf backends which now resolves to empty slices (:issue:`10867`, :pull:`10870`). - By `Kai Mühlbauer `_ -- Fix error handling issue in ``decode_cf_variables`` when decoding fails - the exception is now re-raised - correctly, with a note added about the variable name that caused the error (:issue:`10873`, :pull:`10886`). - By `Jonas L. Bertelsen `_ + - When assigning an indexed coordinate to a data variable or coordinate, coerce it from ``IndexVariable`` to ``Variable`` (:issue:`9859`, :issue:`10829`, :pull:`10909`) By `Julia Signell `_ - - The NetCDF4 backend will now claim to be able to read any URL except for one that contains the substring zarr. This restores backward compatibility after :pull:`10804` broke workflows that relied on ``xr.open_dataset("http://...")``