added 3rd party packages, elog, bigtree

This commit is contained in:
2024-02-27 15:40:00 +01:00
parent 277c22f800
commit 6b59fe16ce
69 changed files with 17449 additions and 0 deletions
+479
View File
@@ -0,0 +1,479 @@
from typing import Any, Callable, Iterable, List, Tuple, TypeVar, Union
from bigtree.node.basenode import BaseNode
from bigtree.node.dagnode import DAGNode
from bigtree.node.node import Node
from bigtree.utils.exceptions import SearchError
from bigtree.utils.iterators import preorder_iter
__all__ = [
"findall",
"find",
"find_name",
"find_names",
"find_relative_path",
"find_full_path",
"find_path",
"find_paths",
"find_attr",
"find_attrs",
"find_children",
"find_child",
"find_child_by_name",
]
T = TypeVar("T", bound=BaseNode)
NodeT = TypeVar("NodeT", bound=Node)
DAGNodeT = TypeVar("DAGNodeT", bound=DAGNode)
def findall(
tree: T,
condition: Callable[[T], bool],
max_depth: int = 0,
min_count: int = 0,
max_count: int = 0,
) -> Tuple[T, ...]:
"""
Search tree for nodes matching condition (callable function).
Examples:
>>> from bigtree import Node, findall
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("d", age=40, parent=c)
>>> findall(root, lambda node: node.age > 62)
(Node(/a, age=90), Node(/a/b, age=65))
Args:
tree (BaseNode): tree to search
condition (Callable): function that takes in node as argument, returns node if condition evaluates to `True`
max_depth (int): maximum depth to search for, based on the `depth` attribute, defaults to None
min_count (int): checks for minimum number of occurrences,
raise SearchError if the number of results do not meet min_count, defaults to None
max_count (int): checks for maximum number of occurrences,
raise SearchError if the number of results do not meet min_count, defaults to None
Returns:
(Tuple[BaseNode, ...])
"""
result = tuple(preorder_iter(tree, filter_condition=condition, max_depth=max_depth))
if min_count and len(result) < min_count:
raise SearchError(
f"Expected more than {min_count} element(s), found {len(result)} elements\n{result}"
)
if max_count and len(result) > max_count:
raise SearchError(
f"Expected less than {max_count} element(s), found {len(result)} elements\n{result}"
)
return result
def find(tree: T, condition: Callable[[T], bool], max_depth: int = 0) -> T:
"""
Search tree for *single node* matching condition (callable function).
Examples:
>>> from bigtree import Node, find
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("d", age=40, parent=c)
>>> find(root, lambda node: node.age == 65)
Node(/a/b, age=65)
>>> find(root, lambda node: node.age > 5)
Traceback (most recent call last):
...
bigtree.utils.exceptions.SearchError: Expected less than 1 element(s), found 4 elements
(Node(/a, age=90), Node(/a/b, age=65), Node(/a/c, age=60), Node(/a/c/d, age=40))
Args:
tree (BaseNode): tree to search
condition (Callable): function that takes in node as argument, returns node if condition evaluates to `True`
max_depth (int): maximum depth to search for, based on the `depth` attribute, defaults to None
Returns:
(BaseNode)
"""
result = findall(tree, condition, max_depth, max_count=1)
if result:
return result[0]
def find_name(tree: NodeT, name: str, max_depth: int = 0) -> NodeT:
"""
Search tree for single node matching name attribute.
Examples:
>>> from bigtree import Node, find_name
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("d", age=40, parent=c)
>>> find_name(root, "c")
Node(/a/c, age=60)
Args:
tree (Node): tree to search
name (str): value to match for name attribute
max_depth (int): maximum depth to search for, based on the `depth` attribute, defaults to None
Returns:
(Node)
"""
return find(tree, lambda node: node.node_name == name, max_depth)
def find_names(tree: NodeT, name: str, max_depth: int = 0) -> Iterable[NodeT]:
"""
Search tree for multiple node(s) matching name attribute.
Examples:
>>> from bigtree import Node, find_names
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("b", age=40, parent=c)
>>> find_names(root, "c")
(Node(/a/c, age=60),)
>>> find_names(root, "b")
(Node(/a/b, age=65), Node(/a/c/b, age=40))
Args:
tree (Node): tree to search
name (str): value to match for name attribute
max_depth (int): maximum depth to search for, based on the `depth` attribute, defaults to None
Returns:
(Iterable[Node])
"""
return findall(tree, lambda node: node.node_name == name, max_depth)
def find_relative_path(tree: NodeT, path_name: str) -> Iterable[NodeT]:
r"""
Search tree for single node matching relative path attribute.
- Supports unix folder expression for relative path, i.e., '../../node_name'
- Supports wildcards, i.e., '\*/node_name'
- If path name starts with leading separator symbol, it will start at root node.
Examples:
>>> from bigtree import Node, find_relative_path
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("d", age=40, parent=c)
>>> find_relative_path(d, "..")
(Node(/a/c, age=60),)
>>> find_relative_path(d, "../../b")
(Node(/a/b, age=65),)
>>> find_relative_path(d, "../../*")
(Node(/a/b, age=65), Node(/a/c, age=60))
Args:
tree (Node): tree to search
path_name (str): value to match (relative path) of path_name attribute
Returns:
(Iterable[Node])
"""
sep = tree.sep
if path_name.startswith(sep):
resolved_node = find_full_path(tree, path_name)
return (resolved_node,)
path_name = path_name.rstrip(sep).lstrip(sep)
path_list = path_name.split(sep)
wildcard_indicator = "*" in path_name
resolved_nodes: List[NodeT] = []
def resolve(node: NodeT, path_idx: int) -> None:
"""Resolve node based on path name
Args:
node (Node): current node
path_idx (int): current index in path_list
"""
if path_idx == len(path_list):
resolved_nodes.append(node)
else:
path_component = path_list[path_idx]
if path_component == ".":
resolve(node, path_idx + 1)
elif path_component == "..":
if node.is_root:
raise SearchError("Invalid path name. Path goes beyond root node.")
resolve(node.parent, path_idx + 1)
elif path_component == "*":
for child in node.children:
resolve(child, path_idx + 1)
else:
node = find_child_by_name(node, path_component)
if not node:
if not wildcard_indicator:
raise SearchError(
f"Invalid path name. Node {path_component} cannot be found."
)
else:
resolve(node, path_idx + 1)
resolve(tree, 0)
return tuple(resolved_nodes)
def find_full_path(tree: NodeT, path_name: str) -> NodeT:
"""
Search tree for single node matching path attribute.
- Path name can be with or without leading tree path separator symbol.
- Path name must be full path, works similar to `find_path` but faster.
Examples:
>>> from bigtree import Node, find_full_path
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("d", age=40, parent=c)
>>> find_full_path(root, "/a/c/d")
Node(/a/c/d, age=40)
Args:
tree (Node): tree to search
path_name (str): value to match (full path) of path_name attribute
Returns:
(Node)
"""
sep = tree.sep
path_list = path_name.rstrip(sep).lstrip(sep).split(sep)
if path_list[0] != tree.root.node_name:
raise ValueError(
f"Path {path_name} does not match the root node name {tree.root.node_name}"
)
parent_node = tree.root
child_node = parent_node
for child_name in path_list[1:]:
child_node = find_child_by_name(parent_node, child_name)
if not child_node:
break
parent_node = child_node
return child_node
def find_path(tree: NodeT, path_name: str) -> NodeT:
"""
Search tree for single node matching path attribute.
- Path name can be with or without leading tree path separator symbol.
- Path name can be full path or partial path (trailing part of path) or node name.
Examples:
>>> from bigtree import Node, find_path
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("d", age=40, parent=c)
>>> find_path(root, "c")
Node(/a/c, age=60)
>>> find_path(root, "/c")
Node(/a/c, age=60)
Args:
tree (Node): tree to search
path_name (str): value to match (full path) or trailing part (partial path) of path_name attribute
Returns:
(Node)
"""
path_name = path_name.rstrip(tree.sep)
return find(tree, lambda node: node.path_name.endswith(path_name))
def find_paths(tree: NodeT, path_name: str) -> Tuple[NodeT, ...]:
"""
Search tree for multiple nodes matching path attribute.
- Path name can be with or without leading tree path separator symbol.
- Path name can be partial path (trailing part of path) or node name.
Examples:
>>> from bigtree import Node, find_paths
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("c", age=40, parent=c)
>>> find_paths(root, "/a/c")
(Node(/a/c, age=60),)
>>> find_paths(root, "/c")
(Node(/a/c, age=60), Node(/a/c/c, age=40))
Args:
tree (Node): tree to search
path_name (str): value to match (full path) or trailing part (partial path) of path_name attribute
Returns:
(Tuple[Node, ...])
"""
path_name = path_name.rstrip(tree.sep)
return findall(tree, lambda node: node.path_name.endswith(path_name))
def find_attr(
tree: BaseNode, attr_name: str, attr_value: Any, max_depth: int = 0
) -> BaseNode:
"""
Search tree for single node matching custom attribute.
Examples:
>>> from bigtree import Node, find_attr
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("d", age=40, parent=c)
>>> find_attr(root, "age", 65)
Node(/a/b, age=65)
Args:
tree (BaseNode): tree to search
attr_name (str): attribute name to perform matching
attr_value (Any): value to match for attr_name attribute
max_depth (int): maximum depth to search for, based on the `depth` attribute, defaults to None
Returns:
(BaseNode)
"""
return find(
tree,
lambda node: bool(node.get_attr(attr_name) == attr_value),
max_depth,
)
def find_attrs(
tree: BaseNode, attr_name: str, attr_value: Any, max_depth: int = 0
) -> Tuple[BaseNode, ...]:
"""
Search tree for node(s) matching custom attribute.
Examples:
>>> from bigtree import Node, find_attrs
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=65, parent=root)
>>> d = Node("d", age=40, parent=c)
>>> find_attrs(root, "age", 65)
(Node(/a/b, age=65), Node(/a/c, age=65))
Args:
tree (BaseNode): tree to search
attr_name (str): attribute name to perform matching
attr_value (Any): value to match for attr_name attribute
max_depth (int): maximum depth to search for, based on the `depth` attribute, defaults to None
Returns:
(Tuple[BaseNode, ...])
"""
return findall(
tree,
lambda node: bool(node.get_attr(attr_name) == attr_value),
max_depth,
)
def find_children(
tree: Union[T, DAGNodeT],
condition: Callable[[Union[T, DAGNodeT]], bool],
min_count: int = 0,
max_count: int = 0,
) -> Tuple[Union[T, DAGNodeT], ...]:
"""
Search children for nodes matching condition (callable function).
Examples:
>>> from bigtree import Node, find_children
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("d", age=40, parent=c)
>>> find_children(root, lambda node: node.age > 30)
(Node(/a/b, age=65), Node(/a/c, age=60))
Args:
tree (BaseNode/DAGNode): tree to search for its children
condition (Callable): function that takes in node as argument, returns node if condition evaluates to `True`
min_count (int): checks for minimum number of occurrences,
raise SearchError if the number of results do not meet min_count, defaults to None
max_count (int): checks for maximum number of occurrences,
raise SearchError if the number of results do not meet min_count, defaults to None
Returns:
(BaseNode/DAGNode)
"""
result = tuple([node for node in tree.children if node and condition(node)])
if min_count and len(result) < min_count:
raise SearchError(
f"Expected more than {min_count} element(s), found {len(result)} elements\n{result}"
)
if max_count and len(result) > max_count:
raise SearchError(
f"Expected less than {max_count} element(s), found {len(result)} elements\n{result}"
)
return result
def find_child(
tree: Union[T, DAGNodeT],
condition: Callable[[Union[T, DAGNodeT]], bool],
) -> Union[T, DAGNodeT]:
"""
Search children for *single node* matching condition (callable function).
Examples:
>>> from bigtree import Node, find_child
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("d", age=40, parent=c)
>>> find_child(root, lambda node: node.age > 62)
Node(/a/b, age=65)
Args:
tree (BaseNode/DAGNode): tree to search for its child
condition (Callable): function that takes in node as argument, returns node if condition evaluates to `True`
Returns:
(BaseNode/DAGNode)
"""
result = find_children(tree, condition, max_count=1)
if result:
return result[0]
def find_child_by_name(
tree: Union[NodeT, DAGNodeT], name: str
) -> Union[NodeT, DAGNodeT]:
"""
Search tree for single node matching name attribute.
Examples:
>>> from bigtree import Node, find_child_by_name
>>> root = Node("a", age=90)
>>> b = Node("b", age=65, parent=root)
>>> c = Node("c", age=60, parent=root)
>>> d = Node("d", age=40, parent=c)
>>> find_child_by_name(root, "c")
Node(/a/c, age=60)
>>> find_child_by_name(c, "d")
Node(/a/c/d, age=40)
Args:
tree (Node/DAGNode): tree to search, parent node
name (str): value to match for name attribute, child node
Returns:
(Node/DAGNode)
"""
return find_child(tree, lambda node: node.node_name == name)