sartre
Simple ARTifact REpository — a generic, content-addressed versioned binary artifact repository for Python.
"Hell is other people's artifact repositories" — Jean-Paul Sartre
Publish a tree of files as an immutable, content-addressed version; move
mutable pointers (head, or named aliases like stable/prod) at it; fetch
any version back — lazily, verified, and deduplicated by content hash. Every
change records who made it and why.
Two cleanly separated planes:
- Manifest plane (
Registry) — a transactional metadata store mapping(name, env, version, path) → content_hash, plus mutable pointers (Head/Alias/Pin) that resolve to immutable versions. Backed by SQLite (local) or Postgres (shared). - Blob plane (
Store) — a dumb content-addressed object store holding immutable bytes keyed by their own hash, over any fsspec filesystem (local disk, S3, GCS, …). Dedup is structural; GC is mark-and-sweep.
A Version is the hash of its manifest entries, so identical content is stored
once and promotes across environments for free. Reads never materialize a whole
blob into memory, so multi-hundred-megabyte artifacts stream.
Install
pip install sartre # library core (fsspec only)
pip install "sartre[cli]" # + the `sartre` command line
| Extra | Pulls in | For |
|---|---|---|
cli |
typer |
the sartre command-line tool |
postgres |
psycopg |
a shared Postgres manifest plane |
s3 |
boto3, s3fs |
S3 blob storage |
gcs |
gcsfs |
Google Cloud Storage blob storage |
Requires Python 3.12+. The package ships type information (PEP 561 py.typed).
Quickstart — command line
A local repository is just a directory (registry.db + blobs/):
# publish a directory as a new version, attributing who and why
sartre --repo ./repo publish models/prod ./checkpoint --as alice -m "initial train"
# → sha256:554b7a…
# ...change the files, publish again — a new version, head advances
sartre --repo ./repo publish models/prod ./checkpoint --as alice -m "retrain on Q3"
# promote a specific version to a named pointer (compare-and-swap safe)
sartre --repo ./repo point models/prod:stable sha256:554b7a… --as bob -m "passed eval"
sartre --repo ./repo show models/prod # version, author, reason, entries
sartre --repo ./repo log models/prod # commit history with author + reason
sartre --repo ./repo history models/prod # who moved which pointer, from → to, why
sartre --repo ./repo ls models/prod:stable # list a version's files (no download)
sartre --repo ./repo cat models/prod cfg.json # stream one file (verified) to stdout
sartre --repo ./repo checkout models/prod ./out # materialize the whole tree
Reference grammar: name/env (head) · name/env:alias · name/env@sha256:… (pin).
Add --json to any command for machine-readable output. head/ls are bare for
scripting. Point a command at a shared repo with --registry <dsn> --blobs <url>,
$SARTRE_REPO, or a profile in ~/.config/sartre/config.toml.
Quickstart — library
from pathlib import Path
from sartre import open_local, Coordinate, Alias
repo = open_local("./repo") # SQLite + local blobs; reopen to recover
coord = Coordinate("models", "prod")
# publish a mapping of logical path -> bytes (or Path); returns the version id
version = repo.publish(
coord,
{"w/model.bin": b"...weights...", "cfg.json": b"{}"},
actor="alice",
reason="initial train",
)
# resolve a pointer to an immutable snapshot (manifest only — no blob bytes yet)
snap = repo.resolve(coord) # head by default
print(snap.version, [e.path for e in snap.entries])
# materialize bytes lazily, verified and content-cached
local_path = repo.open(snap, "cfg.json") # one file
repo.checkout(snap, Path("./out")) # the whole tree
# move a mutable pointer to an existing version (promote / roll back)
repo.point(coord, "stable", version, expected=None, actor="bob", reason="passed eval")
assert repo.head(coord, Alias("stable")) == version
# provenance is on the event, not the shared manifest
for entry in repo.list_log(coord): # who published/promoted each tip, and why
print(entry.version, entry.actor, entry.reason)
for move in repo.list_pointer_history(coord): # the pointer-move audit trail
print(move.name, move.from_version, "->", move.to_version, move.actor)
A shared, cloud-backed repository
from sartre import open_cloud
repo = open_cloud(
"postgresql://user:pw@host/db", # Postgres manifest plane (needs the `postgres` extra)
"s3://my-bucket/artifacts", # any fsspec blob URL (needs `s3`/`gcs` as appropriate)
cache_dir="/var/cache/sartre", # optional local read-through cache
)
The API is identical; only the backends differ. AsyncRepository wraps a
Repository with awaitable equivalents.
What you get
- Content-addressed & deduplicated — a version is its manifest hash; identical files are stored once and promote across environments with no copy.
- Change provenance — every commit and every pointer move records
actor+reason; the pointer-move history answers "who changed prod, from what, and why." - Streaming — publish and fetch never buffer a whole blob in memory.
- Safe concurrency — pointer moves are compare-and-swap; publishes hold a TTL lease so a concurrent garbage collector never reclaims in-flight blobs (the GC/lease protocol is verified in TLA+).
- Garbage collection — mark-and-sweep with retention policies
(
keep_last_n,keep_within, a blob grace period).
See binary-artifact-repo-design.md for the
full design memo.
Development
This project uses:
- uv — Python environment & packaging (
uv sync,uv run pytest) - jj (colocated with git) — version control
- nix (
shell.nix+direnv) — reproducible dev shell - OpenSpec — spec-driven change workflow (
openspec/,/opsx:*skills) - TLA+ — protocol model checking (
tla-*skills,tla-verifieragent)
nix-shell # or: direnv allow
uv sync # create .venv and install dev tooling
uv run pytest # run tests
uv run ruff check # lint
uv run pyright # type-check
Layout
src/sartre/ library source (registry backends, stores, repository, CLI)
tests/ tests (unit, property-based, differential, e2e)
openspec/ spec-driven change proposals & living specs
binary-artifact-repo-design.md design memo
License
Licensed under the Apache License 2.0 — permissive, commercial-use
friendly, with an explicit patent grant. See NOTICE.
Metadata
Release files for sartre 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| sartre-0.1.0.tar.gz | 50.6 kB | Without provenance | Details for sartre-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| sartre-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for sartre-0.1.0-py3-none-any.whl |
Total release size: 113.2 kB
Release files / sartre-0.1.0.tar.gz
| Download URL | sartre-0.1.0.tar.gz |
|---|---|
| Size | 50.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bdb5b9bd10de7d8f669dc2f6d6ae7f2ce6d94cb589ccf66cda69115acbd8f96b
|
|
BLAKE2b-256 checksum How to use checksums |
99ed4598806e06106ce5cee960abe3f5034896da2b6012d92f253e280436a725
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.11","id":"zokor","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / sartre-0.1.0-py3-none-any.whl
| Download URL | sartre-0.1.0-py3-none-any.whl |
|---|---|
| Size | 62.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5be4d7710ac1397a6bba3f5b9b2d7c6118512276043c4912ca18a827c7732f32
|
|
BLAKE2b-256 checksum How to use checksums |
e461f545e94edb4694212c7846356d044cda084552c4a2f41bf7e4bec12b9115
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.11","id":"zokor","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for sartre 0.1.0
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.