Skip to content

Commit

Permalink
Forbid modifying names of DataTree objects with parents (#9494)
Browse files Browse the repository at this point in the history
  • Loading branch information
shoyer authored Sep 14, 2024
1 parent 18e5c87 commit 8db6bc9
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 10 deletions.
30 changes: 21 additions & 9 deletions xarray/core/treenode.py
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,14 @@ def same_tree(self, other: Tree) -> bool:
AnyNamedNode = TypeVar("AnyNamedNode", bound="NamedNode")


def _validate_name(name: str | None) -> None:
if name is not None:
if not isinstance(name, str):
raise TypeError("node name must be a string or None")
if "/" in name:
raise ValueError("node names cannot contain forward slashes")


class NamedNode(TreeNode, Generic[Tree]):
"""
A TreeNode which knows its own name.
Expand All @@ -653,8 +661,8 @@ class NamedNode(TreeNode, Generic[Tree]):

def __init__(self, name=None, children=None):
super().__init__(children=children)
self._name = None
self.name = name
_validate_name(name)
self._name = name

@property
def name(self) -> str | None:
Expand All @@ -663,11 +671,13 @@ def name(self) -> str | None:

@name.setter
def name(self, name: str | None) -> None:
if name is not None:
if not isinstance(name, str):
raise TypeError("node name must be a string or None")
if "/" in name:
raise ValueError("node names cannot contain forward slashes")
if self.parent is not None:
raise ValueError(
"cannot set the name of a node which already has a parent. "
"Consider creating a detached copy of this node via .copy() "
"on the parent node."
)
_validate_name(name)
self._name = name

def __repr__(self, level=0):
Expand All @@ -677,11 +687,13 @@ def __repr__(self, level=0):
return repr_value

def __str__(self) -> str:
return f"NamedNode('{self.name}')" if self.name else "NamedNode()"
name_repr = repr(self.name) if self.name is not None else ""
return f"NamedNode({name_repr})"

def _post_attach(self: AnyNamedNode, parent: AnyNamedNode, name: str) -> None:
"""Ensures child has name attribute corresponding to key under which it has been stored."""
self.name = name
_validate_name(name) # is this check redundant?
self._name = name

def _copy_node(
self: AnyNamedNode,
Expand Down
20 changes: 19 additions & 1 deletion xarray/tests/test_datatree.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,28 @@ def test_empty(self):
assert dt.children == {}
assert_identical(dt.to_dataset(), xr.Dataset())

def test_unnamed(self):
def test_name(self):
dt = DataTree()
assert dt.name is None

dt = DataTree(name="foo")
assert dt.name == "foo"

dt.name = "bar"
assert dt.name == "bar"

dt = DataTree(children={"foo": DataTree()})
assert dt["/foo"].name == "foo"
with pytest.raises(
ValueError, match="cannot set the name of a node which already has a parent"
):
dt["/foo"].name = "bar"

detached = dt["/foo"].copy()
assert detached.name == "foo"
detached.name = "bar"
assert detached.name == "bar"

def test_bad_names(self):
with pytest.raises(TypeError):
DataTree(name=5) # type: ignore[arg-type]
Expand Down

0 comments on commit 8db6bc9

Please sign in to comment.