From b227711b97723e15298f688897c655a236039d1d Mon Sep 17 00:00:00 2001 From: nawaaaaaAaar <198336470+nawaaaaaAaar@users.noreply.github.com> Date: Thu, 22 Oct 2026 14:36:13 +0000 Subject: [PATCH] fix: reject ambiguous CF compression level names --- cf_xarray/coding.py | 14 ++++++++++++++ cf_xarray/tests/test_coding.py | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/cf_xarray/coding.py b/cf_xarray/coding.py index a80356e9..b7651eaa 100644 --- a/cf_xarray/coding.py +++ b/cf_xarray/coding.py @@ -23,6 +23,12 @@ def encode_multi_index_as_compress(ds, idxnames=None): xarray.Dataset Encoded Dataset with ``name`` as a integer coordinate with a ``"compress"`` attribute. + Raises + ------ + ValueError + If a MultiIndex level name is empty, is not a string, or contains + whitespace, which cannot be represented in the CF ``compress`` attribute. + References ---------- CF conventions on `compression by gathering `_ @@ -43,6 +49,14 @@ def encode_multi_index_as_compress(ds, idxnames=None): if not idxnames: raise ValueError("No MultiIndex-ed dimensions found in Dataset.") + for idxname in idxnames: + for name in ds.indexes[idxname].names: + if not isinstance(name, str) or not name or any(c.isspace() for c in name): + raise ValueError( + "MultiIndex level names must be nonempty strings without whitespace " + f"to be encoded in a CF 'compress' attribute. Got {name!r}." + ) + encoded = ds.reset_index(idxnames) for idxname in idxnames: mindex = ds.indexes[idxname] diff --git a/cf_xarray/tests/test_coding.py b/cf_xarray/tests/test_coding.py index a509715a..01dee8ea 100644 --- a/cf_xarray/tests/test_coding.py +++ b/cf_xarray/tests/test_coding.py @@ -6,6 +6,24 @@ import cf_xarray as cfxr +@pytest.mark.parametrize( + "name", ["lat name", " leading", "trailing ", "tab\tname", "line\nname", ""] +) +@pytest.mark.parametrize("level", [0, 1]) +def test_compression_rejects_ambiguous_level_names(name, level): + names = ["lat", "lon"] + names[level] = name + index = pd.MultiIndex.from_product([[1, 2], [3, 4]], names=names) + dataset = xr.Dataset( + {"data": ("point", [1, 2, 3, 4])}, + coords=xr.Coordinates.from_pandas_multiindex(index, "point"), + ) + before = dataset.copy(deep=True) + with pytest.raises(ValueError, match="nonempty strings without whitespace"): + cfxr.encode_multi_index_as_compress(dataset) + xr.testing.assert_identical(dataset, before) + + @pytest.mark.parametrize("with_data", [False, True]) def test_compression_preserves_unrelated_coordinates(with_data): index = pd.MultiIndex.from_product([[1, 2], [3, 4]], names=("x", "y"))