0
# Path Resolution and Navigation
1
2
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.
3
4
## Capabilities
5
6
### Resolver - Path-Based Node Access
7
8
Main class for resolving node paths using attribute-based addressing with Unix-like path syntax.
9
10
```python { .api }
11
class Resolver:
12
"""
13
Resolve NodeMixin paths using attribute pathattr.
14
15
Args:
16
pathattr: Name of the node attribute to be used for resolving (default "name")
17
ignorecase: Enable case insensitive handling (default False)
18
relax: Do not raise an exception on failed resolution (default False)
19
"""
20
def __init__(self, pathattr="name", ignorecase=False, relax=False): ...
21
22
def get(self, node, path): ...
23
def glob(self, node, path): ...
24
def is_valid_name(self, name): ...
25
```
26
27
**Usage Example:**
28
29
```python
30
from anytree import Node, Resolver
31
32
# Create tree structure
33
root = Node("root")
34
usr = Node("usr", parent=root)
35
local = Node("local", parent=usr)
36
bin_dir = Node("bin", parent=local)
37
lib_dir = Node("lib", parent=local)
38
Node("python", parent=bin_dir)
39
Node("gcc", parent=bin_dir)
40
41
# Create resolver
42
resolver = Resolver()
43
44
# Absolute paths from root
45
python_node = resolver.get(root, "/usr/local/bin/python")
46
print(python_node) # Node('/root/usr/local/bin/python')
47
48
lib_node = resolver.get(root, "/usr/local/lib")
49
print(lib_node) # Node('/root/usr/local/lib')
50
51
# Relative paths from any node
52
bin_from_usr = resolver.get(usr, "local/bin")
53
print(bin_from_usr) # Node('/root/usr/local/bin')
54
55
# Parent directory navigation
56
usr_from_bin = resolver.get(bin_dir, "../..")
57
print(usr_from_bin) # Node('/root/usr')
58
```
59
60
### Path Resolution Methods
61
62
#### get - Single Node Resolution
63
64
Resolve a single node by path with error handling options.
65
66
```python { .api }
67
def get(self, node, path):
68
"""
69
Return instance at path.
70
71
Args:
72
node: Starting node for path resolution
73
path: Unix-style path string (absolute or relative)
74
75
Returns:
76
Node at specified path
77
78
Raises:
79
ResolverError: If path cannot be resolved (unless relax=True)
80
"""
81
```
82
83
**Usage Example:**
84
85
```python
86
from anytree import Node, Resolver, ResolverError
87
88
root = Node("Company")
89
engineering = Node("Engineering", parent=root)
90
backend = Node("Backend", parent=engineering)
91
Node("TeamA", parent=backend)
92
93
resolver = Resolver()
94
95
# Successful resolution
96
team = resolver.get(root, "/Engineering/Backend/TeamA")
97
print(team.name) # TeamA
98
99
# Error handling
100
try:
101
missing = resolver.get(root, "/Engineering/Frontend/TeamB")
102
except ResolverError as e:
103
print(f"Resolution failed: {e}")
104
105
# Relaxed mode (no exceptions)
106
relaxed_resolver = Resolver(relax=True)
107
missing = relaxed_resolver.get(root, "/Engineering/Frontend/TeamB")
108
print(missing) # None
109
```
110
111
#### glob - Pattern Matching
112
113
Resolve multiple nodes using glob pattern matching.
114
115
```python { .api }
116
def glob(self, node, path):
117
"""
118
Return nodes matching glob pattern.
119
120
Args:
121
node: Starting node for glob pattern matching
122
path: Unix-style path with glob patterns (* ? [])
123
124
Returns:
125
Generator yielding matching nodes
126
"""
127
```
128
129
**Usage Example:**
130
131
```python
132
from anytree import Node, Resolver
133
134
root = Node("root")
135
docs = Node("docs", parent=root)
136
Node("readme.txt", parent=docs)
137
Node("manual.pdf", parent=docs)
138
Node("guide.txt", parent=docs)
139
Node("changelog.md", parent=docs)
140
141
resolver = Resolver()
142
143
# Match all files
144
all_files = list(resolver.glob(root, "/docs/*"))
145
print(f"All files: {[n.name for n in all_files]}")
146
147
# Match txt files only
148
txt_files = list(resolver.glob(root, "/docs/*.txt"))
149
print(f"Text files: {[n.name for n in txt_files]}")
150
151
# Character class matching
152
doc_files = list(resolver.glob(root, "/docs/[rm]*")) # readme, manual
153
print(f"Doc files: {[n.name for n in doc_files]}")
154
155
# Recursive globbing
156
all_nodes = list(resolver.glob(root, "**"))
157
print(f"All nodes: {len(all_nodes)}")
158
```
159
160
### Resolver Configuration Options
161
162
#### Case Sensitivity Control
163
164
```python
165
from anytree import Node, Resolver
166
167
root = Node("Root")
168
Node("CamelCase", parent=root)
169
Node("lowercase", parent=root)
170
171
# Case sensitive (default)
172
case_sensitive = Resolver()
173
try:
174
node = case_sensitive.get(root, "/camelcase") # Won't match
175
except ResolverError:
176
print("Case sensitive matching failed")
177
178
# Case insensitive
179
case_insensitive = Resolver(ignorecase=True)
180
node = case_insensitive.get(root, "/camelcase") # Matches CamelCase
181
print(node.name) # CamelCase
182
```
183
184
#### Custom Path Attributes
185
186
```python
187
from anytree import Node, Resolver
188
189
# Create nodes with custom path attribute
190
root = Node("root", path_id="company")
191
Node("Engineering", parent=root, path_id="eng")
192
Node("Marketing", parent=root, path_id="mkt")
193
194
# Use custom attribute for path resolution
195
id_resolver = Resolver(pathattr="path_id")
196
eng_node = id_resolver.get(root, "/eng")
197
print(eng_node.name) # Engineering
198
199
mkt_node = id_resolver.get(root, "/mkt")
200
print(mkt_node.name) # Marketing
201
```
202
203
### Walker - Path Calculation
204
205
Calculate walking paths between any two nodes in the tree, useful for navigation and path finding.
206
207
```python { .api }
208
class Walker:
209
"""Walk from one node to another."""
210
211
@staticmethod
212
def walk(start, end):
213
"""
214
Walk from start node to end node.
215
216
Args:
217
start: Starting node
218
end: Destination node
219
220
Returns:
221
Tuple (upwards, common, downwards):
222
- upwards: List of nodes to go upward to
223
- common: Common ancestor node
224
- downwards: List of nodes to go downward to
225
226
Raises:
227
WalkError: If nodes have no common root
228
"""
229
```
230
231
**Usage Example:**
232
233
```python
234
from anytree import Node, Walker, WalkError
235
236
# Create tree structure
237
root = Node("Company")
238
engineering = Node("Engineering", parent=root)
239
marketing = Node("Marketing", parent=root)
240
backend = Node("Backend", parent=engineering)
241
frontend = Node("Frontend", parent=engineering)
242
content = Node("Content", parent=marketing)
243
social = Node("Social", parent=marketing)
244
245
walker = Walker()
246
247
# Walk from backend to frontend (same parent)
248
upwards, common, downwards = walker.walk(backend, frontend)
249
print(f"From Backend to Frontend:")
250
print(f" Up: {[n.name for n in upwards]}") # ['Backend']
251
print(f" Common: {common.name}") # Engineering
252
print(f" Down: {[n.name for n in downwards]}") # ['Frontend']
253
254
# Walk from backend to social (different subtrees)
255
upwards, common, downwards = walker.walk(backend, social)
256
print(f"From Backend to Social:")
257
print(f" Up: {[n.name for n in upwards]}") # ['Backend', 'Engineering']
258
print(f" Common: {common.name}") # Company
259
print(f" Down: {[n.name for n in downwards]}") # ['Marketing', 'Social']
260
261
# Walk to same node
262
upwards, common, downwards = walker.walk(backend, backend)
263
print(f"Same node walk: up={len(upwards)}, down={len(downwards)}") # 0, 0
264
```
265
266
## Advanced Path Resolution
267
268
### Relative Path Navigation
269
270
Full support for Unix-style relative path navigation:
271
272
```python
273
from anytree import Node, Resolver
274
275
# Create deep hierarchy
276
root = Node("root")
277
a = Node("a", parent=root)
278
b = Node("b", parent=a)
279
c = Node("c", parent=b)
280
d = Node("d", parent=c)
281
282
# Also create siblings
283
a2 = Node("a2", parent=root)
284
b2 = Node("b2", parent=a)
285
286
resolver = Resolver()
287
288
# Current directory (.)
289
current = resolver.get(c, ".")
290
print(current.name) # c
291
292
# Parent directory (..)
293
parent = resolver.get(c, "..")
294
print(parent.name) # b
295
296
# Grandparent (../..)
297
grandparent = resolver.get(c, "../..")
298
print(grandparent.name) # a
299
300
# Sibling via parent (../b2)
301
sibling = resolver.get(b, "../b2")
302
print(sibling.name) # b2
303
304
# Complex relative path
305
complex_path = resolver.get(d, "../../..") # From d to a
306
print(complex_path.name) # a
307
```
308
309
### Glob Pattern Examples
310
311
Comprehensive glob pattern matching capabilities:
312
313
```python
314
from anytree import Node, Resolver
315
316
# Create file system-like structure
317
root = Node("project")
318
src = Node("src", parent=root)
319
tests = Node("tests", parent=root)
320
docs = Node("docs", parent=root)
321
322
# Source files
323
Node("main.py", parent=src)
324
Node("utils.py", parent=src)
325
Node("config.json", parent=src)
326
327
# Test files
328
Node("test_main.py", parent=tests)
329
Node("test_utils.py", parent=tests)
330
331
# Documentation
332
Node("README.md", parent=docs)
333
Node("API.md", parent=docs)
334
335
resolver = Resolver()
336
337
# All Python files
338
py_files = list(resolver.glob(root, "**/*.py"))
339
print(f"Python files: {[n.name for n in py_files]}")
340
341
# All test files
342
test_files = list(resolver.glob(root, "**/test_*.py"))
343
print(f"Test files: {[n.name for n in test_files]}")
344
345
# All markdown files
346
md_files = list(resolver.glob(root, "**/*.md"))
347
print(f"Markdown files: {[n.name for n in md_files]}")
348
349
# Files in specific directory
350
src_files = list(resolver.glob(root, "/src/*"))
351
print(f"Source files: {[n.name for n in src_files]}")
352
353
# Character classes
354
main_files = list(resolver.glob(root, "**/[tm]ain.py")) # main.py and test_main.py
355
print(f"Main files: {[n.name for n in main_files]}")
356
```
357
358
### Path Validation
359
360
Check if names are valid for path resolution:
361
362
```python
363
from anytree import Resolver
364
365
resolver = Resolver()
366
367
# Valid names
368
print(resolver.is_valid_name("valid_name")) # True
369
print(resolver.is_valid_name("also-valid")) # True
370
print(resolver.is_valid_name("123numeric")) # True
371
372
# Invalid names (contain path separators)
373
print(resolver.is_valid_name("invalid/name")) # False
374
print(resolver.is_valid_name("also\\invalid")) # False
375
print(resolver.is_valid_name("")) # False
376
```
377
378
## Exception Handling
379
380
### ResolverError Hierarchy
381
382
```python { .api }
383
class ResolverError(RuntimeError):
384
"""Base exception for path resolution errors."""
385
386
class RootResolverError(ResolverError):
387
"""Exception when resolving from wrong root."""
388
389
class ChildResolverError(ResolverError):
390
"""Exception when child cannot be resolved."""
391
```
392
393
**Usage Example:**
394
395
```python
396
from anytree import Node, Resolver, ResolverError, ChildResolverError
397
398
root1 = Node("root1")
399
root2 = Node("root2")
400
Node("child", parent=root1)
401
402
resolver = Resolver()
403
404
try:
405
# Try to resolve from wrong tree
406
result = resolver.get(root2, "/child")
407
except ChildResolverError as e:
408
print(f"Child not found: {e}")
409
410
try:
411
# Try invalid path
412
result = resolver.get(root1, "/nonexistent/path")
413
except ResolverError as e:
414
print(f"Resolution failed: {e}")
415
print(f"Error type: {type(e).__name__}")
416
```
417
418
### WalkError Exception
419
420
```python { .api }
421
class WalkError(RuntimeError):
422
"""Exception raised during tree walking operations."""
423
```
424
425
**Usage Example:**
426
427
```python
428
from anytree import Node, Walker, WalkError
429
430
# Create separate trees
431
tree1_root = Node("tree1")
432
tree2_root = Node("tree2")
433
434
walker = Walker()
435
436
try:
437
# Try to walk between disconnected trees
438
walker.walk(tree1_root, tree2_root)
439
except WalkError as e:
440
print(f"Walk failed: {e}")
441
```
442
443
## Common Use Cases
444
445
### File System Navigation
446
447
```python
448
from anytree import Node, Resolver
449
450
def create_fs_tree():
451
root = Node("/")
452
usr = Node("usr", parent=root)
453
local = Node("local", parent=usr)
454
bin_dir = Node("bin", parent=local)
455
456
# Add executables
457
Node("python3", parent=bin_dir)
458
Node("gcc", parent=bin_dir)
459
Node("make", parent=bin_dir)
460
461
return root, Resolver()
462
463
fs_root, resolver = create_fs_tree()
464
465
# Find executable
466
python = resolver.get(fs_root, "/usr/local/bin/python3")
467
print(f"Found: {python.name}")
468
469
# List all executables
470
executables = list(resolver.glob(fs_root, "/usr/local/bin/*"))
471
print(f"Executables: {[n.name for n in executables]}")
472
```
473
474
### Configuration Path Resolution
475
476
```python
477
from anytree import Node, Resolver
478
479
# Application configuration tree
480
config = Node("config")
481
database = Node("database", parent=config)
482
Node("host", parent=database, value="localhost")
483
Node("port", parent=database, value=5432)
484
Node("name", parent=database, value="myapp")
485
486
api = Node("api", parent=config)
487
Node("timeout", parent=api, value=30)
488
Node("retries", parent=api, value=3)
489
490
resolver = Resolver()
491
492
# Get configuration values
493
db_host = resolver.get(config, "/database/host")
494
print(f"DB Host: {db_host.value}")
495
496
api_timeout = resolver.get(config, "/api/timeout")
497
print(f"API Timeout: {api_timeout.value}")
498
499
# Get all database settings
500
db_settings = list(resolver.glob(config, "/database/*"))
501
for setting in db_settings:
502
print(f"{setting.name}: {setting.value}")
503
```
504
505
### Dynamic Path Building
506
507
```python
508
from anytree import Node, Resolver, Walker
509
510
class PathBuilder:
511
def __init__(self, root):
512
self.root = root
513
self.resolver = Resolver()
514
self.walker = Walker()
515
516
def get_relative_path(self, from_node, to_node):
517
"""Get relative path string from one node to another"""
518
upwards, common, downwards = self.walker.walk(from_node, to_node)
519
520
# Build relative path
521
path_parts = [".."] * len(upwards[1:]) # Exclude starting node
522
path_parts.extend([node.name for node in downwards])
523
524
return "/".join(path_parts) if path_parts else "."
525
526
def resolve_from_context(self, context_node, target_path):
527
"""Resolve path relative to context node"""
528
return self.resolver.get(context_node, target_path)
529
530
# Example usage
531
root = Node("project")
532
src = Node("src", parent=root)
533
tests = Node("tests", parent=root)
534
main_py = Node("main.py", parent=src)
535
test_main = Node("test_main.py", parent=tests)
536
537
builder = PathBuilder(root)
538
539
# Get relative path from test to main
540
rel_path = builder.get_relative_path(test_main, main_py)
541
print(f"Relative path: {rel_path}") # ../src/main.py
542
543
# Resolve from context
544
main_from_test = builder.resolve_from_context(test_main, "../src/main.py")
545
print(f"Resolved: {main_from_test.name}") # main.py
546
```