Skip to main content

Airflow-3-native data-quality & data-contract plugin with an integrated quality dashboard

Project description

airflow-dq

An Airflow-3-native data-quality & data-contract plugin with an integrated quality dashboard.

You author ODCS data contracts inside the Airflow UI, they compile into real check-tasks in your DAG, and the results land in a built-in dashboard โ€” no external SaaS backend, no separate platform.

Status: ๐ŸŸข v0.2.1 โ€” feature-complete beta. 330 unit tests, end-to-end integration test in CI, built UI ships in the wheel. Not yet battle-tested in a multi-team production deployment.

๐Ÿ“– Documentation: https://fbnschneider.github.io/airflow-dq/ โ€” getting started, the full rules reference, configuration, REST API and deployment guides.

Licensed under Apache-2.0 ยท Changelog ยท Contributing ยท Security policy. airflow-dq is an independent community project โ€” it is not endorsed by or affiliated with the Apache Software Foundation or the Apache Airflow project (portions of the UI are derived from Apache Airflow, see NOTICE).

airflow-dq quality dashboard


Why

Data-quality tooling (Soda, Great Expectations, Elementary, Monte Carlo) is mostly checks-as-code in YAML files next to the pipeline, often backed by external SaaS. None of them give you contract-native DQ that lives entirely inside the orchestrator: visual authoring โ†’ codegen to real tasks โ†’ integrated dashboard, as pure self-hosted OSS.

Features

  • 12 rule types โ€” notNull, unique, valueRange, acceptedValues, pattern (regex), referentialIntegrity, freshness (max-age or row-age, day/hour units, DATE + TIMESTAMP), rowCount, schema drift, custom SQL expression, z-score anomaly, cross-table reconciliation โ€” all compiled to push-down SQL with a single failing predicate feeding count, bad-row sample, and quarantine.
  • ODCS v3.1 SLA checks โ€” slaProperties (latency, frequency, retention) compile into sla.* checks with the same gate/monitor semantics; unsupported properties are surfaced as compile warnings.
  • Gate / monitor / quarantine โ€” one contract runs as an inline blocking gate (on_fail="fail"), a record-only asset-triggered monitor (on_fail="warn"), or a bad-row quarantine (on_fail="quarantine"), fanned out via Dynamic Task Mapping into one visible task per check.
  • Quarantine triage UI โ€” browse dq.dq_quarantine (filter by dataset/check/state), inspect quarantined rows, mark them resolved. Quarantine writes are retry-safe (replace-on-retry by run key).
  • Overview dashboard โ€” per-contract portfolio view (pass rate, failing checks, last run, SLA status, anomaly count) plus the per-contract dashboard: KPI row, pass-rate trend, per-dimension breakdown, expandable bad-row samples, deep links to the Airflow run.
  • Anomaly detection, two levels โ€” row-level z-score outliers (with baselineWhere to keep the baseline honest), and server-side metric anomalies: each run's failing-row count is tested against a trailing window (excluding the run itself), persisted to dq.dq_metric_anomalies, flagged on the trend chart, and alertable.
  • Breaking-change detection + PR propose flow โ€” every save diffs against the previous version (column removed / type changed / new required / uniqueness added = breaking); versions are immutable (server-side numbering, HTTP 409 on conflict, atomic writes) and mirrored into a registry (dq_contracts / dq_contract_versions). With DQ_CONTRACTS_GIT=true, "propose" commits the new version on a dq/contract-<id>-v<N> branch for PR review instead of writing to the tree.
  • dbt import โ€” convert an existing dbt schema.yml (not_null / unique / accepted_values / relationships) into a contract draft via POST /dq/api/contracts/import/dbt.
  • Auth via the Airflow auth manager โ€” API routes validate Airflow-issued JWTs โ€” the plugin UI reuses the Airflow UI's own token (shared same-origin storage), and the _token cookie works too; no token is ever injected into served HTML. A legacy shared secret (DQ_API_TOKEN) remains supported for machine clients (CI, curl).
  • 6 warehouse backends โ€” Postgres and DuckDB (stable), Snowflake, BigQuery, Databricks and Trino (experimental: SQL generation is unit-tested, live connections are not). Backends resolve as Airflow Connections (conn_id == backend) with env-DSN fallbacks; pooled engines, statement timeouts.
  • Alerting with fatigue controls โ€” one grouped alert per (run, contract), retry-safe, for fail and error statuses; channels selected by route URL scheme: Slack-compatible webhook, mailto: (SMTP), pagerduty:// (Events v2), opsgenie://; per-owner and per-severity routing via DQ_ALERT_ROUTES.
  • OpenLineage emission โ€” DataQualityAssertions + metrics facets per check, best-effort, with spec-compliant dataset naming (namespace derived from the executor's connection, e.g. postgres://host:5432).
  • Operability โ€” versioned SQL migrations applied automatically (dq.dq_schema_version), error status for infra failures (monitors don't crash on a store outage), idempotent results/quarantine/alert writes keyed by (dag_id, run_id, task_id, check_name), paginated + filterable results API, optional retention (DQ_RESULTS_RETENTION_DAYS).

Architecture

flowchart TD
    A[Contract Editor<br/>React view in Airflow UI] -->|409-safe save + diff| B[Contract Store<br/>immutable ODCS-YAML versions + registry DB]
    B -->|version diff| C[Breaking-Change Detector]
    B -->|propose=true| P[Git branch<br/>PR approval flow]
    D[Your DAG] -->|build_contract_checks| E[Contract Compiler<br/>rules + SLA properties]
    B --> E
    E -->|Dynamic Task Mapping| F[One check task per rule]
    F --> G[CheckExecutor<br/>Airflow Connection or DSN]
    G -->|Postgres / DuckDB / Snowflake* / BigQuery* / Databricks* / Trino*| H[(Warehouse)]
    G -->|idempotent write| I[(Results Store<br/>dq schema + migrations)]
    G -->|best-effort| J[OpenLineage facets]
    G -->|grouped, deduped| K[Alerts<br/>webhook / email / PagerDuty / Opsgenie]
    I --> L[FastAPI plugin /dq<br/>auth: Airflow JWT or legacy token]
    L --> M[Overview ยท Dashboard ยท Quarantine Triage ยท Editor<br/>React views in Airflow UI]

* experimental backends

Dataflow in one sentence: author a contract โ†’ it is stored as an immutable version โ†’ a DAG calls build_contract_checks() โ†’ the compiler turns rules + SLAs into mapped check-tasks โ†’ the executor runs push-down SQL against the backend โ†’ results (plus samples/quarantine/anomalies) land idempotently in the results store and, on failure, one grouped alert goes to the owner's channel โ†’ the embedded dashboard reads the store.

Repository layout

airflow-dq/
โ”œโ”€โ”€ src/airflow_dq/        # the installable Python package (the actual product)
โ”‚   โ”œโ”€โ”€ contracts/           # ODCS v3.1 models, ContractStore, registry, dbt import, git flow, diffing
โ”‚   โ”œโ”€โ”€ compiler/            # Contract -> CheckSpec[] (rules + SLA properties)
โ”‚   โ”œโ”€โ”€ executors/           # pluggable CheckExecutor + dialect layer (postgres, duckdb, snowflake*, ...)
โ”‚   โ”œโ”€โ”€ operators/           # DataContractCheckOperator + build_contract_checks() factory
โ”‚   โ”œโ”€โ”€ results/             # ResultsStore + SQL migrations (dq schema, own Postgres)
โ”‚   โ”œโ”€โ”€ alerting/            # grouped notifier + webhook/email/PagerDuty/Opsgenie channels
โ”‚   โ””โ”€โ”€ plugin/              # Airflow plugin: FastAPI app (api/) + auth + served React UI
โ”œโ”€โ”€ ui/                      # React + TS: overview, dashboard, quarantine triage, contract editor
โ”œโ”€โ”€ dags/                    # demo medallion DAG (gate) + asset-triggered monitor
โ”œโ”€โ”€ contracts/               # versioned ODCS YAML (source of truth for the demo)
โ”œโ”€โ”€ seeds/                   # intentionally "dirty" demo data
โ”œโ”€โ”€ tests/                   # 327 unit tests (compiler/executors/store/API/operator/alerting/...)
โ”œโ”€โ”€ scripts/                 # integration test used by CI and `make integration`
โ”œโ”€โ”€ docker-compose.yaml      # Airflow 3 + Postgres (results + demo data)
โ””โ”€โ”€ DESIGN.md                # decisions & tradeoffs

Quickstart

cp .env.example .env
docker compose up -d                 # Airflow 3 + Postgres
make ui-build                        # build the React views (served by the plugin)
make test                            # unit tests

# Airflow UI:        http://localhost:8080
# Quality Dashboard: http://localhost:8080/dq/app/   (after a demo_medallion run)
# Health:            http://localhost:8080/dq/api/health

Useful targets: make integration (full compose-up โ†’ DAG run โ†’ results assertion โ†’ teardown), make db-migrate (apply pending schema migrations to DQ_RESULTS_DSN), make reseed (reload the dirty demo data so demo outcomes stop drifting with volume age), make clean.

Learn by example: examples/README.md is a 15-minute guided tour โ€” five runnable example DAGs (dags/examples/, auto-loaded into the demo stack) covering the quickstart gate, quarantine + triage, incremental/partition-scoped checks, SLA + anomaly detection, and asset-triggered monitoring, plus dbt import, alert routing and the PR flow.

Using it in a DAG

from airflow_dq import build_contract_checks

validate_silver = build_contract_checks(
    contract="orders@v3",        # pinned, immutable version
    backend="postgres_dwh",      # Airflow conn_id (env-DSN fallback)
    on_fail="quarantine",        # "fail" | "warn" | "quarantine"
    retries=1,                   # task tuning passes through
)
bronze >> silver >> validate_silver >> gold

Two deployment patterns from the same contract:

  • Gate โ€” inline in the producer DAG (demo_medallion): checks run at the layer boundary and can block / quarantine before data flows downstream.
  • Monitor โ€” asset-triggered (dq_monitor): the table is an Airflow Asset; the monitor DAG schedule=[orders_asset] re-runs the contract whenever the producer emits the asset โ€” on_fail="warn", record-only. Airflow 3 data-aware scheduling.

Both write to the same results store, so the dashboard reflects gate and monitor runs.

Configuration reference

All knobs are read through airflow_dq.config.settings() (src/airflow_dq/config.py). See .env.example for a commented template.

Env var Default Purpose
DQ_RESULTS_DSN compose demo DSN SQLAlchemy DSN of the DQ results/registry store (schema dq, never Airflow's metadata DB)
DQ_DWH_DSN compose demo DSN Fallback warehouse DSN for postgres* backends when no Airflow Connection matches
DQ_DUCKDB_PATH :memory: DuckDB database path for duckdb* backends
DQ_CONTRACTS_DIR contracts Directory of versioned contract YAML (shared by API server + workers)
DQ_SNOWFLAKE_DSN unset Snowflake DSN fallback (experimental adapter)
DQ_BIGQUERY_DSN unset BigQuery DSN fallback (experimental adapter)
DQ_DATABRICKS_DSN unset Databricks DSN fallback (experimental adapter)
DQ_TRINO_DSN unset Trino DSN fallback (experimental adapter)
DQ_API_TOKEN unset Legacy shared secret for machine clients; unset = auth-manager JWT/cookie only
DQ_UI_DIST unset Override for the built React bundle location (wheel/checkout auto-detected)
DQ_BASE_URL empty Absolute base URL for clickable links in alerts
DQ_STATEMENT_TIMEOUT_S 300 Statement timeout for check/preview/profile queries (seconds)
DQ_SAMPLE_LIMIT 10 Max bad rows captured per failing check
DQ_QUARANTINE_LIMIT 1000 Max rows quarantined per failing check
DQ_ALERT_WEBHOOK unset Default alert channel (scheme-routed: https/mailto/pagerduty/opsgenie)
DQ_ALERT_ROUTES unset JSON ownerโ†’channel map; values may be {fail: url, error: url}
DQ_SMTP_HOST unset SMTP host for mailto: routes
DQ_SMTP_PORT 587 SMTP port
DQ_SMTP_FROM unset From address for email alerts
DQ_SMTP_USER unset SMTP username (optional)
DQ_SMTP_PASSWORD unset SMTP password (optional)
DQ_OL_NAMESPACE airflow-dq Explicit OpenLineage namespace override (default: connection-derived)
DQ_CONTRACTS_GIT false Enable the git-based "propose" (PR) save flow
DQ_CONTRACTS_GIT_REMOTE origin Remote used by the propose flow
DQ_RESULTS_RETENTION_DAYS 0 Days to keep raw results/anomalies (0 = forever)

Upgrading from 0.1.x โ€” breaking changes

  • Freshness default is now mode: max_age โ€” the check measures the age of max(col) (one aggregate, no per-row predicate) instead of counting every historical row older than the threshold. The 0.1 behavior is available as params: { mode: row_age }. unit: day|hour is now honored, and TIMESTAMP columns are supported.
  • Quarantine no longer fires on tolerated passes โ€” rows are quarantined only when the check's final status is fail or warn; a check whose failing rows are within tolerance (status pass) no longer writes quarantine rows.
  • do_xcom_push defaults to off โ€” check tasks no longer push their result dict (with bad-row samples) into XCom. Pass return_result=True to build_contract_checks() if you consumed it.
  • Token-in-HTML removed โ€” /dq/app/ no longer injects DQ_API_TOKEN into the page. The UI authenticates via the Airflow UI's own JWT (shared same-origin storage) or the _token cookie; DQ_API_TOKEN still works as a Bearer header for machine clients. Any scraper that relied on reading the token from the HTML breaks (deliberately).
  • ODCS v3.1 output shape โ€” saved contracts now emit apiVersion: v3.1.0, a semver version string ("N.0.0", integer retained as the dqVersion custom property), and dataset/owner under customProperties. Readers accept both the old dialect and the new shape; unknown spec fields round-trip instead of being stripped.
  • Infra errors are now status='error' โ€” executor exceptions no longer crash record-only monitors; they are recorded (and alerted) as error, and only on_fail="fail" tasks re-raise.
  • Contract versions are immutable โ€” saving an existing version returns HTTP 409; the editor auto-targets max(existing)+1.

Production notes

  • psycopg2: the wheel depends on psycopg2-binary for friction-free dev/demo installs. For production, follow the psycopg2 project's advice and install the source package instead (pip install psycopg2) so it links against your system libpq/libssl.
  • Constraints: install with the Airflow constraints file matching your Airflow/Python version (CI does the same): pip install airflow-dq --constraint https://raw.githubusercontent.com/apache/airflow/constraints-3.1.0/constraints-3.12.txt
  • Auth: Airflow 3 does not auto-protect plugin FastAPI routes. They are secured explicitly in plugin/auth.py: Airflow auth-manager JWT (header or session cookie) with DQ_API_TOKEN as a documented machine-client fallback; write routes additionally consult is_authorized_custom_view when available.
  • UI hosting: the React app is served whole by the plugin's FastAPI app at /dq/app/ and embedded in the Airflow UI as a nav item via the stable external_views (iframe) API. Wheel installs serve the packaged bundle (airflow_dq/ui_dist); source checkouts serve ui/dist (build with make ui-build); DQ_UI_DIST overrides both.
  • Experimental backends (Snowflake, BigQuery, Databricks, Trino): SQL generation is unit-tested per dialect, but no live-connection test coverage exists yet. Install the matching extra (pip install "airflow-dq[snowflake]" etc.) and treat them as preview quality.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

airflow_dq-0.2.2.tar.gz (1.9 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

airflow_dq-0.2.2-py3-none-any.whl (528.3 kB view details)

Uploaded Python 3

File details

Details for the file airflow_dq-0.2.2.tar.gz.

File metadata

  • Download URL: airflow_dq-0.2.2.tar.gz
  • Upload date:
  • Size: 1.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for airflow_dq-0.2.2.tar.gz
Algorithm Hash digest
SHA256 e637ce716dd96ac6f17dbd4f49b506731f237eb06eb5fe05f9a428ca4cf7d61a
MD5 7781931ff6ef0959c288c572e1f6a722
BLAKE2b-256 e1795e827ed75837dedb7b4235380eae9fb5a13e8994336b6286020341934e76

See more details on using hashes here.

Provenance

The following attestation bundles were made for airflow_dq-0.2.2.tar.gz:

Publisher: release.yml on fbnschneider/airflow-dq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file airflow_dq-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: airflow_dq-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 528.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for airflow_dq-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 999664b3ea92c85cf3b1dd226be75c0ad7cb4ce44fad59ab2d5df0be98eb605f
MD5 3918ed8e847c136d760ce23eda694f3e
BLAKE2b-256 2604b9651d6d5049e139eeeca75b7e1d810f30397de5329439924cebaacbca36

See more details on using hashes here.

Provenance

The following attestation bundles were made for airflow_dq-0.2.2-py3-none-any.whl:

Publisher: release.yml on fbnschneider/airflow-dq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page