Reble
Git-style branches for your Iceberg warehouse. Change a model, run it on an isolated zero-copy branch, review the exact rows that changed, then fast-forward production. No warehouse clones, no copies, no merges.
You know this problem: to test a change to one model you either re-run your pipeline against a copy of the warehouse (slow, expensive, constantly out of date) or you cross your fingers on prod. Reble gives every change its own branch of only the tables it touches — your edited models plus their downstream closure — built on native Iceberg branch refs that cost nothing to create. Before anything reaches production, you see the row-level diff.
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
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.
Runs itself — no manual runs required
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.
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 | incremental
-- key: order_id (diff key; required for incremental)
select ... from stg_orders join raw_customers using (customer_id)
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.
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 (today), Spark (behind the 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.
- Scoped branching — scope = edited models ∪ downstream closure, capped
by
--depth;reble run --refreshscopes by data movement instead (nightly refreshes rebuild exactly what ingested). - Pinned inputs — upstream tables pinned with Iceberg tags
(
reble_pin__*) at run time; tags blockexpire_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 configurableengines.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.
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.ymlschema, command reference, JSON envelope, event streams, provenance, exit codes.DECISIONS.md— recorded behavior decisions.
Requirements
- Python 3.10+
- An Iceberg catalog (Glue, Polaris, Nessie, Hive, or any REST-compliant one)
- SQL models under
models/(path configurable vialineage.models_path)
Status
v0.3 — the full branch → run → diff → promote loop on DuckDB + pyiceberg,
with change-set keying, event streams, catalog-side provenance, the MCP
tool surface for agents, streaming out-of-core reads
(iceberg_scan + spill), and reble estimate. Branch creation is
metadata-only — measured at <10 ms on a 5M-row table. Next: the Spark
runner.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file reble-0.3.2.tar.gz.
File metadata
- Download URL: reble-0.3.2.tar.gz
- Upload date:
- Size: 90.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8bce01c20c96c81dd1180ee511f0168d2a4c44d63a6746ef7159246fa679796
|
|
| MD5 |
9834fb085d60a4ac58b285bf5be33d4a
|
|
| BLAKE2b-256 |
5ae35523b6450146a3f4049c822e8dfa5a42ead86477547ca402803ce631e4ed
|
Provenance
The following attestation bundles were made for reble-0.3.2.tar.gz:
Publisher:
publish.yml on satya1395/reble
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reble-0.3.2.tar.gz -
Subject digest:
b8bce01c20c96c81dd1180ee511f0168d2a4c44d63a6746ef7159246fa679796 - Sigstore transparency entry: 2692927697
- Sigstore integration time:
-
Permalink:
satya1395/reble@95dedbb21636b3324387528aba40419e59f5e511 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/satya1395
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@95dedbb21636b3324387528aba40419e59f5e511 -
Trigger Event:
push
-
Statement type:
File details
Details for the file reble-0.3.2-py3-none-any.whl.
File metadata
- Download URL: reble-0.3.2-py3-none-any.whl
- Upload date:
- Size: 55.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f1e5444d02d27c8d5378b3cb448640690eb3c311482014b372dc9cff79b9981
|
|
| MD5 |
39c77ee92e3ca695bec495d12337a427
|
|
| BLAKE2b-256 |
43bd8a820497a39b213e8505574346afc1b568138596636de03e7ee99933b095
|
Provenance
The following attestation bundles were made for reble-0.3.2-py3-none-any.whl:
Publisher:
publish.yml on satya1395/reble
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reble-0.3.2-py3-none-any.whl -
Subject digest:
6f1e5444d02d27c8d5378b3cb448640690eb3c311482014b372dc9cff79b9981 - Sigstore transparency entry: 2692927742
- Sigstore integration time:
-
Permalink:
satya1395/reble@95dedbb21636b3324387528aba40419e59f5e511 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/satya1395
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@95dedbb21636b3324387528aba40419e59f5e511 -
Trigger Event:
push
-
Statement type: