Filesystem-like pathing and searching for dictionaries
—
Powerful search capabilities for finding paths and values matching glob patterns in nested dictionaries. These operations support filtering, yielding, and flexible result formats for different use cases.
Search dictionaries for paths matching glob patterns, returning structured results that preserve the original data hierarchy.
def search(obj, glob, yielded=False, separator="/", afilter=None, dirs=True):
"""
Search for paths/values matching glob pattern.
Parameters:
- obj (MutableMapping): Target dictionary to search
- glob (Glob): Path pattern as string or sequence
- yielded (bool): If True, return generator of (path, value) tuples instead of dict
- separator (str): Path separator character (default "/")
- afilter (Filter): Optional function to filter results by value
- dirs (bool): Include non-leaf (directory-like) nodes in results (default True)
Returns:
dict or generator: Dictionary preserving structure, or generator of (path_str, value) tuples if yielded=True
"""import dpath
data = {
"users": {
"john": {"age": 30, "city": "NYC", "hobbies": ["reading", "gaming"]},
"jane": {"age": 25, "city": "LA", "hobbies": ["hiking", "cooking"]},
"bob": {"age": 35, "city": "Chicago"}
},
"settings": {"theme": "dark", "lang": "en"}
}
# Search with wildcard - returns structured dict
cities = dpath.search(data, "users/*/city")
# Returns: {'users': {'john': {'city': 'NYC'}, 'jane': {'city': 'LA'}, 'bob': {'city': 'Chicago'}}}
# Search with yielded=True - returns generator of (path, value) pairs
for path, value in dpath.search(data, "users/*/city", yielded=True):
print(f"{path}: {value}")
# Output:
# users/john/city: NYC
# users/jane/city: LA
# users/bob/city: Chicago
# Recursive search with **
all_values = dpath.search(data, "**/age")
# Returns all age values regardless of depth
# Search excluding directories (dirs=False) - only leaf values
leaves_only = dpath.search(data, "users/**", dirs=False)
# Excludes intermediate dict objects, only includes final values
# Filter search results
def young_filter(value):
return isinstance(value, dict) and value.get("age", 0) < 30
young_users = dpath.search(data, "users/*", afilter=young_filter)
# Returns only jane's record
# Complex pattern matching
hobbies = dpath.search(data, "users/*/hobbies/*") # All individual hobbies
first_hobbies = dpath.search(data, "users/*/hobbies/0") # First hobby of each userExtract just the values that match glob patterns, returning a simple list rather than preserving the dictionary structure.
def values(obj, glob, separator="/", afilter=None, dirs=True):
"""
Get list of all values matching glob pattern.
Parameters:
- obj (MutableMapping): Target dictionary to search
- glob (Glob): Path pattern as string or sequence
- separator (str): Path separator character (default "/")
- afilter (Filter): Optional function to filter results by value
- dirs (bool): Include non-leaf (directory-like) nodes in results (default True)
Returns:
list: List of matching values in order found
"""import dpath
data = {
"products": {
"electronics": {
"laptop": {"price": 1200, "stock": 5},
"phone": {"price": 800, "stock": 12}
},
"books": {
"python_guide": {"price": 45, "stock": 20},
"data_science": {"price": 60, "stock": 8}
}
}
}
# Extract all prices as a simple list
prices = dpath.values(data, "products/*/*/price")
# Returns: [1200, 800, 45, 60]
# Extract with filtering
def expensive_filter(price):
return isinstance(price, (int, float)) and price > 50
expensive_prices = dpath.values(data, "products/*/*/price", afilter=expensive_filter)
# Returns: [1200, 800, 60]
# Extract all product info (directories included by default)
all_products = dpath.values(data, "products/*/*")
# Returns list of product dictionaries
# Extract only leaf values (dirs=False)
leaf_values = dpath.values(data, "products/**", dirs=False)
# Returns: [1200, 5, 800, 12, 45, 20, 60, 8] (all leaf values)
# Extract with custom separator
values_custom_sep = dpath.values(data, "products.*.price", separator=".")dpath supports rich glob patterns for flexible searching:
# Single-level wildcards
dpath.search(data, "users/*") # All direct children of users
dpath.search(data, "users/*/age") # Age field of all users
# Multi-level wildcards
dpath.search(data, "users/**") # Everything under users (recursive)
dpath.search(data, "**/age") # All age fields at any depth
# Character classes and ranges
dpath.search(data, "users/[jb]*") # Users starting with 'j' or 'b'
dpath.search(data, "item[0-9]") # Items with single digit suffix
# Complex combinations
dpath.search(data, "*/settings/**/lang") # Lang settings at any depth under any top-level keyCreate sophisticated filters to narrow search results:
# Filter by value type
def string_filter(value):
return isinstance(value, str)
strings = dpath.values(data, "**", afilter=string_filter)
# Filter by value content
def contains_email(value):
return isinstance(value, str) and "@" in value
emails = dpath.values(data, "**", afilter=contains_email)
# Filter by dictionary structure
def has_required_fields(value):
if not isinstance(value, dict):
return False
return all(field in value for field in ["name", "age", "email"])
valid_users = dpath.search(data, "users/*", afilter=has_required_fields)
# Combine filters
def active_adult_filter(user):
return (isinstance(user, dict) and
user.get("active", False) and
user.get("age", 0) >= 18)When using yielded=True, work efficiently with large datasets:
# Process results incrementally
def process_large_dataset(data):
for path, value in dpath.search(data, "records/**", yielded=True):
# Process one item at a time without loading everything into memory
if meets_criteria(value):
yield transform(value)
# Early termination
def find_first_match(data, pattern, condition):
for path, value in dpath.search(data, pattern, yielded=True):
if condition(value):
return path, value
return None, None
# Collect specific information
paths_and_types = [(path, type(value).__name__)
for path, value in dpath.search(data, "**", yielded=True)]Search operations integrate seamlessly with other dpath functions:
# Find then modify
matching_paths = [path for path, _ in dpath.search(data, "users/*/active", yielded=True)]
for path in matching_paths:
dpath.set(data, path, False)
# Search and extract for processing
user_ages = dpath.values(data, "users/*/age")
average_age = sum(user_ages) / len(user_ages)
# Complex workflows
active_users = dpath.search(data, "users/*", afilter=lambda u: u.get("active"))
for user_path, user_data in dpath.search(active_users, "**", yielded=True):
# Process each active user
passInstall with Tessl CLI
npx tessl i tessl/pypi-dpath