Skip to main content

Python/SQL-first data platform: transformation, built-in orchestration, and durable streaming ingestion

Project description

interlace

Python/SQL-first data platform: transformation, orchestration, and durable streaming — one process.

interlace is an independent, MIT-licensed alternative to dbt/SQLMesh that also replaces the orchestrator (no Airflow) and the ingestion layer (Cloudflare-Pipelines-style durable streams). Models are .sql files or Python functions; state is versioned snapshots with virtual environments and a terraform-style plan/apply; everything runs in a single daemon on DuckDB + DuckLake by default.

Status: 1.0. Requires Python 3.12+. The package is published to PyPI as interlaced; the import name and CLI are interlace.

uv pip install "interlaced[service]"   # extras: service, adbc, postgres, polars, pandas, all

Sixty seconds

interlace init my-project && cd my-project
interlace plan            # terraform-style preview: added / breaking / non-breaking / reuse
interlace apply           # build changed models, run checks, promote the environment
interlace serve           # the daemon: web UI (/ui) + HTTP API + scheduler + streams, one process

Every model builds into a fingerprinted physical table (interlace__main.orders__a1b2c3); environments are views over those tables, so promotion and rollback are atomic view swaps and a dev environment reuses prod's tables for free. Production is the unprefixed namespace — consumers query main.orders; sandboxes are prefixed (dev__main.orders). Commands default to prod; pass --env dev while developing.

Models

SQL — a file per model; upstreams referenced by model name, dependencies inferred by parsing (sqlglot), config in a leading comment block:

/* interlace:
  strategy: scd_type_2
  key: customer_id
  schedule: {cron: "0 * * * *"}
  checks:
    - not_null: customer_id
    - unique: customer_id
*/
SELECT customer_id, name, tier FROM raw_customers

Python — functions whose parameters name their upstreams; data crosses as Arrow (never pandas), streamed with bounded memory:

from interlace import model

@model(strategy="merge_by_key", key="order_id", cursor="updated_at")
def orders(cursor, this):
    """Incremental API extract: `cursor` is max(updated_at) already in the
    warehouse (None on first run); `this` is the previous materialisation."""
    rows = fetch_orders(since=cursor)          # your code
    return pyarrow.Table.from_pylist(rows)     # or RecordBatchReader / generator of batches

Strategies: full, view, ephemeral (CTE-inlined), merge_by_key (upsert), full_merge (full-state source applied as a minimal diff), incremental_by_time (windowed, interval-ledger backfill/catchup), scd_type_2 (history with validity windows).

Plan / apply

$ interlace plan
 Model         Change    Category      Build
 orders        modified  non_breaking  rebuild
 order_stats   modified  non_breaking  reuse      <- output provably identical: not rebuilt
  • Changes classify breaking / non-breaking / forward-only; a plan with breaking changes refuses to apply without --force. Downstream models whose output is provably identical (column-pruned impact analysis) reuse their existing tables instead of rebuilding — an improvement over model-granular invalidation.
  • apply --forward-only lets history-keeping models (scd2/merge/incremental) survive a definition change: the existing table is copied to the new version, the new logic applies to the copy going forward, and checks gate before views move.
  • Checks gate promotion: 10 built-in types (not_null, unique, accepted_values, row_count, freshness, expression, relationships, pattern, range, sql) plus @check Python functions — an error-severity failure blocks before the environment view moves. interlace checks run re-runs them ad hoc against any environment's promoted tables.
  • --select state:modified scopes a plan/apply to models whose fingerprint drifted from the target environment, plus everything downstream — the CI diff, one flag. interlace impact model.column shows the column-level blast radius of a change.
  • interlace env rollback repoints an environment's views at any earlier promotion — nothing rebuilds, the views move; every apply records a generation, so a bad deploy reverts in one command. interlace gc removes snapshots no environment references (reference-aware: tables shared through reuse survive).

Streaming

Declare a stream; POST to it; rows are durable (SQLite WAL log) before the 200, deduplicated by idempotency key, and materialized exactly-once into streams.<name> — a micro-batch flusher commits the data and the watermark in one warehouse transaction, and SQL models just read the table. A flush triggers the models that consume the stream.

from interlace import stream

@stream("orders", schema={"order_id": "string", "total": "double"},
        idempotency_key="order_id", retention="7d", on_schema_drift="evolve")
def orders(event): ...
curl -X POST localhost:8000/streams/orders -d '{"order_id": "o1", "total": 49.5}'

Schema drift is yours to choose: reject (400), evolve (new columns appear), or quarantine (bad events divert to <stream>__quarantine). When the warehouse falls behind, publishes get 429 backpressure instead of unbounded backlog.

Reverse ETL

Attach external databases and deliver model results into them — the live table is never dropped, keyed modes reuse the same merge strategies:

# interlace.yaml
attach:
  crm: "postgres:host=... dbname=crm"
/* interlace: {export: {to: table, target: crm.public.accounts, mode: merge_by_key, key: id}} */
SELECT id, tier, lifetime_value FROM account_summary

File exports (to: parquet|csv|json) work the same way. Sinks are environment-gated: by default the side effect fires only from prod — a dev apply never writes to a live external table (opt in with environments: [dev, prod]).

Multi-engine

Models run on named engines: DuckDB/DuckLake by default, Postgres natively over ADBC (pip install 'interlaced[adbc]'), with per-model pinning:

engines:
  pg: {type: postgres, database: "${PG_DSN}"}
/* interlace: {engine: pg, strategy: merge_by_key, key: id} */

Strategies execute inside the pinned engine (no DuckDB middleman); cross-engine dependencies appear as explicit transfer lines in the plan and move as Arrow (or a federated ATTACH fast lane when possible). Contract: docs/architecture/MULTI_ENGINE.md.

The daemon

interlace serve runs everything in one process:

  • the web UI at /ui (in-package, zero build step) — ten views: overview, lineage canvas with column-level tracing, models, plan/apply with SQL diffs, live runs, query console, streams, checks, environments, and system — live over SSE;
  • the HTTP API (Litestar + msgspec, OpenAPI at /schema/scalar) with the same surface as the CLI: plan/apply, runs, checks, streams, engines, schedules, lineage, query, gc;
  • the scheduler: cron/interval triggers enqueue onto a durable run queue (leases, retries, cooperative cancellation — interlace cancel <id> or POST /runs/{id}/cancel);
  • stream ingestion and retention.

Scoped API keys (interlace apikey create ci --scope read) lock it down; a durable event log backs GET /events/stream (SSE with Last-Event-ID replay).

Add --quack quack:localhost:4213 to serve the warehouse itself over DuckDB's quack protocol — other processes (CLI runs, ad-hoc DuckDB clients) then share it concurrently by setting database: quack:localhost:4213.

Architecture in five lines

  • The IR is a sqlglot AST; the wire format is an Arrow RecordBatchReader; strategies are AST builders and dialect appears only at transpile().
  • Storage defaults to DuckLake (Parquet + SQL catalog) opened as DuckDB's primary database.
  • Control plane (snapshots, intervals, queue, events, keys) is SQLite WAL; Postgres is the scale-out swap.
  • Streams live in their own durable log; the materializer commits data + watermark in one warehouse transaction — exactly-once without distributed coordination.
  • No Jinja, no pandas in core, no external orchestrator.

The full design rationale lives in docs/architecture/architecture.md.

Development

Toolchain is pinned with proto, tasks run via moon, uv owns the virtualenv:

proto install
moon run interlace:sync      # install deps
moon run interlace:test      # 350+ tests
moon run interlace:check     # black + ruff (CI equivalent)
moon run interlace:typecheck # mypy

MIT licensed.

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

interlaced-1.0.2.tar.gz (178.1 kB view details)

Uploaded Source

Built Distribution

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

interlaced-1.0.2-py3-none-any.whl (212.4 kB view details)

Uploaded Python 3

File details

Details for the file interlaced-1.0.2.tar.gz.

File metadata

  • Download URL: interlaced-1.0.2.tar.gz
  • Upload date:
  • Size: 178.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for interlaced-1.0.2.tar.gz
Algorithm Hash digest
SHA256 b2b2b4c233bbc5bedfe4857fc940415361ec4205c9c3620d3a0ed13c1e10c287
MD5 930ee1806494ac17733bbb214ec0f77f
BLAKE2b-256 e332894a0d909cbb46ccb48d2cd1341045bfa18ebbbabaff178727586c3da958

See more details on using hashes here.

Provenance

The following attestation bundles were made for interlaced-1.0.2.tar.gz:

Publisher: publish.yml on interlace-sh/interlace

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

File details

Details for the file interlaced-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: interlaced-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 212.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for interlaced-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7594b4d13eb9282be8e71eaa6dc2df9b737ab5bb8faa68e41a1d0cc9071a2888
MD5 786be9532ea42d4675cecd71c41e245e
BLAKE2b-256 0ca2229226507fcb6f9ab6bfbb46176da7b8b1c9a6428a4d6590fd7ff25ef64d

See more details on using hashes here.

Provenance

The following attestation bundles were made for interlaced-1.0.2-py3-none-any.whl:

Publisher: publish.yml on interlace-sh/interlace

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