Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions lib/iris/_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
multidim_lazy_stack,
)
from iris.common import CoordMetadata, CubeMetadata
from iris.common._split_attribute_dicts import (
_convert_splitattrs_to_pairedkeys_dict as convert_splitattrs_to_pairedkeys_dict,
)
import iris.coords
import iris.cube
import iris.exceptions
Expand Down Expand Up @@ -391,23 +394,27 @@ def _defn_msgs(self, other_defn):
)
)
if self_defn.attributes != other_defn.attributes:
diff_keys = set(self_defn.attributes.keys()) ^ set(
other_defn.attributes.keys()
attrs_1, attrs_2 = self_defn.attributes, other_defn.attributes
diff_keys = sorted(
set(attrs_1.globals) ^ set(attrs_2.globals)
| set(attrs_1.locals) ^ set(attrs_2.locals)
)
if diff_keys:
msgs.append(
"cube.attributes keys differ: "
+ ", ".join(repr(key) for key in diff_keys)
)
else:
attrs_1, attrs_2 = [
convert_splitattrs_to_pairedkeys_dict(dic)
for dic in (attrs_1, attrs_2)
]
diff_attrs = [
repr(key)
for key in self_defn.attributes
if np.all(
self_defn.attributes[key] != other_defn.attributes[key]
)
repr(key[1])
for key in attrs_1
if np.all(attrs_1[key] != attrs_2[key])
]
diff_attrs = ", ".join(diff_attrs)
diff_attrs = ", ".join(sorted(diff_attrs))
msgs.append(
"cube.attributes values differ for keys: {}".format(
diff_attrs
Expand Down
82 changes: 82 additions & 0 deletions lib/iris/tests/test_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from iris._lazy_data import as_lazy_data
from iris.coords import AuxCoord, DimCoord
import iris.cube
from iris.cube import CubeAttrsDict
import iris.exceptions
import iris.tests.stock

Expand Down Expand Up @@ -1108,5 +1109,86 @@ def test_ancillary_variable_error_msg(self):
_ = iris.cube.CubeList([cube1, cube2]).merge_cube()


class TestCubeMerge__split_attributes__error_messages(tests.IrisTest):
"""
Specific tests for the detection and wording of attribute-mismatch errors.

In particular, the adoption of 'split' attributes with the new
:class:`iris.cube.CubeAttrsDict` introduces some more subtle possible discrepancies
in attributes, where this has also impacted the messaging, so this aims to probe
those cases.
"""

def _check_merge_error(self, attrs_1, attrs_2, expected_message):
"""
Check the error from a merge failure caused by a mismatch of attributes.

Build a pair of cubes with given attributes, merge them + check for a match
to the expected error message.
"""
cube_1 = iris.cube.Cube(
[0],
aux_coords_and_dims=[(AuxCoord([1], long_name="x"), None)],
attributes=attrs_1,
)
cube_2 = iris.cube.Cube(
[0],
aux_coords_and_dims=[(AuxCoord([2], long_name="x"), None)],
attributes=attrs_2,
)
with self.assertRaisesRegex(
iris.exceptions.MergeError, expected_message
):
iris.cube.CubeList([cube_1, cube_2]).merge_cube()

def test_keys_differ__single(self):
self._check_merge_error(
attrs_1=dict(a=1, b=2),
attrs_2=dict(a=1),
# Note: matching key 'a' does *not* appear in the message
expected_message="cube.attributes keys differ: 'b'",
)

def test_keys_differ__multiple(self):
self._check_merge_error(
attrs_1=dict(a=1, b=2),
attrs_2=dict(a=1, c=2),
expected_message="cube.attributes keys differ: 'b', 'c'",
)

def test_values_differ__single(self):
self._check_merge_error(
attrs_1=dict(a=1, b=2), # Note: matching key 'a' does not appear
attrs_2=dict(a=1, b=3),
expected_message="cube.attributes values differ for keys: 'b'",
)

def test_values_differ__multiple(self):
self._check_merge_error(
attrs_1=dict(a=1, b=2),
attrs_2=dict(a=12, b=22),
expected_message="cube.attributes values differ for keys: 'a', 'b'",
)

def test_splitattrs_keys_local_global_mismatch(self):
# Since Cube.attributes is now a "split-attributes" dictionary, it is now
# possible to have "cube1.attributes != cube1.attributes", but also
# "set(cube1.attributes.keys()) == set(cube2.attributes.keys())".
# I.E. it is now necessary to specifically compare ".globals" and ".locals" to
# see *what* differs between two attributes dictionaries.
self._check_merge_error(
attrs_1=CubeAttrsDict(globals=dict(a=1), locals=dict(b=2)),
attrs_2=CubeAttrsDict(locals=dict(a=2)),
expected_message="cube.attributes keys differ: 'a', 'b'",
)

def test_splitattrs_keys_local_match_masks_global_mismatch(self):
self._check_merge_error(
attrs_1=CubeAttrsDict(globals=dict(a=1), locals=dict(a=3)),
attrs_2=CubeAttrsDict(globals=dict(a=2), locals=dict(a=3)),
expected_message="cube.attributes values differ for keys: 'a'",
)


if __name__ == "__main__":
tests.main()