ContractModel
Python-native data contracts — import ODCS (Open Data Contract Standard), validate with Pydantic, diff versions for breaking changes, and export to JSON Schema, OpenAPI, and more.
pip install contractmodel · CLI: contract · typed (PEP 561)
At a glance
from contractmodel import DataContract, ValidationMode
from contractmodel.examples import example_path, load_example
contract = load_example("customer_events.odcs.yaml")
CustomerEvent = contract.to_pydantic()
result = contract.validate_record(
{
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"customer_id": "C123",
"event_timestamp": "2026-06-23T12:00:00",
"event_type": "created",
},
mode=ValidationMode.STRICT,
)
if result:
print("valid")
else:
result.raise_for_errors()
Works immediately after pip install — examples ship inside the package.
Who this is for
| Audience | Use case |
|---|---|
| Data platform engineers | ODCS YAML contracts that drive runtime validation in Python pipelines |
| API and analytics teams | Pydantic models generated from a shared contract — not hand-maintained duplicates |
| Governance-minded teams | Breaking-change detection when contracts evolve across services or datasets |
Why ContractModel?
| Approach | Strength | Gap |
|---|---|---|
| Pydantic alone | Fast record validation | No ODCS import, contract diffing, or governance format |
| Great Expectations / Soda | Data quality suites | Not contract-first; weak ODCS/CCM round-trip |
| Raw ODCS tooling | Standard YAML contracts | No unified validation, diff, and Pydantic generation |
| ContractModel | CCM hub: ODCS ↔ Pydantic ↔ validation ↔ diff ↔ export | 0.1.x — see STABILITY.md for experimental areas |
0.1.x stability — Core
DataContractAPIs are stable; plugins, registry publish, and some quality rules are experimental. See STABILITY.md before adopting in production.
Installation
pip install contractmodel
Optional extras:
pip install "contractmodel[pandas]" # Pandas + CSV validation
pip install "contractmodel[polars]" # Polars validation
pip install "contractmodel[parquet]" # Parquet file validation
pip install "contractmodel[semantic]" # RDF / SHACL / OWL export
pip install "contractmodel[all]" # everything
Requires Python 3.10+. The contract CLI is included in the base install.
Quick start
Load a contract
Auto-detects CCM vs ODCS from file content. Use load() for extension-based loading, or the format-specific helpers:
from contractmodel import DataContract
from contractmodel.examples import example_path, load_example, list_examples
# Bundled examples (pip install)
contract = load_example("customer_events.odcs.yaml")
print(contract.name, contract.version, contract.schema.fields)
# Same contract, native CCM format
ccm_contract = DataContract.load(example_path("customer_events.ccm.yaml"))
# From a git clone — repository paths also work
repo_contract = DataContract.from_odcs("examples/customer_events.odcs.yaml")
print(list_examples()) # ['customer_events.ccm.yaml', 'customer_events.odcs.yaml', ...]
Validate data
Validate records, files, or in-memory payloads. Reuse the Pydantic model from to_pydantic() for application code; use validate_* for contract enforcement and structured CM_* error codes.
from contractmodel import DataContract, ValidationMode
from contractmodel.examples import example_path, load_example
contract = load_example("customer_events.odcs.yaml")
# Single record — see "At a glance" for a full payload
result = contract.validate_record({...}, mode=ValidationMode.STRICT)
# File — format inferred from extension; optional size limits
result = contract.validate(
example_path("data/customer_event.json"),
max_bytes=1_000_000,
max_rows=10_000,
)
# CSV (requires contractmodel[pandas])
result = contract.validate_csv(example_path("data/events.csv"), mode=ValidationMode.STRICT)
if not result:
for err in result.errors:
print(err.code, err.field, err.message)
result.raise_for_errors()
ValidationResult is truthy when validation succeeds (if result: / if not result:).
Diff contract versions
from contractmodel import CompatibilityMode, DataContract
old = DataContract.load("v1.yaml")
new = DataContract.load("v2.yaml")
diff = old.diff(new, mode=CompatibilityMode.BACKWARD)
if diff.is_breaking:
for change in diff.breaking_changes:
print(change.message)
# Or the boolean shortcut
assert not old.has_breaking_changes(new)
Renames linked by field aliases are diffed for definition changes; required-field removal is breaking in FORWARD mode.
Export and round-trip
contract.to_json_schema()
contract.to_openapi()
contract.to_markdown()
contract.to_odcs()
contract.save("out.ccm.yaml") # write CCM YAML
contract.to_yaml("out.ccm.yaml") # same, explicit
contract.to_shacl() # requires contractmodel[semantic]
Generate typed models (subclass ContractModel, cached per contract and mode):
CustomerEvent = contract.to_pydantic(mode=ValidationMode.STRICT)
# model fields match the contract schema
Reverse direction — build a contract from an existing Pydantic model:
contract = DataContract.from_pydantic(CustomerEvent, name="customer_events")
CLI
contract init contract.yaml
contract init myapp --template fastapi
contract validate contract.yaml data.json
contract validate contract.yaml data.json --output sarif # CI / GitHub Code Scanning
contract diff old.yaml new.yaml
contract generate pydantic contract.yaml --output models.py
contract export contract.yaml --to json-schema
contract export contract.yaml --to shacl
contract publish contract.yaml --registry https://registry.example.com
contract doctor # list installed plugins (names only)
From a git clone:
contract validate examples/customer_events.odcs.yaml examples/data/customer_event.json
contract validate examples/customer_events.odcs.yaml examples/data/events.csv --format csv
See the CLI walkthrough and CI with SARIF tutorials.
Features
| Area | Capabilities |
|---|---|
| Formats | ODCS import/export, native CCM YAML/JSON, Pydantic round-trip |
| Validation | STRICT, PERMISSIVE, SCHEMA_ONLY, QUALITY_ONLY modes |
| Data sources | Records, JSON, CSV, Parquet, Pandas, Polars (optional extras) |
| Diff | Field-level changes, breaking vs non-breaking, rename detection |
| Export | JSON Schema, OpenAPI, Markdown, ODCS, RDF, SHACL, OWL |
| Extensibility | Plugin SDK for validators, exporters, registries (experimental) |
| Tooling | contract CLI, bundled examples, CM_* error catalog for CI |
Validation modes
| Mode | Behavior |
|---|---|
STRICT |
Reject extra fields; full constraint validation |
PERMISSIVE |
Allow extra fields (including nested objects) |
SCHEMA_ONLY |
Structure and types only |
QUALITY_ONLY |
Run CCM quality rules (completeness; freshness is a stub warning) |
Glossary
| Term | Meaning |
|---|---|
| CCM | Canonical Contract Model — ContractModel's internal, format-agnostic representation |
| ODCS | Open Data Contract Standard — YAML contract format from Bitol |
| DataContract | Main Python facade — load, validate, diff, and export contracts |
contract |
CLI installed with the package |
Performance
Validation loads full datasets into memory. For 0.1.x, keep files under ~100 MB and ~1 million rows unless you benchmark larger workloads. Call to_pydantic() once per contract and reuse the model class.
Optional max_bytes and max_rows on validation entry points guard against oversized payloads. Non-positive limits raise ValueError.
Plugins (experimental)
Register plugins via pyproject.toml entry points. Installed plugins run after built-in validation and can extend export/publish when their target matches the requested format.
[project.entry-points."contractmodel.validators"]
my_validator = "my_package:MyValidator"
Run contract doctor to list plugin names (without loading plugin code). See STABILITY.md for API guarantees.
Security
See SECURITY.md for registry trust, plugin install guidance, and data file limits.
Documentation
Hosted docs: eddiethedean.github.io/contractmodel
| Resource | Link |
|---|---|
| Getting started | docs/tutorials/getting-started.md |
| CLI walkthrough | docs/tutorials/cli-walkthrough.md |
| Pydantic round-trip | docs/tutorials/pydantic-roundtrip.md |
| Diff in CI | docs/tutorials/diff-workflow.md |
| SARIF / GitHub Actions | docs/tutorials/ci-sarif.md |
| API reference | docs/reference/api.md |
Error codes (CM_*) |
docs/reference/error-codes.md |
| Examples | examples/README.md |
| Changelog | CHANGELOG.md |
| Architecture | docs/architecture/ |
| Format roadmap | docs/roadmap/03-data-contract-formats.md |
Architecture
All external representations flow through the CCM:
flowchart LR
ODCS["ODCS YAML/JSON"]
CCM["Canonical Contract Model"]
PYD["Pydantic models"]
VAL["Validation"]
DIF["Diff"]
EXP["Export"]
ODCS --> CCM
PYD <--> CCM
CCM --> VAL
CCM --> DIF
CCM --> EXP
The CCM is format-agnostic. Adapters handle conversion; engines operate only on the canonical model.
Development
See CONTRIBUTING.md for setup, pre-commit, and PR expectations.
git clone https://github.com/eddiethedean/contractmodel.git
cd contractmodel
pip install -e ".[all]" --group dev
pre-commit install
pytest
Build docs locally: mkdocs serve (see docs/README.md).
License
MIT — see LICENSE.
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 contractmodel-0.1.2.tar.gz.
File metadata
- Download URL: contractmodel-0.1.2.tar.gz
- Upload date:
- Size: 85.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb121644c61f3c122a787c6e64a259849e344d94691adcb4ae1bd7be04fcb28d
|
|
| MD5 |
9c1ab5cad5f00d1daa19e75f65cf0fb8
|
|
| BLAKE2b-256 |
69ccf2035240c3ebf09692685dc4f5ef7f0491d0ad16539267d8e596fc52e8bf
|
File details
Details for the file contractmodel-0.1.2-py3-none-any.whl.
File metadata
- Download URL: contractmodel-0.1.2-py3-none-any.whl
- Upload date:
- Size: 52.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd98fa8ce86c176e1afc83ad2c1bd98c5f16957a3b5a81d4eea7fbb9bc76575e
|
|
| MD5 |
9d40c4c9328bf7b2a8f686f4762f4fa4
|
|
| BLAKE2b-256 |
ff85a15e556429542040575c7a8af5d4819024383505b495ee85ddb0d41b76a8
|