Skip to main content

Reble

CI PyPI License: Apache-2.0

Reble — pronounced re-bl (the final e is silent).

Reble is an open SQL engine for your Iceberg lakehouse. Your models are plain SQL files; Reble derives their dependencies, builds the tables into your catalog and bucket, and refreshes exactly what moved — triggered by cron, CI, Airflow, or an agent.

Every data team ends up building the same expensive hack around that job: a copy of the warehouse for testing changes. You refresh it, you queue for it, you hope it still matches prod — and you pay for it twice. The Apache Iceberg table format already has the primitive that makes it unnecessary: a branch ref is metadata-only, so a "copy" of a 5M-row table costs 10 ms and zero bytes on any compliant catalog. What was missing is the workflow — deciding what a change touches, making its inputs reproducible, showing what it will do to production rows before anyone accepts it.

Reble is that workflow, and it isn't bolted on: scope, pin, run, diff, promote are what the engine does. When you change a model, it branches, re-runs only the blast radius — your edited models plus their downstream closure, derived from the SQL — shows you the exact rows that will change, and fast-forwards production when you accept. There is no merge step, on purpose: promote or discard, never three-way-merge data, because data merges are where correctness goes to die.

The Reble loop: build, edit on a branch, diff the rows, promote

How it works

flowchart TB
    WHO["who triggers — cron · CI · Airflow · AI agents (MCP)"]
    MODELS["your models — models/*.sql, plain SQL + a 3-line header"]
    REBLE["Reble — SQLGlot lineage · scope · pin · run · diff · promote"]
    ENGINE["compute — DuckDB (default) · Spark (same interface)"]
    CAT["your Iceberg catalog — Glue · Polaris · Nessie · Hive · REST · sql"]
    STORE[("your storage — S3 · GCS · local disk")]
    WHO -->|"invokes one verb"| REBLE
    MODELS --> REBLE
    REBLE --> ENGINE
    ENGINE -->|"branch refs · tag pins · snapshots"| CAT
    CAT --> STORE

Reble owns the transformation layer — models, lineage, execution, branching — the shape dbt-core has, without the templating or YAML. It does not own scheduling: cron or Airflow decides when; Reble is the step they run. And it's built on native Iceberg branch refs — a per-table Iceberg spec feature supported by any catalog (Glue, Polaris, Nessie, Hive, or any REST-compliant catalog). It is not a catalog and requires no new infrastructure. A branch ref is metadata-only: zero bytes are copied.

Quick start

pip install reble
reble init                # writes reble.yml; probes your catalog
git switch -c fix-orders  # or: --change-set agent-42 — git is one adapter
# ...edit two models...
reble run                 # → data branch: edited models + downstream closure
                          #   written; upstream inputs pinned via Iceberg tags
reble diff                # schema + row-level diff vs. branch base
reble status              # un-run edits, drifted pins, branch age/expiry
reble promote             # fast-forward if base is current; forced re-run with
                          #   fresh diff if main moved. No merge. Ever.

What Reble is — and isn't

  • Is: a transformation engine (models + lineage + execution) that works with the Iceberg catalog you already run (Glue, Polaris, Nessie, Hive, any REST catalog). No server, no new infrastructure.
  • Isn't: a scheduler (cron/Airflow's job — Reble is the step they run), a catalog, or a merge tool. There is no three-way data merge, ever — a change is either fast-forwarded or re-run.
  • See how Reble compares to lakeFS, Nessie, and warehouse clones.

The loop in detail

flowchart LR
    M[("main<br/>(Iceberg tables)")]
    E["edited SQL"] -->|"scope: AST-changed ∪<br/>downstream closure"| RUN["reble run"]
    M -->|"upstream inputs pinned<br/>via Iceberg tags"| RUN
    RUN -->|"zero-copy branch refs"| B[("data branch")]
    B --> D["reble diff<br/>rows + schema"]
    D --> P{"reble promote"}
    P -->|"pinned bases still<br/>equal main"| FF["fast-forward main"]
    P -->|"drift"| RR["scoped re-run +<br/>fresh promote-time diff"]
    RR --> FF
  • Scoped branching — scope = edited models ∪ downstream closure, capped by --depth; reble run --refresh scopes by data movement instead (nightly refreshes rebuild exactly what ingested).
  • Pinned inputs — upstream tables pinned with Iceberg tags (reble_pin__*) at run time; tags block expire_snapshots, so branch reads stay correct even while main moves.
  • Row-level diffs — computed on your compute via DuckDB, streaming through iceberg_scan (out-of-core; spills under a configurable engines.duckdb.memory_limit).
  • Promote semantics — fast-forward only when every pinned base still equals current main; otherwise a scoped re-run and a fresh, promote-time diff. The PR diff is advisory; the promote diff is authoritative.
  • Atomic per-table commits — every model write and every promotion step commits a table atomically: no partial states, no half-applied changes, interrupted work resumes instead of repeating. Cross-table consistency is verified at promote time (every pin still equals main) — not reconciled afterward by a merge you have to trust.

Performance

Measured, reproducible, no clusters — full numbers and reproduction commands on the performance page:

  • Branch a 5M-row table: < 10 ms. Branches are metadata-only.
  • Full lifecycle on AWS (Glue + S3, 1M rows, from a laptop): scoped run ~13 s, keyed diff ~4 s, drift check ~2 s — with streaming reads verified engaged (iceberg_scan, zero fallbacks).
  • Reads spill under a configurable memory_limit — working set bounded by config, not by RAM.

Models are plain SQL

"Model" is just our word for one SQL file that creates one table. If your team keeps a folder of SQLs and schedules them some way — a DAG, cron, an internal webapp — you already have models; point Reble at the folder. No orchestrator required, no dbt required: models/**/*.sql, one file is one model, the file stem is the table name, and a minimal header comment block carries the semantics:

-- model: mart_orders      (optional; defaults to file name)
-- kind: table | view
-- key: order_id           (diff key)
select ... from stg_orders join raw_customers using (customer_id)

Every run fully rebuilds its scope — replace, never append — and reble run --force rebuilds even unchanged SQL. (There is deliberately no incremental kind yet: it arrives when watermark / insert-overwrite execution is real, not as a word that recomputes everything.)

Lineage is parsed with SQLGlot: a table reference that matches another model is an edge; anything else is an upstream input, pinned with an Iceberg tag at run time. Cosmetic edits (whitespace, comments, casing) hash identically on the canonical AST and never trigger a run. Every branch snapshot carries provenance (reble.model, reble.ast_hash, reble.run_id) in its summary — "which code produced this table state" is answered from the catalog itself.

Set it up once, it runs itself

No manual runs. Put one command in cron, CI, or Airflow — the same command whether you have two models or two hundred:

# nightly: rebuild exactly what changed
0 3 * * * cd /srv/warehouse && reble run --refresh && reble gc

Safe to trigger repeatedly: a night with no new data does nothing. A crashed run resumes from where it stopped. A retried promote never double-applies.

For teams, state lives in Postgres so multiple workers share it safely (one line in reble.yml, validated at startup — no shared filesystem needed):

state:
  store: postgres
  uri: ${REBLE_STATE_URI}   # postgresql://user:pass@host:5432/reble

Install with pip install 'reble[postgres]'.

The verbs are idempotent and the exit codes are a contract, so any job can drive Reble: scheduled, or triggered by whatever lands your data. A double trigger is harmless — a quiet night computes an empty scope from one catalog listing.

# .github/workflows/refresh.yml — rebuild exactly what moved, nightly and
# on demand (your ingestion job can dispatch it when new data lands)
on:
  schedule: [{cron: "0 3 * * *"}]
  workflow_dispatch:
jobs:
  refresh:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install reble
      - run: reble run --refresh     # + reble gc to drop expired branches
        env: { REBLE_CHANGE_SET: local }   # catalog/warehouse creds as secrets

PRs get the same treatment: reble status exits 3 on drift and reble diff prints the row-level consequences — cheap checks to wire into any CI.

Agents (MCP)

Any MCP host can drive the same verbs — the agent has no special powers:

{
  "mcpServers": {
    "reble": {
      "command": "reble-mcp",
      "env": { "REBLE_PROJECT_DIR": "/path/to/project" }
    }
  }
}

Install with pip install 'reble[mcp]'. reble_run generates and returns a change-set id; errors carry the spec exit codes as structured error.code (3 = drift, 4 = promote-blocked). Tool docstrings are the agent-facing spec.

Agents and CI are first-class everywhere, not just over MCP: every command speaks a stable --json envelope with documented exit codes, run/diff stream versioned --events (NDJSON), and work is keyed by change-set (--change-set <id> or REBLE_CHANGE_SET) so it never depends on git — --branch resumes an existing data branch under a new change-set.

Documentation

  • Docs site — getting started, concepts, comparisons, and the command reference.
  • SPEC.md — normative CLI specification (v0.2): invariants, on-disk layout, reble.yml schema, command reference, JSON envelope, event streams, provenance, exit codes.
  • DECISIONS.md — recorded behavior decisions.

Requirements

  • Python 3.10–3.13 (3.14 not yet tested)
  • An S3 bucket + an Iceberg catalog (Glue, Polaris, Nessie, Hive, or any REST-compliant one) — on AWS, pip install 'reble[aws]' and follow the AWS walkthrough (covers bucket creation, credentials, and every step from zero)
  • SQL models under models/ (path configurable via lineage.models_path)

Roadmap to 1.0

Release Theme Highlights Status
0.4 Runs on AWS Glue + S3 verified end-to-end, credential auto-config, self-cleaning AWS smoke ✅ shipped
0.5 Bigger warehouses Spark runner (local first, then serverless); GCS + ADLS verification; partitioned tables; incremental execution (watermark / insert-overwrite) Spark runner ✅ (0.6.0); rest ongoing
0.6 Backfills & teams Date-range / partition-scoped backfills (branch + insert-overwrite); documented CI recipes (PR checks, promote gates); multi-writer etiquette; reble doctor planned
0.7 Interop REST catalogs verified (Polaris, Nessie); Trino read adapter on demand; Iceberg views planned
0.8 Operations Metrics/log hooks; estimate v2; Windows support planned
1.0 GA See criteria below

GA criteria — 1.0 ships when, not before: the JSON envelope, event streams, and exit codes have held stable through a full minor release; the lifecycle is green in CI on Glue + one REST catalog + sql; both engines (DuckDB, Spark) are real; at least three non-trivial warehouse deployments have run promote in production; and a security pass is done.

License

Apache-2.0.

Download files

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

Source Distribution

reble-0.6.0.tar.gz (902.5 kB view details)

Uploaded Source

Built Distribution

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

reble-0.6.0-py3-none-any.whl (70.8 kB view details)

Uploaded Python 3

File details

Details for the file reble-0.6.0.tar.gz.

File metadata

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

File hashes

Hashes for reble-0.6.0.tar.gz
Algorithm Hash digest
SHA256 5274cc387337dfa844e9a62179fc40765c1ef0f4c86dbc479ac6b35a46219ddc
MD5 e0a39ff93b8ddb7028fc3efc3da820b1
BLAKE2b-256 364e076693d2a947e931fc5dfd152d2ab4a9c3fad83ca9ba6f101fb4435ef8c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for reble-0.6.0.tar.gz:

Publisher: publish.yml on satya1395/reble

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

File details

Details for the file reble-0.6.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for reble-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1d194a7facdce6c4a84ee722379ab89011d05d5bc54dfd25ef77793c8c488db7
MD5 05ff2c562f1a28bd1dad28bd34a7a14a
BLAKE2b-256 191df701ada9d343474638e9484ba2044d7635413d70c2b52c458c25299842e3

See more details on using hashes here.

Provenance

The following attestation bundles were made for reble-0.6.0-py3-none-any.whl:

Publisher: publish.yml on satya1395/reble

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

Release history Release notifications | RSS feed

0.6.1

2 files

This release

0.6.0 This release

2 files

0.5.1

2 files

0.5.0

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

0.0.9

2 files

0.0.8

2 files

0.0.7

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

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