1212 "find_name" ,
1313 "find_names" ,
1414 "find_relative_path" ,
15+ "find_relative_paths" ,
1516 "find_full_path" ,
1617 "find_path" ,
1718 "find_paths" ,
2829DAGNodeT = TypeVar ("DAGNodeT" , bound = DAGNode )
2930
3031
32+ def __check_result_count (
33+ result : Tuple [Any , ...], min_count : int , max_count : int
34+ ) -> None :
35+ """Check result fulfil min_count and max_count requirements
36+
37+ Args:
38+ result (Tuple[Any]): result of search
39+ min_count (int): checks for minimum number of occurrences,
40+ raise SearchError if the number of results do not meet min_count, defaults to None
41+ max_count (int): checks for maximum number of occurrences,
42+ raise SearchError if the number of results do not meet min_count, defaults to None
43+ """
44+ if min_count and len (result ) < min_count :
45+ raise SearchError (
46+ f"Expected more than or equal to { min_count } element(s), found { len (result )} elements\n { result } "
47+ )
48+ if max_count and len (result ) > max_count :
49+ raise SearchError (
50+ f"Expected less than or equal to { max_count } element(s), found { len (result )} elements\n { result } "
51+ )
52+
53+
3154def findall (
3255 tree : T ,
3356 condition : Callable [[T ], bool ],
@@ -36,7 +59,7 @@ def findall(
3659 max_count : int = 0 ,
3760) -> Tuple [T , ...]:
3861 """
39- Search tree for nodes matching condition (callable function).
62+ Search tree for one or more nodes matching condition (callable function).
4063
4164 Examples:
4265 >>> from bigtree import Node, findall
@@ -60,20 +83,13 @@ def findall(
6083 (Tuple[BaseNode, ...])
6184 """
6285 result = tuple (preorder_iter (tree , filter_condition = condition , max_depth = max_depth ))
63- if min_count and len (result ) < min_count :
64- raise SearchError (
65- f"Expected more than { min_count } element(s), found { len (result )} elements\n { result } "
66- )
67- if max_count and len (result ) > max_count :
68- raise SearchError (
69- f"Expected less than { max_count } element(s), found { len (result )} elements\n { result } "
70- )
86+ __check_result_count (result , min_count , max_count )
7187 return result
7288
7389
7490def find (tree : T , condition : Callable [[T ], bool ], max_depth : int = 0 ) -> T :
7591 """
76- Search tree for * single node* matching condition (callable function).
92+ Search tree for a single node matching condition (callable function).
7793
7894 Examples:
7995 >>> from bigtree import Node, find
@@ -86,7 +102,7 @@ def find(tree: T, condition: Callable[[T], bool], max_depth: int = 0) -> T:
86102 >>> find(root, lambda node: node.age > 5)
87103 Traceback (most recent call last):
88104 ...
89- bigtree.utils.exceptions.SearchError: Expected less than 1 element(s), found 4 elements
105+ bigtree.utils.exceptions.SearchError: Expected less than or equal to 1 element(s), found 4 elements
90106 (Node(/a, age=90), Node(/a/b, age=65), Node(/a/c, age=60), Node(/a/c/d, age=40))
91107
92108 Args:
@@ -104,7 +120,7 @@ def find(tree: T, condition: Callable[[T], bool], max_depth: int = 0) -> T:
104120
105121def find_name (tree : NodeT , name : str , max_depth : int = 0 ) -> NodeT :
106122 """
107- Search tree for single node matching name attribute.
123+ Search tree for a single node matching name attribute.
108124
109125 Examples:
110126 >>> from bigtree import Node, find_name
@@ -128,7 +144,7 @@ def find_name(tree: NodeT, name: str, max_depth: int = 0) -> NodeT:
128144
129145def find_names (tree : NodeT , name : str , max_depth : int = 0 ) -> Iterable [NodeT ]:
130146 """
131- Search tree for multiple node(s) matching name attribute.
147+ Search tree for one or more nodes matching name attribute.
132148
133149 Examples:
134150 >>> from bigtree import Node, find_names
@@ -152,9 +168,9 @@ def find_names(tree: NodeT, name: str, max_depth: int = 0) -> Iterable[NodeT]:
152168 return findall (tree , lambda node : node .node_name == name , max_depth )
153169
154170
155- def find_relative_path (tree : NodeT , path_name : str ) -> Iterable [ NodeT ] :
171+ def find_relative_path (tree : NodeT , path_name : str ) -> NodeT :
156172 r"""
157- Search tree for single node matching relative path attribute.
173+ Search tree for a single node matching relative path attribute.
158174
159175 - Supports unix folder expression for relative path, i.e., '../../node_name'
160176 - Supports wildcards, i.e., '\*/node_name'
@@ -167,18 +183,64 @@ def find_relative_path(tree: NodeT, path_name: str) -> Iterable[NodeT]:
167183 >>> c = Node("c", age=60, parent=root)
168184 >>> d = Node("d", age=40, parent=c)
169185 >>> find_relative_path(d, "..")
170- ( Node(/a/c, age=60), )
186+ Node(/a/c, age=60)
171187 >>> find_relative_path(d, "../../b")
172- ( Node(/a/b, age=65), )
188+ Node(/a/b, age=65)
173189 >>> find_relative_path(d, "../../*")
190+ Traceback (most recent call last):
191+ ...
192+ bigtree.utils.exceptions.SearchError: Expected less than or equal to 1 element(s), found 2 elements
174193 (Node(/a/b, age=65), Node(/a/c, age=60))
175194
176195 Args:
177196 tree (Node): tree to search
178197 path_name (str): value to match (relative path) of path_name attribute
179198
180199 Returns:
181- (Iterable[Node])
200+ (Node)
201+ """
202+ result = find_relative_paths (tree , path_name , max_count = 1 )
203+
204+ if result :
205+ return result [0 ]
206+
207+
208+ def find_relative_paths (
209+ tree : NodeT ,
210+ path_name : str ,
211+ min_count : int = 0 ,
212+ max_count : int = 0 ,
213+ ) -> Tuple [NodeT , ...]:
214+ r"""
215+ Search tree for one or more nodes matching relative path attribute.
216+
217+ - Supports unix folder expression for relative path, i.e., '../../node_name'
218+ - Supports wildcards, i.e., '\*/node_name'
219+ - If path name starts with leading separator symbol, it will start at root node.
220+
221+ Examples:
222+ >>> from bigtree import Node, find_relative_paths
223+ >>> root = Node("a", age=90)
224+ >>> b = Node("b", age=65, parent=root)
225+ >>> c = Node("c", age=60, parent=root)
226+ >>> d = Node("d", age=40, parent=c)
227+ >>> find_relative_paths(d, "..")
228+ (Node(/a/c, age=60),)
229+ >>> find_relative_paths(d, "../../b")
230+ (Node(/a/b, age=65),)
231+ >>> find_relative_paths(d, "../../*")
232+ (Node(/a/b, age=65), Node(/a/c, age=60))
233+
234+ Args:
235+ tree (Node): tree to search
236+ path_name (str): value to match (relative path) of path_name attribute
237+ min_count (int): checks for minimum number of occurrences,
238+ raise SearchError if the number of results do not meet min_count, defaults to None
239+ max_count (int): checks for maximum number of occurrences,
240+ raise SearchError if the number of results do not meet min_count, defaults to None
241+
242+ Returns:
243+ (Tuple[Node, ...])
182244 """
183245 sep = tree .sep
184246 if path_name .startswith (sep ):
@@ -220,13 +282,14 @@ def resolve(node: NodeT, path_idx: int) -> None:
220282 resolve (node , path_idx + 1 )
221283
222284 resolve (tree , 0 )
223-
224- return tuple (resolved_nodes )
285+ result = tuple (resolved_nodes )
286+ __check_result_count (result , min_count , max_count )
287+ return result
225288
226289
227290def find_full_path (tree : NodeT , path_name : str ) -> NodeT :
228291 """
229- Search tree for single node matching path attribute.
292+ Search tree for a single node matching path attribute.
230293
231294 - Path name can be with or without leading tree path separator symbol.
232295 - Path name must be full path, works similar to `find_path` but faster.
@@ -265,7 +328,7 @@ def find_full_path(tree: NodeT, path_name: str) -> NodeT:
265328
266329def find_path (tree : NodeT , path_name : str ) -> NodeT :
267330 """
268- Search tree for single node matching path attribute.
331+ Search tree for a single node matching path attribute.
269332
270333 - Path name can be with or without leading tree path separator symbol.
271334 - Path name can be full path or partial path (trailing part of path) or node name.
@@ -292,9 +355,9 @@ def find_path(tree: NodeT, path_name: str) -> NodeT:
292355 return find (tree , lambda node : node .path_name .endswith (path_name ))
293356
294357
295- def find_paths (tree : NodeT , path_name : str ) -> Tuple [NodeT , ... ]:
358+ def find_paths (tree : NodeT , path_name : str ) -> Iterable [NodeT ]:
296359 """
297- Search tree for multiple nodes matching path attribute.
360+ Search tree for one or more nodes matching path attribute.
298361
299362 - Path name can be with or without leading tree path separator symbol.
300363 - Path name can be partial path (trailing part of path) or node name.
@@ -315,7 +378,7 @@ def find_paths(tree: NodeT, path_name: str) -> Tuple[NodeT, ...]:
315378 path_name (str): value to match (full path) or trailing part (partial path) of path_name attribute
316379
317380 Returns:
318- (Tuple [Node, ... ])
381+ (Iterable [Node])
319382 """
320383 path_name = path_name .rstrip (tree .sep )
321384 return findall (tree , lambda node : node .path_name .endswith (path_name ))
@@ -325,7 +388,7 @@ def find_attr(
325388 tree : BaseNode , attr_name : str , attr_value : Any , max_depth : int = 0
326389) -> BaseNode :
327390 """
328- Search tree for single node matching custom attribute.
391+ Search tree for a single node matching custom attribute.
329392
330393 Examples:
331394 >>> from bigtree import Node, find_attr
@@ -354,9 +417,9 @@ def find_attr(
354417
355418def find_attrs (
356419 tree : BaseNode , attr_name : str , attr_value : Any , max_depth : int = 0
357- ) -> Tuple [BaseNode , ... ]:
420+ ) -> Iterable [BaseNode ]:
358421 """
359- Search tree for node(s) matching custom attribute.
422+ Search tree for one or more nodes matching custom attribute.
360423
361424 Examples:
362425 >>> from bigtree import Node, find_attrs
@@ -374,7 +437,7 @@ def find_attrs(
374437 max_depth (int): maximum depth to search for, based on the `depth` attribute, defaults to None
375438
376439 Returns:
377- (Tuple [BaseNode, ... ])
440+ (Iterable [BaseNode])
378441 """
379442 return findall (
380443 tree ,
@@ -390,7 +453,7 @@ def find_children(
390453 max_count : int = 0 ,
391454) -> Tuple [Union [T , DAGNodeT ], ...]:
392455 """
393- Search children for nodes matching condition (callable function).
456+ Search children for one or more nodes matching condition (callable function).
394457
395458 Examples:
396459 >>> from bigtree import Node, find_children
@@ -410,17 +473,10 @@ def find_children(
410473 raise SearchError if the number of results do not meet min_count, defaults to None
411474
412475 Returns:
413- (BaseNode/ DAGNode)
476+ (Tuple[Union[ BaseNode, DAGNode], ...] )
414477 """
415478 result = tuple ([node for node in tree .children if node and condition (node )])
416- if min_count and len (result ) < min_count :
417- raise SearchError (
418- f"Expected more than { min_count } element(s), found { len (result )} elements\n { result } "
419- )
420- if max_count and len (result ) > max_count :
421- raise SearchError (
422- f"Expected less than { max_count } element(s), found { len (result )} elements\n { result } "
423- )
479+ __check_result_count (result , min_count , max_count )
424480 return result
425481
426482
@@ -429,7 +485,7 @@ def find_child(
429485 condition : Callable [[Union [T , DAGNodeT ]], bool ],
430486) -> Union [T , DAGNodeT ]:
431487 """
432- Search children for * single node* matching condition (callable function).
488+ Search children for a single node matching condition (callable function).
433489
434490 Examples:
435491 >>> from bigtree import Node, find_child
@@ -456,7 +512,7 @@ def find_child_by_name(
456512 tree : Union [NodeT , DAGNodeT ], name : str
457513) -> Union [NodeT , DAGNodeT ]:
458514 """
459- Search tree for single node matching name attribute.
515+ Search tree for a single node matching name attribute.
460516
461517 Examples:
462518 >>> from bigtree import Node, find_child_by_name
0 commit comments