Skip to main content

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-verifier agent)
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.2.0

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

Source distribution (sdist)

Source distribution for sartre 0.2.0
File Size Uploaded Provenance Actions
sartre-0.2.0.tar.gz 57.0 kB Without provenance Details for sartre-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sartre 0.2.0
File Interpreter ABI Platform Actions
sartre-0.2.0-py3-none-any.whl Python 3 none any Details for sartre-0.2.0-py3-none-any.whl

Total release size: 126.1 kB

Release files / sartre-0.2.0.tar.gz

Download URL sartre-0.2.0.tar.gz
Size 57.0 kB
Tags Source
SHA-256 checksum
How to use checksums
d7d2bd09b0f713140bc948d04245ee27ad71438c8fece0f56a4ae954468ce695
BLAKE2b-256 checksum
How to use checksums
a6383cf4209096d1bfc72129f4491e2c63588ec93116e18c52f89ded7eecc45a
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.2.0-py3-none-any.whl

Download URL sartre-0.2.0-py3-none-any.whl
Size 69.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d92e1b9b5fa6a3c622b0a474f6f3ce8340c1edce5a1ccaebbb9e7dc59b2d436c
BLAKE2b-256 checksum
How to use checksums
e4d4f4d6468dc62026a839769a020dde4762381c7e9a900a4ebaa16883f5e0f4
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.2.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.

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