CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-anytree

Powerful and Lightweight Python Tree Data Structure with various plugins

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

search.mddocs/

Search and Filtering

Comprehensive search functionality for finding nodes by custom filters or attribute values. Provides flexible node finding with depth limits, result count constraints, and performance-optimized cached variants.

Capabilities

Single Node Search

Find the first node that matches specified criteria.

find - Custom Filter Search

Find single node using custom filter function.

def find(node, filter_=None, stop=None, maxlevel=None):
    """
    Find the first node matching filter.
    
    Args:
        node: Starting node for search
        filter_: Function called with every node as argument, node is returned if True
        stop: Stop iteration at node if stop function returns True for node
        maxlevel: Maximum descending in the node hierarchy
        
    Returns:
        First matching node or None if no match found
    """

Usage Example:

from anytree import Node, find

# Create tree structure
root = Node("Company")  
engineering = Node("Engineering", parent=root)
marketing = Node("Marketing", parent=root)
backend = Node("Backend", parent=engineering, team_size=5)
frontend = Node("Frontend", parent=engineering, team_size=3)
content = Node("Content", parent=marketing, team_size=2)

# Find by name
eng_node = find(root, filter_=lambda n: n.name == "Engineering")
print(eng_node)  # Node('/Company/Engineering')

# Find by attribute
large_team = find(root, filter_=lambda n: getattr(n, 'team_size', 0) >= 5)
print(large_team)  # Node('/Company/Engineering/Backend')

# Find with custom logic
def has_substring(node):
    return "end" in node.name.lower()

node_with_end = find(root, filter_=has_substring)
print(node_with_end)  # Node('/Company/Engineering/Backend')

find_by_attr - Attribute Value Search

Find single node by attribute value with exact matching.

def find_by_attr(node, value, name="name", maxlevel=None):
    """
    Find single node by attribute value.
    
    Args:
        node: Starting node for search
        value: Value to match
        name: Attribute name to check (default "name")
        maxlevel: Maximum search depth
        
    Returns:
        First node with matching attribute value or None
    """

Usage Example:

from anytree import Node, find_by_attr

root = Node("Company")
Node("Engineering", parent=root, department_code="ENG")
Node("Marketing", parent=root, department_code="MKT")
Node("Backend", parent=root, department_code="ENG")

# Find by name (default attribute)
marketing = find_by_attr(root, "Marketing")
print(marketing)  # Node('/Company/Marketing')

# Find by custom attribute
eng_dept = find_by_attr(root, "ENG", name="department_code")
print(eng_dept)  # Node('/Company/Engineering') - first match

# With depth limit
shallow_search = find_by_attr(root, "Backend", maxlevel=2)
print(shallow_search)  # None (Backend is at level 2, but search stops at level 2)

Multiple Node Search

Find all nodes that match specified criteria.

findall - Custom Filter Search

Find all nodes using custom filter function with optional count constraints.

def findall(node, filter_=None, stop=None, maxlevel=None, mincount=None, maxcount=None):
    """
    Find all nodes matching filter.
    
    Args:
        node: Starting node for search
        filter_: Function called with every node as argument, node is returned if True
        stop: Stop iteration at node if stop function returns True for node
        maxlevel: Maximum descending in the node hierarchy
        mincount: Minimum number of nodes required
        maxcount: Maximum number of nodes allowed
        
    Returns:
        Tuple of matching nodes
        
    Raises:
        CountError: If result count violates mincount/maxcount constraints
    """

Usage Example:

from anytree import Node, findall, CountError

# Create tree structure
root = Node("Company")
engineering = Node("Engineering", parent=root, budget=100000)
marketing = Node("Marketing", parent=root, budget=50000)
backend = Node("Backend", parent=engineering, budget=60000)
frontend = Node("Frontend", parent=engineering, budget=40000)
content = Node("Content", parent=marketing, budget=30000)

# Find all nodes with budget > 50000
high_budget = findall(root, filter_=lambda n: getattr(n, 'budget', 0) > 50000)
print(len(high_budget))  # 3 nodes
for node in high_budget:
    print(f"{node.name}: ${node.budget}")

# Find with count constraints
try:
    # Require at least 2 matches
    teams = findall(root, 
                   filter_=lambda n: hasattr(n, 'budget') and n.budget < 100000,
                   mincount=2)
    print(f"Found {len(teams)} teams with budget < $100k")
except CountError as e:
    print(f"Count constraint failed: {e}")

# Find with max count limit
limited_results = findall(root,
                         filter_=lambda n: hasattr(n, 'budget'),
                         maxcount=3)
print(f"Limited to first {len(limited_results)} results")

findall_by_attr - Attribute Value Search

Find all nodes with matching attribute values.

def findall_by_attr(node, value, name="name", maxlevel=None, mincount=None, maxcount=None):
    """
    Find all nodes by attribute value.
    
    Args:
        node: Starting node for search
        value: Value to match
        name: Attribute name to check (default "name")
        maxlevel: Maximum search depth
        mincount: Minimum number of nodes required
        maxcount: Maximum number of nodes allowed
        
    Returns:
        Tuple of nodes with matching attribute value
        
    Raises:
        CountError: If result count violates mincount/maxcount constraints
    """

Usage Example:

from anytree import Node, findall_by_attr

root = Node("Company")
Node("TeamA", parent=root, status="active")
Node("TeamB", parent=root, status="active") 
Node("TeamC", parent=root, status="inactive")
Node("TeamD", parent=root, status="active")

# Find all active teams
active_teams = findall_by_attr(root, "active", name="status")
print(f"Active teams: {len(active_teams)}")
for team in active_teams:
    print(f"  {team.name}")

# Find all nodes with specific name pattern (multiple matches)
nodes_with_team = findall_by_attr(root, "Team", name="name")  # Won't match - exact comparison
print(f"Exact 'Team' matches: {len(nodes_with_team)}")  # 0

# For partial matching, use findall with filter
team_nodes = findall(root, filter_=lambda n: "Team" in n.name)
print(f"Nodes containing 'Team': {len(team_nodes)}")  # 4

Advanced Search Patterns

Stop Functions

Control search termination to avoid traversing certain subtrees:

from anytree import Node, findall

root = Node("Company")
public_dir = Node("public", parent=root)
private_dir = Node("private", parent=root, access="restricted")
Node("readme.txt", parent=public_dir, type="file")
Node("config.txt", parent=private_dir, type="file")
Node("secret.txt", parent=private_dir, type="file")

# Stop at restricted directories
public_files = findall(root, 
                      filter_=lambda n: getattr(n, 'type', None) == 'file',
                      stop=lambda n: getattr(n, 'access', None) == 'restricted')

print(f"Public files: {len(public_files)}")  # Only finds readme.txt

Depth-Limited Search

Search only within specified depth levels:

from anytree import Node, findall

# Create deep hierarchy
root = Node("L0")
l1 = Node("L1", parent=root)
l2 = Node("L2", parent=l1)
l3 = Node("L3", parent=l2)
Node("deep_target", parent=l3)
Node("shallow_target", parent=l1)

# Search only first 2 levels
shallow_search = findall(root, 
                        filter_=lambda n: "target" in n.name,
                        maxlevel=2)
print(f"Shallow search found: {len(shallow_search)} targets")  # 1

# Search all levels
deep_search = findall(root, filter_=lambda n: "target" in n.name)
print(f"Deep search found: {len(deep_search)} targets")  # 2

Complex Filter Logic

Combine multiple conditions in filter functions:

from anytree import Node, findall

root = Node("Company")
Node("Engineer_A", parent=root, role="engineer", experience=5, active=True)
Node("Manager_B", parent=root, role="manager", experience=8, active=True)
Node("Engineer_C", parent=root, role="engineer", experience=3, active=False)
Node("Engineer_D", parent=root, role="engineer", experience=7, active=True)

# Complex filter: active senior engineers (5+ years experience)
def senior_active_engineer(node):
    return (getattr(node, 'role', '') == 'engineer' and
            getattr(node, 'experience', 0) >= 5 and
            getattr(node, 'active', False))

senior_engineers = findall(root, filter_=senior_active_engineer)
print(f"Senior active engineers: {len(senior_engineers)}")

for eng in senior_engineers:
    print(f"  {eng.name}: {eng.experience} years")

Cached Search Functions

High-performance cached versions of search functions for repeated queries on the same tree.

Module: cachedsearch

# Import cached search functions
from anytree import cachedsearch

# Same signatures as regular search functions but with caching
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

# Create large tree
root = Node("root")
for i in range(1000):
    Node(f"node_{i}", parent=root, category=f"cat_{i % 10}")

# First search - builds cache
start_time = time.time()
results1 = cachedsearch.findall_by_attr(root, "cat_5", name="category")
time1 = time.time() - start_time

# Second search - uses cache
start_time = time.time()  
results2 = cachedsearch.findall_by_attr(root, "cat_5", name="category")
time2 = time.time() - start_time

print(f"First search: {time1:.4f}s")
print(f"Cached search: {time2:.4f}s (speedup: {time1/time2:.1f}x)")

Exception Handling

CountError Exception

Raised when search results don't meet count constraints:

from anytree import Node, findall, CountError

root = Node("root")
Node("child1", parent=root)
Node("child2", parent=root)

try:
    # Require at least 5 children
    results = findall(root, 
                     filter_=lambda n: n.parent == root,
                     mincount=5)
except CountError as e:
    print(f"Not enough results: {e}")

try:
    # Allow maximum 1 result
    results = findall(root,
                     filter_=lambda n: n.parent == root,
                     maxcount=1)
except CountError as e:
    print(f"Too many results: {e}")

Search Performance Tips

Filter Function Optimization

from anytree import Node, findall

root = Node("root")
# Add many nodes...

# Efficient: Check cheap conditions first
def efficient_filter(node):
    # Quick attribute check first
    if not hasattr(node, 'expensive_property'):
        return False
    # Expensive computation only if needed
    return expensive_computation(node.expensive_property)

# Inefficient: Expensive check for every node
def inefficient_filter(node):
    return expensive_computation(getattr(node, 'expensive_property', None))

# Use efficient filter
results = findall(root, filter_=efficient_filter)

Using Cached Search

For repeated searches on the same tree structure, use cached search functions:

from anytree import cachedsearch

# Multiple searches on same tree - use cached versions
users_in_eng = cachedsearch.findall_by_attr(org_root, "Engineering", name="department")
managers = cachedsearch.findall_by_attr(org_root, "Manager", name="role")
senior_staff = cachedsearch.findall(org_root, filter_=lambda n: n.experience > 10)

Early Termination

Use stop functions to avoid unnecessary traversal:

from anytree import Node, findall

# Stop searching once target is found in specific subtree
target_found = False

def stop_when_found(node):
    global target_found
    if node.name == "target":
        target_found = True
    return target_found and node.name == "expensive_subtree_root"

result = findall(root, filter_=lambda n: n.name == "target", stop=stop_when_found)

Install with Tessl CLI

npx tessl i tessl/pypi-anytree

docs

import-export.md

index.md

node-construction.md

path-resolution.md

search.md

tree-iteration.md

tree-rendering.md

utilities.md

tile.json