Skip to main content

schemapact

Versioned data contracts that catch breaking schema changes before they reach production.

CI Docs Python License


The problem

Every data pipeline depends on assumptions that live nowhere in particular. Which columns exist. What types they are. What ranges are plausible. Which categories are possible.

Those assumptions are real, load-bearing and invisible. When one changes — a column renamed upstream, a dtype quietly shifting from int64 to object after a join, a new category appearing in production — nothing fails loudly. The pipeline runs. The model returns numbers. The numbers are wrong, and you find out from a dashboard a week later.

This is worst in machine learning, where a model will happily produce confident predictions from data that no longer means what it did during training.

The approach

Write the assumptions down in a form a machine can check.

from schemapact import Contract, DType, Feature

contract = Contract(
    name="customer_features",
    version="1.0.0",
    features=[
        Feature("customer_id", DType.INTEGER, nullable=False, unique=True),
        Feature("age", DType.INTEGER, nullable=False, min=18, max=120),
        Feature("country", DType.CATEGORICAL, allowed_values=["IN", "US", "UK"]),
    ],
)

report = contract.validate(dataframe)
if not report.is_valid:
    print(report.summary())
customer_features v1.0.0 against DataFrame (12043 rows x 3 columns)
FAILED: 2 error(s), 0 warning(s)

  ERROR   SPX203 age: Column 'age' has 3 value(s) below the declared min of 18;
          furthest is 12.
          e.g. row 41=12, row 88=15, row 203=17
          fix: Clip or filter the offending rows, or relax min if the data is
               legitimately wider than the contract assumed.
  ERROR   SPX205 country: Column 'country' has 1 value(s) outside the allowed
          set. Unexpected: FR.

What makes it different

Plenty of libraries validate a dataframe. The distinguishing question here is not "is this data valid?" but "will this change break the people downstream?"

A pact, not a schema: an agreement between the system producing data and the systems consuming it, versioned like any other interface and checked in both directions.

schemapact check-compatibility contracts/v1.yaml contracts/v2.yaml --mode backward

Contracts are versioned and diffable. Comparing two versions tells you what changed, whether it is breaking, and for whom — because adding a required field breaks every producer and no consumer, while removing one does the reverse.

Change backward forward
Required field added breaks safe
Required field removed safe breaks
Constraint tightened breaks safe
Constraint relaxed safe breaks

A single boolean cannot express that, which is why compatibility is always asked in a direction.

Installation

pip install schemapact

No required dependencies. Contracts are defined, validated, serialized to JSON and diffed using the standard library alone.

Extra Command Adds
YAML pip install "schemapact[yaml]" YAML contracts
pandas pip install "schemapact[pandas]" DataFrame validation
Both pip install "schemapact[all]"

Python 3.10+. Tested on 3.10–3.13, on Linux, macOS and Windows.

Quickstart

Read a contract off data you already have:

schemapact init --from-csv customers.csv --output contract.yaml
schemapact validate contract.yaml customers.csv

Inference is deliberately cautious. Types generalise from a sample; observed minima and maxima do not, so ranges are omitted unless you ask for them with --infer-ranges. Review what it produces and tighten it where you know more than the sample does.

Validating

validate() accepts a list of dictionaries, a path to a .csv or .tsv file, or a pandas DataFrame.

report = contract.validate(data)

report.is_valid
report.errors  # violations that invalidate the data
report.warnings  # notable, but not invalidating
report.codes()  # ('SPX203', 'SPX205') — for alerting
report.for_feature("age")
report.summary()  # human-readable
report.to_json()  # machine-readable
report.raise_for_status()  # opt into fail-fast

Validation returns a report rather than raising. A real dataset usually has several problems at once, and raising on the first turns a single CI run into a queue of them.

Offending values make a report far more actionable, and they are raw data. Pass sample_values=False, or --no-samples on the CLI, when reports are written somewhere that should not hold it.

Contracts

spec_version: '1'          # the file format's version, owned by the library
name: customer_features    # stable across versions
version: 1.2.0             # your version, under semantic versioning

enforce_column_order: false
allow_extra_columns: true
min_rows: 1

features:
  customer_id:
    type: integer
    nullable: false
    unique: true
  country:
    type: categorical
    allowed_values: [IN, US, UK]
    description: ISO country code.

Types: integer, float, boolean, string, categorical, datetime, date. Common aliases (int64, str, category, timestamp) are accepted when reading a file.

Constraints: nullable, required, min, max, allowed_values, pattern, unique, max_null_fraction. A constraint that cannot apply to a type is rejected when the contract is built — a regex on a float is a mistake worth hearing about immediately.

Unknown keys are rejected with a suggestion rather than ignored. Silently dropping a misspelled nullabe would leave a contract that looks stricter than it is, which is the exact failure this library exists to prevent.

Command line

schemapact validate CONTRACT DATA
schemapact diff OLD NEW
schemapact check-compatibility OLD NEW --mode backward|forward|full
schemapact init [--from-csv PATH]
schemapact version
Exit code Meaning
0 Success
1 The data violated the contract
2 The change is not compatible in the direction requested
3 Usage error
4 The contract itself is invalid

1 and 2 are deliberately distinct — "this dataset is bad" and "this schema change breaks your consumers" call for different pipeline responses.

--format json is available on validate, diff and check-compatibility. Errors go to standard error, so standard output stays parseable when a command fails.

In CI

- run: pip install "schemapact[yaml,pandas]"

- name: Validate data
  run: schemapact validate contracts/input.yaml data/sample.csv

- name: Contract compatibility
  run: |
    git show origin/main:contracts/input.yaml > /tmp/base.yaml
    schemapact check-compatibility /tmp/base.yaml contracts/input.yaml --mode backward

An incompatible schema change becomes a red build instead of an incident.

Design commitments

  • Dependency-free core. Four of CI's ten test jobs install no extras at all and fail if pandas or PyYAML becomes importable. The promise is verified, not asserted.
  • One contract, many encodings. JSON and YAML are encodings of a single representation, so the same contract in either produces an identical object. Property tests prove it for generated contracts, not just examples.
  • Reports, not exceptions, by default.
  • Renames are declared, never guessed. A compatibility tool that is confidently wrong is worse than one that admits it does not know.
  • Error codes are permanent. Never reused, renumbered or repurposed, because people grep logs and configure alerts on them.
  • The validation engine imports no data library. Every backend is an adapter behind a six-method protocol, which is why adding one is a small file rather than a refactor.

Performance

Same checks, same data, same machine, 100,000 rows:

Approach Median Peak memory
Hand-written pandas assertions 1.6 ms 0.5 MB
schemapact, pandas adapter 7.9 ms 0.5 MB
pandera, lazy validation 10.3 ms 3.6 MB

Around 13 million rows/second. Hand-written assertions are faster and always will be — they do nothing but compute booleans, while schemapact returns structured violations with error codes, affected-row counts, sampled values and remediation text.

Contract operations are effectively free: a diff is 0.04 ms, a full compatibility check 0.09 ms.

Every figure comes from python benchmarks/run_all.py; see the performance docs for methodology, caveats and full results.

Comparison with alternatives

schemapact pandera Great Expectations
Dataframe validation Yes Yes Yes
Contract versioning Yes No No
Breaking-change detection Yes No No
Directional compatibility Yes No No
Dependency-free core Yes No No
Works without pandas Yes No Partial
Built-in check library Modest Extensive Extensive
Profiling and data docs No Partial Yes

If you need a large library of statistical checks, use pandera or Great Expectations. If you need to know whether a schema change will break the systems downstream, that is what this is for. They compose: nothing stops you running pandera in the hot path and schemapact in CI.

Documentation

Examples

Every example in examples/ is executed by CI with assertions on its output, so none can quietly stop being true.

Example Shows
01_quickstart.py Validating a list of dictionaries, no dependencies
02_csv_and_inference.py Inferring a contract from a CSV
03_breaking_changes.py Diffing versions and reading the direction
04_pandas.py DataFrames, including pandas' integer-to-float promotion
05_ci_gate.py Failing a build on an incompatible change

Roadmap

Version Scope
0.1.0 Data contracts: schema, constraints, reports, JSON/YAML, diff, compatibility, pandas, CLI
0.2.0 Model contracts, prediction validation, metric thresholds, scikit-learn
0.3.0 Polars, PyTorch, XGBoost, custom validator hooks
1.0.0 Stable contract format and Python API

Versioning and stability

This project follows Semantic Versioning. Before 1.0.0, minor versions may contain breaking API changes; each is described in CHANGELOG.md.

Two versions evolve independently:

  • The Python API, versioned by releases of this package.
  • The contract file format, carried in each contract's spec_version. A release that cannot read an older format is a breaking change and will say so.

schemapact.__all__ is the compatibility promise. Anything not listed there is internal and may change without notice, and a test fails if a public symbol disappears without the snapshot being updated in the same commit.

Contributing

git clone https://github.com/AratameX/schemapact
cd schemapact
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pre-commit install
python scripts/check.py

scripts/check.py runs the same lint, type and test sequence CI does, so a green run locally means a green build. See CONTRIBUTING.md.

Support

License

Apache-2.0. See LICENSE and NOTICE.

Metadata

Release files for schemapact 0.1.1

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

Source distribution (sdist)

Source distribution for schemapact 0.1.1
File Size Uploaded
schemapact-0.1.1.tar.gz 68.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for schemapact 0.1.1
File Interpreter ABI Platform
schemapact-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 141.9 kB

Release files / schemapact-0.1.1.tar.gz

Download URL schemapact-0.1.1.tar.gz
Size 68.8 kB
Tags Source
SHA-256 checksum
How to use checksums
5bace9088c7197cf7c8aaef944754a030b2c664b0973dd119303f63146645331
BLAKE2b-256 checksum
How to use checksums
b9d814cf063048997a7c15814e6e58cbcaf94d56048bc13adb8832b007526dcf
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 Aug 22, 2026.

Transparency log

Release files / schemapact-0.1.1-py3-none-any.whl

Download URL schemapact-0.1.1-py3-none-any.whl
Size 73.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8af74f4d73a0a336e16d42133debe9a4219c75e6b70838c5b7edecf046450b8d
BLAKE2b-256 checksum
How to use checksums
07a287f0ff42fef278cf79294fd9fb55c240b662d63199217082aa78cc009147
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 Aug 22, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

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