datacontract-framework — Lightweight Data Contract Framework for automated data quality enforcement in lakehouse pipelines
Project description
datacontract-framework
A lightweight Python library for defining, enforcing, and monitoring data quality contracts on batch datasets. Built for data engineering pipelines — works standalone, in Airflow, or any Python environment.
Published on PyPI: pip install datacontract-framework
What it does
When a producer pipeline changes a dataset (renames a column, drops rows, adds bad values), downstream consumers break silently. datacontract-framework gives you:
- A contract file (YAML) that declares what the dataset must look like
- An enforcement engine that validates the dataset against the contract at pipeline runtime
- A registry that stores contract versions and validation history
- A CLI for manual validation and contract management
- A dashboard to monitor all contracts across your data platform
Features
Validation Rules
| Feature | What it checks |
|---|---|
| Schema — column existence | Every declared column is present in the dataset |
| Schema — column types | Column dtype matches declared type (string, integer, float, boolean, date, timestamp) |
| Schema — nullable | Non-nullable columns have zero null values |
| Schema — allowed values | Column contains only the declared set of allowed values |
| Schema — no extra columns | Dataset has no undeclared columns (optional, off by default) |
| Freshness | Dataset was updated within max_age_hours; uses file mtime or max(check_column) |
| Volume | Row count is within min_rows / max_rows bounds |
| Quality — null rate | Column null percentage does not exceed max_null_percentage |
| Quality — duplicate rate | Column duplicate percentage does not exceed max_duplicate_percentage |
| Quality — value range | Column values are within min_value / max_value bounds |
Dataset Formats
| Format | How |
|---|---|
| Parquet | Local path or S3 via pyarrow |
| SQL | Any SQLAlchemy-supported database (PostgreSQL, MySQL, SQLite) via table_name + connection_string |
Breach Modes
| Mode | Behaviour |
|---|---|
on_breach: warn |
Returns result with is_breach=True, pipeline continues |
on_breach: fail |
Raises DataContractBreachError, pipeline halts |
Contract Loading
| Method | When to use |
|---|---|
contract_path="contracts/sales.yaml" |
Dev machine, CI — file is local |
contract_name="daily_sales" + registry_url=... |
Airflow workers, remote runners — no local file needed |
Notifications
- Webhook — POST JSON breach payload to any URL (Slack, Teams, PagerDuty)
- Email — SMTP with configurable recipients; password stored in env var, never in YAML
Registry
- REST API (FastAPI) — publish contracts, fetch by name, list versions, store validation results
- PostgreSQL backend for production; SQLite for local dev
- Interactive API docs at
/docs
Observability Dashboard
- FastAPI + Jinja2 + Tailwind (CDN, no build step) — overview of all contracts, compliant vs breach counts, per-contract validation history, breach history with status filters, contract discovery/search
CLI
dcf check— parse and validate YAML syntax without touching data (CI-safe)dcf publish— register a contract versiondcf validate— run full validation, exit 1 on breach (CI-friendly)dcf list— list all current contracts in registrydcf history— show recent validation runs for a contract
Developer Experience
validate_dataframe(df, contract)— skip storage reads in unit tests, pass a DataFrame directly- Injectable
_http_clientand_registry_client— test the full SDK without a real server - Custom validator plugin API — extend with your own business rules
- Split dependencies —
pip install datacontract-framework(core only) keeps Airflow worker environments lean
Installation
pip install datacontract-framework # core — Airflow workers, pipelines
pip install "datacontract-framework[registry]" # + registry server
pip install "datacontract-framework[all]" # everything
Quick Start
1. Write a contract
# contracts/sales.yaml
apiVersion: datacontract/v1
kind: DataContract
metadata:
name: daily_sales
version: "1.0.0"
owner:
team: Data Engineering
email: data@example.com
dataset:
format: parquet
location: /data/sales/daily.parquet
on_breach: warn
schema:
columns:
- name: sale_id
type: string
nullable: false
- name: amount
type: float
nullable: false
- name: currency_code
type: string
allowed_values: [MYR, USD, SGD]
volume:
min_rows: 1000
quality:
- column: sale_id
max_null_percentage: 0.0
max_duplicate_percentage: 0.0
2. Validate
dcf validate --contract contracts/sales.yaml
# ✓ daily_sales v1.0.0: COMPLIANT
# On breach:
# ✗ daily_sales v1.0.0: BREACH
# Failed clauses:
# - [schema.allowed_values] [currency_code] Contains values not in allowed list: ['JPY']
Workflow
Step 1 — Check contract syntax
dcf check --contract contracts/sales.yaml
# OK daily_sales v1.0.0 — contract is valid
Step 2 — Start the registry
docker compose up -d
- Registry API:
http://localhost:8000 - Dashboard:
http://localhost:8501
Step 3 — Publish the contract
dcf publish --contract contracts/sales.yaml --registry-url http://localhost:8000
# Published daily_sales v1.0.0
Step 4 — Validate in your pipeline
From a local file (dev / CI):
from dcf import DataContractValidator, DataContractBreachError
result = DataContractValidator(
contract_path="contracts/sales.yaml",
registry_url="http://localhost:8000",
).validate()
print(result.overall_status) # COMPLIANT or BREACH
print(result.row_count) # 48203
From the registry by name (Airflow workers — no local file needed):
result = DataContractValidator(
contract_name="daily_sales",
registry_url="http://dcf-registry:8000",
).validate()
Step 5 — Use in Airflow
from airflow.sdk import dag, task
from dcf import DataContractValidator
import os
REGISTRY_URL = os.environ.get("DCF_REGISTRY_URL", "http://dcf-registry:8000")
@dag(schedule="@daily", ...)
def sales_pipeline():
@task
def extract_and_load() -> int:
# write dataset to /data/sales/daily.parquet
...
@task
def validate(row_count: int) -> str:
result = DataContractValidator(
contract_name="daily_sales", # fetched from registry — no local file
registry_url=REGISTRY_URL,
notifiers=[],
).validate()
if result.is_breach:
raise ValueError(f"Contract breach — pipeline halted")
return result.overall_status.value
@task
def transform(status: str) -> None:
... # only runs when validation passes
rows = extract_and_load()
status = validate(rows)
transform(status)
On breach: validate raises → Airflow marks it FAILED → transform is skipped — bad data never reaches downstream consumers.
Contract YAML Reference
apiVersion: datacontract/v1
kind: DataContract
metadata:
name: daily_sales # unique identifier
version: "1.0.0" # semantic version
owner:
team: Data Engineering
email: data@example.com
tags: [finance, daily]
dataset:
format: parquet # parquet | sql
location: /data/sales/daily.parquet
# SQL datasets:
# format: sql
# connection_string: postgresql://user:pass@host:5432/db
# table_name: daily_sales
on_breach: warn # warn | fail
schema:
enforce_no_extra_columns: false
columns:
- name: sale_id
type: string # string | integer | float | boolean | date | timestamp
nullable: false
allowed_values: [SALE, REFUND]
freshness:
max_age_hours: 25
check_column: sale_date # optional — uses max(column) instead of file mtime
volume:
min_rows: 1000
max_rows: 10000000
quality:
- column: sale_id
max_null_percentage: 0.0
max_duplicate_percentage: 0.0
- column: amount
min_value: 0.01
max_value: 9999999.0
notifications:
webhook:
url: https://hooks.slack.com/services/YOUR/WEBHOOK/URL
email:
smtp_host: smtp.example.com
smtp_port: 587
smtp_user: alerts@example.com
smtp_password_env: SMTP_PASSWORD
recipients:
- data-team@example.com
CLI Reference
dcf check --contract PATH
dcf publish --contract PATH --registry-url URL
dcf validate --contract PATH [--registry-url URL] [--output text|json]
dcf list --registry-url URL
dcf history --name NAME --registry-url URL [--limit N]
Python SDK Reference
DataContractValidator
from dcf import DataContractValidator, DataContractBreachError
# Option A — from local file
validator = DataContractValidator(
contract_path="contracts/sales.yaml",
registry_url="http://localhost:8000", # optional — enables breach history
extra_validators=[MyValidator()], # optional plugins
notifiers=[], # [] disables notifications
)
# Option B — from registry by name (Airflow / remote workers)
validator = DataContractValidator(
contract_name="daily_sales",
registry_url="http://localhost:8000",
)
result = validator.validate()
ValidationResult
result.overall_status # OverallStatus.COMPLIANT | BREACH | ERROR
result.is_breach # bool
result.row_count # int
result.failed_clauses # List[ClauseResult]
for c in result.failed_clauses:
print(c.clause_type) # e.g. "schema.allowed_values"
print(c.clause_target) # column name
print(c.message) # human-readable explanation
validate_dataframe() — for unit testing
from dcf.engine import validate_dataframe
import pandas as pd
df = pd.DataFrame({"sale_id": ["A", "B"], "amount": [10.0, 20.0]})
result = validate_dataframe(df, contract)
Custom validator plugin
from dcf.validators.base import Validator
from dcf.models.result import ClauseResult, ClauseStatus
class MyValidator(Validator):
def validate(self, df, contract, reader_last_modified):
ok = df["amount"].sum() > 0
return [ClauseResult(
clause_type="custom.positive_total",
clause_target="amount",
status=ClauseStatus.PASS if ok else ClauseStatus.FAIL,
expected="> 0",
observed=str(df["amount"].sum()),
message="" if ok else "Total amount must be positive",
)]
DataContractValidator(
contract_path="contracts/sales.yaml",
extra_validators=[MyValidator()],
).validate()
Development Setup
git clone <repo-url>
cd dcf
# Install with all extras + dev tools
pip install uv
uv sync
# Run tests
uv run pytest
# Unit tests only (no Docker)
uv run pytest tests/unit/ -v
# Integration tests (SQLite, no Docker)
uv run pytest tests/integration/ -v
# Start registry locally
uv run uvicorn registry.main:app --reload --port 8000
# Start dashboard
uv run uvicorn dashboard.main:app --reload --port 8501
# Start everything (registry + dashboard + postgres)
docker compose up -d
Project structure
dcf/
├── dcf/ # Core package — install this on Airflow workers
│ ├── models/ # Contract and result Pydantic models
│ ├── readers/ # ParquetReader, SQLReader
│ ├── validators/ # Schema, Freshness, Volume, Quality validators
│ ├── notifiers/ # Webhook, Email notifiers
│ ├── engine.py # Orchestrates readers + validators
│ ├── sdk.py # DataContractValidator — main public API
│ ├── cli.py # dcf CLI
│ └── registry_client.py # HTTP client for the registry
├── registry/ # FastAPI registry service
├── dashboard/ # FastAPI + Jinja2 + Tailwind observability dashboard
├── lab/ # End-to-end Docker test lab
├── tests/
│ ├── unit/ # Validator unit tests
│ ├── integration/ # Engine + registry API tests
│ └── fixtures/ # Sample contract YAML files
├── contracts/ # Example contracts
├── docker-compose.yml
└── pyproject.toml
datacontract-framework v1.1.0 — Faizal Azman — MMU MCS Software Engineering — 2026
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 datacontract_framework-1.1.0.tar.gz.
File metadata
- Download URL: datacontract_framework-1.1.0.tar.gz
- Upload date:
- Size: 30.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
557b4c34be3dd1b8c20887fa40cf0578b4b90a5af0be40b85381820b1add1a50
|
|
| MD5 |
a6de228b1fd25b08c8561c4d349bcca8
|
|
| BLAKE2b-256 |
517eb2188e98c186cd11cda71b5553238de89732347b00abe68a861fd587dccd
|
File details
Details for the file datacontract_framework-1.1.0-py3-none-any.whl.
File metadata
- Download URL: datacontract_framework-1.1.0-py3-none-any.whl
- Upload date:
- Size: 36.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
59f07ed3601bb378cecf5773b37bc5d3ae78923e5316148d092cf1a816bd2e60
|
|
| MD5 |
324ff6f7533466a828980e4023ba3ed8
|
|
| BLAKE2b-256 |
8af65ce50748d04a08275280c57f6dee44d741fe3f14fc4ce6146bc0d9826507
|