Skip to main content

data-contract-registry

CI Python License: MIT

In-memory reference registry for data contracts. Semver version history, compatibility reports (backward / forward / full), declared owners, and freshness SLAs. It does not authenticate callers, approve contracts, persist state across restarts, or prove that downstream readers accept every row.

The headline endpoint is POST /contracts — register a new version, get back a deterministic compatibility report or a 422 with every breaking change called out by field name and kind.


Why

The thing that gets data teams paged at 2am isn't a missing test. It's a producer who quietly removed ltv because "we never use it anymore" while three downstream dashboards still join on it. Schema registries (Confluent, Buf, etc.) solved this for streaming and gRPC; data pipelines need the same hardness in a shape that fits the things data teams actually argue about:

  • owners — who do I page when this dataset goes stale
  • freshness SLA — when does "stale" become "broken"
  • primary key — changes are flagged regardless of the version bump
  • enum drift — narrowing breaks backward checks; widening breaks forward checks
  • deprecation policy — flag a version with the URI of the migration plan; don't delete it

This package is the smallest thing that does all of those.


Install

pip install data-contract-registry
# with the FastAPI surface:
pip install "data-contract-registry[api]"

Python 3.11+. Runtime deps: pydantic, PyYAML, and httpx for the optional audit sink.


Library quickstart

from data_contract_registry import (
    ContractRegistry,
    DataContract,
    DataField,
    Owner,
)

registry = ContractRegistry()

v1 = DataContract(
    dataset_id="users.daily_active",
    version="1.0.0",
    primary_key=["user_id", "active_date"],
    owners=[Owner(team="growth-platform", contact="#growth-platform")],
    fields=[
        DataField(name="user_id",     type="string"),
        DataField(name="active_date", type="timestamp"),
        DataField(name="plan",        type="string", enum=["free", "pro", "enterprise"]),
        DataField(name="ltv",         type="number", required=False),
    ],
    status="active",
)
registry.register(v1)

# Compatible promotion (added an optional field).
v1_1 = v1.model_copy(update={
    "version": "1.1.0",
    "fields": [*v1.fields, DataField(name="signup_source", type="string", required=False)],
})
report = registry.register(v1_1)
print(report.compatible)   # True

# Incompatible promotion — removing a field breaks backward compatibility.
v2 = v1.model_copy(update={"version": "2.0.0", "fields": [f for f in v1.fields if f.name != "ltv"]})
report = registry.register(v2)
print(report.compatible)               # False
print(report.errors[0].kind)           # "field_removed"
print(report.errors[0].message)        # "field 'ltv' was removed; old data will fail validation"

Compatibility modes

Mode Meaning
backward Checks whether a new schema may reject previous rows. Default. Consumers upgrade first.
forward Checks whether a previous schema may reject new rows. Producers upgrade first.
full Both.
none Skip field checks; version and primary-key rules still apply. Use only with a reviewed migration.

These are conservative schema-promotion rules, not full row validation or a substitute for a consumer test. Optional new fields are permitted by policy; a strict parser may still reject them. Compatibility is checked against the latest registered version, even if it is archived, so versions cannot move backward.

The checks the engine knows how to flag (each carries a structured kind so you can build CI gates around specific failures):

Kind Severity Mode
field_removed error backward
field_type_changed error backward / forward
field_required_added error backward (optional→required or new required field) / forward (new required field)
field_required_removed error forward (required→optional or removed)
field_enum_shrunk error backward (including introducing an enum)
field_enum_expanded error forward (including removing an enum)
primary_key_changed error always
version_not_increasing error always
owner_missing error always

FastAPI surface

pip install "data-contract-registry[api]"
uvicorn data_contract_registry.app:app --host 127.0.0.1 --port 8090
Method Path What it does
GET / Service info.
GET /healthz Liveness probe.
GET /datasets List registered dataset IDs.
POST /contracts Register / promote a contract. 422 with a structured issue list when incompatible.
POST /contracts/check Dry-run compatibility check — does not register.
GET /contracts/{ds}/latest Latest active contract for a dataset.
GET /contracts/{ds}/versions Full version history.
GET /contracts/{ds}/versions/{v} One specific version.
POST /contracts/{ds}/versions/{v}/deprecate Mark deprecated with a migration URI.
POST /contracts/{ds}/versions/{v}/archive Archive a version (history preserved).
POST /contracts/owners/from-decision-card Cross-ecosystem hook — suggest owner records from Decision Card fields.

Contracts are process-local memory only. Restarts erase them; multiple workers do not share state. No caller authentication, authorization, tenant isolation, approval workflow, rate limiting, or durable audit trail is provided. Keep this reference server on loopback with synthetic or public data. A customer-facing deployment needs an external trust boundary and durable store.

AUDIT_STREAM_URL optionally sends best-effort event summaries to the private sink base URL or its exact /events endpoint. The URL requires HTTPS except for numeric loopback HTTP addresses. When set, configure AUDIT_STREAM_TOKEN with the sink's separate bearer credential (at least 32 visible ASCII characters) from a secret store. Invalid configuration prevents outbound delivery and logs a failure; HTTP errors are logged without the URL or token. Failures do not block writes, and events are not retried or durably queued, so this is not a durable audit record. The event includes dataset ID, version and issue kinds, but omits owner contacts and field-level compatibility messages.


The cross-ecosystem hook

The bridge maps buyer.name and optional decision_maker.role/name to candidate owner records. It does not validate the Decision Card, decision status, signatures, buyer identity, or on-call authority. A data steward must confirm the owners and contact before registration. Even a pending, rejected, or withdrawn card can produce the same candidates; the mapping is not an approval gate.

curl -X POST http://localhost:8090/contracts/owners/from-decision-card \
  -H 'Content-Type: application/json' \
  -d @decision-card.json
# -> [
#   {"team": "Springfield USD",                            "contact": "#data-platform"},
#   {"team": "Director of Data (Alex Chen)",               "contact": null}
# ]

Review and correct that list before putting it into DataContract.owners. decision_maker.authority describes approval authority and is not treated as a paging contact.


YAML authoring

# contracts/users-daily-active.yaml
dataset_id: users.daily_active
version: "1.0.0"
owners:
  - team: growth-platform
    contact: "#growth-platform"
freshness_sla:
  max_lag_seconds: 86400
fields:
  - {name: user_id,      type: string}
  - {name: active_date,  type: timestamp}
  - {name: plan,         type: string, enum: [free, pro, enterprise]}

Hand-author in YAML, validate in CI, register from Python. The matching serialized JSON fixture is an exported DataContract that csv-data-quality-rs can consume. The separately reviewed sql-contract-enforcer adapter, when present in the selected SQL package version, maps a strict subset of this v0.2 JSON shape into a proposal for its different SQL model. The adapter is not part of this package, rejects unsupported semantics, and has not executed DDL against a target engine. A number enum fixture demonstrates finite fractional values in the registry shape:

import yaml
from pathlib import Path
from data_contract_registry import ContractRegistry, DataContract

raw = yaml.safe_load(Path("contracts/users-daily-active.yaml").read_text())
ContractRegistry().register(DataContract.model_validate(raw))

Tests

pip install -e ".[dev]"
ruff check src tests scripts && ruff format --check src tests scripts
mypy src
pytest -v

CI matrix runs Python 3.11 / 3.12 / 3.13.



License

MIT. See LICENSE.

Metadata

Release files for data-contract-registry 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for data-contract-registry 0.2.0
File Size Uploaded
data_contract_registry-0.2.0.tar.gz 24.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for data-contract-registry 0.2.0
File Interpreter ABI Platform
data_contract_registry-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 44.1 kB

Release files / data_contract_registry-0.2.0.tar.gz

Download URL data_contract_registry-0.2.0.tar.gz
Size 24.5 kB
Tags Source
SHA-256 checksum
How to use checksums
ac8c65a7174236b4097919dfa1783756d6934917eec675e21b3de2f9111d366b
BLAKE2b-256 checksum
How to use checksums
180355c2ec8a67ed7beddccb20d5f82c6fc50ebbeaf1eaf769fc6ced1bec3f0b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / data_contract_registry-0.2.0-py3-none-any.whl

Download URL data_contract_registry-0.2.0-py3-none-any.whl
Size 19.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c3f01f69f9dd24e17ec4a146790e33c1552dd7d798ebdfe3e0065c2702dfe75f
BLAKE2b-256 checksum
How to use checksums
5a225dc51bfc96d2791bead7766fd825a9fe3c34902b0cd29ec4dfe612d5be18
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page