Processing Utilities

The surety.process module provides utility functions for working with dictionaries. These are part of the core surety package.

Merging Dictionaries

merge_with_updates creates a deep copy of a dictionary with updates applied:

from surety.process.dictionary import merge_with_updates

base = {'db': {'host': 'localhost', 'port': 5432}, 'debug': False}
overrides = {'db': {'port': 5433}, 'debug': True}

result = merge_with_updates(base, overrides)
# {'db': {'host': 'localhost', 'port': 5433}, 'debug': True}

Use extend_only=True to prevent overwriting existing values. Raises ValueError on conflict:

merge_with_updates(
    {'db': {'host': 'localhost'}},
    {'db': {'port': 5432}},
    extend_only=True
)
# {'db': {'host': 'localhost', 'port': 5432}}

Use merge_lists=True to merge list elements instead of replacing:

merge_with_updates(
    {'tags': [{'id': 1, 'name': 'alpha'}]},
    {'tags': [{'name': 'beta'}]},
    merge_lists=True
)
# {'tags': [{'id': 1, 'name': 'beta'}]}

Pattern Matching

matches_pattern checks if a dictionary matches a value/filter pattern. Pattern values can be exact values or callable predicates:

from surety.process.dictionary import matches_pattern, dict_predicate

@dict_predicate(name='positive')
def is_positive(value):
    return value > 0

matches_pattern(
    {'quantity': 5, 'name': 'Widget'},
    {'quantity': is_positive}
)
# True

matches_pattern(
    {'quantity': -1, 'name': 'Widget'},
    {'quantity': is_positive}
)
# False

Extra keys in the initial dictionary are allowed. Extra keys in the pattern cause the match to fail:

matches_pattern({1: 1, 2: 2}, {2: 2})    # True — extra key 1 is fine
matches_pattern({1: 1}, {1: 1, 2: 2})    # False — key 2 not in initial

Applying Modifiers

apply_modifier transforms dictionary values using callables:

from surety.process.dictionary import apply_modifier

data = {'price': 19, 'tags': [1, 2, 3]}

# Convert price to float
apply_modifier(data, {'price': float})
# {'price': 19.0, 'tags': [1, 2, 3]}

# Apply modifier to all list elements
apply_modifier(data, {'tags': [float]})
# {'price': 19, 'tags': [1.0, 2.0, 3.0]}

# Use a callable for full transformation
apply_modifier(data, lambda d: {**d, 'currency': 'USD'})
# {'price': 19, 'tags': [1, 2, 3], 'currency': 'USD'}

Normalizing Order

normalize reorders lists in actual to match the order in expected, using id as the default key for matching dict elements:

from surety.process.dictionary import normalize

actual = {
    'items': [
        {'id': 'b', 'val': 2},
        {'id': 'a', 'val': 1}
    ]
}
expected = {
    'items': [
        {'id': 'a'},
        {'id': 'b'}
    ]
}

normalize(actual, expected)
# {'items': [{'id': 'a', 'val': 1}, {'id': 'b', 'val': 2}]}

Use the keys parameter when elements don’t have an id field:

normalize(
    actual=[{'name': 'beta'}, {'name': 'alpha'}],
    expected=[{'name': 'alpha'}, {'name': 'beta'}],
    keys=['name']
)
# [{'name': 'alpha'}, {'name': 'beta'}]

Filtering Dictionaries

filter_dict removes entries matching a value or predicate:

from surety.process.common import filter_dict

# Filter by value — removes entries equal to the value
filter_dict(0, {'a': 0, 'b': 42, 'c': 0})
# {'b': 42}

# Filter by predicate — keeps entries where predicate returns True
filter_dict(lambda x: x > 10, {'a': 5, 'b': 42, 'c': 3})
# {'b': 42}

Works recursively on nested dictionaries:

filter_dict(0, {'a': {'c': 0, 'd': 42}, 'b': 0})
# {'a': {'d': 42}}

Excluding None Values

exclude_none_from_kwargs removes None entries from a dictionary:

from surety.process.common import exclude_none_from_kwargs

exclude_none_from_kwargs({'a': None, 'b': 1234, 'c': None})
# {'b': 1234}