Comparison and Validation (surety-diff)¶
The surety-diff extension provides structured comparison between expected
and actual data.
pip install surety-diff
Basic Comparison¶
compare checks actual data against expected and raises AssertionError
with a structured diff on mismatch:
from surety.diff import compare
compare(
actual={'item_id': 42, 'name': 'Widget', 'price': 19.99},
expected={'item_id': 42, 'name': 'Widget', 'price': 19.99}
)
# Passes silently
compare(
actual={'item_id': 42, 'name': 'Gadget', 'price': 19.99},
expected={'item_id': 42, 'name': 'Widget', 'price': 19.99}
)
# Raises AssertionError with detailed diff
Using Diff Directly¶
The Diff class can be used for inspection without raising:
from surety.diff import Diff
result = Diff(
expected={'item_id': 42, 'name': 'Widget'},
actual={'item_id': 42, 'name': 'Gadget'}
)
if result:
print("Differences found:", result)
Diff reports these change types:
TypeChanged— value type differsValueChanged— value differsDictItemAdded— unexpected key presentDictItemRemoved— expected key missingIterableItemAdded— extra element in listIterableItemRemoved— missing element in listRulesViolated— comparison rule returnedFalseRulesUnapplied— rule defined but not matched
Comparison Rules¶
Rules define custom matching logic for specific fields. A rule is a callable
that takes two arguments (expected, actual) and returns True if they match.
from surety.diff import compare, compare_rule
@compare_rule(name='within_tolerance')
def within_tolerance(expected, actual):
return abs(expected - actual) < 0.01
compare(
actual={'amount': 99.994},
expected={'amount': 99.99},
rules={'amount': within_tolerance}
)
Rules are passed as a dictionary mapping field names to rule functions:
compare(
actual=response_data,
expected=contract.value,
rules={
'order_id': has_some_value,
'created_at': timestamp_equal_with_delta_3s,
'total': within_tolerance
}
)
Built-in Rules¶
Import from surety.diff.rules:
Rule |
Description |
|---|---|
|
Both values are not |
|
Values are different (changed). |
|
Both values are valid UUID format. |
|
Lists contain the same elements regardless of order. |
|
Floats match to 4 decimal places. |
|
Timestamps within 3-second tolerance. |
|
Timestamps within 5-second tolerance. |
|
Timestamps within 10-second tolerance. |
|
Dates within 3-second tolerance. |
Example with built-in rules:
from surety.diff import compare
from surety.diff.rules import is_valid_uuid, timestamp_equal_with_delta_3s
compare(
actual={
'id': 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
'created_at': '2025-03-15T10:30:01Z'
},
expected={
'id': '11111111-2222-3333-4444-555555555555',
'created_at': '2025-03-15T10:30:03Z'
},
rules={
'id': is_valid_uuid,
'created_at': timestamp_equal_with_delta_3s
}
)
Forbidding Unapplied Rules¶
By default, compare raises an error if a rule is defined for a field that
doesn’t exist in the data (forbid_unapplied_rules=True). Set it to
False to allow unused rules:
compare(
actual={'name': 'Widget'},
expected={'name': 'Widget'},
rules={'missing_field': has_some_value},
forbid_unapplied_rules=False
)