# 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.