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.0 โ€” feature-complete beta. 327 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.

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).


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 (header or the Airflow UI session cookie); 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 session cookie / auth-manager JWT; 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.0.tar.gz (682.7 kB 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.0-py3-none-any.whl (528.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: airflow_dq-0.2.0.tar.gz
  • Upload date:
  • Size: 682.7 kB
  • 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.0.tar.gz
Algorithm Hash digest
SHA256 0bc2dabda4a649f9adea66d0f7673ff78496ff4f519dbd1d21ac5cf6ca7f9482
MD5 a7fee8dfa2366c034cea14ac0b4043b2
BLAKE2b-256 8ec018d2f32d5b2444df9ca39f10ad6a9515de6f35a501daae5b55878d949d41

See more details on using hashes here.

Provenance

The following attestation bundles were made for airflow_dq-0.2.0.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.0-py3-none-any.whl.

File metadata

  • Download URL: airflow_dq-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 528.1 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0632e84961e72107f3427092d376569a2d428f6d5b845c81f5be14f6df1c3656
MD5 04867dc738869de426222a79a70159da
BLAKE2b-256 a5ef586ed43f910f23318eac5de694034130eba08c783145ff3bd7535279f0c7

See more details on using hashes here.

Provenance

The following attestation bundles were made for airflow_dq-0.2.0-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