From a6f257a3f29b77e9449901ecbdb7b89c0177d083 Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Tue, 26 Sep 2023 17:45:09 +0200 Subject: [PATCH] Avoid a crash if dataset has supplementary variables (#2198) Co-authored-by: Valeriu Predoi --- esmvalcore/_recipe/from_datasets.py | 19 +++++++++++++++++++ tests/unit/recipe/test_from_datasets.py | 7 +++++++ 2 files changed, 26 insertions(+) diff --git a/esmvalcore/_recipe/from_datasets.py b/esmvalcore/_recipe/from_datasets.py index c54931806f..0e454c7350 100644 --- a/esmvalcore/_recipe/from_datasets.py +++ b/esmvalcore/_recipe/from_datasets.py @@ -160,6 +160,23 @@ def _group_identical_facets(variable: Mapping[str, Any]) -> Recipe: return result +class _SortableDict(dict): + """A `dict` class that can be sorted.""" + + def __lt__(self, other): + return tuple(self.items()) < tuple(other.items()) + + +def _change_dict_type(item, dict_type): + """Change the dict type in a nested structure.""" + change_dict_type = partial(_change_dict_type, dict_type=dict_type) + if isinstance(item, dict): + return dict_type((k, change_dict_type(v)) for k, v in item.items()) + if isinstance(item, (list, tuple, set)): + return type(item)(change_dict_type(elem) for elem in item) + return item + + def _group_ensemble_members(dataset_facets: Iterable[Facets]) -> list[Facets]: """Group ensemble members. @@ -171,6 +188,7 @@ def grouper(facets): return tuple((k, facets[k]) for k in sorted(facets) if k != 'ensemble') result = [] + dataset_facets = _change_dict_type(dataset_facets, _SortableDict) dataset_facets = sorted(dataset_facets, key=grouper) for group_facets, group in itertools.groupby(dataset_facets, key=grouper): ensembles = [f['ensemble'] for f in group if 'ensemble' in f] @@ -181,6 +199,7 @@ def grouper(facets): facets = dict(group_facets) facets['ensemble'] = ensemble result.append(facets) + result = _change_dict_type(result, dict) return result diff --git a/tests/unit/recipe/test_from_datasets.py b/tests/unit/recipe/test_from_datasets.py index 137f55f0c1..c2032724a4 100644 --- a/tests/unit/recipe/test_from_datasets.py +++ b/tests/unit/recipe/test_from_datasets.py @@ -8,6 +8,7 @@ _group_ensemble_names, _group_identical_facets, _move_one_level_up, + _SortableDict, _to_frozen, datasets_to_recipe, ) @@ -136,6 +137,12 @@ def test_supplementary_datasets_to_recipe(): assert datasets_to_recipe([dataset]) == recipe +def test_sortable_dict(): + assert _SortableDict({'a': 1}) < _SortableDict({'a': 2}) + assert _SortableDict({'a': 1}) < _SortableDict({'a': 1, 'b': 1}) + assert _SortableDict({'a': 1}) < _SortableDict({'b': 1}) + + def test_datasets_to_recipe_group_ensembles(): datasets = [ Dataset(