Skip to main content

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.

pip install 'interlaced[service]'   # the CLI + daemon; core CLI only: pip install interlaced
# more extras: [adbc] postgres/redshift · [spark] · [polars] · [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
  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", 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: replace, view, ephemeral (CTE-inlined), merge (upsert), full_merge (full-state source applied as a minimal diff), incremental_by_time (windowed, interval-ledger backfill/catchup), scd (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, 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]'), Spark (beta, [spark] extra), plus alpha adapters for MotherDuck, Redshift, Snowflake and BigQuery (wired and dialect-correct, not yet run against a live account), with per-model pinning:

engines:
  pg: {type: postgres, database: "${PG_DSN}"}
/* interlace: {engine: pg, strategy: merge, 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.

Download files

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

Source Distribution

interlaced-2.0.3.tar.gz (190.0 kB view details)

Uploaded Source

Built Distribution

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

interlaced-2.0.3-py3-none-any.whl (228.9 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for interlaced-2.0.3.tar.gz
Algorithm Hash digest
SHA256 0bf178d7a74afbaf85df2f6c37d590af8e78354a678d8c2b3e7b658fb280473f
MD5 c849d407d6359996111598959b38d7a0
BLAKE2b-256 e6a40d555d6274f863584e9332e034aa5200c44db2c6de5e9c1b47325c6b1538

See more details on using hashes here.

Provenance

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

File metadata

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

File hashes

Hashes for interlaced-2.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 a42ba4f048fbedd24c2f5808d6a23df868504725596eb0dcfa3142fed9dd3e2e
MD5 28322a2acc2c3bcb86cd429d1b0b4d18
BLAKE2b-256 08a726c26aca789df81a7f820055289f202365374c88f87b28bc16be826b3ad5

See more details on using hashes here.

Provenance

The following attestation bundles were made for interlaced-2.0.3-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.

Release history Release notifications | RSS feed

2.4.1

2 files

2.4.0

2 files

2.3.0

2 files

2.2.0

2 files

2.1.0

2 files

This release

2.0.3 This release

2 files

2.0.2

2 files

2.0.1

2 files

2.0.0

2 files

1.0.2

2 files

1.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page