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
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.
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')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) # NoneResolve 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)}")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) # CamelCasefrom 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) # MarketingCalculate 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, 0Full 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) # aComprehensive 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]}")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("")) # Falseclass 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__}")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}")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]}")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}")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.pyInstall with Tessl CLI
npx tessl i tessl/pypi-anytree