Skip to content

Commit 3deee7b

Browse files
keewisdcherian
andauthored
properly diff objects with arrays as attributes on variables (#9169)
* move the attr comparison into a common function * check that we can actually diff objects with array attrs * whats-new entry * Add property test * Add more dtypes * Better test * Fix skip * Use simple attrs strategy --------- Co-authored-by: Deepak Cherian <deepak@cherian.net> Co-authored-by: Deepak Cherian <dcherian@users.noreply.github.com>
1 parent caed274 commit 3deee7b

File tree

5 files changed

+66
-7
lines changed

5 files changed

+66
-7
lines changed

doc/whats-new.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ Bug fixes
3939
~~~~~~~~~
4040
- Make :py:func:`testing.assert_allclose` work with numpy 2.0 (:issue:`9165`, :pull:`9166`).
4141
By `Pontus Lurcock <https://github.com/pont-us>`_.
42+
- Allow diffing objects with array attributes on variables (:issue:`9153`, :pull:`9169`).
43+
By `Justus Magin <https://github.com/keewis>`_.
4244
- Promote floating-point numeric datetimes before decoding (:issue:`9179`, :pull:`9182`).
4345
By `Justus Magin <https://github.com/keewis>`_.
4446

properties/test_properties.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import pytest
2+
3+
pytest.importorskip("hypothesis")
4+
5+
from hypothesis import given
6+
7+
import xarray as xr
8+
import xarray.testing.strategies as xrst
9+
10+
11+
@given(attrs=xrst.simple_attrs)
12+
def test_assert_identical(attrs):
13+
v = xr.Variable(dims=(), data=0, attrs=attrs)
14+
xr.testing.assert_identical(v, v.copy(deep=True))
15+
16+
ds = xr.Dataset(attrs=attrs)
17+
xr.testing.assert_identical(ds, ds.copy(deep=True))

xarray/core/formatting.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,12 @@ def _diff_mapping_repr(
765765
a_indexes=None,
766766
b_indexes=None,
767767
):
768+
def compare_attr(a, b):
769+
if is_duck_array(a) or is_duck_array(b):
770+
return array_equiv(a, b)
771+
else:
772+
return a == b
773+
768774
def extra_items_repr(extra_keys, mapping, ab_side, kwargs):
769775
extra_repr = [
770776
summarizer(k, mapping[k], col_width, **kwargs[k]) for k in extra_keys
@@ -801,11 +807,7 @@ def extra_items_repr(extra_keys, mapping, ab_side, kwargs):
801807
is_variable = True
802808
except AttributeError:
803809
# compare attribute value
804-
if is_duck_array(a_mapping[k]) or is_duck_array(b_mapping[k]):
805-
compatible = array_equiv(a_mapping[k], b_mapping[k])
806-
else:
807-
compatible = a_mapping[k] == b_mapping[k]
808-
810+
compatible = compare_attr(a_mapping[k], b_mapping[k])
809811
is_variable = False
810812

811813
if not compatible:
@@ -821,7 +823,11 @@ def extra_items_repr(extra_keys, mapping, ab_side, kwargs):
821823

822824
attrs_to_print = set(a_attrs) ^ set(b_attrs)
823825
attrs_to_print.update(
824-
{k for k in set(a_attrs) & set(b_attrs) if a_attrs[k] != b_attrs[k]}
826+
{
827+
k
828+
for k in set(a_attrs) & set(b_attrs)
829+
if not compare_attr(a_attrs[k], b_attrs[k])
830+
}
825831
)
826832
for m in (a_mapping, b_mapping):
827833
attr_s = "\n".join(

xarray/testing/strategies.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,14 @@ def dimension_sizes(
192192
max_side=2,
193193
max_dims=2,
194194
),
195-
dtype=npst.scalar_dtypes(),
195+
dtype=npst.scalar_dtypes()
196+
| npst.byte_string_dtypes()
197+
| npst.unicode_string_dtypes(),
196198
)
197199
_attr_values = st.none() | st.booleans() | _readable_strings | _small_arrays
198200

201+
simple_attrs = st.dictionaries(_attr_keys, _attr_values)
202+
199203

200204
def attrs() -> st.SearchStrategy[Mapping[Hashable, Any]]:
201205
"""

xarray/tests/test_formatting.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,36 @@ def test_diff_attrs_repr_with_array(self) -> None:
399399
actual = formatting.diff_attrs_repr(attrs_a, attrs_c, "equals")
400400
assert expected == actual
401401

402+
def test__diff_mapping_repr_array_attrs_on_variables(self) -> None:
403+
a = {
404+
"a": xr.DataArray(
405+
dims="x",
406+
data=np.array([1], dtype="int16"),
407+
attrs={"b": np.array([1, 2], dtype="int8")},
408+
)
409+
}
410+
b = {
411+
"a": xr.DataArray(
412+
dims="x",
413+
data=np.array([1], dtype="int16"),
414+
attrs={"b": np.array([2, 3], dtype="int8")},
415+
)
416+
}
417+
actual = formatting.diff_data_vars_repr(a, b, compat="identical", col_width=8)
418+
expected = dedent(
419+
"""\
420+
Differing data variables:
421+
L a (x) int16 2B 1
422+
Differing variable attributes:
423+
b: [1 2]
424+
R a (x) int16 2B 1
425+
Differing variable attributes:
426+
b: [2 3]
427+
""".rstrip()
428+
)
429+
430+
assert actual == expected
431+
402432
def test_diff_dataset_repr(self) -> None:
403433
ds_a = xr.Dataset(
404434
data_vars={

0 commit comments

Comments
 (0)