How to use
- Copy the installation command and review the source package before installing it in your own agent environment.
- Check the source document for required tools, credentials and supported hosts.
- Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/TerminalSkills/skills --skill sodaBefore you start
- Installation does not include a model subscription, API allowance or permission to access your files.
- Repository stars and installation counts describe activity, not an independent quality or security test.
Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
Soda
Overview
Soda Core (v4, current release 4.28.0 on PyPI) verifies data contracts. A contract is a YAML file with a dataset: name, a columns: list and checks; soda contract verify runs the checks against your database and exits non-zero when one fails. Packages are named soda- plus the database (soda-postgres, soda-bigquery, soda-snowflake, soda-duckdb, soda-databricks) and pull in soda-core.
The older Soda Core v3 (soda-core-postgres 3.5.x, soda scan, SodaCL such as checks for orders: and missing_count(x) = 0) is legacy and receives no new releases. Do not mix its syntax with v4 contracts. To stay on it you must pin ~=3.5.0; Soda ships a migration guide (docs.soda.io, "Migrate from v3 to v4") that converts SodaCL to contracts.
Instructions
Step 1: Install and describe the connection
python -m venv .venv && source .venv/bin/activate
pip install soda-postgres # or soda-duckdb, soda-bigquery, soda-snowflake
soda data-source create -f soda/shop_ds.yml -t postgres # writes a template# soda/shop_ds.yml - secrets come from environment variables
type: postgres
name: shop
connection:
host: ${env.POSTGRES_HOST}
port: 5432
user: ${env.POSTGRES_USER}
password: ${env.POSTGRES_PW}
database: analyticssoda data-source test -ds soda/shop_ds.ymlUse a read-only database user. For DuckDB the connection is database: "shop.duckdb".
Step 2: Write a contract
The dataset name is the data source name, then the prefixes, then the table, separated by slashes; for Postgres the prefixes are database and schema (shop/analytics/public/orders), for DuckDB just the schema (shop/main/orders). Casing must match the database. A contract needs a columns: list even if it is empty (columns: []), and a key such as checks: may appear only once per mapping.
# soda/contracts/orders.yml
dataset: shop/analytics/public/orders
columns:
- name: order_id
checks:
- missing:
- duplicate:
- name: customer_id
checks:
- missing:
- name: status
checks:
- invalid:
valid_values: [pending, processing, shipped, delivered, cancelled, refunded]
- name: total_amount
checks:
- invalid:
name: No negative order amounts
valid_min: 0
- name: shipping_address
checks:
- missing:
name: Under 50 percent of addresses missing
threshold:
metric: percent
must_be_less_than: 50
additional: # second threshold on the same metric
must_be_less_than: 10
level: warn
checks:
- schema: # lists every column that must exist, no extras
- row_count:
threshold:
must_be_between:
greater_than: 0
less_than: 10000
- freshness:
column: created_at
threshold:
unit: hour
must_be_less_than: 24
- failed_rows:
name: Delivered orders must have a delivery date
expression: status = 'delivered' AND delivered_at IS NULLRules that trip people up:
- Every check has a default threshold of zero (
missing,duplicate,invalid,failed_rowsmust be 0;row_countmust be above 0). Athresholdcompares the check's own metric (the number or percent of bad rows), never the column values. To limit values usevalid_min,valid_max,valid_valuesorvalid_formatin aninvalidcheck. - The
schemacheck fails on columns in the table that the contract does not list (allow_extra_columns: falseis the default). List all columns, or setallow_extra_columns: true. thresholdtakesmust_be,must_not_be,must_be_greater_than,must_be_less_than,must_be_betweenand similar keys;level: warnmakes a breach a warning instead of a failure.- A dataset-wide
filter: "created_at >= CURRENT_DATE - INTERVAL '1 day'"limits every check to recent rows; a single check can have its ownfilter. Multi-column uniqueness:duplicate: {columns: [customer_id, order_date]}at the dataset level. Custom SQL:metricwithexpression:orquery:, andaggregatewithfunction: avg. ${var.NAME}references a variable declared undervariables:and set with--set NAME=value.
Step 3: Verify
soda contract test -c soda/contracts/orders.yml # syntax only, no database
soda contract verify -c soda/contracts/orders.yml -ds soda/shop_ds.ymlThe exit code is 1 when any check fails and 0 otherwise (warnings pass). Useful flags: -v for logs, -cp with a check path to run only some checks, -cf "column=status" to filter checks, -p with -sc soda_cloud.yml to publish results to Soda Cloud.
Step 4: Python and CI
from soda_core.contracts import verify_contract_locally
result = verify_contract_locally(
data_source_file_path="soda/shop_ds.yml",
contract_file_path="soda/contracts/orders.yml",
publish=False,
)
if result.has_errors:
raise RuntimeError(result.get_errors_str())
result.assert_ok() # raises when a check failed; result.is_warned tells about warnings# .github/workflows/data-quality.yml
name: Data quality
on:
schedule:
- cron: "0 */4 * * *"
workflow_dispatch:
jobs:
soda:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install soda-postgres
- run: soda contract verify -c soda/contracts/orders.yml -ds soda/shop_ds.yml
env:
POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }}
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
POSTGRES_PW: ${{ secrets.POSTGRES_PW }}Examples
Example 1: Check a freshly loaded orders table
User prompt: "After the nightly load, make sure orders has no duplicate IDs, no missing customers and data newer than 24 hours."
Create soda/contracts/orders.yml as in Step 2 (keep the order_id duplicate check, the customer_id missing check and the 24-hour freshness check), run soda contract verify, and read the result table. A failure looks like | order_id | No duplicate values | ... | FAILED | duplicate_count: 1 duplicate_percent: 20.0 | and the command returns exit code 1, which fails the pipeline step.
Example 2: Local trial with DuckDB
User prompt: "Try Soda on my shop.duckdb file before I wire it to the warehouse."
# ds_config.yml (the create command only has a postgres template, so write this by hand)
type: duckdb
name: shop
connection:
database: "shop.duckdb"pip install soda-duckdb
soda contract verify -c contracts/orders.yml -ds ds_config.ymlUse dataset: shop/main/orders (data source name, schema main, table). The summary at the end lists checks passed, failed, warned and not evaluated.
Guidelines
- Anomaly detection and dashboards are Soda Cloud features, not part of a local contract run; the open-source engine runs the deterministic checks above.
- A "Not evaluated" check means its query failed (wrong dataset path or casing, missing permission). Read the error lines before the table.
- Run contracts after each load (dbt, Airbyte, your own job) so bad data is caught before dashboards read it.
- Prefer SQL
expression:over a fullquery:inmetricandfailed_rows; expressions are combined into fewer queries. - Keep credentials in environment variables referenced as
${env.NAME}; never commit them to the data source file.

