From 9c829d19f65695e447ad59f6af4ba496b1f7f7eb Mon Sep 17 00:00:00 2001 From: Mike German Date: Sun, 12 Jul 2026 12:29:05 -0400 Subject: [PATCH] fix: reassigning Node.edge no longer detaches node from its parent Node._set_edge() removed the node from its parent's _child_nodes list as a side effect of swapping in a new Edge object, without re-adding it anywhere or clearing _parent_node. Since a node's tail_node is derived from head_node._parent_node (not stored independently), this detachment had no bookkeeping purpose and only corrupted the tree: after 'node.edge = new_edge', the node vanished from its parent's children while node.parent_node still pointed at the old parent. Signed-off-by: Mike German --- src/dendropy/datamodel/treemodel/_node.py | 5 ----- .../test_datamodel_tree_node_fundamentals.py | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/dendropy/datamodel/treemodel/_node.py b/src/dendropy/datamodel/treemodel/_node.py index 427f73df..cec3b06c 100644 --- a/src/dendropy/datamodel/treemodel/_node.py +++ b/src/dendropy/datamodel/treemodel/_node.py @@ -979,11 +979,6 @@ def _set_edge(self, new_edge): # raise ValueError("A Node cannot have 'None' for an edge") if new_edge is self._edge: return - if self._parent_node is not None: - try: - self._parent_node._child_nodes.remove(self) - except ValueError: - pass ## Minimal management self._edge = new_edge diff --git a/tests/unittests/test_datamodel_tree_node_fundamentals.py b/tests/unittests/test_datamodel_tree_node_fundamentals.py index da1a0dd8..01b50e84 100644 --- a/tests/unittests/test_datamodel_tree_node_fundamentals.py +++ b/tests/unittests/test_datamodel_tree_node_fundamentals.py @@ -322,6 +322,25 @@ def test_edge_head_node_setting(self): self.assertIs(node.edge, edge2) self.assertIs(node.edge.head_node, node) + def test_edge_setting_does_not_corrupt_parent_child_bookkeeping(self): + # Reassigning a node's ``edge`` is unrelated to the node's position + # in the tree, and should not affect the parent's list of children + # or the node's own parent reference. + parent = dendropy.Node(label="parent") + child1 = dendropy.Node(label="child1") + child2 = dendropy.Node(label="child2") + parent.set_child_nodes([child1, child2]) + self.assertEqual(len(parent.child_nodes()), 2) + self.assertIn(child1, parent.child_nodes()) + self.assertIs(child1.parent_node, parent) + + child1.edge = dendropy.Edge(length=5.0) + + self.assertEqual(len(parent.child_nodes()), 2) + self.assertIn(child1, parent.child_nodes()) + self.assertIs(child1.parent_node, parent) + + class TestNodeDistanceFromRoot(unittest.TestCase): def test_none_edge_length_with_parent_also_none(self):