Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

tether

PyPI Python CI Coverage Docs License

Version control (fingerprints, pins, & forks) for heterogeneous datasets.

A dataset rarely lives in one place: an Icechunk repository, a directory of raw files, a Postgres database, an Iceberg table, and the code that produced them. tether tracks all of them from one git or jj repository. Each commit records every object's exact state -- and, where the system allows it, pins that state with a native ref so it stays readable, and forks a writable branch off it when you want to change things without touching main.

Think of it as DVC for branchable systems: like DVC it commits small manifests to your repository, but where DVC only fingerprints files, tether also pins and forks live systems.

Status: pre-release (0.1.0 betas). ROADMAP.md says what has run against real services, what is still experimental, and what blocks 0.1.0; the changelog says what changed. Vibe coded with Claude Fable 5.1. USE AT YOUR OWN RISK.

Install

pip install "tether-vcs[cli]"                 # core + CLI
pip install "tether-vcs[cli,icechunk,lance]"  # add the backends you use
pip install "tether-vcs[all]"                 # everything

The distribution is tether-vcs; the package you import and the command you run are both tether. Python 3.11 or newer; git 2.38+ and/or jj 0.43+ on PATH (see the CLI guide). Extras: objectstore (S3/GCS/Azure for file), icechunk, neon, iceberg, delta, lance, ducklake, dolt, postgres, all.

Windows: commands that write are refused, since the locks need fcntl; status, verify, diff, log, ops and gc --dry-run work, and a default open is read-only. Use WSL to write.

Quickstart

tether lives inside a git or jj repository. Nothing below needs a cloud account: point it at an Icechunk repository and a directory you have on disk.

jj git init my-dataset && cd my-dataset      # or: git init my-dataset
tether init
tether add zarr/imaging --kind icechunk ../data/imaging.icechunk
tether add raw/plate1   --kind file     ../data/raw/plate1/

tether status                       # new / clean / modified per object
tether commit -m "Baseline"         # pin the Icechunk snapshot; record the directory; jj/git commit
  pinned zarr/imaging -> tether.4e9503fc.73e341e966c83d38
  recorded raw/plate1 (not recoverable)
committed b55326ca55a0

That commit id names the exact state of every object, forever: tether open zarr/imaging --rev b55326ca55a0 opens the pinned snapshot read-only, and TETHER_REV=b55326ca55a0 python report.py makes every repo.open() in a script do the same.

To change data without touching main, work on a bookmark:

tether new -b relabel               # one branch per system, created on first write
python relabel.py                   # writes through repo.open("zarr/imaging")
tether commit -m "Relabel plate1"   # pin the branch head; move the bookmark
tether promote                      # fast-forward main in every system, then move the main bookmark
tether verify --all-history         # every pin any commit ever named still resolves
from tether import Repo

repo = Repo.find(".")
h = repo.open("zarr/imaging")  # writable IcechunkHandle on this bookmark's branch
ro = repo.open("zarr/imaging", rev="main")  # read-only at main's pin

The commands that plan before they write -- commit, new, restore, promote, gc, drop, forget-workspace, import, repair, upgrade -- take --dry-run (and --plan FILE / --from-plan FILE) so the store writes can be reviewed first. The Getting Started guide runs this walkthrough with full output; the user guide takes it from there.

How it works

  • A dataset is a git/jj repository with one small manifest per object. History, branching, workspaces, and sharing are the VCS's.
  • Commits hold pins. Each dataset commit records, per object, an exact state and -- where the system allows -- a pin: a native, GC-proof ref (an Icechunk tag, a Neon branch, a git tag) that holds that state. tether open KEY --rev C reads it back.
  • Bookmarks hold branches. The trunk bookmark (main) stands for every object's upstream branch; working on it writes there. Any other bookmark stands for one branch per system, tether.ws.<dataset>.<bookmark>, forked from the pins where it started and created on the first write.
  • Three verbs. commit pins the bookmark's branch heads and moves the bookmark. promote moves each system's upstream branch to those heads, then moves the main bookmark. pull reads what upstream has now and commits it.
  • tether is never in the data path. open returns the system's native handle -- an Icechunk session, a Postgres URL, a DeltaTable -- and steps aside.

What the VCS cannot see -- what tether did to the stores -- is in tether's own operation log (tether ops, tether undo, tether repair).

What each backend can do

Every backend is fingerprinted; the rest depends on what the system offers.

Backend Recover Pin Fork Promote Merge Diff History
local files and directories; object-store prefixes (S3, GCS, Azure) ❌ ❌ ❌ ❌ ❌ ✅ ❌
single object-store objects with versioning enabled 🟡 ❌ ❌ ❌ ❌ ✅ ❌
git / jj repositories ✅ ✅ ✅ ✅ ✅ ✅ ✅
Icechunk repositories ✅ ✅ ✅ ✅ ❌ ✅ ✅
Lance datasets ✅ ✅ ✅ ❌ ❌ ✅ ✅
Delta Lake tables 🟡 ❌ ❌ ❌ ❌ ✅ ✅
Neon Postgres databases (experimental) ✅ ✅ ✅ ❌ ❌ ❌ ❌
Apache Iceberg tables (experimental) ✅ ✅ ✅ ✅ ❌ ✅ ✅
DuckLake catalogs (experimental) 🟡 ❌ ❌ ❌ ❌ ✅ ✅
Dolt databases (experimental) ✅ ✅ ✅ ✅ ✅ ✅ ✅

Recover: can a committed state be opened again later? ✅ held by a pin; 🟡 re-openable by version id for as long as the system keeps that version; ❌ tether can only tell you it changed. Pin: a durable native ref created at commit time. Fork: a writable branch off a pin for tether new. Promote / Merge: fast-forward, or three-way merge, the upstream branch to the fork. Diff: tether diff --content describes what changed inside the object. History: tether log lists the object's own snapshots. Experimental backends have run against a fake of the service, not the service itself. Stable ones run their full lifecycle in CI; for file and Icechunk that means local storage, and their S3, GCS and Azure paths have not run against a cloud service yet (see the roadmap). The full matrix -- state fields, flags, per-backend caveats -- is in the backends guide.

jj or tether?

A tether command exists where an operation has two halves -- one in the VCS, one in the stores -- that must happen together: commit, new, pull, promote, restore, abandon, drop, forget-workspace, and undo / repair for what tether itself did. Everything that only touches files and history -- describe, squash, rebase, push -- is the VCS's, and tether notices what it needs to: a bookmark deleted, renamed, or moved by hand shows up in status with what to do. The concepts guide has the table.

Non-goals

  • Sitting in the data path: open hands back the system's native handle and steps aside.
  • Running between commands: no daemon, no watcher; states are compared when a command asks.
  • Reimplementing history, branching, or sharing: that is the VCS's job. tether ops / undo / repair cover only what tether did to the stores.
  • Cross-system transactions or a query layer.

Documentation

https://evanlyall.com/tether/ -- getting started, concepts, worked examples, CLI guide, backends, sharing and CI, troubleshooting, and the generated API and CLI reference.

Why this exists

Every existing tool versions a single layer:

Tool Scope Relationship to tether
DVC (lakeFS-owned) files/objects in git closest structural analog; no pin/fork of live systems
lakeFS, Quilt, Oxen, DataChain objects / files analog of our file objects
Dolt, pgGit, Neon, Databricks Lakebase one database vendor-bound; no external-object pins; Dolt and Neon are backends
Nessie, Bauplan Iceberg catalog branching Iceberg-only
Icechunk, Lance, Delta, DuckLake one dataset / table / catalog we use them as backends
Yggdrasil cross-system snapshot / branch / merge over Git, ZFS, IPFS, Iceberg, lakeFS, Dolt, ... the closest conceptual sibling; a JVM library, not adoptable from Python
Dagster observable assets staleness detection analog of our snapshot/drift step; no branching

Nothing provides unified version control across files + Icechunk + Postgres + Iceberg with pinning and forking. So tether borrows jj's working-copy and operation-log ideas (new REV, stale-working-copy detection, undo), DVC's manifests-in-VCS layout, and Yggdrasil's observe-then-record shape: a workspace that watches independent systems and records their snapshots, rather than a store that holds the data.

Development

uv sync --all-extras
uv run ruff format --check . && uv run ruff check .
uv run ty check
uv run pytest

The publish / import tests start an ephemeral PostgreSQL cluster through pytest-postgresql; they need pg_ctl on PATH or a Homebrew / Debian install (brew install postgresql@16; GitHub's Ubuntu runners ship it) and skip otherwise. The use-cases story (tests/test_use_cases.py) regenerates the guide's command output with TETHER_UPDATE_DOCS=1.

License

Apache-2.0. See LICENSE.

Metadata

Release files for tether-vcs 0.1.0b4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tether-vcs 0.1.0b4
File Size Uploaded Provenance Actions
tether_vcs-0.1.0b4.tar.gz 296.7 kB Without provenance Details for tether_vcs-0.1.0b4.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for tether-vcs 0.1.0b4
File Interpreter ABI Platform Actions
tether_vcs-0.1.0b4-py3-none-any.whl Python 3 none any Details for tether_vcs-0.1.0b4-py3-none-any.whl

Total release size: 620.5 kB

Release files / tether_vcs-0.1.0b4.tar.gz

Download URL tether_vcs-0.1.0b4.tar.gz
Size 296.7 kB
Tags Source
SHA-256 checksum
How to use checksums
7b5c138e44a98d32d19af5781c574b2961ec45874a4f2519a743aabe42689525
BLAKE2b-256 checksum
How to use checksums
b82bfd345349f6882068aa820f02d6d9eee08b57398fe33367dde4990dfca63d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","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}

Release files / tether_vcs-0.1.0b4-py3-none-any.whl

Download URL tether_vcs-0.1.0b4-py3-none-any.whl
Size 323.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f5dfb40a98d45c070547a0f111e5f6f4725bda5851cdf7e50c4fd5ecba0d6cfc
BLAKE2b-256 checksum
How to use checksums
3dc61dbc5eabdcb77dc2f2e952cf4a8fc3b5b949274333c69b1df56d62e64e92
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","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}

Security for tether-vcs 0.1.0b4

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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