# snapdir for Python The Python binding wraps the same Rust core (`snapdir-api` → `snapdir-core`) as the CLI, so the manifests and snapshot IDs it produces are bit-identical to `snapdir` and every other binding. Snapshot IDs are 64-character lowercase hex BLAKE3 digests. The surface is the same everywhere: synchronous `id` / `manifest`, plus async `push` / `pull` / `fetch` / `diff` / `sync`, in Python's idiom. ## Install The binding is a prebuilt `abi3` wheel — the Rust core is compiled in, so there is nothing to build: ```sh pip install snapdir ``` Requires Python 3.10 or newer. PyPI serves prebuilt `manylinux` and `musllinux` wheels (x86-64 + aarch64) and macOS wheels; the `musllinux` wheel runs on Alpine with no compiler. See [Install](/install/python/) for the platform matrix. ## Usage `id` and `manifest` compute content-addressed data without touching a store; the I/O operations (`push`, `pull`, `fetch`, `diff`, `sync`) are `async`. The store URI is always an argument, wrapped in `StoreUri`; snapshot IDs are wrapped in `SnapshotId`. Use `file://$PWD/store` for a local store and `gs://bucket/prefix` for Google Cloud. ```python import asyncio import os import snapdir from snapdir import DiffOptions, SnapshotId, StoreUri async def main() -> None: src = "./project" store = StoreUri(f"file://{os.getcwd()}/store") # Content-addressed id + manifest (no store I/O). snap_id = await snapdir.id(src) manifest = await snapdir.manifest(src) print(snap_id) # 64-char lowercase hex, e.g. for entry in manifest.entries: print(entry.path_type, entry.path, entry.size) # Stage + upload; returns the snapshot id. pushed = await snapdir.push(src, store) # Download into the local cache, then materialise into a directory. await snapdir.fetch(SnapshotId(pushed), store) await snapdir.pull(SnapshotId(pushed), store, "./restored") # Diff two pinned snapshots. The diff ref convention is store@id # (the last @ splits the URI from the 64-hex id). opts = DiffOptions.from_refs( [f"{store}@{snap_id}"], [f"{store}@{pushed}"], ) for change in await snapdir.diff(opts): print(change.status, change.path) # A / D / M / = asyncio.run(main()) ``` Swap the `file://` store for a `gs://…`, `s3://…`, or `b2://…` URI and the same calls publish to and restore from the cloud. For S3-compatible endpoints, only the endpoint and credentials come from the environment (`SNAPDIR_S3_STORE_ENDPOINT_URL`, `AWS_*`) — the store URI stays an argument. ## Errors Every failure raises a subclass of `SnapdirError`, which carries a stable `.code` string drawn from the 8-code taxonomy shared by all bindings. Match on the class or the code: ```python import snapdir from snapdir import SnapshotId, StoreUri try: await snapdir.pull(SnapshotId(""), StoreUri("file:///srv/store"), "./restored") except snapdir.SnapdirError as err: print(err.code) # e.g. "HASH_MISMATCH" ``` | Code | Meaning | | --- | --- | | `IO_ERROR` | Local filesystem read/write failure. | | `HASH_MISMATCH` | Fetched content did not match its checksum. | | `STORE_ERROR` | Store-level I/O or protocol error. | | `IN_FLUX` | Snapshot was still being written (concurrent modification). | | `CATALOG_ERROR` | Local catalog / cache integrity error. | | `INVALID_ID` | Malformed snapshot ID (not 64-hex). | | `INVALID_STORE` | Malformed or unsupported store URI. | | `CONFLICT` | Destination already holds conflicting content. | ## Platform support | Platform | Supported | | --- | --- | | Linux glibc (x64 + arm64) | yes | | Linux musl / Alpine (x64 + arm64) | yes | | macOS (Intel + Apple Silicon) | yes | | Windows | no | The Python wheel runs on Linux (glibc and Alpine/musl, x64 and arm64) and macOS with no libc caveat. Only the Java binding is glibc-only today (musl planned for 1.11.1). Windows is unsupported: snapdir is Unix-only.