Powerful and Lightweight Python Tree Data Structure with various plugins
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Helper functions for common tree operations including finding common ancestors, accessing sibling nodes, and performance-optimized cached search operations. These utilities provide convenient shortcuts for frequent tree manipulation tasks.
Determine common ancestors of multiple nodes, useful for finding shared parent nodes and calculating relationships between tree branches.
def commonancestors(*nodes):
"""
Determine common ancestors of nodes.
Args:
*nodes: Variable number of nodes to find common ancestors for
Returns:
Tuple of common ancestor nodes from closest to root
"""Usage Example:
from anytree import Node, util
# Create tree structure
root = Node("Company")
engineering = Node("Engineering", parent=root)
marketing = Node("Marketing", parent=root)
backend = Node("Backend", parent=engineering)
frontend = Node("Frontend", parent=engineering)
content = Node("Content", parent=marketing)
social = Node("Social", parent=marketing)
# Find common ancestors of nodes in same subtree
backend_frontend_ancestors = util.commonancestors(backend, frontend)
print(f"Backend & Frontend ancestors: {[n.name for n in backend_frontend_ancestors]}")
# Output: ['Company', 'Engineering']
# Find common ancestors of nodes in different subtrees
backend_content_ancestors = util.commonancestors(backend, content)
print(f"Backend & Content ancestors: {[n.name for n in backend_content_ancestors]}")
# Output: ['Company']
# Common ancestors of multiple nodes
all_teams_ancestors = util.commonancestors(backend, frontend, content, social)
print(f"All teams ancestors: {[n.name for n in all_teams_ancestors]}")
# Output: ['Company']
# Single node (returns all ancestors)
single_ancestors = util.commonancestors(backend)
print(f"Backend ancestors: {[n.name for n in single_ancestors]}")
# Output: ['Company', 'Engineering']
# No nodes
no_ancestors = util.commonancestors()
print(f"No nodes ancestors: {no_ancestors}")
# Output: ()Return the left (previous) sibling of a node, useful for navigation and ordering operations.
def leftsibling(node):
"""
Return Left Sibling of node.
Args:
node: Node to find left sibling for
Returns:
Left sibling node or None if node is first child or root
"""Usage Example:
from anytree import Node, util
# Create ordered children
parent = Node("Parent")
first = Node("First", parent=parent)
second = Node("Second", parent=parent)
third = Node("Third", parent=parent)
fourth = Node("Fourth", parent=parent)
# Get left siblings
print(f"First left sibling: {util.leftsibling(first)}") # None
print(f"Second left sibling: {util.leftsibling(second)}") # Node('/Parent/First')
print(f"Third left sibling: {util.leftsibling(third)}") # Node('/Parent/Second')
print(f"Fourth left sibling: {util.leftsibling(fourth)}") # Node('/Parent/Third')
# Root node has no sibling
print(f"Root left sibling: {util.leftsibling(parent)}") # NoneReturn the right (next) sibling of a node, useful for navigation and ordering operations.
def rightsibling(node):
"""
Return Right Sibling of node.
Args:
node: Node to find right sibling for
Returns:
Right sibling node or None if node is last child or root
"""Usage Example:
from anytree import Node, util
# Create ordered children
parent = Node("Parent")
first = Node("First", parent=parent)
second = Node("Second", parent=parent)
third = Node("Third", parent=parent)
fourth = Node("Fourth", parent=parent)
# Get right siblings
print(f"First right sibling: {util.rightsibling(first)}") # Node('/Parent/Second')
print(f"Second right sibling: {util.rightsibling(second)}") # Node('/Parent/Third')
print(f"Third right sibling: {util.rightsibling(third)}") # Node('/Parent/Fourth')
print(f"Fourth right sibling: {util.rightsibling(fourth)}") # None
# Root node has no sibling
print(f"Root right sibling: {util.rightsibling(parent)}") # NoneHigh-performance cached versions of search functions for repeated queries on the same tree structure. These functions provide significant performance improvements when performing multiple searches on static or slowly-changing trees.
# Import cached search functions
from anytree import cachedsearch
# Cached versions of search functions with same signatures
def cachedsearch.find(node, filter_=None, stop=None, maxlevel=None): ...
def cachedsearch.find_by_attr(node, value, name="name", maxlevel=None): ...
def cachedsearch.findall(node, filter_=None, stop=None, maxlevel=None, mincount=None, maxcount=None): ...
def cachedsearch.findall_by_attr(node, value, name="name", maxlevel=None, mincount=None, maxcount=None): ...Usage Example:
from anytree import Node, cachedsearch
import time
# Create large tree for performance testing
def create_large_tree(depth=5, breadth=10):
root = Node("root")
def add_children(parent, current_depth):
if current_depth >= depth:
return
for i in range(breadth):
child = Node(f"{parent.name}_child_{i}",
parent=parent,
level=current_depth,
category=f"cat_{i % 3}")
add_children(child, current_depth + 1)
add_children(root, 0)
return root
large_tree = create_large_tree(depth=4, breadth=8)
print(f"Created tree with {len(list(large_tree.descendants)) + 1} nodes")
# Compare performance: regular vs cached search
def time_search(search_func, *args, **kwargs):
start = time.time()
result = search_func(*args, **kwargs)
end = time.time()
return result, end - start
# Regular search
from anytree import findall_by_attr
regular_results, regular_time = time_search(
findall_by_attr, large_tree, "cat_1", name="category"
)
# First cached search (builds cache)
cached_results1, cached_time1 = time_search(
cachedsearch.findall_by_attr, large_tree, "cat_1", name="category"
)
# Second cached search (uses cache)
cached_results2, cached_time2 = time_search(
cachedsearch.findall_by_attr, large_tree, "cat_1", name="category"
)
print(f"Regular search: {regular_time:.4f}s")
print(f"First cached search: {cached_time1:.4f}s")
print(f"Second cached search: {cached_time2:.4f}s")
print(f"Cache speedup: {regular_time/cached_time2:.1f}x")
print(f"Results identical: {len(regular_results) == len(cached_results2)}")Build navigation utilities using sibling functions:
from anytree import Node, util
class TreeNavigator:
@staticmethod
def get_siblings(node):
"""Get all siblings of a node (excluding the node itself)"""
if not node.parent:
return ()
return tuple(child for child in node.parent.children if child != node)
@staticmethod
def get_sibling_by_offset(node, offset):
"""Get sibling at specified offset (negative for left, positive for right)"""
if not node.parent:
return None
siblings = node.parent.children
current_index = siblings.index(node)
target_index = current_index + offset
if 0 <= target_index < len(siblings):
return siblings[target_index]
return None
@staticmethod
def move_node(node, direction):
"""Move node left or right among siblings"""
if direction == "left":
left = util.leftsibling(node)
if left:
# Swap positions by manipulating parent's children
parent = node.parent
children = list(parent.children)
node_idx = children.index(node)
left_idx = children.index(left)
children[node_idx], children[left_idx] = children[left_idx], children[node_idx]
parent.children = children
return True
elif direction == "right":
right = util.rightsibling(node)
if right:
# Similar swap logic
parent = node.parent
children = list(parent.children)
node_idx = children.index(node)
right_idx = children.index(right)
children[node_idx], children[right_idx] = children[right_idx], children[node_idx]
parent.children = children
return True
return False
# Example usage
parent = Node("Tasks")
task1 = Node("Task 1", parent=parent, priority=1)
task2 = Node("Task 2", parent=parent, priority=2)
task3 = Node("Task 3", parent=parent, priority=3)
navigator = TreeNavigator()
# Get all siblings of task2
siblings = navigator.get_siblings(task2)
print(f"Task 2 siblings: {[s.name for s in siblings]}") # ['Task 1', 'Task 3']
# Get sibling by offset
prev_task = navigator.get_sibling_by_offset(task2, -1) # Task 1
next_task = navigator.get_sibling_by_offset(task2, 1) # Task 3
print(f"Previous: {prev_task.name}, Next: {next_task.name}")Use common ancestors for tree analysis and relationship detection:
from anytree import Node, util
def analyze_relationships(nodes):
"""Analyze relationships between multiple nodes"""
if len(nodes) < 2:
return "Insufficient nodes for analysis"
# Find common ancestors
ancestors = util.commonancestors(*nodes)
if not ancestors:
return "Nodes are in separate trees"
closest_ancestor = ancestors[-1] # Last in tuple is closest to nodes
# Calculate relationship metrics
depths_from_ancestor = []
for node in nodes:
depth = 0
current = node
while current != closest_ancestor:
current = current.parent
depth += 1
depths_from_ancestor.append(depth)
analysis = {
"common_ancestors": [a.name for a in ancestors],
"closest_common_ancestor": closest_ancestor.name,
"relationship_distances": dict(zip([n.name for n in nodes], depths_from_ancestor)),
"max_distance": max(depths_from_ancestor),
"are_siblings": len(set(n.parent for n in nodes if n.parent)) == 1,
"are_cousins": len(ancestors) > 1 and all(d == 2 for d in depths_from_ancestor)
}
return analysis
# Example usage
company = Node("Company")
eng = Node("Engineering", parent=company)
mkt = Node("Marketing", parent=company)
backend = Node("Backend", parent=eng)
frontend = Node("Frontend", parent=eng)
content = Node("Content", parent=mkt)
# Analyze siblings
sibling_analysis = analyze_relationships([backend, frontend])
print("Sibling analysis:", sibling_analysis)
# Analyze cousins
cousin_analysis = analyze_relationships([backend, content])
print("Cousin analysis:", cousin_analysis)Combine utilities for advanced tree manipulation:
from anytree import Node, util
class TreeManipulator:
@staticmethod
def swap_siblings(node1, node2):
"""Swap two sibling nodes"""
if node1.parent != node2.parent:
raise ValueError("Nodes must be siblings")
parent = node1.parent
children = list(parent.children)
idx1 = children.index(node1)
idx2 = children.index(node2)
children[idx1], children[idx2] = children[idx2], children[idx1]
parent.children = children
@staticmethod
def sort_children(parent, key_func=None, reverse=False):
"""Sort children of a node"""
if key_func is None:
key_func = lambda n: n.name
sorted_children = sorted(parent.children, key=key_func, reverse=reverse)
parent.children = sorted_children
@staticmethod
def insert_between_siblings(new_node, left_sibling, right_sibling):
"""Insert new node between two siblings"""
if left_sibling.parent != right_sibling.parent:
raise ValueError("Siblings must have same parent")
if util.rightsibling(left_sibling) != right_sibling:
raise ValueError("Nodes must be adjacent siblings")
parent = left_sibling.parent
children = list(parent.children)
right_idx = children.index(right_sibling)
# Set parent and insert at correct position
new_node.parent = parent
children.insert(right_idx, new_node)
parent.children = children
# Example usage
project = Node("Project")
phase1 = Node("Phase 1", parent=project)
phase3 = Node("Phase 3", parent=project)
manipulator = TreeManipulator()
# Insert phase 2 between phase 1 and 3
phase2 = Node("Phase 2")
manipulator.insert_between_siblings(phase2, phase1, phase3)
print(f"Project phases: {[child.name for child in project.children]}")
# Output: ['Phase 1', 'Phase 2', 'Phase 3']
# Sort children alphabetically
team = Node("Team")
Node("Charlie", parent=team, role="developer")
Node("Alice", parent=team, role="manager")
Node("Bob", parent=team, role="designer")
print(f"Before sort: {[child.name for child in team.children]}")
manipulator.sort_children(team)
print(f"After sort: {[child.name for child in team.children]}")
# Output: ['Alice', 'Bob', 'Charlie']Use cached search efficiently in applications:
from anytree import Node, cachedsearch
from functools import lru_cache
class OptimizedTreeService:
def __init__(self, root):
self.root = root
self._cache_warmed = False
def warm_cache(self):
"""Pre-warm search cache with common queries"""
if self._cache_warmed:
return
# Common searches to pre-cache
cachedsearch.findall(self.root, filter_=lambda n: True) # Cache all nodes
# Cache searches by common attributes
for attr_name in ['name', 'type', 'category', 'status']:
try:
cachedsearch.findall_by_attr(self.root, "", name=attr_name)
except:
pass # Attribute might not exist
self._cache_warmed = True
@lru_cache(maxsize=128)
def find_by_path(self, path_parts):
"""Cached path-based node finding"""
current = self.root
for part in path_parts:
found = cachedsearch.find_by_attr(current, part, name="name")
if not found:
return None
current = found
return current
def bulk_find(self, search_criteria):
"""Efficiently perform multiple searches"""
self.warm_cache()
results = {}
for criterion_name, (filter_func, max_results) in search_criteria.items():
matches = cachedsearch.findall(
self.root,
filter_=filter_func,
maxcount=max_results
)
results[criterion_name] = matches
return results
# Example usage
# Create service tree
service_root = Node("Services")
web_service = Node("WebService", parent=service_root, type="web", status="active")
db_service = Node("DatabaseService", parent=service_root, type="database", status="active")
cache_service = Node("CacheService", parent=service_root, type="cache", status="inactive")
# Create optimized service
tree_service = OptimizedTreeService(service_root)
# Perform bulk searches
search_criteria = {
"active_services": (lambda n: getattr(n, 'status', '') == 'active', 10),
"web_services": (lambda n: getattr(n, 'type', '') == 'web', 5),
"all_services": (lambda n: hasattr(n, 'type'), 100)
}
results = tree_service.bulk_find(search_criteria)
for category, nodes in results.items():
print(f"{category}: {[n.name for n in nodes]}")# Import individual utility functions
from anytree.util import commonancestors, leftsibling, rightsibling
# Import utility module
from anytree import util
# Use utilities
ancestors = util.commonancestors(node1, node2)
left = util.leftsibling(node)
right = util.rightsibling(node)# Import cached search module
from anytree import cachedsearch
# Use cached search functions
results = cachedsearch.findall_by_attr(root, "target", name="category")
node = cachedsearch.find(root, filter_=lambda n: n.value > 100)commonancestors for: relationship analysis, finding merge points, calculating distancesleftsibling/rightsibling for: navigation UI, ordering operations, sequential processingInstall with Tessl CLI
npx tessl i tessl/pypi-anytree