Skip to main content

SQL orchestration with templating, semantic layer, and table/column/value lineage

Project description

unwind

SQL orchestration with Jinja templating, a semantic layer, and lineage at the table, column, and value level. An alternative to Dbt and SQLMesh, focused on deterministic value-lineage backed by a SQLGlot AST and a DuckDB execution engine.

A project is a directory of .sql and .py files. SQL models give full table/column/value lineage via the SQLGlot AST. Python models are opaque to lineage but participate in the DAG like any other node — typical use cases are sources (loading data from Oracle / Postgres / Parquet) and sinks (exporting a final table elsewhere). The two kinds compose freely.

Status: alpha — table, column, and deterministic value lineage all work end-to-end, plus a web UI and an optional LLM investigator (pydantic-ai, multi-provider).

Install

The core install (duckdb, jinja2, sqlglot) is enough to load a project, plan its DAG, run it, and compute table / column / value lineage in Python. Anything that talks to the outside world — the web UI, the LLM investigator, the source connectors, the SQL-stored-in-DB loader — lives behind an optional extra so you only install what you actually use.

Extra What it enables Pulls in
web Project.show() (FastAPI/Uvicorn DAG explorer) fastapi, uvicorn[standard]
llm Project.get_investigator() (multi-provider via pydantic-ai) pydantic-ai
db unwind.load_from_db() + connectors.sqlalchemy(...) sqlalchemy
connectors unwind.connectors.parquet/oracle/... for Python models pyarrow, oracledb
all Everything above, no decisions required sum of the four extras

Pick what you need:

pip install unwind-sql                    # core only
pip install "unwind-sql[web]"             # add the web UI
pip install "unwind-sql[web,llm]"         # web UI + LLM investigator
pip install "unwind-sql[connectors]"      # Python-model loaders (parquet, oracle, …)
pip install "unwind-sql[all]"             # everything — recommended for trying it out

For local development on this repo:

uv sync --all-extras    # equivalent to the `[all]` extra

Run the example

The bundled example computes net margin per order over 5 raw/ref tables, with a Jinja macro and a final aggregation by warehouse:

cd example/
uv run python generate_data.py   # one-time: write the 5 parquet sources
uv run python main.py

The script:

  1. Loads models/ (mixed .sql + .py) and runs the full DAG on DuckDB.
  2. Prints the table & column lineage of int_tax_costs.local_tax_fee.
  3. Traces local_tax_fee for order_id="ORD-7892" back to the raw values that contributed (raw_orders.gross_sales = 500.0, ref_local_taxes.tax_pct = 0.20, …).
  4. If OPENAI_API_KEY is set, asks an LLM to explain the trace in plain language and flag suspicious values (uses pydantic-ai, swap providers by passing llm_provider="anthropic" etc.).
  5. Opens a browser tab on the web UI (Cytoscape.js DAG + per-column lineage tree). Press Ctrl+C to stop.

raw_orders in the example is a Python model (example/models/raw_orders.py) backed by a one-line load_data() helper. Switch its backend without touching any SQL:

UNWIND_SOURCE_MODE=parquet   uv run python main.py   # default
UNWIND_SOURCE_MODE=oracle    uv run python main.py   # needs oracledb + ORACLE_DSN

See example/main.py and example/README.md for the model walkthrough.

Python models

A file in models/ whose Python module defines a top-level callable model(context) is recognised as a node of the DAG. Anything else in models/*.py is imported as a plain helper module — so from helpers import load_data just works.

# models/raw_orders.py
from helpers import load_data

GROUP = "costs"
MATERIALIZED = "view"     # "table" (default) or "view"
DEPENDS_ON = ()           # tuple of upstream model names

def model(context):
    # context.duckdb (live connection), context.variables (Jinja vars),
    # context.project_root (the path passed to unwind.load)
    return load_data("raw_orders")  # pyarrow.Table, DataFrame, str (SQL), or None

The return value is registered into DuckDB (zero-copy for Arrow). For a sink, do the work via context.duckdb and return None.

unwind.connectors ships tiny helpers (parquet, oracle, sqlalchemy) so your load_data() reduces to a few lines of branching.

Test

uv run pytest
uv run ruff check
uv run ty check

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

unwind_sql-0.2.0.tar.gz (615.1 kB view details)

Uploaded Source

Built Distribution

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

unwind_sql-0.2.0-py3-none-any.whl (627.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: unwind_sql-0.2.0.tar.gz
  • Upload date:
  • Size: 615.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for unwind_sql-0.2.0.tar.gz
Algorithm Hash digest
SHA256 3c22bb22cdbdf2d5010ea5d4d46710115c2bc4841219c3c9009505d55030c50e
MD5 d87d05ffd42fb89f5246cb0902fd70ad
BLAKE2b-256 e4b9bb4be607cdc7fa6cb7351337fc6d6db7fff84f4c0e68965a9f4f606b033c

See more details on using hashes here.

File details

Details for the file unwind_sql-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: unwind_sql-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 627.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for unwind_sql-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2ec7aa2140ff10e490d2cf5f06ccbc1595566f0795a1e7492b0d54984a40f1f3
MD5 c7e61cb9c91a958405279ab705293f41
BLAKE2b-256 47ac508cecf25b073a652a12a1d16a68f9450c0355dd4dc051edf761b8aee1ef

See more details on using hashes here.

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