or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

import-export.mdindex.mdnode-construction.mdpath-resolution.mdsearch.mdtree-iteration.mdtree-rendering.mdutilities.md

path-resolution.mddocs/

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

```