Skip to content

Commit dd162d5

Browse files
authored
Merge pull request #2 from leonardbinet/remove_node_hierarchy
Remove node hierarchy
2 parents e77f1b9 + 1f11462 commit dd162d5

File tree

7 files changed

+35
-93
lines changed

7 files changed

+35
-93
lines changed

lighttree/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from .node import Node
2-
from .tree import Tree
1+
from .tree import Tree, Node
32
from .interactive import TreeBasedObj
43

54
__all__ = ["Tree", "Node", "TreeBasedObj"]

lighttree/node.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
23

34
from __future__ import unicode_literals
5+
import copy
46
from future.utils import python_2_unicode_compatible, string_types
57

68
import uuid
79

810

911
@python_2_unicode_compatible
1012
class Node(object):
11-
def __init__(self, identifier=None, auto_uuid=False, _children=None):
13+
def __init__(self, identifier=None, auto_uuid=False):
1214
"""
1315
:param identifier: node identifier, must be unique per tree
1416
"""
@@ -22,18 +24,15 @@ def __init__(self, identifier=None, auto_uuid=False, _children=None):
2224
raise ValueError("Required identifier")
2325
identifier = uuid.uuid4()
2426
self.identifier = identifier
25-
# children type is not checked here, it is at insertion in tree
26-
# only allowed types should be Node, or Tree, but cannot ensure whether it's a Tree since it would cause
27-
# a recursive import error
28-
if _children is not None and not isinstance(_children, (list, tuple)):
29-
raise ValueError("Invalid children declaration.")
30-
self._children = _children
3127

3228
def line_repr(self, depth, **kwargs):
3329
"""Control how node is displayed in tree representation.
3430
"""
3531
return self.identifier
3632

33+
def clone(self, deep=False):
34+
return copy.deepcopy(self) if deep else copy.copy(self)
35+
3736
def serialize(self, *args, **kwargs):
3837
return {"identifier": self.identifier}
3938

@@ -47,6 +46,11 @@ def deserialize(cls, d, *args, **kwargs):
4746
def _deserialize(cls, d, *args, **kwargs):
4847
return cls(d.get("identifier"))
4948

49+
def __eq__(self, other):
50+
if not isinstance(other, self.__class__):
51+
return False
52+
return self.identifier == other.identifier
53+
5054
def __str__(self):
5155
return "%s, id=%s" % (self.__class__.__name__, self.identifier)
5256

lighttree/tree.py

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
from future.utils import python_2_unicode_compatible, iteritems
66

77
from collections import defaultdict
8-
from copy import deepcopy
98
from operator import attrgetter
109

11-
from .node import Node
10+
from lighttree.node import Node
1211
from .utils import STYLES
1312
from .exceptions import MultipleRootError, NotFoundNodeError, DuplicatedNodeError
1413

@@ -72,7 +71,7 @@ def _validate_node_insertion(self, node):
7271
)
7372

7473
def _validate_tree_insertion(self, tree):
75-
if not isinstance(tree, self.__class__):
74+
if not isinstance(tree, Tree):
7675
raise ValueError(
7776
"Tree must be instance of <%s>, got <%s>"
7877
% (self.__class__.__name__, type(tree))
@@ -114,11 +113,8 @@ def clone(self, with_tree=True, deep=False, new_root=None):
114113

115114
for nid in self.expand_tree(nid=new_root):
116115
node = self.get(nid)
117-
if deep:
118-
node = deepcopy(node)
119116
pid = None if nid == self.root or nid == new_root else self.parent(nid)
120-
# with_children only makes sense when using "node hierarchy" syntax
121-
new_tree.insert_node(node, parent_id=pid, with_children=False)
117+
new_tree.insert_node(node, parent_id=pid, deep=deep)
122118
return new_tree
123119

124120
def parent(self, nid, id_only=True):
@@ -210,39 +206,36 @@ def insert(
210206
'"item" parameter must either be a Node, or a Tree, got <%s>.' % type(item)
211207
)
212208

213-
def insert_node(
214-
self, node, parent_id=None, child_id=None, deep=False, with_children=True
215-
):
209+
def insert_node(self, node, parent_id=None, child_id=None, deep=False):
210+
"""Make a copy of inserted node, and insert it.
211+
212+
Note: when using "Node hierarchy" syntax, _children attribute of copied node are reset so that insertion occurs
213+
once only.
214+
"""
216215
self._validate_node_insertion(node)
217-
node = deepcopy(node) if deep else node
216+
node = node.clone(deep=deep)
218217
if parent_id is not None and child_id is not None:
219218
raise ValueError('Can declare at most "parent_id" or "child_id"')
220219
if child_id is not None:
221220
self._insert_node_above(node, child_id=child_id)
222221
return self
223-
self._insert_node_below(node, parent_id=parent_id, with_children=with_children)
222+
self._insert_node_below(node, parent_id=parent_id)
224223
return self
225224

226-
def _insert_node_below(self, node, parent_id, with_children=True):
225+
def _insert_node_below(self, node, parent_id):
227226
# insertion at root
228227
if parent_id is None:
229228
if not self.is_empty():
230229
raise MultipleRootError("A tree takes one root merely.")
231230
self.root = node.identifier
232231
self._nodes_map[node.identifier] = node
233-
if with_children and hasattr(node, "_children"):
234-
for child in node._children or []:
235-
self.insert(child, parent_id=node.identifier)
236232
return
237233

238234
self._ensure_present(parent_id)
239235
node_id = node.identifier
240236
self._nodes_map[node_id] = node
241237
self._nodes_parent[node_id] = parent_id
242238
self._nodes_children[parent_id].add(node_id)
243-
if with_children and hasattr(node, "_children"):
244-
for child in node._children or []:
245-
self.insert(child, parent_id=node.identifier)
246239

247240
def _insert_node_above(self, node, child_id):
248241
self._ensure_present(child_id)
@@ -280,10 +273,8 @@ def _insert_tree_below(self, new_tree, parent_id, deep):
280273
for new_nid in new_tree.expand_tree():
281274
node = new_tree.get(new_nid)
282275
pid = parent_id if new_nid == new_tree.root else new_tree.parent(new_nid)
283-
# with_children only makes sense when using "node hierarchy" syntax
284-
self.insert_node(
285-
deepcopy(node) if deep else node, parent_id=pid, with_children=False
286-
)
276+
# node copy is handled in insert_node method
277+
self.insert_node(node, parent_id=pid, deep=deep)
287278
return self
288279

289280
def _insert_tree_above(self, new_tree, child_id, child_id_below, deep):

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from setuptools import setup
55

6-
__version__ = "0.0.3"
6+
__version__ = "0.0.5"
77

88

99
setup(

tests/test_interactive.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,7 @@ class InteractiveTree(TreeBasedObj):
188188
└── a2
189189
"""
190190
self.assertTrue(hasattr(a, "a1"))
191-
# check that initial tree, and child tree reference the same nodes
192-
self.assertIs(a._tree.get("a1"), obj._tree.get("a1"))
191+
self.assertEqual(a._tree.get("a1"), obj._tree.get("a1"))
193192

194193
# test representations
195194
self.assertEqual(

tests/test_tree.py

Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_insert_node_below(self):
7070
node_a = Node("a")
7171
t.insert_node(node_a, parent_id="root_id")
7272
self.assertSetEqual(set(t._nodes_map.keys()), {"root_id", "a"})
73-
self.assertIs(t._nodes_map["a"], node_a)
73+
self.assertEqual(t._nodes_map["a"], node_a)
7474
self.assertEqual(t._nodes_parent["root_id"], None)
7575
self.assertEqual(t._nodes_parent["a"], "root_id")
7676
self.assertSetEqual(t._nodes_children["a"], set())
@@ -245,7 +245,7 @@ def test_clone_with_tree(self):
245245
self.assertIs(t.mutable_object, t_shallow_clone.mutable_object)
246246
# nodes are shallow copies
247247
for nid, node in iteritems(t._nodes_map):
248-
self.assertIs(t_shallow_clone._nodes_map[nid], node)
248+
self.assertEqual(t_shallow_clone._nodes_map[nid], node)
249249
tree_sanity_check(t)
250250
tree_sanity_check(t_shallow_clone)
251251

@@ -637,7 +637,7 @@ def test_insert_tree_below(self):
637637
)
638638
self.assertTrue(all(nid in t for nid in ("c", "c1", "c2", "c12")))
639639
# by default pasted new tree is a shallow copy
640-
self.assertIs(t.get("c"), t_to_paste.get("c"))
640+
self.assertEqual(t.get("c"), t_to_paste.get("c"))
641641

642642
# cannot repaste tree, because then there would be node duplicates
643643
with self.assertRaises(DuplicatedNodeError):
@@ -779,8 +779,7 @@ def test_merge(self):
779779
# new tree root is not conserved
780780
self.assertTrue("c" not in t)
781781
self.assertTrue(all(nid in t for nid in ("c1", "c2", "c12")))
782-
# by default merged new tree is a shallow copy
783-
self.assertIs(t.get("c1"), t_to_merge.get("c1"))
782+
self.assertEqual(t.get("c1"), t_to_merge.get("c1"))
784783

785784
# cannot remerge tree, because then there would be node duplicates
786785
with self.assertRaises(DuplicatedNodeError):
@@ -888,58 +887,5 @@ def test_drop_subtree(self):
888887
"""a1
889888
├── a11
890889
└── a12
891-
""",
892-
)
893-
894-
def test_node_hierarchy_deserialization(self):
895-
node_hierarchy = Node(
896-
identifier="root",
897-
_children=[
898-
Node(
899-
identifier="a",
900-
_children=[Node(identifier="a1"), Node(identifier="a2")],
901-
),
902-
Node(identifier="b", _children=[Node(identifier="b1")]),
903-
],
904-
)
905-
t = Tree()
906-
t.insert(node_hierarchy)
907-
self.assertEqual(
908-
t.show(),
909-
"""root
910-
├── a
911-
│ ├── a1
912-
│ └── a2
913-
└── b
914-
└── b1
915-
""",
916-
)
917-
918-
def test_node_hierarchy_with_tree_deserialization(self):
919-
node_hierarchy = Node(
920-
identifier="root",
921-
_children=[
922-
Node(
923-
identifier="a",
924-
_children=[Node(identifier="a1"), Node(identifier="a2")],
925-
),
926-
Node(identifier="b", _children=[Node(identifier="b1")]),
927-
get_sample_tree_2(),
928-
],
929-
)
930-
t = Tree()
931-
t.insert(node_hierarchy)
932-
self.assertEqual(
933-
t.show(),
934-
"""root
935-
├── a
936-
│ ├── a1
937-
│ └── a2
938-
├── b
939-
│ └── b1
940-
└── c
941-
├── c1
942-
│ └── c12
943-
└── c2
944890
""",
945891
)

tests/testing_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ def __init__(self, identifier, key):
102102
self.key = key
103103
super(CustomNode, self).__init__(identifier=identifier)
104104

105+
def clone(self, deep=False):
106+
return self.__class__(identifier=self.identifier, key=self.key,)
107+
105108
def serialize(self, *args, **kwargs):
106109
with_key = kwargs.pop("with_key", None)
107110
d = super(CustomNode, self).serialize()

0 commit comments

Comments
 (0)