0
# Advanced Operations
1
2
Low-level utilities for custom path manipulation and advanced use cases, providing direct access to the underlying path walking, matching, and transformation algorithms that power dpath's high-level functions.
3
4
## Capabilities
5
6
### Path Walking and Traversal
7
8
Core traversal functions that provide the foundation for all dpath operations, offering fine-grained control over how nested structures are navigated.
9
10
```python { .api }
11
from dpath.segments import walk, make_walkable, leaf, leafy
12
13
def walk(obj, location=()):
14
"""
15
Walk object yielding (segments, value) pairs in breadth-first order.
16
17
Parameters:
18
- obj: Object to walk (dict, list, or other nested structure)
19
- location (tuple): Current location tuple (used internally for recursion)
20
21
Yields:
22
Tuple[Tuple[PathSegment, ...], Any]: (path_segments, value) pairs
23
24
Note:
25
Walks right-to-left for sequences to support safe deletion patterns.
26
"""
27
28
def make_walkable(node):
29
"""
30
Create iterator for (key, value) pairs regardless of node type.
31
32
Parameters:
33
- node: Dict, list, tuple, or other container
34
35
Returns:
36
Iterator[Tuple[PathSegment, Any]]: Iterator of (key, value) pairs
37
"""
38
39
def leaf(thing):
40
"""
41
Check if object is a leaf (primitive type: str, int, float, bool, None, bytes).
42
43
Parameters:
44
- thing: Any object to test
45
46
Returns:
47
bool: True if object is a primitive leaf value
48
"""
49
50
def leafy(thing):
51
"""
52
Check if object is leaf-like (primitive or empty container).
53
54
Parameters:
55
- thing: Any object to test
56
57
Returns:
58
bool: True if object is primitive or empty sequence/mapping
59
"""
60
```
61
62
#### Usage Examples
63
64
```python
65
from dpath.segments import walk, make_walkable, leaf, leafy
66
67
data = {
68
"users": {
69
"john": {"age": 30, "hobbies": ["reading", "gaming"]},
70
"jane": {"age": 25, "hobbies": []}
71
}
72
}
73
74
# Walk entire structure
75
for path_segments, value in walk(data):
76
path_str = "/".join(str(seg) for seg in path_segments)
77
print(f"{path_str}: {value} (leaf: {leaf(value)})")
78
79
# Output includes:
80
# users: {'john': {...}, 'jane': {...}} (leaf: False)
81
# users/john: {'age': 30, 'hobbies': [...]} (leaf: False)
82
# users/john/age: 30 (leaf: True)
83
# users/john/hobbies: ['reading', 'gaming'] (leaf: False)
84
# users/john/hobbies/0: reading (leaf: True)
85
# etc.
86
87
# Make any container walkable
88
mixed_data = [{"key": "value"}, "string", 42]
89
for key, value in make_walkable(mixed_data):
90
print(f"Index {key}: {value}")
91
92
# Test leaf conditions
93
print(leaf("string")) # True
94
print(leaf(42)) # True
95
print(leaf([])) # False
96
print(leafy([])) # True (empty containers are leafy)
97
print(leafy({"a": 1})) # False (non-empty containers)
98
```
99
100
### Pattern Matching and Filtering
101
102
Advanced pattern matching utilities for implementing custom search and filter logic.
103
104
```python { .api }
105
from dpath.segments import match, view, has, get
106
107
def match(segments, glob):
108
"""
109
Check if path segments match glob pattern.
110
111
Parameters:
112
- segments (Path): Path segments as tuple or list
113
- glob (Glob): Glob pattern as tuple or list
114
115
Returns:
116
bool: True if segments match the glob pattern
117
118
Note:
119
Supports *, **, character classes, and complex patterns.
120
Converts integers to strings for comparison.
121
"""
122
123
def view(obj, glob):
124
"""
125
Create filtered deep copy view of object matching glob.
126
127
Parameters:
128
- obj (MutableMapping): Object to create view from
129
- glob (Glob): Glob pattern to match
130
131
Returns:
132
MutableMapping: New object containing only matching paths (deep copied)
133
"""
134
135
def has(obj, segments):
136
"""
137
Check if path exists in object.
138
139
Parameters:
140
- obj: Object to check
141
- segments: Path segments as sequence
142
143
Returns:
144
bool: True if path exists
145
"""
146
147
def get(obj, segments):
148
"""
149
Get value at path segments (lower-level than dpath.get).
150
151
Parameters:
152
- obj: Object to navigate
153
- segments: Path segments as sequence
154
155
Returns:
156
Any: Value at path
157
158
Raises:
159
- PathNotFound: If path doesn't exist
160
"""
161
```
162
163
#### Usage Examples
164
165
```python
166
from dpath.segments import match, view, has, get
167
168
data = {
169
"api": {
170
"v1": {"users": ["john", "jane"]},
171
"v2": {"users": ["bob"], "admin": ["alice"]}
172
}
173
}
174
175
# Test pattern matching directly
176
segments = ("api", "v1", "users")
177
pattern = ("api", "*", "users")
178
print(match(segments, pattern)) # True
179
180
pattern2 = ("api", "v2", "admin")
181
print(match(segments, pattern2)) # False
182
183
# Create views with deep copying
184
api_v1_view = view(data, ("api", "v1", "**"))
185
# Returns: {"api": {"v1": {"users": ["john", "jane"]}}}
186
187
users_view = view(data, ("**", "users"))
188
# Returns: {"api": {"v1": {"users": ["john", "jane"]}, "v2": {"users": ["bob"]}}}
189
190
# Path existence checking
191
print(has(data, ("api", "v1", "users"))) # True
192
print(has(data, ("api", "v3", "users"))) # False
193
print(has(data, ("api", "v1", "users", 0))) # True (first user exists)
194
195
# Direct path access
196
users = get(data, ("api", "v1", "users")) # Returns: ["john", "jane"]
197
first_user = get(data, ("api", "v1", "users", 0)) # Returns: "john"
198
```
199
200
### Advanced Path Manipulation
201
202
Low-level path construction and manipulation utilities for implementing custom creators and transformers.
203
204
```python { .api }
205
from dpath.segments import set, extend, types, expand, int_str, _default_creator
206
207
def set(obj, segments, value, creator=_default_creator, hints=()):
208
"""
209
Set value at path, optionally creating missing components.
210
211
Parameters:
212
- obj (MutableMapping): Object to modify
213
- segments: Path segments sequence
214
- value: Value to set
215
- creator (Creator): Function to create missing path components (default _default_creator)
216
- hints (Hints): Type hints for creator function
217
218
Returns:
219
MutableMapping: Modified object
220
"""
221
222
def _default_creator(current, segments, i, hints=()):
223
"""
224
Default creator function that creates dicts or lists based on next segment type.
225
226
Parameters:
227
- current: Current container being modified
228
- segments: Full path segments
229
- i (int): Current segment index
230
- hints (Hints): Type hints for creation
231
232
Creates:
233
- List if next segment is numeric
234
- Dict otherwise
235
"""
236
237
def extend(thing, index, value=None):
238
"""
239
Extend sequence to contain at least index+1 elements.
240
241
Parameters:
242
- thing (MutableSequence): Sequence to extend
243
- index (int): Required minimum index
244
- value: Fill value for new elements (default None)
245
246
Returns:
247
MutableSequence: Extended sequence
248
"""
249
250
def types(obj, segments):
251
"""
252
Get type information for each level of path.
253
254
Parameters:
255
- obj: Object to analyze
256
- segments: Path segments
257
258
Returns:
259
Tuple[Tuple[PathSegment, type], ...]: (segment, type) pairs for each level
260
"""
261
262
def expand(segments):
263
"""
264
Generate progressively longer path prefixes.
265
266
Parameters:
267
- segments: Full path segments
268
269
Yields:
270
Tuple: Path prefixes from shortest to full length
271
"""
272
273
def int_str(segment):
274
"""
275
Convert integer segments to strings.
276
277
Parameters:
278
- segment (PathSegment): Path segment to convert
279
280
Returns:
281
PathSegment: String if input was int, otherwise unchanged
282
"""
283
```
284
285
#### Usage Examples
286
287
```python
288
from dpath.segments import set, extend, types, expand, int_str, _default_creator
289
290
# Use default creator (automatic dict/list selection)
291
data = {}
292
set(data, ("users", 0, "name"), "john") # Uses _default_creator
293
# Creates: {"users": [{"name": "john"}]} - list because next segment is 0
294
295
# Custom creator functions
296
def always_list_creator(current, segments, i, hints=()):
297
"""Creator that always makes lists instead of dicts"""
298
segment = segments[i]
299
if isinstance(current, list):
300
extend(current, segment)
301
current[segment] = []
302
303
# Use custom creator
304
data2 = {}
305
set(data2, ("items", 0, "name"), "first", creator=always_list_creator)
306
# Creates: {"items": [{"name": "first"}]} but with all list structure
307
308
# Extend sequences manually
309
my_list = ["a", "b"]
310
extend(my_list, 5, "empty") # Extends to ["a", "b", "empty", "empty", "empty", "empty"]
311
312
# Analyze path types
313
data = {"users": [{"name": "john", "age": 30}]}
314
path_types = types(data, ("users", 0, "name"))
315
# Returns: (("users", <class 'dict'>), (0, <class 'list'>), ("name", <class 'dict'>))
316
317
# Generate path prefixes
318
for prefix in expand(("a", "b", "c", "d")):
319
print(prefix)
320
# Output:
321
# ("a",)
322
# ("a", "b")
323
# ("a", "b", "c")
324
# ("a", "b", "c", "d")
325
326
# Convert path segments for display
327
mixed_path = ("users", 0, "profile", 1, "email")
328
display_path = "/".join(int_str(seg) for seg in mixed_path)
329
# Returns: "users/0/profile/1/email"
330
```
331
332
### Functional Operations
333
334
Higher-order functions for implementing custom operations over nested structures.
335
336
```python { .api }
337
from dpath.segments import fold, foldm, leaves
338
339
def fold(obj, f, acc):
340
"""
341
Fold (reduce) over all paths in object with read-only access.
342
343
Parameters:
344
- obj: Object to fold over
345
- f: Function(obj, (path_segments, value), accumulator) -> bool|Any
346
- acc: Initial accumulator value
347
348
Returns:
349
Any: Final accumulator value
350
351
Note:
352
If f returns False (exactly), folding stops early.
353
"""
354
355
def foldm(obj, f, acc):
356
"""
357
Fold with mutation support - allows modifying obj during iteration.
358
359
Parameters:
360
- obj: Object to fold over (may be modified)
361
- f: Function(obj, (path_segments, value), accumulator) -> bool|Any
362
- acc: Initial accumulator value
363
364
Returns:
365
Any: Final accumulator value
366
367
Note:
368
Loads all paths into memory first to support safe mutations.
369
"""
370
371
def leaves(obj):
372
"""
373
Generate only leaf (path, value) pairs.
374
375
Parameters:
376
- obj: Object to traverse
377
378
Yields:
379
Tuple[Tuple[PathSegment, ...], Any]: (path_segments, leaf_value) pairs
380
"""
381
```
382
383
#### Usage Examples
384
385
```python
386
from dpath.segments import fold, foldm, leaves
387
388
data = {
389
"users": {
390
"john": {"age": 30, "active": True},
391
"jane": {"age": 25, "active": False}
392
},
393
"settings": {"theme": "dark"}
394
}
395
396
# Count all paths
397
def counter(obj, pair, count):
398
count[0] += 1
399
return True
400
401
total_paths = fold(data, counter, [0])
402
print(f"Total paths: {total_paths[0]}")
403
404
# Find first match with early termination
405
def find_age_30(obj, pair, result):
406
path_segments, value = pair
407
if value == 30:
408
result.append(path_segments)
409
return False # Stop folding
410
return True
411
412
result = fold(data, find_age_30, [])
413
if result:
414
print(f"Found age 30 at: {result[0]}")
415
416
# Mutating fold - deactivate all users
417
def deactivate_users(obj, pair, count):
418
path_segments, value = pair
419
if (isinstance(value, dict) and
420
"active" in value and
421
len(path_segments) >= 2 and
422
path_segments[0] == "users"):
423
value["active"] = False
424
count[0] += 1
425
return True
426
427
changes = foldm(data, deactivate_users, [0])
428
print(f"Deactivated {changes[0]} users")
429
430
# Process only leaf values
431
for path_segments, value in leaves(data):
432
path_str = "/".join(str(seg) for seg in path_segments)
433
print(f"Leaf {path_str}: {value}")
434
435
# Custom aggregations
436
def sum_numeric_leaves(obj, pair, acc):
437
path_segments, value = pair
438
if isinstance(value, (int, float)):
439
acc["sum"] += value
440
acc["count"] += 1
441
return True
442
443
result = fold(data, sum_numeric_leaves, {"sum": 0, "count": 0})
444
if result["count"] > 0:
445
average = result["sum"] / result["count"]
446
print(f"Average numeric value: {average}")
447
```
448
449
## Advanced Integration Patterns
450
451
### Custom Path Operations
452
453
```python
454
# Implement custom search with transformation
455
def transform_search(obj, pattern, transformer):
456
"""Search and transform matching values"""
457
results = {}
458
459
def transform_match(obj, pair, results):
460
path_segments, value = pair
461
if match(path_segments, pattern):
462
transformed = transformer(value)
463
set(results, path_segments, transformed)
464
return True
465
466
fold(obj, transform_match, results)
467
return results
468
469
# Usage
470
def uppercase_strings(value):
471
return value.upper() if isinstance(value, str) else value
472
473
transformed = transform_search(data, ("**",), uppercase_strings)
474
```
475
476
### Performance Optimization
477
478
```python
479
# Early termination patterns
480
def find_first_matching_path(obj, condition):
481
"""Find first path matching condition"""
482
def finder(obj, pair, result):
483
path_segments, value = pair
484
if condition(value):
485
result.append(path_segments)
486
return False # Stop immediately
487
return True
488
489
result = fold(obj, finder, [])
490
return result[0] if result else None
491
492
# Memory-efficient processing
493
def process_large_structure(obj, processor):
494
"""Process structure without loading all paths"""
495
for path_segments, value in walk(obj):
496
if leaf(value):
497
processor(path_segments, value)
498
```