diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index 2242e57e482..e4b8fe948f4 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -9,6 +9,7 @@ import numpy as np import pandas as pd +from pandas.errors import InvalidIndexError from xarray.core import formatting, nputils, utils from xarray.core.coordinate_transform import CoordinateTransform @@ -1367,7 +1368,14 @@ def sel(self, labels, method=None, tolerance=None) -> IndexSelResult: elif isinstance(label, tuple): if _is_nested_tuple(label): - indexer = self.index.get_locs(label) + # When the tuple contains sub-tuples/lists/slices, it could be: + # 1. A single key with tuple-valued levels (e.g., ((1, 1), 2)) + # 2. A multi-value selection (e.g., (1, slice(1, 2))) + # Try get_loc first for case 1, fall back to get_locs for case 2 + try: + indexer = self.index.get_loc(label) + except (InvalidIndexError, KeyError): + indexer = self.index.get_locs(label) elif len(label) == self.index.nlevels: indexer = self.index.get_loc(label) else: diff --git a/xarray/namedarray/utils.py b/xarray/namedarray/utils.py index 3490a76aa8d..e4267637e17 100644 --- a/xarray/namedarray/utils.py +++ b/xarray/namedarray/utils.py @@ -60,6 +60,9 @@ def module_available(module: str, minversion: str | None = None) -> bool: if minversion is not None: version = importlib.metadata.version(module) + if version is None: + return False + return Version(version) >= Version(minversion) return True diff --git a/xarray/tests/test_indexes.py b/xarray/tests/test_indexes.py index 94adcc3b935..a43095c2711 100644 --- a/xarray/tests/test_indexes.py +++ b/xarray/tests/test_indexes.py @@ -559,6 +559,22 @@ def test_copy(self) -> None: assert actual.dim == expected.dim assert actual.level_coords_dtype == expected.level_coords_dtype + def test_sel_nested_tuple_key_collapses_dimension(self) -> None: + # Regression test for GH#11341 + # When indexing with a single nested tuple key (where one level has + # tuple-valued entries), the dimension should collapse like scalar selection + nested_level_0 = pd.Index( + [(1, 1), (1, 1), (2, 2), (3, 3)], name="a", tupleize_cols=False + ) + nested_level_1 = pd.Index([1, 2, 10, 20], name="b") + nested_mi = pd.MultiIndex.from_arrays([nested_level_0, nested_level_1]) + index = PandasMultiIndex(nested_mi, "index") + + # A single nested tuple key should produce a scalar indexer (int), + # not an array, collapsing the dimension + actual = index.sel({"index": ((1, 1), 2)}) + assert isinstance(actual.dim_indexers["index"], (int, np.integer)) + class TestIndexes: @pytest.fixture diff --git a/xarray/tests/test_namedarray.py b/xarray/tests/test_namedarray.py index 09ff1795ef4..073e6fe4849 100644 --- a/xarray/tests/test_namedarray.py +++ b/xarray/tests/test_namedarray.py @@ -666,3 +666,16 @@ def test_fake_target_chunksize_cftime() -> None: assert faked_chunksize == 73 assert dtype == np.float64 + + +def test_module_available_with_none_version(monkeypatch): + """Regression test for https://github.com/pydata/xarray/issues/11344.""" + import importlib.metadata + + from xarray.namedarray.utils import module_available + + module_available.cache_clear() + monkeypatch.setattr(importlib.metadata, "version", lambda name: None) + + assert module_available("numpy", minversion="1.0.0") is False + assert module_available("numpy") is True