Filesystem-like pathing and searching for dictionaries
—
Core functions for getting and setting values at specific paths in nested dictionaries. These operations support both exact paths and glob patterns, with robust handling of missing keys and type coercion.
Retrieve single values from nested dictionaries using path notation with support for default values and error handling.
def get(obj, glob, separator="/", default=_DEFAULT_SENTINEL):
"""
Get single value matching glob pattern.
Parameters:
- obj (MutableMapping): Target dictionary to search
- glob (Glob): Path pattern as string or sequence of path segments
- separator (str): Path separator character (default "/")
- default (Any): Default value if not found (raises KeyError if not provided)
Returns:
Any: The matched value
Raises:
- KeyError: If glob not found and no default provided
- ValueError: If glob matches multiple values
"""import dpath
data = {
"users": {
"john": {"age": 30, "profile": {"city": "NYC"}},
"jane": {"age": 25, "profile": {"city": "LA"}}
}
}
# Get exact path
age = dpath.get(data, "users/john/age") # Returns: 30
# Get with default value
phone = dpath.get(data, "users/john/phone", default="N/A") # Returns: "N/A"
# Get root object
root = dpath.get(data, "/") # Returns: entire data dict
# Use list-based paths (useful for keys containing separators)
city = dpath.get(data, ["users", "john", "profile", "city"]) # Returns: "NYC"
# Custom separator
dpath.get(data, "users.john.age", separator=".") # Returns: 30Create new paths in dictionaries, automatically generating missing intermediate dictionaries or lists as needed.
def new(obj, path, value, separator="/", creator=None):
"""
Create new path and set value, creating missing intermediate keys.
Parameters:
- obj (MutableMapping): Target dictionary to modify
- path (Path): Path as string or sequence (NOT treated as glob)
- value (Any): Value to set at the path
- separator (str): Path separator character (default "/")
- creator (Creator): Optional function to create missing path components
Returns:
MutableMapping: The modified input object
Note:
Path is treated literally - globbing characters become part of key names.
"""import dpath
data = {}
# Create nested structure automatically
dpath.new(data, "config/database/host", "localhost")
# Result: {'config': {'database': {'host': 'localhost'}}}
# Create with numeric keys (creates lists)
dpath.new(data, "items/0/name", "first item")
# Result: {'items': [{'name': 'first item'}]}
# Mix of dict and list creation
dpath.new(data, "users/0/contacts/email", "john@example.com")
# Result: {'users': [{'contacts': {'email': 'john@example.com'}}]}
# Custom creator function
def list_creator(current, segments, i, hints=()):
"""Always create lists instead of dicts"""
segment = segments[i]
current[segment] = []
dpath.new(data, "always_list/key", "value", creator=list_creator)
# Globbing characters become literal keys
dpath.new(data, "weird/*/key", "value") # Creates key literally named "*"Update values at paths that already exist, with support for glob patterns to update multiple locations simultaneously.
def set(obj, glob, value, separator="/", afilter=None):
"""
Set value for all existing elements matching glob.
Parameters:
- obj (MutableMapping): Target dictionary to modify
- glob (Glob): Path pattern as string or sequence
- value (Any): Value to set at matching paths
- separator (str): Path separator character (default "/")
- afilter (Filter): Optional function to filter which values to set
Returns:
int: Number of elements changed
Note:
Only modifies existing paths - does not create new ones.
"""import dpath
data = {
"users": {
"john": {"age": 30, "active": True},
"jane": {"age": 25, "active": False},
"bob": {"age": 35, "active": True}
}
}
# Set all user ages to same value
count = dpath.set(data, "users/*/age", 999) # Returns: 3
# Result: All users now have age 999
# Set with filter - only update active users
def active_filter(value):
return isinstance(value, dict) and value.get("active", False)
# First search for active users, then update their ages
count = dpath.set(data, "users/*", {"age": 100, "active": True},
afilter=lambda user: user.get("active", False))
# Set single exact path
count = dpath.set(data, "users/john/age", 31) # Returns: 1
# Use custom separator
count = dpath.set(data, "users.*.active", False, separator=".")
# Returns 0 if no existing paths match
count = dpath.set(data, "nonexistent/*/field", "value") # Returns: 0All path access functions support multiple path formats:
"users/john/age" - Standard slash-separated paths"users.john.age" - Custom separator paths"users/*/age" - Glob patterns with wildcards"users/**/age" - Recursive glob patterns["users", "john", "age"] - Explicit path segments (separator ignored)["users", "*", "age"] - Glob patterns in list format"items/0/name" - Automatically converted to int for sequences["items", 0, "name"] - Direct integer access["items", -1, "name"] - Supported for sequences# KeyError for missing paths without default
try:
value = dpath.get(data, "missing/path")
except KeyError as e:
print(f"Path not found: {e}")
# ValueError for ambiguous gets
try:
value = dpath.get(data, "users/*/age") # Multiple matches
except ValueError as e:
print(f"Ambiguous path: {e}")
# PathNotFound from dpath.exceptions
from dpath.exceptions import PathNotFound
try:
# Some operations may raise PathNotFound instead of KeyError
pass
except PathNotFound as e:
print(f"Path operation failed: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-dpath