duckdb-safe-snapshot
Create private, checksummed snapshot sets of a DuckDB database and its WAL while cooperating writers share one external file lock. It is a small Linux library and CLI for applications that own their own scheduling and recovery process.
The image is an illustrative static terminal transcript with synthetic values; it is not captured output from a snapshot run.
python -m pip install duckdb-safe-snapshot==0.1.0
Every path and owner policy is explicit; the package has no application, account, service, or production-path defaults.
duckdb-safe-snapshot \
--database /var/lib/example/app.duckdb \
--wal /var/lib/example/app.duckdb.wal \
--lock /run/example/writer.lock \
--backup-root /var/backups/example/snapshots \
--state-root /var/lib/example/snapshot-state \
--source-owner-uid 1001 \
--lock-owner-uid 1001 \
--snapshot-owner-uid 1002 \
--database-artifact database.duckdb \
--wal-artifact app.duckdb.wal \
--metadata-json '{"application":"example"}' \
--release release-42 \
snapshot --keep 7
duckdb-safe-snapshot \
--database /var/lib/example/app.duckdb --wal /var/lib/example/app.duckdb.wal \
--lock /run/example/writer.lock \
--backup-root /var/backups/example/snapshots \
--state-root /var/lib/example/snapshot-state \
--source-owner-uid 1001 --lock-owner-uid 1001 --snapshot-owner-uid 1002 \
--database-artifact database.duckdb --wal-artifact app.duckdb.wal \
verify latest
The JSON response includes the timestamped snapshot id, artifact SHA-256 values, and pruned recognized snapshots. Copy the database artifact and, when present, the WAL artifact to the same recovery location before opening the recovery copy.
Safety model
This package does not make an uncoordinated DuckDB writer safe. Every writer
that can modify the database or WAL must acquire the exact same Linux flock
lock before changing either file. The snapshot takes that exclusive lock, checks
source identity before and after copying, and fails if it observes a change.
Completed snapshot directories are created privately (0700) and contain only
the configured database artifact, optional WAL, a canonical JSON manifest, and
its SHA-256 checksum (0600). A temporary directory becomes visible as a
completed set only through rename; the set is then verified before latest
is atomically updated. Verification rejects symlinks, hardlinked files,
unexpected ownership or modes, missing/extra entries, malformed manifests, and
checksum or size mismatches. Retention deletes only recognized, already-verified
timestamped sets.
Config resolves existing path aliases and rejects relative paths, output-root
aliases, output-root nesting, and
database/lock paths inside private output roots. It also requires all three
owner UIDs and simple, distinct database/WAL artifact names. The caller must
create the shared lock under an ownership/mode policy accepted by Config.
For a service launcher that acquires this lock before selecting application
code, inherited_lock_fd may carry that open descriptor into create_snapshot.
It must be a non-negative integer descriptor for the exact configured lock;
the package validates its inode and reuses it without closing or unlocking it,
so the launcher retains the lock for the full child-process lifetime.
This is intentionally not a restore tool, service manager, or scheduler. Its
file checks protect leaf files and configured private roots; callers must also
keep each parent directory trusted against replacement. Use a
disposable recovery copy to test your own restoration procedure. The package
uses POSIX fcntl.flock, O_NOFOLLOW, directory fsync, and POSIX permissions;
it is supported on Linux filesystems with those semantics. Network filesystems
and writers that do not honor the shared lock are outside its consistency claim.
Python API
from pathlib import Path
from duckdb_safe_snapshot import Config, create_snapshot, verify_latest
config = Config(
database_path=Path("/var/lib/example/app.duckdb"),
wal_path=Path("/var/lib/example/app.duckdb.wal"),
lock_path=Path("/run/example/writer.lock"),
backup_root=Path("/var/backups/example/snapshots"),
state_root=Path("/var/lib/example/snapshot-state"),
source_owner_uid=1001,
lock_owner_uid=1001,
snapshot_owner_uid=1002,
database_artifact_name="database.duckdb",
wal_artifact_name="app.duckdb.wal",
metadata={"application": "example"},
release=lambda: "release-42", # optional caller-owned identifier
)
created = create_snapshot(config, keep=7)
assert verify_latest(config)["snapshot_id"] == created["snapshot_id"]
Compatibility and maintenance
The source requires Python 3.10–3.12. CI tests those Python versions with DuckDB 1.5.5, the version used to produce the initial real database/WAL recovery test. DuckDB is not a runtime dependency because this package copies files; it is a test dependency only. No compatibility promise is made for other DuckDB, Python, operating-system, or filesystem versions until they are tested.
An application can pass release as a string or no-argument callback. Its
result (a string or null) is recorded as the manifest's top-level release
field while the writer lock is held. Release meaning and validation stay with
the application; existing schema-1 manifests without that field remain
verifiable.
This project was extracted from a private application maintained by Sid Kalla. The public package contains synthetic tests and generic configuration only. It is Apache-2.0 licensed; that license does not grant rights to Gale Finance names, logos, or visual identity. Support is best effort from the current release.
See CONTRIBUTING.md, SECURITY.md, and AGENT_INTEGRATION.md.
The public history begins with the 2026 extraction work. The private source mapping is retained outside this repository because it names Gale-only paths and operational context. Some early development used Claude as a coding assistant; Sid Kalla selected, reviewed and maintains this code.
Release files for duckdb-safe-snapshot 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 | |
|---|---|---|---|
| duckdb_safe_snapshot-0.1.0.tar.gz | 19.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| duckdb_safe_snapshot-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.2 kB
Release files / duckdb_safe_snapshot-0.1.0.tar.gz
| Download URL | duckdb_safe_snapshot-0.1.0.tar.gz |
|---|---|
| Size | 19.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
25f98da66646de538480fb5f419edb134def54ed019a044e123eaf00e81b2950
|
|
BLAKE2b-256 checksum How to use checksums |
36620708f164e46425b5d853449f1c65447e2d4727f11c06d9bd11a4dcfeecb7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency logRelease files / duckdb_safe_snapshot-0.1.0-py3-none-any.whl
| Download URL | duckdb_safe_snapshot-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
997c97a1c87ea285d07908e1d5bc0551277dd79cdfab20f65a137874f7645eb4
|
|
BLAKE2b-256 checksum How to use checksums |
68f62a6aac8e2c39850bd72fa926068c942099c970cf06cc9bf015c8c17d51f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency log