Structural drift detection for nested JSON / dict data. Learn the shape of your payloads, freeze it as a contract, catch breaking changes before they hit your code.
Project description
contractguard
Structural drift detection for nested JSON / dict data.
Learn the shape of your nested payloads from real samples, freeze it as a contract, and catch breaking structural changes before they silently break your code.
import contractguard as cg
# 1. Learn the shape from known-good samples
contract = cg.learn([sample_a, sample_b, sample_c])
contract.save("api_contract.json")
# 2. Later: check new payloads against the frozen contract
report = contract.check(new_payload)
if report.drifted:
print(report)
3 change(s) detected:
- TypeChanged: items[0].price (float -> str)
- TypeChanged: user.age (int -> str)
- NewKey: user.phone (unexpected str)
Why this exists
APIs and config files break silently. A backend renames a field, flips an
int to a str, drops a key, or turns a list into an object — your code
doesn't crash immediately, but something downstream quietly goes wrong, and
you lose an afternoon finding it.
contractguard learns the structure of your data from real examples and
tells you, in plain language, exactly what changed and where.
How it's different
| Tool | What it does | What contractguard does |
|---|---|---|
| genson | Infers a JSON schema from data | Infers it and enforces it over time |
| data-drift-detector | Statistical drift on flat dataframes | Structural drift on nested JSON |
| pydantic / jsonschema | You hand-write the schema | It learns the schema from real data |
The key gap it fills: every statistical drift tool assumes flat rows and
columns. contractguard walks arbitrarily nested dicts and lists, so it works
on real API payloads, event streams, and config files.
Features
- Zero dependencies — pure standard library.
- Nested-aware — reports dotted paths like
user.address.zip. - Lenient by default — fields missing from some learning samples are treated as optional, so you don't get false alarms.
- Nullable-aware — if
nullwas seen during learning,nullis allowed. - No cascade noise — a
list -> dictchange reports one root cause, not a flood of child errors. - Saveable contracts — freeze a contract to JSON, commit it, check against it in CI.
Install
pip install contractguard
What it detects
TypeChanged— a value's type changed (int -> str)KeyMissing— a required key disappearedNewKey— a key appeared that wasn't in the learned shapeCardinalityChanged— a list element's type changed, or a container's kind changed (list -> dict)
Command-line usage
contractguard ships a CLI, so you can use it without writing any Python:
# Learn a contract from sample payloads
contractguard learn sample1.json sample2.json -o contract.json
# Check a new payload against it
contractguard check payload.json --against contract.json
check exits with status 1 when drift is found and 0 when clean, so it
drops straight into CI pipelines:
contractguard check response.json --against contract.json || echo "API changed!"
Add --strict to learn to mark every observed field as required.
pytest integration
Guard against API shape changes inside your own test suite:
from contractguard import assert_no_drift
def test_user_endpoint_shape(client):
response = client.get("/api/user/1").json()
assert_no_drift("contracts/user.json", response)
If the response structure drifts, the test fails with a readable breakdown of
exactly what changed. The contract argument accepts a saved-contract path, a
Contract instance, or a list of sample payloads to learn from on the fly.
Strict vs lenient
By default, learning is lenient: a field missing from some samples is
treated as optional and won't trigger drift later. Pass strict=True (or
--strict on the CLI) to require every field that was ever observed:
contract = cg.learn(samples, strict=True)
Roadmap
- HTML / JSON report output
- GitHub Action for CI
- Configurable type coercion (e.g. treat
intandfloatas compatible)
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file contractguard-0.2.2.tar.gz.
File metadata
- Download URL: contractguard-0.2.2.tar.gz
- Upload date:
- Size: 11.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d8192ee699feb366ed1e81f9a7d9fb64f2dcaa6b82411a9abe86765b60083531
|
|
| MD5 |
a05c472a3ad1a7e2d436b4a34c6cc864
|
|
| BLAKE2b-256 |
7da608ff923c40c3be65a1c7af96b65f7d2116454e8d16bd582251c3f79d925f
|
File details
Details for the file contractguard-0.2.2-py3-none-any.whl.
File metadata
- Download URL: contractguard-0.2.2-py3-none-any.whl
- Upload date:
- Size: 12.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5bec44dd621eabd1a1e512a5effedcec33c1ac99af3088f996e9495d271386f0
|
|
| MD5 |
3ad11139a6f7aa416227217a41def67a
|
|
| BLAKE2b-256 |
e4b3e806af48f87f4811bf88328c5f5ab026e5e822b394407453131ca43fdeb4
|