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

search.mddocs/

0

# Search and Filtering

1

2

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.

3

4

## Capabilities

5

6

### Single Node Search

7

8

Find the first node that matches specified criteria.

9

10

#### find - Custom Filter Search

11

12

Find single node using custom filter function.

13

14

```python { .api }

15

def find(node, filter_=None, stop=None, maxlevel=None):

16

"""

17

Find the first node matching filter.

18

19

Args:

20

node: Starting node for search

21

filter_: Function called with every node as argument, node is returned if True

22

stop: Stop iteration at node if stop function returns True for node

23

maxlevel: Maximum descending in the node hierarchy

24

25

Returns:

26

First matching node or None if no match found

27

"""

28

```

29

30

**Usage Example:**

31

32

```python

33

from anytree import Node, find

34

35

# Create tree structure

36

root = Node("Company")

37

engineering = Node("Engineering", parent=root)

38

marketing = Node("Marketing", parent=root)

39

backend = Node("Backend", parent=engineering, team_size=5)

40

frontend = Node("Frontend", parent=engineering, team_size=3)

41

content = Node("Content", parent=marketing, team_size=2)

42

43

# Find by name

44

eng_node = find(root, filter_=lambda n: n.name == "Engineering")

45

print(eng_node) # Node('/Company/Engineering')

46

47

# Find by attribute

48

large_team = find(root, filter_=lambda n: getattr(n, 'team_size', 0) >= 5)

49

print(large_team) # Node('/Company/Engineering/Backend')

50

51

# Find with custom logic

52

def has_substring(node):

53

return "end" in node.name.lower()

54

55

node_with_end = find(root, filter_=has_substring)

56

print(node_with_end) # Node('/Company/Engineering/Backend')

57

```

58

59

#### find_by_attr - Attribute Value Search

60

61

Find single node by attribute value with exact matching.

62

63

```python { .api }

64

def find_by_attr(node, value, name="name", maxlevel=None):

65

"""

66

Find single node by attribute value.

67

68

Args:

69

node: Starting node for search

70

value: Value to match

71

name: Attribute name to check (default "name")

72

maxlevel: Maximum search depth

73

74

Returns:

75

First node with matching attribute value or None

76

"""

77

```

78

79

**Usage Example:**

80

81

```python

82

from anytree import Node, find_by_attr

83

84

root = Node("Company")

85

Node("Engineering", parent=root, department_code="ENG")

86

Node("Marketing", parent=root, department_code="MKT")

87

Node("Backend", parent=root, department_code="ENG")

88

89

# Find by name (default attribute)

90

marketing = find_by_attr(root, "Marketing")

91

print(marketing) # Node('/Company/Marketing')

92

93

# Find by custom attribute

94

eng_dept = find_by_attr(root, "ENG", name="department_code")

95

print(eng_dept) # Node('/Company/Engineering') - first match

96

97

# With depth limit

98

shallow_search = find_by_attr(root, "Backend", maxlevel=2)

99

print(shallow_search) # None (Backend is at level 2, but search stops at level 2)

100

```

101

102

### Multiple Node Search

103

104

Find all nodes that match specified criteria.

105

106

#### findall - Custom Filter Search

107

108

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

109

110

```python { .api }

111

def findall(node, filter_=None, stop=None, maxlevel=None, mincount=None, maxcount=None):

112

"""

113

Find all nodes matching filter.

114

115

Args:

116

node: Starting node for search

117

filter_: Function called with every node as argument, node is returned if True

118

stop: Stop iteration at node if stop function returns True for node

119

maxlevel: Maximum descending in the node hierarchy

120

mincount: Minimum number of nodes required

121

maxcount: Maximum number of nodes allowed

122

123

Returns:

124

Tuple of matching nodes

125

126

Raises:

127

CountError: If result count violates mincount/maxcount constraints

128

"""

129

```

130

131

**Usage Example:**

132

133

```python

134

from anytree import Node, findall, CountError

135

136

# Create tree structure

137

root = Node("Company")

138

engineering = Node("Engineering", parent=root, budget=100000)

139

marketing = Node("Marketing", parent=root, budget=50000)

140

backend = Node("Backend", parent=engineering, budget=60000)

141

frontend = Node("Frontend", parent=engineering, budget=40000)

142

content = Node("Content", parent=marketing, budget=30000)

143

144

# Find all nodes with budget > 50000

145

high_budget = findall(root, filter_=lambda n: getattr(n, 'budget', 0) > 50000)

146

print(len(high_budget)) # 3 nodes

147

for node in high_budget:

148

print(f"{node.name}: ${node.budget}")

149

150

# Find with count constraints

151

try:

152

# Require at least 2 matches

153

teams = findall(root,

154

filter_=lambda n: hasattr(n, 'budget') and n.budget < 100000,

155

mincount=2)

156

print(f"Found {len(teams)} teams with budget < $100k")

157

except CountError as e:

158

print(f"Count constraint failed: {e}")

159

160

# Find with max count limit

161

limited_results = findall(root,

162

filter_=lambda n: hasattr(n, 'budget'),

163

maxcount=3)

164

print(f"Limited to first {len(limited_results)} results")

165

```

166

167

#### findall_by_attr - Attribute Value Search

168

169

Find all nodes with matching attribute values.

170

171

```python { .api }

172

def findall_by_attr(node, value, name="name", maxlevel=None, mincount=None, maxcount=None):

173

"""

174

Find all nodes by attribute value.

175

176

Args:

177

node: Starting node for search

178

value: Value to match

179

name: Attribute name to check (default "name")

180

maxlevel: Maximum search depth

181

mincount: Minimum number of nodes required

182

maxcount: Maximum number of nodes allowed

183

184

Returns:

185

Tuple of nodes with matching attribute value

186

187

Raises:

188

CountError: If result count violates mincount/maxcount constraints

189

"""

190

```

191

192

**Usage Example:**

193

194

```python

195

from anytree import Node, findall_by_attr

196

197

root = Node("Company")

198

Node("TeamA", parent=root, status="active")

199

Node("TeamB", parent=root, status="active")

200

Node("TeamC", parent=root, status="inactive")

201

Node("TeamD", parent=root, status="active")

202

203

# Find all active teams

204

active_teams = findall_by_attr(root, "active", name="status")

205

print(f"Active teams: {len(active_teams)}")

206

for team in active_teams:

207

print(f" {team.name}")

208

209

# Find all nodes with specific name pattern (multiple matches)

210

nodes_with_team = findall_by_attr(root, "Team", name="name") # Won't match - exact comparison

211

print(f"Exact 'Team' matches: {len(nodes_with_team)}") # 0

212

213

# For partial matching, use findall with filter

214

team_nodes = findall(root, filter_=lambda n: "Team" in n.name)

215

print(f"Nodes containing 'Team': {len(team_nodes)}") # 4

216

```

217

218

## Advanced Search Patterns

219

220

### Stop Functions

221

222

Control search termination to avoid traversing certain subtrees:

223

224

```python

225

from anytree import Node, findall

226

227

root = Node("Company")

228

public_dir = Node("public", parent=root)

229

private_dir = Node("private", parent=root, access="restricted")

230

Node("readme.txt", parent=public_dir, type="file")

231

Node("config.txt", parent=private_dir, type="file")

232

Node("secret.txt", parent=private_dir, type="file")

233

234

# Stop at restricted directories

235

public_files = findall(root,

236

filter_=lambda n: getattr(n, 'type', None) == 'file',

237

stop=lambda n: getattr(n, 'access', None) == 'restricted')

238

239

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

240

```

241

242

### Depth-Limited Search

243

244

Search only within specified depth levels:

245

246

```python

247

from anytree import Node, findall

248

249

# Create deep hierarchy

250

root = Node("L0")

251

l1 = Node("L1", parent=root)

252

l2 = Node("L2", parent=l1)

253

l3 = Node("L3", parent=l2)

254

Node("deep_target", parent=l3)

255

Node("shallow_target", parent=l1)

256

257

# Search only first 2 levels

258

shallow_search = findall(root,

259

filter_=lambda n: "target" in n.name,

260

maxlevel=2)

261

print(f"Shallow search found: {len(shallow_search)} targets") # 1

262

263

# Search all levels

264

deep_search = findall(root, filter_=lambda n: "target" in n.name)

265

print(f"Deep search found: {len(deep_search)} targets") # 2

266

```

267

268

### Complex Filter Logic

269

270

Combine multiple conditions in filter functions:

271

272

```python

273

from anytree import Node, findall

274

275

root = Node("Company")

276

Node("Engineer_A", parent=root, role="engineer", experience=5, active=True)

277

Node("Manager_B", parent=root, role="manager", experience=8, active=True)

278

Node("Engineer_C", parent=root, role="engineer", experience=3, active=False)

279

Node("Engineer_D", parent=root, role="engineer", experience=7, active=True)

280

281

# Complex filter: active senior engineers (5+ years experience)

282

def senior_active_engineer(node):

283

return (getattr(node, 'role', '') == 'engineer' and

284

getattr(node, 'experience', 0) >= 5 and

285

getattr(node, 'active', False))

286

287

senior_engineers = findall(root, filter_=senior_active_engineer)

288

print(f"Senior active engineers: {len(senior_engineers)}")

289

290

for eng in senior_engineers:

291

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

292

```

293

294

## Cached Search Functions

295

296

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

297

298

### Module: cachedsearch

299

300

```python { .api }

301

# Import cached search functions

302

from anytree import cachedsearch

303

304

# Same signatures as regular search functions but with caching

305

def cachedsearch.find(node, filter_=None, stop=None, maxlevel=None): ...

306

def cachedsearch.find_by_attr(node, value, name="name", maxlevel=None): ...

307

def cachedsearch.findall(node, filter_=None, stop=None, maxlevel=None, mincount=None, maxcount=None): ...

308

def cachedsearch.findall_by_attr(node, value, name="name", maxlevel=None, mincount=None, maxcount=None): ...

309

```

310

311

**Usage Example:**

312

313

```python

314

from anytree import Node, cachedsearch

315

316

# Create large tree

317

root = Node("root")

318

for i in range(1000):

319

Node(f"node_{i}", parent=root, category=f"cat_{i % 10}")

320

321

# First search - builds cache

322

start_time = time.time()

323

results1 = cachedsearch.findall_by_attr(root, "cat_5", name="category")

324

time1 = time.time() - start_time

325

326

# Second search - uses cache

327

start_time = time.time()

328

results2 = cachedsearch.findall_by_attr(root, "cat_5", name="category")

329

time2 = time.time() - start_time

330

331

print(f"First search: {time1:.4f}s")

332

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

333

```

334

335

## Exception Handling

336

337

### CountError Exception

338

339

Raised when search results don't meet count constraints:

340

341

```python

342

from anytree import Node, findall, CountError

343

344

root = Node("root")

345

Node("child1", parent=root)

346

Node("child2", parent=root)

347

348

try:

349

# Require at least 5 children

350

results = findall(root,

351

filter_=lambda n: n.parent == root,

352

mincount=5)

353

except CountError as e:

354

print(f"Not enough results: {e}")

355

356

try:

357

# Allow maximum 1 result

358

results = findall(root,

359

filter_=lambda n: n.parent == root,

360

maxcount=1)

361

except CountError as e:

362

print(f"Too many results: {e}")

363

```

364

365

## Search Performance Tips

366

367

### Filter Function Optimization

368

369

```python

370

from anytree import Node, findall

371

372

root = Node("root")

373

# Add many nodes...

374

375

# Efficient: Check cheap conditions first

376

def efficient_filter(node):

377

# Quick attribute check first

378

if not hasattr(node, 'expensive_property'):

379

return False

380

# Expensive computation only if needed

381

return expensive_computation(node.expensive_property)

382

383

# Inefficient: Expensive check for every node

384

def inefficient_filter(node):

385

return expensive_computation(getattr(node, 'expensive_property', None))

386

387

# Use efficient filter

388

results = findall(root, filter_=efficient_filter)

389

```

390

391

### Using Cached Search

392

393

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

394

395

```python

396

from anytree import cachedsearch

397

398

# Multiple searches on same tree - use cached versions

399

users_in_eng = cachedsearch.findall_by_attr(org_root, "Engineering", name="department")

400

managers = cachedsearch.findall_by_attr(org_root, "Manager", name="role")

401

senior_staff = cachedsearch.findall(org_root, filter_=lambda n: n.experience > 10)

402

```

403

404

### Early Termination

405

406

Use stop functions to avoid unnecessary traversal:

407

408

```python

409

from anytree import Node, findall

410

411

# Stop searching once target is found in specific subtree

412

target_found = False

413

414

def stop_when_found(node):

415

global target_found

416

if node.name == "target":

417

target_found = True

418

return target_found and node.name == "expensive_subtree_root"

419

420

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

421

```