diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 7e3badc7143..b0033f9c6fd 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -29,12 +29,17 @@ Bug Fixes - Ensure that ``keep_attrs='drop'`` and ``keep_attrs=False`` remove attrs from result, even when there is only one xarray object given to ``apply_ufunc`` (:issue:`10982` :pull:`10997`). By `Julia Signell `_. +- Forbid slashes in ``DataTree`` coordinate names (:issue:`#9485` :pull:`11015`). + By `Ewan Short `_. Documentation ~~~~~~~~~~~~~ - Better description of ``keep_attrs`` option on ``xarray.where`` docstring (:issue:`10982` :pull:`10997`). By `Julia Signell `_. +- Clarify ``DataTree.coords`` and ``DataTree.data_vars`` docstrings to indicate +they refer to the coordinates and variables of that node only (:issue:`9485` :pull:`11015`). + By `Ewan Short `_. Internal Changes ~~~~~~~~~~~~~~~~ diff --git a/xarray/core/coordinates.py b/xarray/core/coordinates.py index 9aa64a57ff2..c58223436ef 100644 --- a/xarray/core/coordinates.py +++ b/xarray/core/coordinates.py @@ -1019,6 +1019,11 @@ def _update_coords( ) -> None: from xarray.core.datatree import check_alignment + # For now, ensure coordinate keys do not contain '/' character. See + # https://github.com/pydata/xarray/issues/9485 + # https://github.com/pydata/xarray/pull/9492 + _validate_coordinate_names(coords.keys()) + # create updated node (`.to_dataset` makes a copy so this doesn't modify in-place) node_ds = self._data.to_dataset(inherit=False) node_ds.coords._update_coords(coords, indexes) @@ -1060,6 +1065,19 @@ def _ipython_key_completions_(self): ] +def _validate_coordinate_names(coordinates: Iterable[Hashable]) -> None: + offending_coordinate_names = [ + name for name in coordinates if isinstance(name, str) and "/" in name + ] + if len(offending_coordinate_names) > 0: + raise ValueError( + "Given coordinate names contain the '/' character: " + f"{offending_coordinate_names}. Accessing the coordinates of other nodes " + "in the tree is not yet supported here. Retrieve the other node first, " + "then access its coordinates." + ) + + class DataArrayCoordinates(Coordinates, Generic[T_DataArray]): """Dictionary like container for DataArray coordinates (variables + indexes). diff --git a/xarray/core/datatree.py b/xarray/core/datatree.py index e079332780c..f6b8ad9f331 100644 --- a/xarray/core/datatree.py +++ b/xarray/core/datatree.py @@ -1467,13 +1467,14 @@ def xindexes(self) -> Indexes[Index]: @property def coords(self) -> DataTreeCoordinates: """Dictionary of xarray.DataArray objects corresponding to coordinate - variables + variables at this node. """ return DataTreeCoordinates(self) @property def data_vars(self) -> DataVariables: - """Dictionary of DataArray objects corresponding to data variables""" + """Dictionary of DataArray objects corresponding to data variables at this + node.""" return DataVariables(self.to_dataset()) def isomorphic(self, other: DataTree) -> bool: diff --git a/xarray/tests/test_datatree.py b/xarray/tests/test_datatree.py index 0cd888f5782..b55ddd1b0ea 100644 --- a/xarray/tests/test_datatree.py +++ b/xarray/tests/test_datatree.py @@ -762,6 +762,23 @@ def test_inherited(self) -> None: # expected = child.assign_coords({"c": 11}) # assert_identical(expected, actual) + def test_forbid_access_other_node_coords(self) -> None: + ds = Dataset(coords={"x": 0}) + tree = DataTree(ds, children={"child": DataTree()}) + expected = DataTree(ds, children={"child": DataTree(Dataset(coords={"y": 2}))}) + with pytest.raises( + ValueError, + match=re.escape( + "Given coordinate names contain the '/' character: ['/child/y']. " + "Accessing the coordinates of other nodes in the tree is not yet " + "supported here. Retrieve the other node first, then access its " + "coordinates." + ), + ): + tree.coords["/child/y"] = 2 + tree["child"].coords["y"] = DataArray(2) + assert_equal(tree, expected) + def test_delitem() -> None: ds = Dataset({"a": 0}, coords={"x": ("x", [1, 2]), "z": "a"})