Skip to content

Commit 42957f3

Browse files
committed
filter_through, nodes hierarchy deserialization, root insertion
1 parent e5b559c commit 42957f3

File tree

3 files changed

+169
-85
lines changed

3 files changed

+169
-85
lines changed

lighttree/node.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
@python_2_unicode_compatible
1010
class Node(object):
11-
def __init__(self, identifier=None, auto_uuid=False):
11+
def __init__(self, identifier=None, auto_uuid=False, _children=None):
1212
"""
1313
:param identifier: node identifier, must be unique per tree
1414
"""
@@ -22,6 +22,12 @@ def __init__(self, identifier=None, auto_uuid=False):
2222
raise ValueError("Required identifier")
2323
identifier = uuid.uuid4()
2424
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
2531

2632
def line_repr(self, depth, **kwargs):
2733
"""Control how node is displayed in tree representation.

lighttree/tree.py

Lines changed: 62 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -101,33 +101,24 @@ def _clone_init(self, deep):
101101
"""
102102
return self.__class__()
103103

104-
def _clone_nodes_with_hierarchy(self, new_tree, deep, new_root=None):
105-
"""Clone nodes and node hierarchies from current tree to new tree."""
106-
self._validate_tree_insertion(new_tree)
107-
if new_root is not None:
108-
self._ensure_present(new_root)
109-
else:
110-
new_root = self.root
111-
for node in self.expand_tree(new_root, id_only=False):
112-
new_tree._nodes_map[node.identifier] = deepcopy(node) if deep else node
113-
new_tree._nodes_parent[node.identifier] = self._nodes_parent[
114-
node.identifier
115-
]
116-
new_tree._nodes_children[node.identifier] = set(
117-
self._nodes_children[node.identifier]
118-
)
119-
120-
new_tree.root = new_root
121-
new_tree._nodes_parent[new_root] = None
122-
return new_tree
123-
124104
def clone(self, with_tree=True, deep=False, new_root=None):
125105
"""Clone current instance, with or without tree.
126106
:rtype: :class:`ltree.Tree`
127107
"""
128108
new_tree = self._clone_init(deep)
129-
if with_tree:
130-
self._clone_nodes_with_hierarchy(new_tree, new_root=new_root, deep=deep)
109+
if not with_tree:
110+
return new_tree
111+
if new_root is None:
112+
new_tree.insert(self, deep=deep)
113+
return new_tree
114+
115+
for nid in self.expand_tree(nid=new_root):
116+
node = self.get(nid)
117+
if deep:
118+
node = deepcopy(node)
119+
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)
131122
return new_tree
132123

133124
def parent(self, nid, id_only=True):
@@ -219,41 +210,45 @@ def insert(
219210
'"item" parameter must either be a Node, or a Tree, got <%s>.' % type(item)
220211
)
221212

222-
def insert_node(self, node, parent_id=None, child_id=None, deep=False):
213+
def insert_node(
214+
self, node, parent_id=None, child_id=None, deep=False, with_children=True
215+
):
223216
self._validate_node_insertion(node)
224217
node = deepcopy(node) if deep else node
225218
if parent_id is not None and child_id is not None:
226219
raise ValueError('Can declare at most "parent_id" or "child_id"')
227-
if parent_id is None and child_id is None:
228-
self._insert_node_at_root(node)
229-
return self
230-
if parent_id is not None:
231-
self._insert_node_below(node, parent_id=parent_id)
220+
if child_id is not None:
221+
self._insert_node_above(node, child_id=child_id)
232222
return self
233-
self._insert_node_above(node, child_id=child_id)
223+
self._insert_node_below(node, parent_id=parent_id, with_children=with_children)
234224
return self
235225

236-
def _insert_node_at_root(self, node):
237-
if not self.is_empty():
238-
raise MultipleRootError("A tree takes one root merely.")
239-
self.root = node.identifier
240-
self._nodes_map[node.identifier] = node
226+
def _insert_node_below(self, node, parent_id, with_children=True):
227+
# insertion at root
228+
if parent_id is None:
229+
if not self.is_empty():
230+
raise MultipleRootError("A tree takes one root merely.")
231+
self.root = node.identifier
232+
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)
236+
return
241237

242-
def _insert_node_below(self, node, parent_id):
243238
self._ensure_present(parent_id)
244239
node_id = node.identifier
245240
self._nodes_map[node_id] = node
246241
self._nodes_parent[node_id] = parent_id
247242
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)
248246

249247
def _insert_node_above(self, node, child_id):
250248
self._ensure_present(child_id)
251249
parent_id = self.parent(child_id)
252250
child_subtree = self.drop_subtree(child_id)
253-
if parent_id is None:
254-
self._insert_node_at_root(node)
255-
else:
256-
self._insert_node_below(node, parent_id)
251+
self._insert_node_below(node, parent_id)
257252
self._insert_tree_below(child_subtree, node.identifier, False)
258253

259254
def insert_tree(
@@ -264,34 +259,32 @@ def insert_tree(
264259
return self
265260
if parent_id is not None and child_id is not None:
266261
raise ValueError('Can declare at most "parent_id" or "child_id"')
267-
if parent_id is None and child_id is None:
268-
self._insert_tree_at_root(new_tree, deep=deep)
269-
return self
270-
if parent_id is not None:
271-
self._insert_tree_below(new_tree, parent_id=parent_id, deep=deep)
272-
return self
273-
self._insert_tree_above(
274-
new_tree, child_id=child_id, child_id_below=child_id_below, deep=deep
275-
)
276-
return self
277-
278-
def _insert_tree_at_root(self, new_tree, deep):
279-
# replace tree, allowed only if initial tree is empty
280-
if not self.is_empty():
281-
raise MultipleRootError("A tree takes one root merely.")
282-
new_tree._clone_nodes_with_hierarchy(self, deep=deep)
262+
if child_id is not None:
263+
return self._insert_tree_above(
264+
new_tree, child_id=child_id, child_id_below=child_id_below, deep=deep
265+
)
266+
return self._insert_tree_below(new_tree, parent_id=parent_id, deep=deep)
283267

284268
def _insert_tree_below(self, new_tree, parent_id, deep):
269+
if parent_id is None:
270+
# insertion at root requires tree to be empty
271+
if not self.is_empty():
272+
raise MultipleRootError("A tree takes one root merely.")
273+
else:
274+
self._ensure_present(parent_id)
285275
self._validate_tree_insertion(new_tree)
286-
self._ensure_present(parent_id)
287276

288277
if new_tree.is_empty():
289278
return self
290279

291280
for new_nid in new_tree.expand_tree():
292281
node = new_tree.get(new_nid)
293282
pid = parent_id if new_nid == new_tree.root else new_tree.parent(new_nid)
294-
self.insert_node(deepcopy(node) if deep else node, parent_id=pid)
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+
)
287+
return self
295288

296289
def _insert_tree_above(self, new_tree, child_id, child_id_below, deep):
297290
# make all checks before modifying tree
@@ -309,11 +302,7 @@ def _insert_tree_above(self, new_tree, child_id, child_id_below, deep):
309302
child_id_below = new_tree_leaves.pop()
310303
parent_id = self.parent(child_id)
311304
child_subtree = self.drop_subtree(child_id)
312-
if parent_id is None:
313-
self._insert_tree_at_root(new_tree, deep)
314-
else:
315-
self._insert_tree_below(new_tree, parent_id, deep)
316-
305+
self._insert_tree_below(new_tree, parent_id, deep)
317306
self._insert_tree_below(child_subtree, child_id_below, False)
318307

319308
def _drop_node(self, nid):
@@ -360,6 +349,7 @@ def expand_tree(
360349
nid=None,
361350
mode="depth",
362351
filter_=None,
352+
filter_through=False,
363353
key=None,
364354
reverse=False,
365355
id_only=True,
@@ -371,8 +361,8 @@ def expand_tree(
371361
372362
:param nid: Node identifier from which tree traversal will start. If None tree root will be used
373363
:param mode: Traversal mode, may be either "depth" or "width"
374-
:param filter_: filter function performed on nodes. Node excluded from filter function nor their children
375-
won't be yielded by generator.
364+
:param filter_: filter function performed on nodes. Node excluded from filter function won't be yielded.
365+
:param filter_through: if True, excluded nodes don't exclude their children.
376366
:param reverse: the ``reverse`` param for sorting :class:`Node` objects in the same level
377367
:param key: key used to order nodes of same parent
378368
:return: node ids that satisfy the conditions if ``id_only`` is True, else nodes.
@@ -384,23 +374,26 @@ def expand_tree(
384374
key = attrgetter("identifier") if key is None else key
385375
if nid is not None:
386376
node = self.get(nid)
387-
if filter_ is None or filter_(node):
377+
filter_pass_node = filter_ is None or filter_(node)
378+
if filter_pass_node:
388379
yield nid if id_only else node
380+
if filter_pass_node or filter_through:
389381
queue = [
390382
child_node
391383
for child_node in self.children(nid, id_only=False)
392-
if filter_ is None or filter_(child_node)
384+
if filter_ is None or filter_through or filter_(child_node)
393385
]
394386
queue.sort(key=key, reverse=reverse)
395387
while queue:
396388
current_node = queue.pop(0)
397-
yield current_node.identifier if id_only else current_node
389+
if filter_ is None or filter_(current_node):
390+
yield current_node.identifier if id_only else current_node
398391
expansion = [
399392
gchild_node
400393
for gchild_node in self.children(
401394
current_node.identifier, id_only=False
402395
)
403-
if filter_ is None or filter_(gchild_node)
396+
if filter_ is None or filter_through or filter_(gchild_node)
404397
]
405398
expansion.sort(key=key, reverse=reverse)
406399
if mode == "depth":
@@ -549,8 +542,7 @@ def merge(self, new_tree, nid=None, deep=False):
549542
)
550543

551544
if self.is_empty():
552-
new_tree._clone_nodes_with_hierarchy(new_tree=self, deep=deep)
553-
return self
545+
return self._insert_tree_below(new_tree, parent_id=None, deep=deep)
554546

555547
nid = self._ensure_present(nid, defaults_to_root=True)
556548

0 commit comments

Comments
 (0)