sverify-dbt
Sverify-dbt proves a dbt project's data tests and model contracts before
the models run. It reads the project after dbt compile, checks each
model's compiled SQL with sverify against the tests on
the models and sources it reads, and reports per model which tests hold on
every run, which may fail and why, and which source tests would let it prove
more. A test it proves can be left out of dbt build.
It reads DuckDB projects (dbt-duckdb). The other adapters wait on sverify's other engines (#271). The design is in #177.
Installation
Sverify-dbt is published with sverify, at the same version, from substrait-lean's releases on (#732):
pip install sverify-dbt dbt-duckdb
Usage
Compile the project, then check it:
dbt compile
sverify-dbt check .
On the sample project in examples/shop:
stg_orders (models/staging/stg_orders.sql)
proved before the model runs (dbt build may skip them):
not_null(order_id)
unique(order_id)
not_null(customer_id)
dbt_utils.accepted_range(amount)
not proved (dbt build still runs them):
accepted_values(status): sverify cannot rule out a row that fails it, from the tests upstream
customer_revenue_by_category (models/marts/customer_revenue_by_category.sql)
not proved (dbt build still runs them):
dbt_utils.accepted_range(books): sverify cannot rule out a row that fails it, from the tests upstream
contract: columns and types match
primary_key(customer_id): proved
org_depth (models/marts/org_depth.sql)
refused: sverify cannot read WITH RECURSIVE yet
subscription_arr (models/marts/subscription_arr.sql)
not proved (dbt build still runs them):
dbt_utils.accepted_range(arr): proved only with the source tests listed under 'needs'
needs these tests before anything is proved:
on source('app', 'subscriptions'), column mrr: dbt_utils.accepted_range: {arguments: {max_value: 700000000000000000, inclusive: false}}
(overflow in `mrr * 12` otherwise)
--selectors adds a selector, sverify_proved, to the project's
selectors.yml (keeping the others), and dbt build skips the tests it
selects:
sverify-dbt check . --selectors
dbt build --exclude selector:sverify_proved
sverify-dbt suggest prints the tests that would let sverify prove more,
as a dbt properties file to merge into the project's YAML: what each model
needs, and the tests on its inputs under which an unproved test would hold,
each with its reason as a comment (--output FILE writes it). Merge them
by hand, and tighten a bound where your data allows:
examples/shop/PROOF-STATUS.md shows one.
sverify-dbt suggest .
The other options of check:
| Option | What it does |
|---|---|
--select MODEL ... |
Report on these models (their upstream models are checked too) |
--json |
Print the result as JSON |
--counterexamples N |
Small inputs to try per unproved test (default 200; 0 skips the search) |
--fail-on |
Exit with status 1 on a contract mismatch or an unproved constraint (contract, the default), also on any unproved test or refused model (unproved), or never |
--target-path |
The target directory, if not target |
The same from Python:
import sverify_dbt
result = sverify_dbt.check("path/to/project")
print(result)
result["stg_orders"].by_status("proved")
result.write_selectors("path/to/project/selectors.yml")
How a project becomes contracts
Each model is checked on its own, the way dbt builds it. Its compiled SQL is
sverify.verify's query, under DuckDB's semantics (semantics="duckdb"),
over its refs and sources. Their column types come from
target/catalog.json when dbt docs generate wrote one, else from the
data_types in the YAML (a seed's column_types among them), and for a
model from DuckDB's types for its SQL.
- The tests on what a model reads are its preconditions (
requires).dbt buildruns them first and skips the models downstream of a failure, so they hold whenever the model runs. A test with severitywarn, or thresholds (error_if,fail_calc), lets the build go on, and counts only once it is proved. - The model's own tests are its postconditions (
ensures). A proved test holds on every run whose inputs pass their tests. - An enforced contract (
contract: {enforced: true}) is checked too: its columns and types against the model's result, and its constraints (not_null,unique,primary_key,check) as postconditions, before dbt's own check at build time.
Only tests carry from one model to the next. A key that sverify proves of a model's result, but that no test states, is unknown to the models downstream; add the test and it carries.
The tests sverify-dbt reads:
| Test | Condition on each row |
|---|---|
not_null, dbt_expectations.expect_column_values_to_not_be_null |
c IS NOT NULL |
accepted_values, dbt_expectations.expect_column_values_to_be_in_set |
c IS NULL OR c IN (...) |
dbt_expectations.expect_column_values_to_not_be_in_set |
c IS NULL OR c NOT IN (...) |
dbt_utils.accepted_range, dbt_expectations.expect_column_values_to_be_between |
c IS NULL OR (c >= min AND c <= max), strict where the test says |
dbt_utils.expression_is_true |
coalesce(expression, TRUE) |
unique, dbt_expectations.expect_column_values_to_be_unique |
a key (sv.unique) |
dbt_utils.unique_combination_of_columns, dbt_expectations.expect_compound_columns_to_be_unique |
a key over the columns |
relationships |
a foreign key (sv.references), as a precondition only |
NULLs pass each test, as they do in dbt. A test's where config and
dbt-expectations' row_condition limit the condition to the rows they keep.
dbt's unique lets NULLs repeat while a key counts them as one value, so a
unique test is a precondition only on a column a not_null test covers too.
When the proof needs a precondition no test states, such as a bound that
rules out an overflow, the model's tests stay unproved and the report lists
the test to add under needs, as YAML for the input's column (sverify's
Report.needed).
Limits
- Generic data tests only. Singular tests and unit tests (
given/expect) are listed as not checked. - A model is refused where sverify cannot read its SQL (
WITH RECURSIVE,md5, sodbt_utils.generate_surrogate_key), with the construct named, and where it reads a table that is noreforsource. - Sverify tracks strings by their exact conditions. An
accepted_valuestest on a column derived from a string constant or filtered from a wider set stays unproved (API notes). - Python models, and models whose inputs have no column types, are refused.
- An incremental model is checked as its full refresh, as
dbt compilerenders it.
Development
cd dbt
uv sync
uv run pytest
uv sync installs the sverify of this checkout (../python), with the
checker its build hook links or builds.
The tests compile the sample project and a few small ones with dbt's own
runner. CI runs them, then the check of examples/shop, and dbt build
without the proved tests (the dbt job of
ci.yml). API-NOTES.md lists
what building on sverify's public API showed.
Metadata
Release files for sverify-dbt 0.7.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_dbt-0.7.0.tar.gz | 148.2 kB | With provenance | Details for sverify_dbt-0.7.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| sverify_dbt-0.7.0-py3-none-any.whl | Python 3 | none | any | Details for sverify_dbt-0.7.0-py3-none-any.whl |
Total release size: 179.0 kB
Release files / sverify_dbt-0.7.0.tar.gz
| Download URL | sverify_dbt-0.7.0.tar.gz |
|---|---|
| Size | 148.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fdc1a96c96bad5ec7d46c08ff2cf1518d860dd6da8164ecbd38b46c1dc92ccbd
|
|
BLAKE2b-256 checksum How to use checksums |
1b834ea6854e384a950c0e94a77a1f8531f06c1e92b24d9dcdb1e7cab02f3ef5
|
| 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 5, 2026.
Transparency logRelease files / sverify_dbt-0.7.0-py3-none-any.whl
| Download URL | sverify_dbt-0.7.0-py3-none-any.whl |
|---|---|
| Size | 30.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
10566d3d31c764091bc6a583e3c333493b0e196a98d01388434d2e7c09bb27bc
|
|
BLAKE2b-256 checksum How to use checksums |
bca6392d6303434a74848b89e9bc39a30b2c7fc0f671a8202fe10fb27a82d583
|
| 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 5, 2026.
Transparency logSecurity for sverify-dbt 0.7.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
Both files in this release have been uploaded with provenance metadata.
Attestation for sverify_dbt-0.7.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 5, 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.