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.

Status: alpha. See the changelog.

State: vibe coded with Claude Fable 5.1. USE AT YOUR OWN RISK.

Think of tether as DVC for branchable systems: like DVC it commits small manifests into your git/jj repo, but where DVC only fingerprints files, tether also pins and forks live systems. Every backend is fingerprinted; what else tether can do with each 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 ✅ ✅ ✅ ✅ ❌ ✅ ✅
Neon Postgres databases ✅ ✅ ✅ ❌ ❌ ❌ ❌
Apache Iceberg tables ✅ ✅ ✅ ✅ ❌ ✅ ✅
Delta Lake tables 🟡 ❌ ❌ ❌ ❌ ✅ ✅
Lance datasets ✅ ✅ ✅ ❌ ❌ ✅ ✅
lakeFS repositories (experimental) ✅ ✅ ✅ ✅ ✅ ✅ ✅
DuckLake catalogs (experimental) 🟡 ❌ ❌ ❌ ❌ ✅ ✅
Dolt databases (experimental) ✅ ✅ ✅ ✅ ✅ ✅ ✅

Recover: can a committed state be opened again later? ✅ held by a pin; 🟡 reopenable by version id for as long as the system keeps that version (object versioning, Delta/DuckLake snapshots) -- tether records it but has no ref of its own to hold it; ❌ tether can only tell you it changed. Pin: a durable native ref (tag, protected branch) tether creates at commit time. Fork: a writable branch off a pin for tether new. Promote: fast-forward the base branch to the fork; Merge: a native three-way merge when it is not a fast-forward. Diff: tether diff --content describes what changed inside the object. History: tether log lists the object's own snapshots/versions/commits. The full matrix -- state fields, flags, per-backend caveats -- is in the backends guide.

Documentation: https://evanlyall.com/tether/ -- getting started, concepts, pinning, branching and writing, reclaiming storage, use cases, CLI, configuration, backends, registries and SQL, writing a backend, caveats and performance, plus the generated API and CLI reference.

The model

A dataset is a git/jj repository with one small manifest per object, and its bookmarks and the stores' branches are one shape:

  • Commits hold references. Each dataset commit records, per object, an exact state and -- where the system allows it -- a pin: a native, GC-proof ref (an Icechunk tag, a protected Neon branch, a git tag) that holds that state. A commit is a complete, reproducible position for every object at once; tether open KEY --rev C reads it.
  • 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, named after it (tether.ws.<dataset>.feature), forked from the pins of the commit it started at -- decided by tether new -b feature, created on the first write. A working copy on no bookmark is read-only.

commit pins the heads of your bookmark's branches and moves the bookmark to the new commit, the way a git branch follows its commits. promote lands a bookmark on the trunk: each system's upstream branch fast-forwards (or merges) to the bookmark's branch, then main moves to the bookmark's commit. pull is the fetch: on main it reads every object's upstream branch (and, for systems without branches, the object itself) and commits what moved.

Everything history-shaped (commits, bookmarks, workspaces, sharing, undo of the manifests) is the VCS's. What the VCS cannot see -- what tether did to the stores -- is in tether's own operation log (tether ops, tether undo).

Why this exists (prior art)

Every existing tool versions a single layer:

Tool Scope Relationship to tether
lakeFS, Quilt, Oxen, DataChain objects / files analog of our file objects; lakeFS is also a backend
DVC (lakeFS-owned) files/objects in git closest structural analog; no pin/fork of live systems
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 (replikativ) cross-system: Clojure protocol stack (snapshot / branch / merge / watch) over Git, ZFS, Btrfs, IPFS, Iceberg, Datahike, lakeFS, Dolt, Podman, with an HLC-coordinated workspace the closest conceptual sibling; not adoptable from Python (JVM library; its own README marks the Python binding as unmaintained since the initial release)
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).

Install

pip install tether-vcs[cli]                 # core + CLI
pip install tether-vcs[cli,icechunk,neon]   # add backends you need
pip install tether-vcs[all]                 # everything

Extras: cli, objectstore (S3/GCS/Azure for file; s3/gcs/azure are aliases), icechunk, neon, iceberg, delta, lance, lakefs, ducklake, dolt, postgres (tether publish / import against Postgres), all. git/jj must be on PATH.

Quickstart

tether lives inside a git or jj repository and commits small manifests there; that repository's history, branches, and workspaces are the dataset's too. Nothing is contacted until you ask for a state.

jj git init my-dataset && cd my-dataset      # or: git init my-dataset
tether init
tether add zarr/imaging --kind icechunk s3://bucket/imaging.icechunk
tether add db/metrics   --kind neon --project-id prj-123 --database neondb --role runner
tether add raw/plate1   --kind file s3://bucket/raw/plate1/

tether status                       # clean / modified / drifted per object (local; --snapshot re-fingerprints)
tether commit -m "Baseline"         # on main: pin each object's upstream branch, write the manifests, jj/git commit
tether new -b relabel               # a bookmark: one branch per system, named after it, forked on first write
tether open db/metrics              # postgresql://... on the relabel branch
tether commit -m "Relabel plate1"   # pins the branches' heads and moves the bookmark
tether promote                      # land it: each system's main fast-forwards (or merges), then main moves to the commit
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; TETHER_REV=<rev> makes this the default

Every command that writes to a store takes --dry-run (and --plan FILE / --from-plan FILE) so the writes can be reviewed first. The getting started guide walks through this with output; the CLI guide has every command.

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 (pin, commit, move the bookmark), new (create or join a bookmark, then its branches), pull, promote, restore, abandon, forget-workspace, and undo / repair for what tether itself did. Everything that only touches files and history -- describe, squash, rebase, push, jj undo of a non-tether operation -- 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, as does a vanished dataset commit. 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, which the VCS cannot see.
  • Cross-system transactions or a query layer.

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.

License

Apache-2.0. See LICENSE.

Metadata

Release files for tether-vcs 0.1.0a10

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.0a10
File Size Uploaded Provenance Actions
tether_vcs-0.1.0a10.tar.gz 188.1 kB Without provenance Details for tether_vcs-0.1.0a10.tar.gz

Built distribution (wheel)

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

Total release size: 395.3 kB

Release files / tether_vcs-0.1.0a10.tar.gz

Download URL tether_vcs-0.1.0a10.tar.gz
Size 188.1 kB
Tags Source
SHA-256 checksum
How to use checksums
88023e15a3b01f083b55719b413a278126bbe95ece5094ff8f4e4aa3aa2ce426
BLAKE2b-256 checksum
How to use checksums
df0a0ce78da68cc6caa1ffd11e0d2f87d442d6d92f69b6953061f84f19ab8c5c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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.0a10-py3-none-any.whl

Download URL tether_vcs-0.1.0a10-py3-none-any.whl
Size 207.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
811d43862e24dddbb8f07913dcbd43485532cb1a7714597e34584ebc871eca45
BLAKE2b-256 checksum
How to use checksums
2ffdba7a77d47ce57165f08c519ae180eb31cb72cb74462529568466ffcc84d3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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.0a10

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