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

path-resolution.mddocs/

Path Resolution and Navigation

Unix-style path resolution for accessing nodes by path strings, with support for absolute/relative paths, glob patterns, and navigation utilities. Provides powerful tree traversal and node location capabilities.

Capabilities

Resolver - Path-Based Node Access

Main class for resolving node paths using attribute-based addressing with Unix-like path syntax.

class Resolver:
    """
    Resolve NodeMixin paths using attribute pathattr.
    
    Args:
        pathattr: Name of the node attribute to be used for resolving (default "name")
        ignorecase: Enable case insensitive handling (default False)
        relax: Do not raise an exception on failed resolution (default False)
    """
    def __init__(self, pathattr="name", ignorecase=False, relax=False): ...
    
    def get(self, node, path): ...
    def glob(self, node, path): ...
    def is_valid_name(self, name): ...

Usage Example:

from anytree import Node, Resolver

# Create tree structure
root = Node("root")
usr = Node("usr", parent=root)
local = Node("local", parent=usr)
bin_dir = Node("bin", parent=local)
lib_dir = Node("lib", parent=local)
Node("python", parent=bin_dir)
Node("gcc", parent=bin_dir)

# Create resolver
resolver = Resolver()

# Absolute paths from root
python_node = resolver.get(root, "/usr/local/bin/python")
print(python_node)  # Node('/root/usr/local/bin/python')

lib_node = resolver.get(root, "/usr/local/lib")
print(lib_node)  # Node('/root/usr/local/lib')

# Relative paths from any node
bin_from_usr = resolver.get(usr, "local/bin")
print(bin_from_usr)  # Node('/root/usr/local/bin')

# Parent directory navigation
usr_from_bin = resolver.get(bin_dir, "../..")
print(usr_from_bin)  # Node('/root/usr')

Path Resolution Methods

get - Single Node Resolution

Resolve a single node by path with error handling options.

def get(self, node, path):
    """
    Return instance at path.
    
    Args:
        node: Starting node for path resolution
        path: Unix-style path string (absolute or relative)
        
    Returns:
        Node at specified path
        
    Raises:
        ResolverError: If path cannot be resolved (unless relax=True)
    """

Usage Example:

from anytree import Node, Resolver, ResolverError

root = Node("Company")
engineering = Node("Engineering", parent=root)
backend = Node("Backend", parent=engineering)
Node("TeamA", parent=backend)

resolver = Resolver()

# Successful resolution
team = resolver.get(root, "/Engineering/Backend/TeamA")
print(team.name)  # TeamA

# Error handling
try:
    missing = resolver.get(root, "/Engineering/Frontend/TeamB")
except ResolverError as e:
    print(f"Resolution failed: {e}")

# Relaxed mode (no exceptions)
relaxed_resolver = Resolver(relax=True)
missing = relaxed_resolver.get(root, "/Engineering/Frontend/TeamB")
print(missing)  # None

glob - Pattern Matching

Resolve multiple nodes using glob pattern matching.

def glob(self, node, path):
    """
    Return nodes matching glob pattern.
    
    Args:
        node: Starting node for glob pattern matching
        path: Unix-style path with glob patterns (* ? [])
        
    Returns:
        Generator yielding matching nodes
    """

Usage Example:

from anytree import Node, Resolver

root = Node("root")
docs = Node("docs", parent=root)
Node("readme.txt", parent=docs)
Node("manual.pdf", parent=docs)
Node("guide.txt", parent=docs)
Node("changelog.md", parent=docs)

resolver = Resolver()

# Match all files
all_files = list(resolver.glob(root, "/docs/*"))
print(f"All files: {[n.name for n in all_files]}")

# Match txt files only
txt_files = list(resolver.glob(root, "/docs/*.txt"))
print(f"Text files: {[n.name for n in txt_files]}")

# Character class matching
doc_files = list(resolver.glob(root, "/docs/[rm]*"))  # readme, manual
print(f"Doc files: {[n.name for n in doc_files]}")

# Recursive globbing
all_nodes = list(resolver.glob(root, "**"))
print(f"All nodes: {len(all_nodes)}")

Resolver Configuration Options

Case Sensitivity Control

from anytree import Node, Resolver

root = Node("Root")
Node("CamelCase", parent=root)
Node("lowercase", parent=root)

# Case sensitive (default)
case_sensitive = Resolver()
try:
    node = case_sensitive.get(root, "/camelcase")  # Won't match
except ResolverError:
    print("Case sensitive matching failed")

# Case insensitive
case_insensitive = Resolver(ignorecase=True)
node = case_insensitive.get(root, "/camelcase")  # Matches CamelCase
print(node.name)  # CamelCase

Custom Path Attributes

from anytree import Node, Resolver

# Create nodes with custom path attribute
root = Node("root", path_id="company")
Node("Engineering", parent=root, path_id="eng")
Node("Marketing", parent=root, path_id="mkt")

# Use custom attribute for path resolution
id_resolver = Resolver(pathattr="path_id")
eng_node = id_resolver.get(root, "/eng")
print(eng_node.name)  # Engineering

mkt_node = id_resolver.get(root, "/mkt")  
print(mkt_node.name)  # Marketing

Walker - Path Calculation

Calculate walking paths between any two nodes in the tree, useful for navigation and path finding.

class Walker:
    """Walk from one node to another."""
    
    @staticmethod
    def walk(start, end):
        """
        Walk from start node to end node.
        
        Args:
            start: Starting node
            end: Destination node
            
        Returns:
            Tuple (upwards, common, downwards):
            - upwards: List of nodes to go upward to
            - common: Common ancestor node  
            - downwards: List of nodes to go downward to
            
        Raises:
            WalkError: If nodes have no common root
        """

Usage Example:

from anytree import Node, Walker, WalkError

# 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)

walker = Walker()

# Walk from backend to frontend (same parent)
upwards, common, downwards = walker.walk(backend, frontend)
print(f"From Backend to Frontend:")
print(f"  Up: {[n.name for n in upwards]}")      # ['Backend']
print(f"  Common: {common.name}")                 # Engineering
print(f"  Down: {[n.name for n in downwards]}")  # ['Frontend']

# Walk from backend to social (different subtrees)
upwards, common, downwards = walker.walk(backend, social)
print(f"From Backend to Social:")
print(f"  Up: {[n.name for n in upwards]}")      # ['Backend', 'Engineering']
print(f"  Common: {common.name}")                 # Company
print(f"  Down: {[n.name for n in downwards]}")  # ['Marketing', 'Social']

# Walk to same node
upwards, common, downwards = walker.walk(backend, backend)
print(f"Same node walk: up={len(upwards)}, down={len(downwards)}")  # 0, 0

Advanced Path Resolution

Relative Path Navigation

Full support for Unix-style relative path navigation:

from anytree import Node, Resolver

# Create deep hierarchy
root = Node("root")
a = Node("a", parent=root)
b = Node("b", parent=a)
c = Node("c", parent=b)
d = Node("d", parent=c)

# Also create siblings
a2 = Node("a2", parent=root)
b2 = Node("b2", parent=a)

resolver = Resolver()

# Current directory (.)
current = resolver.get(c, ".")
print(current.name)  # c

# Parent directory (..)
parent = resolver.get(c, "..")
print(parent.name)  # b

# Grandparent (../..)
grandparent = resolver.get(c, "../..")
print(grandparent.name)  # a

# Sibling via parent (../b2)
sibling = resolver.get(b, "../b2")
print(sibling.name)  # b2

# Complex relative path
complex_path = resolver.get(d, "../../..")  # From d to a
print(complex_path.name)  # a

Glob Pattern Examples

Comprehensive glob pattern matching capabilities:

from anytree import Node, Resolver

# Create file system-like structure
root = Node("project")
src = Node("src", parent=root)
tests = Node("tests", parent=root)
docs = Node("docs", parent=root)

# Source files
Node("main.py", parent=src)
Node("utils.py", parent=src)
Node("config.json", parent=src)

# Test files
Node("test_main.py", parent=tests)
Node("test_utils.py", parent=tests)

# Documentation
Node("README.md", parent=docs)
Node("API.md", parent=docs)

resolver = Resolver()

# All Python files
py_files = list(resolver.glob(root, "**/*.py"))
print(f"Python files: {[n.name for n in py_files]}")

# All test files
test_files = list(resolver.glob(root, "**/test_*.py"))
print(f"Test files: {[n.name for n in test_files]}")

# All markdown files
md_files = list(resolver.glob(root, "**/*.md"))
print(f"Markdown files: {[n.name for n in md_files]}")

# Files in specific directory
src_files = list(resolver.glob(root, "/src/*"))
print(f"Source files: {[n.name for n in src_files]}")

# Character classes
main_files = list(resolver.glob(root, "**/[tm]ain.py"))  # main.py and test_main.py
print(f"Main files: {[n.name for n in main_files]}")

Path Validation

Check if names are valid for path resolution:

from anytree import Resolver

resolver = Resolver()

# Valid names
print(resolver.is_valid_name("valid_name"))     # True
print(resolver.is_valid_name("also-valid"))    # True
print(resolver.is_valid_name("123numeric"))    # True

# Invalid names (contain path separators)
print(resolver.is_valid_name("invalid/name"))  # False
print(resolver.is_valid_name("also\\invalid")) # False
print(resolver.is_valid_name(""))              # False

Exception Handling

ResolverError Hierarchy

class ResolverError(RuntimeError):
    """Base exception for path resolution errors."""

class RootResolverError(ResolverError):
    """Exception when resolving from wrong root."""

class ChildResolverError(ResolverError):
    """Exception when child cannot be resolved."""

Usage Example:

from anytree import Node, Resolver, ResolverError, ChildResolverError

root1 = Node("root1")
root2 = Node("root2")
Node("child", parent=root1)

resolver = Resolver()

try:
    # Try to resolve from wrong tree
    result = resolver.get(root2, "/child")
except ChildResolverError as e:
    print(f"Child not found: {e}")

try:
    # Try invalid path
    result = resolver.get(root1, "/nonexistent/path")
except ResolverError as e:
    print(f"Resolution failed: {e}")
    print(f"Error type: {type(e).__name__}")

WalkError Exception

class WalkError(RuntimeError):
    """Exception raised during tree walking operations."""

Usage Example:

from anytree import Node, Walker, WalkError

# Create separate trees
tree1_root = Node("tree1")
tree2_root = Node("tree2")

walker = Walker()

try:
    # Try to walk between disconnected trees
    walker.walk(tree1_root, tree2_root)
except WalkError as e:
    print(f"Walk failed: {e}")

Common Use Cases

File System Navigation

from anytree import Node, Resolver

def create_fs_tree():
    root = Node("/")
    usr = Node("usr", parent=root)
    local = Node("local", parent=usr)
    bin_dir = Node("bin", parent=local)
    
    # Add executables
    Node("python3", parent=bin_dir)
    Node("gcc", parent=bin_dir)
    Node("make", parent=bin_dir)
    
    return root, Resolver()

fs_root, resolver = create_fs_tree()

# Find executable
python = resolver.get(fs_root, "/usr/local/bin/python3")
print(f"Found: {python.name}")

# List all executables
executables = list(resolver.glob(fs_root, "/usr/local/bin/*"))
print(f"Executables: {[n.name for n in executables]}")

Configuration Path Resolution

from anytree import Node, Resolver

# Application configuration tree
config = Node("config")
database = Node("database", parent=config)
Node("host", parent=database, value="localhost")
Node("port", parent=database, value=5432)
Node("name", parent=database, value="myapp")

api = Node("api", parent=config)
Node("timeout", parent=api, value=30)
Node("retries", parent=api, value=3)

resolver = Resolver()

# Get configuration values
db_host = resolver.get(config, "/database/host")
print(f"DB Host: {db_host.value}")

api_timeout = resolver.get(config, "/api/timeout")
print(f"API Timeout: {api_timeout.value}")

# Get all database settings
db_settings = list(resolver.glob(config, "/database/*"))
for setting in db_settings:
    print(f"{setting.name}: {setting.value}")

Dynamic Path Building

from anytree import Node, Resolver, Walker

class PathBuilder:
    def __init__(self, root):
        self.root = root
        self.resolver = Resolver()
        self.walker = Walker()
    
    def get_relative_path(self, from_node, to_node):
        """Get relative path string from one node to another"""
        upwards, common, downwards = self.walker.walk(from_node, to_node)
        
        # Build relative path
        path_parts = [".."] * len(upwards[1:])  # Exclude starting node
        path_parts.extend([node.name for node in downwards])
        
        return "/".join(path_parts) if path_parts else "."
    
    def resolve_from_context(self, context_node, target_path):
        """Resolve path relative to context node"""
        return self.resolver.get(context_node, target_path)

# Example usage
root = Node("project")
src = Node("src", parent=root)
tests = Node("tests", parent=root)
main_py = Node("main.py", parent=src)
test_main = Node("test_main.py", parent=tests)

builder = PathBuilder(root)

# Get relative path from test to main
rel_path = builder.get_relative_path(test_main, main_py)
print(f"Relative path: {rel_path}")  # ../src/main.py

# Resolve from context
main_from_test = builder.resolve_from_context(test_main, "../src/main.py")
print(f"Resolved: {main_from_test.name}")  # main.py

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