Skip to content
This repository was archived by the owner on Oct 24, 2024. It is now read-only.

Commit e8aa454

Browse files
authored
Fix loop bug (#105)
* test to check for loops * fix bug when assigning new parent * refactor to use _is_descendant_of method * also check children setter for loops * whatsnew
1 parent 95c6c48 commit e8aa454

File tree

3 files changed

+29
-3
lines changed

3 files changed

+29
-3
lines changed

datatree/tests/test_treenode.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,26 @@ def test_parenting(self):
1818
assert mary.parent == john
1919
assert john.children["Mary"] is mary
2020

21+
def test_no_time_traveller_loops(self):
22+
john = TreeNode()
23+
24+
with pytest.raises(TreeError, match="cannot be a parent of itself"):
25+
john._set_parent(john, "John")
26+
27+
with pytest.raises(TreeError, match="cannot be a parent of itself"):
28+
john.children = {"John": john}
29+
30+
mary = TreeNode()
31+
rose = TreeNode()
32+
mary._set_parent(john, "Mary")
33+
rose._set_parent(mary, "Rose")
34+
35+
with pytest.raises(TreeError, match="is already a descendant"):
36+
john._set_parent(rose, "John")
37+
38+
with pytest.raises(TreeError, match="is already a descendant"):
39+
rose.children = {"John": john}
40+
2141
def test_parent_swap(self):
2242
john = TreeNode()
2343
mary = TreeNode()

datatree/treenode.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,15 @@ def _check_loop(self, new_parent: Tree | None) -> None:
111111
f"Cannot set parent, as node {self} cannot be a parent of itself."
112112
)
113113

114-
_self, *lineage = list(self.lineage)
115-
if any(child is self for child in lineage):
114+
if self._is_descendant_of(new_parent):
116115
raise TreeError(
117-
f"Cannot set parent, as node {self} is already a descendant of node {new_parent}."
116+
f"Cannot set parent, as node {new_parent.name} is already a descendant of this node."
118117
)
119118

119+
def _is_descendant_of(self, node: Tree) -> bool:
120+
_self, *lineage = list(node.lineage)
121+
return any(n is self for n in lineage)
122+
120123
def _detach(self, parent: Tree | None) -> None:
121124
if parent is not None:
122125
self._pre_detach(parent)

docs/source/whats-new.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ Deprecations
3232
Bug fixes
3333
~~~~~~~~~
3434

35+
- Fixed bug with checking that assigning parent or new children did not create a loop in the tree (:pull:`105`)
36+
By `Tom Nicholas <https://github.com/TomNicholas>`_.
37+
3538
Documentation
3639
~~~~~~~~~~~~~
3740

0 commit comments

Comments
 (0)