Repository: BAMWelDX/weldx

Issue:
DataArray attributes missing from Dataset ASDF serialization
When serializing and reading back a `Dataset`, only the main Dataset attributes get restored. Attributes attached to the individual DataArrays are lost.

```python
da = xr.DataArray(np.arange(10),name="arr1",attrs={"name":"sample data"})
ds = da.to_dataset()
print(ds.arr1.attrs)
ds2 = weldx.asdf.util.write_read_buffer({"ds":ds})["ds"]
print(ds2.arr1.attrs)
>> {'name': 'sample data'}
>> {}
```

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/BAMWelDX/weldx/pull/384?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX) Report
> Merging [#384](https://codecov.io/gh/BAMWelDX/weldx/pull/384?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX) (3836664) into [master](https://codecov.io/gh/BAMWelDX/weldx/commit/1999c8dfc1e9346206680f55cd484381c6ef4121?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX) (1999c8d) will **decrease** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/BAMWelDX/weldx/pull/384/graphs/tree.svg?width=650&height=150&src=pr&token=wdof1qQTsn&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX)](https://codecov.io/gh/BAMWelDX/weldx/pull/384?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX)

```diff
@@            Coverage Diff             @@
##           master     #384      +/-   ##
==========================================
- Coverage   97.44%   97.43%   -0.01%     
==========================================
  Files          86       86              
  Lines        5196     5187       -9     
==========================================
- Hits         5063     5054       -9     
  Misses        133      133              
```


| [Impacted Files](https://codecov.io/gh/BAMWelDX/weldx/pull/384?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX) | Coverage Δ | |
|---|---|---|
| [weldx/asdf/tags/weldx/core/common\_types.py](https://codecov.io/gh/BAMWelDX/weldx/pull/384/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX#diff-d2VsZHgvYXNkZi90YWdzL3dlbGR4L2NvcmUvY29tbW9uX3R5cGVzLnB5) | `100.00% <100.00%> (ø)` | |
| [weldx/asdf/tags/weldx/core/dataset.py](https://codecov.io/gh/BAMWelDX/weldx/pull/384/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX#diff-d2VsZHgvYXNkZi90YWdzL3dlbGR4L2NvcmUvZGF0YXNldC5weQ==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/BAMWelDX/weldx/pull/384?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/BAMWelDX/weldx/pull/384?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX). Last update [1999c8d...3836664](https://codecov.io/gh/BAMWelDX/weldx/pull/384?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=BAMWelDX).

marscher: Funnily the asdf tests pass on Python-3.9, so I guess the dataclassses.field(defaultfactory=dict) thing behaves differently on 3.8... 
marscher: Ah this is the schema tests failing. Sry. So do I need to adopt the schema as well, indicating the new optional "attrs" field should be there?
Please enlighten me @vhirtham, @CagtayFabry 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea5da23..1e3a30b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,6 +56,7 @@
 - replace references to base asdf tags with `-1.*` version wildcard [[#373]](https://github.com/BAMWelDX/weldx/pull/373)
 - update `single-pass-weldx.1.0.0.schema` to allow groove types by
   wildcard [[#373]](https://github.com/BAMWelDX/weldx/pull/373)
+- fix attributes serialization of DataSet children [[#384]](https://github.com/BAMWelDX/weldx/pull/384).  
 
 ### fixes
 
diff --git a/weldx/asdf/tags/weldx/core/common_types.py b/weldx/asdf/tags/weldx/core/common_types.py
index 6f4b65b..f9acd8b 100644
--- a/weldx/asdf/tags/weldx/core/common_types.py
+++ b/weldx/asdf/tags/weldx/core/common_types.py
@@ -1,5 +1,6 @@
+import dataclasses
 from dataclasses import dataclass
-from typing import List
+from typing import List, Mapping, Hashable, Any
 
 import numpy as np
 import pint
@@ -35,6 +36,7 @@ class Variable:
     name: str
     dimensions: List
     data: np.ndarray
+    attrs: Mapping[Hashable, Any] = dataclasses.field(default_factory=dict)
 
 
 class VariableTypeASDF(WeldxType):
@@ -105,6 +107,7 @@ class VariableTypeASDF(WeldxType):
             "dimensions": node.dimensions,
             "dtype": dtype,
             "data": data,
+            "attrs": node.attrs,
         }
         if unit:
             tree["unit"] = unit
@@ -135,4 +138,6 @@ class VariableTypeASDF(WeldxType):
         if "unit" in tree:  # convert to pint.Quantity
             data = Q_(data, tree["unit"])
 
-        return Variable(tree["name"], tree["dimensions"], data)
+        attrs = tree.get("attrs", None)
+
+        return Variable(tree["name"], tree["dimensions"], data, attrs)
diff --git a/weldx/asdf/tags/weldx/core/data_array.py b/weldx/asdf/tags/weldx/core/data_array.py
index c99c94d..e3e19ee 100644
--- a/weldx/asdf/tags/weldx/core/data_array.py
+++ b/weldx/asdf/tags/weldx/core/data_array.py
@@ -34,11 +34,11 @@ class XarrayDataArrayASDF(WeldxType):
 
         """
         attributes = node.attrs
-        coordinates = []
-        data = ct.Variable("data", node.dims, node.data)
-
-        for name, coord_data in node.coords.items():
-            coordinates.append(ct.Variable(name, coord_data.dims, coord_data.data))
+        coordinates = [
+            ct.Variable(name, coord_data.dims, coord_data.data, attrs=coord_data.attrs)
+            for name, coord_data in node.coords.items()
+        ]
+        data = ct.Variable("data", node.dims, node.data, attrs={})
 
         tree = {
             "attributes": attributes,
@@ -69,12 +69,8 @@ class XarrayDataArrayASDF(WeldxType):
         """
         data = tree["data"].data
         dims = tree["data"].dimensions
-        coords = {}
-        for coordinate in tree["coordinates"]:
-            coords[coordinate.name] = (coordinate.dimensions, coordinate.data)
-
-        obj = DataArray(data=data, coords=coords, dims=dims)
-
-        obj.attrs = tree["attributes"]
+        coords = {c.name: (c.dimensions, c.data, c.attrs) for c in tree["coordinates"]}
+        attrs = tree["attributes"]
 
-        return obj
+        da = DataArray(data=data, coords=coords, dims=dims, attrs=attrs)
+        return da
diff --git a/weldx/asdf/tags/weldx/core/dataset.py b/weldx/asdf/tags/weldx/core/dataset.py
index fb4125d..876ab37 100644
--- a/weldx/asdf/tags/weldx/core/dataset.py
+++ b/weldx/asdf/tags/weldx/core/dataset.py
@@ -34,18 +34,15 @@ class XarrayDatasetASDF(WeldxType):
 
         """
         attributes = node.attrs
-        coordinates = []
-        dimensions = []
-        variables = []
-
-        for name, length in dict(node.dims).items():
-            dimensions.append(ct.Dimension(name, length))
-
-        for name, data in node.data_vars.items():
-            variables.append(ct.Variable(name, data.dims, data.data))
-
-        for name, data in node.coords.items():
-            coordinates.append(ct.Variable(name, data.dims, data.data))
+        coordinates = [
+            ct.Variable(name, da.dims, da.data, da.attrs)
+            for name, da in node.coords.items()
+        ]
+        dimensions = [ct.Dimension(name, length) for name, length in node.dims.items()]
+        variables = [
+            ct.Variable(name, da.dims, da.data, da.attrs)
+            for name, da in node.data_vars.items()
+        ]
 
         tree = {
             "attributes": attributes,
@@ -54,11 +51,6 @@ class XarrayDatasetASDF(WeldxType):
             "variables": variables,
         }
 
-        # variables = []
-        # for variable_name in node:
-        #    variables.append(netcdf.NetCDFVariable(node[variable_name]))
-        # tree = {"coordinates": dict(node.coords), "variables": variables}
-
         return tree
 
     @classmethod
@@ -80,17 +72,7 @@ class XarrayDatasetASDF(WeldxType):
             An instance of the 'xarray.Dataset' type.
 
         """
-        data_vars = {}
-
-        for variable in tree["variables"]:
-            data_vars[variable.name] = (variable.dimensions, variable.data)
-
-        coords = {}
-        for coordinate in tree["coordinates"]:
-            coords[coordinate.name] = (coordinate.dimensions, coordinate.data)
-
-        obj = Dataset(data_vars=data_vars, coords=coords)
-
-        obj.attrs = tree["attributes"]
+        data_vars = {v.name: (v.dimensions, v.data, v.attrs) for v in tree["variables"]}
+        coords = {c.name: (c.dimensions, c.data, c.attrs) for c in tree["coordinates"]}
 
-        return obj
+        return Dataset(data_vars=data_vars, coords=coords, attrs=tree["attributes"])


Test patch:
diff --git a/weldx/tests/asdf_tests/test_asdf_base_types.py b/weldx/tests/asdf_tests/test_asdf_base_types.py
index b51e7f8..f26c51a 100644
--- a/weldx/tests/asdf_tests/test_asdf_base_types.py
+++ b/weldx/tests/asdf_tests/test_asdf_base_types.py
@@ -1,6 +1,7 @@
 """Tests asdf implementations of python base types."""
 import uuid
-
+import xarray as xr
+import numpy as np
 import pytest
 from asdf import ValidationError
 
@@ -16,3 +17,22 @@ def test_uuid():
 
     with pytest.raises(ValidationError):
         write_read_buffer({"id": uuid.uuid1()})
+
+
+# --------------------------------------------------------------------------------------
+# xarray
+# --------------------------------------------------------------------------------------
+def test_dataarray():
+    da = xr.DataArray(np.random.randn(2, 3), dims=("x", "y"), coords={"x": [10, 20]})
+    da.attrs["long_name"] = "random velocity"
+    # add metadata to coordinate
+    da.x.attrs["units"] = "x units"
+    da2 = write_read_buffer({"da": da})["da"]
+    assert da2.identical(da)
+
+
+def test_dataset_children():
+    da = xr.DataArray(np.arange(10), name="arr1", attrs={"name": "sample data"})
+    ds = da.to_dataset()
+    ds2 = write_read_buffer({"ds": ds})["ds"]
+    assert ds2.identical(ds)