sverify
Prove a data pipeline's contracts before it runs, get the same result on DuckDB, Polars, DataFusion, Spark and Snowflake, and keep it up to date from Delta Lake and Iceberg change feeds.
You write a transformation in Ibis or SQL and say
what it assumes about its inputs (requires) and what it guarantees about
its output (ensures). A checker written in Lean
proves the guarantees for every possible input, or says why it can't. Then:
- Contracts are proved, not tested.
not_null,unique, ranges and relations between columns hold on every input that meets the preconditions, and division by zero, overflow and bad casts are ruled out before the job runs. Preconditions are checked on the data, or proved from Delta and Iceberg metadata. - One plan gives the same answer on every engine. The checked plan runs on DuckDB, Polars or DataFusion, or is written as Snowflake or Spark SQL, with NULLs, integer division, NaN, ties and overflow spelled out, or it is refused where an engine can't match.
- Results are kept up to date from the changes, by delta plans proved equal to a full recompute, read from Delta's Change Data Feed or Iceberg's appends, and written to sinks exactly once, with the contract attached.
pip install sverify # or: uv add sverify
pip install 'sverify[all]' # with every engine and table format
The core runs on DuckDB and writes DuckDB, Snowflake and Spark SQL. Extras:
datafusion (also compiles SQL strings), polars, spark (needs Java 17 or
21), delta, iceberg, or all. Wheels bundle the checker as a small
native binary, for Linux (x86_64, aarch64) and macOS (arm64, x86_64), Python
3.12 and later; elsewhere pip builds from the sdist, which needs
elan.
The API reference
documents every public function and class, for the released version. For
coding agents, the site also publishes
skills for
writing contracts, pipelines and engine code with sverify
(npx skills add https://alexandernenninger.github.io/substrait-lean/); they
ship in the wheel too, under sverify/skills/.
In thirty lines
import pyarrow as pa
import sverify as sv
from ibis import _
from sverify.io import StaticSource
orders = StaticSource( # in memory; DeltaSource and IcebergSource work the same way
pa.table({"id": [1, 2], "customer": ["ada", "bob"], "amount": [30, 5]}), name="orders"
)
@sv.transform(
requires={"orders": [_.amount >= 0, sv.unique("id")]},
ensures=[_.doubled >= 22, _.doubled.notnull(), sv.unique("id")],
)
def big_orders(orders):
return orders.filter(orders.amount > 10).select(orders.id, doubled=orders.amount * 2)
big = big_orders(orders) # proved here, before any data runs
big.refresh() # computed, on DuckDB by default
orders.apply(sv.Changes(inserts=[(3, "cy", 50)], deletes=[(1, "ada", 30)]))
big.refresh(engine="polars") # updated from the changes alone, on another engine
assert big.to_arrow().to_pylist() == [{"id": 3, "doubled": 100}]
print(big_orders.last_report)
# requires orders: (_.amount >= 0)
# requires orders: unique(id)
# ensures (_.doubled >= 22): proved
A postcondition that doesn't follow is refused before anything runs:
@sv.transform(requires={"orders": [_.amount >= 0]}, ensures=[_.total > 5])
def plus_one(orders):
return orders.select(orders.id, total=orders.amount + 1)
from sverify.errors import VerificationError
try:
plus_one(orders)
except VerificationError as e:
print(e)
# could not prove: (_.total > 5) (it is TRUE or FALSE)
SQL models
A transform's body can be SQL, over its inputs by name. It is proved, composed and refreshed like any other:
@sv.transform(
requires={"orders": ["amount >= 0", "amount IS NOT NULL"]},
ensures=["total >= 0", sv.unique("customer")],
)
def per_customer(orders):
return sv.sql(
"SELECT customer, coalesce(sum(amount), 0) AS total FROM orders GROUP BY customer",
orders=orders,
)
totals = per_customer(orders) # proved here, like an Ibis transform
totals.refresh()
assert sorted(totals.to_arrow().to_pylist(), key=str) == [
{"customer": "bob", "total": 5},
{"customer": "cy", "total": 50},
]
Several inputs are several keywords (sv.sql(query, orders=orders, customers=customers)). The SQL is planned by DataFusion
(pip install 'sverify[datafusion]'), or by Isthmus without it.
Workflows
Three complete jobs from a catalogue of 139 real analytical queries, each from source tables to a sink, with what goes wrong without the checker. Open them in the browser:
- Duplicate card payments: a self-join over a Delta table, refreshed from its Change Data Feed, where a watermark job would miss a late duplicate and keep a reversed one.
- Sensor alerts:
LAGover an Iceberg table, where ties and a NaN reading change the alerts, and Snowflake and Spark need proved tie-freedom. - An orders mart: dbt tests as a proved contract on a point-in-time join to a type-2 dimension, in Ibis and in SQL.
The whole tour
explains every feature in a chapter of its own; it runs locally with
uvx marimo edit --sandbox notebooks from a clone.
The API in brief
import json
import ibis
from sverify.engines import SNOWFLAKE, to_sql # also DUCKDB, SPARK
from sverify.io import DeltaSink, DeltaSource # IcebergSource, IcebergSink likewise
sv.verify(
"select id, amount from orders where amount > 0",
tables={"orders": ibis.table({"id": "int64", "amount": "int64"}, name="orders")},
ensures=["amount > 0"],
) # SQL, checked; returns the report
to_sql(json.loads(big.plan), SNOWFLAKE) # the checked plan, or sverify.engines.Unsupported
# p = paid(DeltaSource("s3://.../orders")).write(DeltaSink("s3://.../paid")) # stored: a source
# r = revenue(p).write(DeltaSink("s3://.../revenue"))
# r.refresh(engine="polars"); sv.group(r, other).refresh(); r.to_pandas()
@sv.transform(requires=..., ensures=..., on_unproved="error" | "runtime" | "warn")proves a contract on each call;.last_reportsays what was proved and what is known about every output column.sv.unique(...)states keys.- Called on sources (
sverify.io) or other nodes, transforms build nodes that compose directly: a downstream precondition is proved from the upstream contract (CompositionErrornames a broken link). - Without
.writein between, transforms fuse into one plan;.write(sink)stores a node, exactly once per state, and nodes downstream read it as a versioned source.refresh(engine=...)updates the whole lineage, andsv.group(a, b)several outputs against the same source versions. - Filters, projections, inner and cross joins and
UNION ALLare maintained from the changes; anything else is recomputed and diffed. sverify.engineshas the engines, SQL dialects and plans (run_shardedruns a plan per shard and combines the results, with a proof that the result is the same);sverify.errorsthe exceptions.
Not supported yet: DECIMAL columns (keep money as integer cents),
framed window aggregates, RANK and NTILE. The repository's README has
the coverage table, the full guide, and what is proved and what is trusted.
Configuration
SVERIFY_CHECK: path to asubstrait-checkbinary to use instead of the bundled one, for example your ownlake build.SVERIFY_ISTHMUS: path to an Isthmus binary (otherwiseisthmusonPATH), to compile SQL strings without DataFusion;sv.connect(sql_producer="isthmus")picks it, and it is the default when DataFusion is not installed. sverify doesn't ship it (~120 MB);scripts/fetch_isthmus.sh <dir>downloads the pinned release.- DataFusion is pinned to 51, because Ibis 12's DataFusion backend doesn't connect to later versions.
Development
git clone https://github.com/AlexanderNenninger/substrait-lean
cd substrait-lean/python
uv sync # builds substrait-check with lake (needs elan) and links it in
uv run poe qa # proofs, lint, typecheck, golden, Python tests, notebooks
See the repository for setup details.
Metadata
Release files for sverify 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| sverify-0.2.0.tar.gz | 777.2 kB | With provenance | Details for sverify-0.2.0.tar.gz |
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| sverify-0.2.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details for sverify-0.2.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
| sverify-0.2.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details for sverify-0.2.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
| sverify-0.2.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details for sverify-0.2.0-py3-none-macosx_11_0_arm64.whl |
| sverify-0.2.0-py3-none-macosx_10_15_x86_64.whl | Python 3 | none | macOS 10.15+ x86-64 | Details for sverify-0.2.0-py3-none-macosx_10_15_x86_64.whl |
Total release size: 116.9 MB
Release files / sverify-0.2.0.tar.gz
| Download URL | sverify-0.2.0.tar.gz |
|---|---|
| Size | 777.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c40aa68cb50c0e624ee574b0574ec2f1c9ca940b140f768375d7c8b2a06548e6
|
|
BLAKE2b-256 checksum How to use checksums |
57651de12ccbf68ee7b38d4948828885390bbd2d6a87e16da421f74ebe3f33f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 1, 2026.
Transparency logRelease files / sverify-0.2.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | sverify-0.2.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 29.9 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
af4f2dd41125dcd1b82929420ad42bdc3bbd9cb7a58d36f91b56f8d583ddc0b0
|
|
BLAKE2b-256 checksum How to use checksums |
2faf0648a8e8ada20853724e44ed0a57cce1e58a2d288a7d84081e834d20acef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 1, 2026.
Transparency logRelease files / sverify-0.2.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
| Download URL | sverify-0.2.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
|---|---|
| Size | 29.8 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
bfee6a7377caf615e24f0ce456e31d91a298643eab20bc82c874d5957662c49b
|
|
BLAKE2b-256 checksum How to use checksums |
008779854d2bee1df9eab1e202ee69d70569f8536ffcf50bf9c04be75efd44df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 1, 2026.
Transparency logRelease files / sverify-0.2.0-py3-none-macosx_11_0_arm64.whl
| Download URL | sverify-0.2.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 27.3 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
39a733aba850300c169b2ed0ff7c8ae76f43f54e27e222bb8ba3b87090dc6cc8
|
|
BLAKE2b-256 checksum How to use checksums |
18d7c5deb58aef8985b4765132c483d20622cc0b4a0d74cedb5ea4d46190a162
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 1, 2026.
Transparency logRelease files / sverify-0.2.0-py3-none-macosx_10_15_x86_64.whl
| Download URL | sverify-0.2.0-py3-none-macosx_10_15_x86_64.whl |
|---|---|
| Size | 29.1 MB |
| Tags | Python 3 macOS 10.15+ x86-64 |
|
SHA-256 checksum How to use checksums |
fe2292d409fd61a9504dc45bf9ea80d87ea13726f306bfddbccf7e47975726d0
|
|
BLAKE2b-256 checksum How to use checksums |
ffb8a833907fcaf3721fc9d52c354f16539add1236839c8b762074b026402cac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 1, 2026.
Transparency logSecurity for sverify 0.2.0
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.
Full provenance
All 5 files in this release have been uploaded with provenance metadata.
Attestation for sverify-0.2.0.tar.gz
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 Nov 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.