app.storage¶
Content-addressed blob store — pluggable backends.
BlobStore is a Protocol; get_blob_store() returns the concrete
implementation chosen by settings.blob_backend:
fs(default): bytes live at<blob_root>/<sha[:2]>/<sha>. Writes are atomic viaos.replacefrom a sibling temp file. Reads stream the file directly. This is the v0 backend and the test default.
s3: bytes live ats3://<bucket>/<prefix><sha[:2]>/<sha>. Reads download lazily to the local S3 cache and return paths into that cache, so callers that need a filesystem path (pycolmap, Pillow) work transparently. Configured viasettings.blob_s3_*.
Refcounting is the caller’s responsibility (typically a service) inside a transaction so blob lifecycle stays consistent with referencing rows — that contract is identical across backends.
- class app.storage.blobs.BlobStore(*args, **kwargs)[source]
Bases:
ProtocolSha256-keyed binary store. All methods are sha-addressed; the backend chooses where bytes physically live.
- exists(sha)[source]
- Parameters:
sha (str)
- Return type:
bool
- put_stream(reader, *, chunk_size=Ellipsis)[source]
- Parameters:
reader (BinaryIO)
chunk_size (int)
- Return type:
tuple[str, int]
- put_bytes(data)[source]
- Parameters:
data (bytes)
- Return type:
tuple[str, int]
- open(sha)[source]
- Parameters:
sha (str)
- Return type:
BinaryIO
- aiter_chunks(sha, *, chunk_size=Ellipsis)[source]
- Parameters:
sha (str)
chunk_size (int)
- Return type:
AsyncIterator[bytes]
- delete(sha)[source]
- Parameters:
sha (str)
- Return type:
None
- local_path(sha)[source]
Return a local filesystem path for the blob’s bytes.
For filesystem backends this is the canonical storage path. For remote backends (S3) the bytes are downloaded into the local cache on first access; subsequent calls return the cached path. Callers that need to hand a real path to a native library (pycolmap, Pillow, OpenCV) should use this.
- Parameters:
sha (str)
- Return type:
Path
- class app.storage.blobs.FSBlobStore(settings=None)[source]
Bases:
objectDefault backend — bytes on the local filesystem under
<blob_root>/<sha[:2]>/<sha>.- Parameters:
settings (Settings | None)
- backend: str = 'fs'
- is_singleton: bool = False
- path_for(sha)[source]
- Parameters:
sha (str)
- Return type:
Path
- local_path(sha)[source]
- Parameters:
sha (str)
- Return type:
Path
- exists(sha)[source]
- Parameters:
sha (str)
- Return type:
bool
- put_stream(reader, *, chunk_size=1048576)[source]
- Parameters:
reader (BinaryIO)
chunk_size (int)
- Return type:
tuple[str, int]
- put_bytes(data)[source]
- Parameters:
data (bytes)
- Return type:
tuple[str, int]
- open(sha)[source]
- Parameters:
sha (str)
- Return type:
BinaryIO
- async aiter_chunks(sha, *, chunk_size=1048576)[source]
- Parameters:
sha (str)
chunk_size (int)
- Return type:
AsyncIterator[bytes]
- delete(sha)[source]
- Parameters:
sha (str)
- Return type:
None
- class app.storage.blobs.S3BlobStore(settings=None)[source]
Bases:
objectBytes live at
s3://<bucket>/<prefix><sha[:2]>/<sha>.Reads download lazily into
<workspace>/_blob_cache/<sha[:2]>/<sha>solocal_path()is a true filesystem path. The cache is content- addressed by sha so it never goes stale; deletes on the bucket are mirrored locally on nextdelete().- Parameters:
settings (Settings | None)
- backend: str = 's3'
- is_singleton: bool = False
- bucket: str
- prefix: str
- cache_root: Path
- exists(sha)[source]
- Parameters:
sha (str)
- Return type:
bool
- put_stream(reader, *, chunk_size=1048576)[source]
- Parameters:
reader (BinaryIO)
chunk_size (int)
- Return type:
tuple[str, int]
- put_bytes(data)[source]
- Parameters:
data (bytes)
- Return type:
tuple[str, int]
- local_path(sha)[source]
- Parameters:
sha (str)
- Return type:
Path
- open(sha)[source]
- Parameters:
sha (str)
- Return type:
BinaryIO
- async aiter_chunks(sha, *, chunk_size=1048576)[source]
- Parameters:
sha (str)
chunk_size (int)
- Return type:
AsyncIterator[bytes]
- delete(sha)[source]
- Parameters:
sha (str)
- Return type:
None
- class app.storage.blobs.InMemoryBlobStore(settings=None)[source]
Bases:
objectAll bytes live in a process-local dict.
Used by
ephemeral=truemode: zero disk persistence, ideal for tests, demos, and embedded use. Callers that need a real filesystem path (pycolmap, Pillow) trigger lazy materialization into a per-store temp dir vialocal_path(); that materialization survives untilshutdown()is called.Marked
is_singleton = Trueso the factory caches a single instance per process — bytes live inself._bytesand would be unreachable to a writer-via-call-1 / reader-via-call-2 pattern if the factory built a fresh instance each time. TheL15regression-guard in the contract suite pins this invariant.- Parameters:
settings (Settings | None)
- backend: str = 'memory'
- is_singleton: bool = True
- exists(sha)[source]
- Parameters:
sha (str)
- Return type:
bool
- put_stream(reader, *, chunk_size=1048576)[source]
- Parameters:
reader (BinaryIO)
chunk_size (int)
- Return type:
tuple[str, int]
- put_bytes(data)[source]
- Parameters:
data (bytes)
- Return type:
tuple[str, int]
- open(sha)[source]
- Parameters:
sha (str)
- Return type:
BinaryIO
- async aiter_chunks(sha, *, chunk_size=1048576)[source]
- Parameters:
sha (str)
chunk_size (int)
- Return type:
AsyncIterator[bytes]
- local_path(sha)[source]
- Parameters:
sha (str)
- Return type:
Path
- delete(sha)[source]
- Parameters:
sha (str)
- Return type:
None
- shutdown()[source]
Drop all bytes and remove the scratch dir. Idempotent.
- Return type:
None
- app.storage.blobs.get_blob_store(settings=None)[source]
Build a blob store per
settings.blob_backend.Backends declare their lifetime via
is_singleton:False(FS / S3) constructs a fresh stateless wrapper per call;True(memory) caches one process-local instance because the bytes live in instance state — building a fresh store on every call would drop writes silently.Callers that need to inject a custom backend in tests should pass a constructed instance through dependency injection rather than registering globally;
reset_memory_blob_store_for_tests()drops cached singletons between test cases.- Parameters:
settings (Settings | None)
- Return type:
BlobStore
- app.storage.blobs.reset_memory_blob_store_for_tests()[source]
Drop cached singleton instances. Call between test cases that mutate
settings.blob_backendso each gets a fresh store.- Return type:
None
- class app.storage.blobs.TempUploadStore(settings=None)[source]
Bases:
objectWorking area for in-flight chunked uploads, separate from finalized blob storage. Each upload gets its own file under workspaces/_uploads/{upload_id}.
- Parameters:
settings (Settings | None)
- path_for(upload_id)[source]
- Parameters:
upload_id (str)
- Return type:
Path
- append(upload_id, offset, data)[source]
- Parameters:
upload_id (str)
offset (int)
data (bytes)
- Return type:
int
- size(upload_id)[source]
- Parameters:
upload_id (str)
- Return type:
int
- hash_and_size(upload_id)[source]
- Parameters:
upload_id (str)
- Return type:
tuple[str, int]
- finalize_into(upload_id, blob_store)[source]
- Parameters:
upload_id (str)
blob_store (BlobStore)
- Return type:
tuple[str, int]
- discard(upload_id)[source]
- Parameters:
upload_id (str)
- Return type:
None
- app.storage.blobs.hash_iter(chunks)[source]
- Parameters:
chunks (Iterable[bytes])
- Return type:
tuple[str, int]
- app.storage.blobs.safe_rmtree(p)[source]
- Parameters:
p (Path)
- Return type:
None
Sealed-snapshot writer.
Workers write live progress to sparse/; periodically they “seal” the current state into snapshots/{seq}/ via atomic dir rename so the API can read without racing the writer.
- Sealing protocol:
tmp = snapshots/.tmp_{seq}
copy live sparse/ (and any sidecar JSONs) into tmp
write tmp/.complete last
os.replace(tmp, target) (atomic on the same FS)
update latest pointer (text file holding seq number)
- API readers:
list seq dirs (filter to those with .complete marker)
read inside any sealed dir; data is immutable
- class app.storage.snapshots.SnapshotStore(root)[source]
Bases:
object- Parameters:
root (Path)
- seal(*, seq, source_dir, summary=None)[source]
- Parameters:
seq (int)
source_dir (Path)
summary (dict | None)
- Return type:
Path
- list_sealed()[source]
- Return type:
list[int]
- latest()[source]
- Return type:
int | None
- path_for(seq)[source]
- Parameters:
seq (int)
- Return type:
Path
- gc(*, keep_last=3)[source]
- Parameters:
keep_last (int)
- Return type:
list[int]
MappingInput checkpoint storage helpers.
pycolmap.MappingInput.save/load writes a binary container with magic PCMAPINx00 (v1) holding the pre-mapping state needed to resume an incremental run. We persist these per-job under jobs/{job_id}/checkpoints/{seq}.pcmapin so the worker can reload from the latest checkpoint on resume without re-running upstream stages.
The pycolmap binding is required to read/write — for tests we expose a trivial adapter that round-trips a payload through a regular file so the storage code path can be exercised independently.
- class app.storage.mapping_input.CheckpointRef(seq: 'int', path: 'Path', summary: 'dict')[source]
Bases:
object- Parameters:
seq (int)
path (Path)
summary (dict)
- seq: int
- path: Path
- summary: dict
- app.storage.mapping_input.checkpoint_root(job_dir)[source]
- Parameters:
job_dir (Path)
- Return type:
Path
- app.storage.mapping_input.write_checkpoint(job_dir, *, seq, payload, summary=None)[source]
- Parameters:
job_dir (Path)
seq (int)
payload (bytes)
summary (dict | None)
- Return type:
CheckpointRef
- app.storage.mapping_input.list_checkpoints(job_dir)[source]
- Parameters:
job_dir (Path)
- Return type:
list[CheckpointRef]
- app.storage.mapping_input.latest_checkpoint(job_dir)[source]
- Parameters:
job_dir (Path)
- Return type:
CheckpointRef | None
- app.storage.mapping_input.gc_checkpoints(job_dir, *, keep_last=5)[source]
- Parameters:
job_dir (Path)
keep_last (int)
- Return type:
list[int]
Global S3 LRU cache.
Cache layout: <s3_cache_root>/<bucket>/<sha-of-key>__<etag> so each distinct (bucket, key, etag) tuple lives at a unique path. Stale ETags are evicted; LRU eviction by total bytes when over budget.
Cache is shared across projects/tenants — content addressed by ETag.
- class app.storage.s3_cache.CachedObject(bucket: 'str', key: 'str', etag: 'str', path: 'Path', size: 'int')[source]
Bases:
object- Parameters:
bucket (str)
key (str)
etag (str)
path (Path)
size (int)
- bucket: str
- key: str
- etag: str
- path: Path
- size: int
- class app.storage.s3_cache.S3Cache(settings=None)[source]
Bases:
objectFilesystem-backed LRU.
Atime-driven LRU on the entry’s manifest (.meta.json) so we don’t depend on filesystem atime, which is often disabled.
- Parameters:
settings (Settings | None)
- path_for(bucket, key, etag)[source]
- Parameters:
bucket (str)
key (str)
etag (str)
- Return type:
Path
- lookup(bucket, key, etag)[source]
- Parameters:
bucket (str)
key (str)
etag (str)
- Return type:
CachedObject | None
- insert(*, bucket, key, etag, src_bytes)[source]
- Parameters:
bucket (str)
key (str)
etag (str)
src_bytes (bytes)
- Return type:
CachedObject
- total_bytes()[source]
- Return type:
int
- evict_to(*, max_bytes)[source]
Evict least-recently-used entries until under budget. Returns bytes freed.
- Parameters:
max_bytes (int)
- Return type:
int
- clear()[source]
- Return type:
None
Workspace GC + storage accounting.
GC policy (Phase 5). Old completed jobs (status in
succeeded, failed, cancelled, cancelled_dirty) past
ttl_days are swept in this order:
Drop the job’s
dense/directory first.Drop the job’s
snapshots/directory next (job-scoped output).Drop the job’s
sparse/directory last.Drop the job’s
log.jsonl/events.jsonlonly ifdrop_db_rowsis set.
Reconstruction-level artifacts are NOT touched by job GC because
reconstructions can outlive the job that produced them and may be
referenced by other (still active) jobs. A separate gc_orphan_*
job sweeps reconstructions whose dataset_snapshot_hash no longer
matches any current dataset.
Pinned jobs (Job.pinned=True) are skipped at every step.
Caller passes a now for testability; otherwise datetime.now(UTC).
- app.storage.workspace.workspace_total_bytes(root)[source]
- Parameters:
root (Path)
- Return type:
int
- async app.storage.workspace.gc_completed_jobs(session, *, ttl_days, now=None, drop_db_rows=False)[source]
Sweep completed-and-old jobs. Returns a summary dict for tests.
- Parameters:
session (AsyncSession)
ttl_days (int)
now (datetime | None)
drop_db_rows (bool)
- Return type:
dict
Model-artifact filesystem layout + lazy download/verify.
- app.storage.models.models_root(settings=None)[source]
- Parameters:
settings (Settings | None)
- Return type:
Path
- app.storage.models.artifact_path(family, name, version, filename='weights.pth')[source]
- Parameters:
family (str)
name (str)
version (str)
filename (str)
- Return type:
Path
- app.storage.models.verify_sha(path, expected_sha)[source]
- Parameters:
path (Path)
expected_sha (str)
- Return type:
bool
- app.storage.models.install_artifact(*, family, name, version, src_bytes, expected_sha)[source]
- Parameters:
family (str)
name (str)
version (str)
src_bytes (bytes)
expected_sha (str)
- Return type:
Path