CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-dpath

Filesystem-like pathing and searching for dictionaries

Pending
Overview
Eval results
Files

data-manipulation.mddocs/

Data Manipulation

Operations for modifying dictionary structures including selective deletion and sophisticated deep merging with configurable behavior for different data integration scenarios.

Capabilities

Deletion Operations

Remove elements from nested dictionaries using glob patterns with support for filtering and preservation of data structure integrity.

def delete(obj, glob, separator="/", afilter=None):
    """
    Delete all elements matching glob pattern.
    
    Parameters:
    - obj (MutableMapping): Target dictionary to modify
    - glob (Glob): Path pattern as string or sequence  
    - separator (str): Path separator character (default "/")
    - afilter (Filter): Optional function to filter which elements to delete
    
    Returns:
    int: Number of elements deleted
    
    Raises:
    - PathNotFound: If no matching paths found to delete
    """

Usage Examples

import dpath
from dpath.exceptions import PathNotFound

data = {
    "users": {
        "john": {"age": 30, "active": True, "temp_data": "delete_me"},
        "jane": {"age": 25, "active": False, "temp_data": "also_delete"},
        "bob": {"age": 35, "active": True}
    },
    "temp_settings": {"cache": True},
    "permanent_settings": {"theme": "dark"}
}

# Delete exact path
count = dpath.delete(data, "users/john/temp_data")  # Returns: 1
# Removes temp_data from john's record

# Delete with wildcards - removes matching fields from all users
count = dpath.delete(data, "users/*/temp_data")  # Returns: 2 (john and jane)

# Delete entire user records
count = dpath.delete(data, "users/jane")  # Returns: 1
# Removes jane's entire record

# Delete with filter - only delete inactive users
def inactive_filter(user):
    return isinstance(user, dict) and not user.get("active", True)

count = dpath.delete(data, "users/*", afilter=inactive_filter)
# Only deletes users where active=False

# Delete all temporary data across entire structure
count = dpath.delete(data, "**/temp_*")  # Matches any key starting with "temp_"

# Handle deletion errors
try:
    dpath.delete(data, "nonexistent/path")
except PathNotFound as e:
    print(f"Nothing to delete: {e}")

# Delete list elements
list_data = {"items": ["a", "b", "c", "d"]}
count = dpath.delete(list_data, "items/1")  # Removes "b"
# Note: List behavior depends on position - middle elements become None

Merge Operations

Deeply merge dictionaries with sophisticated control over how conflicts are resolved and data is combined.

def merge(dst, src, separator="/", afilter=None, flags=MergeType.ADDITIVE):
    """
    Deep merge source into destination dictionary.
    
    Parameters:
    - dst (MutableMapping): Destination dictionary (modified in place)
    - src (MutableMapping): Source dictionary to merge from
    - separator (str): Path separator for filtered merging (default "/")
    - afilter (Filter): Optional function to filter which parts of src to merge
    - flags (MergeType): Merge behavior flags (default MergeType.ADDITIVE)
    
    Returns:
    MutableMapping: The modified destination dictionary
    
    Note:
    Creates references, not deep copies. Source objects may be modified 
    by subsequent operations on the destination.
    """

Merge Behavior Flags

from dpath.types import MergeType

class MergeType(IntFlag):
    ADDITIVE = auto()    # Combine lists by concatenation (default)
    REPLACE = auto()     # Replace destination lists with source lists  
    TYPESAFE = auto()    # Raise TypeError when merging incompatible types

Usage Examples

import dpath
from dpath.types import MergeType

# Basic merge - combines dictionaries recursively
dst = {
    "users": {"john": {"age": 30}},
    "settings": {"theme": "light"}
}

src = {
    "users": {"jane": {"age": 25}, "john": {"city": "NYC"}},  
    "settings": {"lang": "en"}
}

dpath.merge(dst, src)
# Result: {
#   "users": {"john": {"age": 30, "city": "NYC"}, "jane": {"age": 25}},
#   "settings": {"theme": "light", "lang": "en"}  
# }

# List merging with ADDITIVE (default)
dst = {"tags": ["python", "data"]}
src = {"tags": ["analysis", "ml"]}
dpath.merge(dst, src)  # tags becomes ["python", "data", "analysis", "ml"]

# List merging with REPLACE
dst = {"tags": ["python", "data"]}
src = {"tags": ["analysis", "ml"]}
dpath.merge(dst, src, flags=MergeType.REPLACE)  # tags becomes ["analysis", "ml"]

# Type-safe merging
dst = {"count": 10}
src = {"count": "ten"}  # String instead of int

try:
    dpath.merge(dst, src, flags=MergeType.TYPESAFE)
except TypeError as e:
    print(f"Type mismatch: {e}")

# Filtered merging - only merge specific parts
def settings_filter(value):
    # Only merge settings, not user data
    return True  # Apply filter logic here

filtered_src = dpath.search(src, "settings/**")
dpath.merge(dst, filtered_src)

# Combined flags
dpath.merge(dst, src, flags=MergeType.REPLACE | MergeType.TYPESAFE)

Advanced Deletion Patterns

Smart List Deletion

# List deletion preserves order for end elements
data = {"items": ["a", "b", "c", "d", "e"]}

# Deleting last element truly removes it
dpath.delete(data, "items/-1")  # or "items/4" 
# Result: ["a", "b", "c", "d"]

# Deleting middle elements sets to None to preserve indices
dpath.delete(data, "items/1")
# Result: ["a", None, "c", "d", "e"]

# Delete multiple list elements
for i in reversed(range(1, 4)):  # Delete backwards to maintain indices
    dpath.delete(data, f"items/{i}")

Conditional Deletion

# Delete based on value properties
data = {
    "products": {
        "item1": {"price": 10, "discontinued": True},
        "item2": {"price": 25, "discontinued": False}, 
        "item3": {"price": 15, "discontinued": True}
    }
}

# Delete discontinued products
def discontinued_filter(product):
    return isinstance(product, dict) and product.get("discontinued", False)

count = dpath.delete(data, "products/*", afilter=discontinued_filter)
# Removes item1 and item3

# Delete based on value ranges
def expensive_filter(product):
    return isinstance(product, dict) and product.get("price", 0) > 20

count = dpath.delete(data, "products/*", afilter=expensive_filter)

Advanced Merge Scenarios

Merging with Deep Copy Prevention

import copy

# Problem: Merge creates references
dst = {"data": {"list": [1, 2]}}
src = {"data": {"list": [3, 4]}}

dpath.merge(dst, src)  # dst["data"]["list"] now references src's list
src["data"]["list"].append(5)  # This also affects dst!

# Solution: Deep copy source before merging
src_copy = copy.deepcopy(src)
dpath.merge(dst, src_copy)  # Now changes to src won't affect dst

Complex Merge Workflows

# Multi-source merging with different behaviors
base_config = {"database": {"host": "localhost"}}

# Merge environment-specific overrides
env_config = {"database": {"port": 5432}}
dpath.merge(base_config, env_config)

# Merge user preferences with replacement for UI settings
user_config = {"ui": {"theme": "dark", "panels": ["editor", "terminal"]}}
dpath.merge(base_config, user_config, flags=MergeType.REPLACE)

# Merge runtime settings type-safely
runtime_config = {"database": {"timeout": 30}}
dpath.merge(base_config, runtime_config, flags=MergeType.TYPESAFE)

Selective Merging

# Merge only specific branches
def merge_branch(dst, src, branch_pattern):
    """Merge only specific branches of source into destination"""
    branch_data = dpath.search(src, branch_pattern)
    dpath.merge(dst, branch_data)

# Usage
merge_branch(dst, src, "settings/**")  # Only merge settings branch
merge_branch(dst, src, "users/*/profile")  # Only merge user profiles

Error Handling and Edge Cases

from dpath.exceptions import PathNotFound, InvalidKeyName

# Handle empty string keys (requires option)
import dpath.options
dpath.options.ALLOW_EMPTY_STRING_KEYS = True

data = {"": "empty key value"}  # Now allowed

# Handle deletion of non-existent paths
try:
    dpath.delete(data, "path/that/does/not/exist")
except PathNotFound:
    print("Nothing to delete")

# Handle invalid key names
try:
    dpath.new(data, "path/with//empty/segment", "value")
except InvalidKeyName as e:
    print(f"Invalid key: {e}")

# Merge type conflicts
dst = {"value": [1, 2, 3]}
src = {"value": {"nested": "dict"}}

# This will replace the list with dict (default behavior)
dpath.merge(dst, src)  # dst["value"] becomes {"nested": "dict"}

Install with Tessl CLI

npx tessl i tessl/pypi-dpath

docs

advanced-operations.md

data-manipulation.md

index.md

path-access.md

search-operations.md

tile.json