Persistence#
The data store: root-level registry plus per-collection catalog, product
tables, and immutable object store. Created only by adapt init; consumers
read through StoreClient.
Store lifecycle: initialization and opening of the on-disk Adapt data store.
The layout is created only by init_store() (surfaced as adapt init);
the pipeline refuses to run against an uninitialized root:
{root}/
├── registry.db # collections + run lifecycle (store_registry.sql)
├── logs/
└── collections/ # provisioned per collection at run start
- exception adapt.persistence.store.AlreadyInitializedError#
Bases:
StoreErrorThe root already contains an initialized store.
- class adapt.persistence.store.Collection(collection_id, path, catalog)#
Bases:
objectOne collection’s on-disk domain: catalog.db + products.db + decisions.db + objects/.
decisions.dbis the analysis-only decision log (segmentation and tracking decisions); the pipeline never reads it.- Parameters:
collection_id (str)
path (Path)
catalog (Catalog)
- close()#
- Return type:
None
- class adapt.persistence.store.Store(root)#
Bases:
objectAn initialized on-disk Adapt data store. Obtain via
open().- Parameters:
root (Path)
- close()#
Close every collection opened through this store.
- Return type:
None
- collection(collection_id)#
Provision (idempotently), validate, and return the collection’s domain.
- Return type:
- Parameters:
collection_id (str)
- classmethod open(root)#
Open an initialized store, failing loudly on legacy or bare roots.
- Return type:
- Parameters:
root (str | Path)
- validate_collection(collection_id)#
Raise when catalog.db and products.db schema snapshots disagree.
- Return type:
None- Parameters:
collection_id (str)
- exception adapt.persistence.store.StoreError#
Bases:
ExceptionA store-level operation failed.
- adapt.persistence.store.init_store(root)#
Create the store layout at
rootand return the resolved root.Creates exactly
registry.db,logs/, andcollections/. RaisesAlreadyInitializedErrorif a store exists, andStoreErroron a root using the obsolete pre-store layout.- Return type:
Path- Parameters:
root (str | Path)
Store-level registry: collections and the single run lifecycle.
Backs {root}/registry.db (schema store_registry.sql). This is the ONE
run lifecycle in the store — module executions and run events are recorded
against it, and per-collection discovery lives in the collection catalog.
Thread-safe singleton per store root (WAL); close_all() releases the
process-wide cache deterministically (FD-leak lesson from the old registry).
- class adapt.persistence.store_registry.RunStart(run_id, collection_id, config_hash, config_json, pipeline_version, environment_json)#
Bases:
objectEverything the registry records when a run begins.
- Parameters:
run_id (str)
collection_id (str)
config_hash (str)
config_json (str)
pipeline_version (str)
environment_json (str)
- collection_id: str#
- config_hash: str#
- config_json: str#
- environment_json: str#
- pipeline_version: str#
- run_id: str#
- class adapt.persistence.store_registry.StoreRegistry(root)#
Bases:
SqliteStoreRegistry over
{root}/registry.db; requires an initialized store.- Parameters:
root (str | Path)
- begin_run(start)#
Record a new run as
running; unknown collection or reused run_id raise.- Return type:
None- Parameters:
start (RunStart)
- classmethod close_all()#
Close every cached registry connection and empty the cache.
- Return type:
None
- ensure_collection_location(collection_id, *, lat, lon)#
Record the collection location once; an existing location is kept.
Deterministic: called with the location from pipeline inputs (the first gridded dataset), never from external lookup tables.
- Return type:
None- Parameters:
collection_id (str)
lat (float)
lon (float)
- finalize_run(run_id, status, *, scans_processed=None, scans_failed=None)#
Move a running run to a terminal status; double-finalize raises.
- Return type:
None- Parameters:
run_id (str)
status (str)
scans_processed (int | None)
scans_failed (int | None)
- get_collection(collection_id)#
- Return type:
dict- Parameters:
collection_id (str)
- classmethod get_instance(root)#
Return the cached registry for
root, creating it on first use.- Return type:
- Parameters:
root (str | Path)
- get_run(run_id)#
- Return type:
dict- Parameters:
run_id (str)
- latest_run(collection_id=None)#
The most recently started run (optionally within one collection).
- Return type:
dict|None- Parameters:
collection_id (str | None)
- list_collections()#
- Return type:
list[dict]
- record_module_execution(run_id, module, *, status, duration_seconds, detail_json)#
Upsert one module’s execution record (re-runs replace their own row).
- Return type:
None- Parameters:
run_id (str)
module (str)
status (str)
duration_seconds (float)
detail_json (str)
- record_run_event(run_id, *, severity, module, message, context_json)#
Append one warning/error event for a run.
- Return type:
None- Parameters:
run_id (str)
severity (str)
module (str)
message (str)
context_json (str)
- register_collection(collection_id, *, source_kind, lat=None, lon=None, metadata_json=None)#
Register a collection; registering an existing id is a no-op.
- Return type:
None- Parameters:
collection_id (str)
source_kind (str)
lat (float | None)
lon (float | None)
metadata_json (str | None)
- resume_run(run_id)#
Re-open an existing run (
--run-idcontinuation): running, no end time.- Return type:
None- Parameters:
run_id (str)
- run_events(run_id)#
- Return type:
list[dict]- Parameters:
run_id (str)
- run_modules(run_id)#
- Return type:
list[dict]- Parameters:
run_id (str)
Discovery + provenance catalog for one collection.
Backs collections/{id}/catalog.db (schema collection_catalog.sql).
Science rows live in products.db; this catalog answers which artifacts and
scans exist, which products each scan has, and where every object came from.
- class adapt.persistence.collection_catalog.ArtifactRecord(artifact_id, artifact_type, producer, run_id, scan_id, observation_time, original_filename, source_uri, object_name, checksum_sha256, size_bytes)#
Bases:
objectOne complete artifacts row; built by the ObjectStore at commit.
- Parameters:
artifact_id (str)
artifact_type (str)
producer (str)
run_id (str)
scan_id (str | None)
observation_time (str | None)
original_filename (str | None)
source_uri (str | None)
object_name (str)
checksum_sha256 (str)
size_bytes (int)
- artifact_id: str#
- artifact_type: str#
- checksum_sha256: str#
- object_name: str#
- observation_time: str | None#
- original_filename: str | None#
- producer: str#
- run_id: str#
- scan_id: str | None#
- size_bytes: int#
- source_uri: str | None#
- class adapt.persistence.collection_catalog.Catalog(collection_dir)#
Bases:
SqliteStoreCatalog over
{collection_dir}/catalog.db.- Parameters:
collection_dir (str | Path)
- add_lineage(child_artifact_id, parent_artifact_id, relationship='derived_from')#
Record one provenance edge; both artifacts must exist, edges are unique.
- Return type:
None- Parameters:
child_artifact_id (str)
parent_artifact_id (str)
relationship (str)
- children_of(artifact_id)#
- Return type:
list[str]- Parameters:
artifact_id (str)
- complete_scans(run_id)#
Complete scans of one run, time-ordered.
- Return type:
list[dict]- Parameters:
run_id (str)
- find_artifact(run_id, scan_id, artifact_type)#
Newest artifact of one type for a scan (re-runs replace, newest wins).
- Return type:
dict|None- Parameters:
run_id (str)
scan_id (str)
artifact_type (str)
- find_artifact_by_scan(scan_id, artifact_type)#
Newest artifact of one type for a scan across ALL runs.
Raw volumes are content-identified (same bytes → same scan_id), so a later run reuses the object an earlier run committed.
- Return type:
dict|None- Parameters:
scan_id (str)
artifact_type (str)
- find_by_source_uri(source_uri)#
Newest artifact acquired from one source URI (skip-if-downloaded lookup).
- Return type:
dict|None- Parameters:
source_uri (str)
- get_artifact(artifact_id)#
- Return type:
dict|None- Parameters:
artifact_id (str)
- get_scan(run_id, scan_id)#
- Return type:
dict|None- Parameters:
run_id (str)
scan_id (str)
- link_scan_product(run_id, scan_id, product, *, artifact_id=None, table_name=None)#
Link one product to a scan; re-linking replaces (module re-run semantics).
Exactly one of
artifact_id/table_nameidentifies where the product lives. Completeness is recomputed after every link.- Return type:
None- Parameters:
run_id (str)
scan_id (str)
product (str)
artifact_id (str | None)
table_name (str | None)
- list_artifacts(run_id=None, artifact_type=None)#
Artifacts filtered by run and/or type, ordered by observation time.
- Return type:
list[dict]- Parameters:
run_id (str | None)
artifact_type (str | None)
- mark_scan_failed(run_id, scan_id)#
Record a scan-processing failure; a later successful re-run re-completes it.
- Return type:
None- Parameters:
run_id (str)
scan_id (str)
- parents_of(artifact_id)#
- Return type:
list[str]- Parameters:
artifact_id (str)
- register_artifact(record)#
Insert one artifacts row; duplicate artifact_id or object_name raise.
- Return type:
None- Parameters:
record (ArtifactRecord)
- register_scan(record)#
Upsert one scan keyed by (run_id, scan_id); starts
pending.scan_timeis stored in the canonical format (to_scan_iso); coverage times are optional per-source metadata kept at full precision. Two different scans sharing one nominal second within a run raise.- Return type:
None- Parameters:
record (ScanRecord)
- register_table_schema(snapshot)#
Mirror one frozen table-schema snapshot (written by the SchemaLedger).
- Return type:
None- Parameters:
snapshot (dict)
- scan_is_complete(run_id, scan_id)#
- Return type:
bool- Parameters:
run_id (str)
scan_id (str)
- scan_products(run_id, scan_id)#
All product links for one scan, keyed by product name.
- Return type:
dict[str,dict]- Parameters:
run_id (str)
scan_id (str)
Immutable object store for one collection’s objects/ directory.
Write protocol: ObjectStore.begin() allocates the uuid4 artifact_id
BEFORE any bytes exist and hands back a staging path inside store-owned
objects/.staging/. Nothing is visible or cataloged until commit(),
which checksums the staged bytes, atomically publishes them via
os.replace, and registers the full catalog row. abort() removes the
staging file and leaves no trace.
- class adapt.persistence.objects.ArtifactMeta(artifact_type, producer, run_id, scan_id, observation_time, original_filename=None, source_uri=None)#
Bases:
objectCaller-supplied metadata for one object; the store computes the rest.
- Parameters:
artifact_type (str)
producer (str)
run_id (str)
scan_id (str | None)
observation_time (datetime | None)
original_filename (str | None)
source_uri (str | None)
- artifact_type: str#
- observation_time: datetime | None#
- original_filename: str | None = None#
- producer: str#
- run_id: str#
- scan_id: str | None#
- source_uri: str | None = None#
- class adapt.persistence.objects.ObjectStore(objects_dir, catalog)#
Bases:
objectAtomic, catalog-registered writes into one collection’s
objects/.- Parameters:
objects_dir (str | Path)
catalog (Catalog)
- abort(handle)#
Discard the staged bytes; nothing was published or cataloged.
- Return type:
None- Parameters:
handle (ObjectWriteHandle)
- begin(*, suffix)#
Allocate an artifact_id and staging path; write bytes there, then commit.
- Return type:
- Parameters:
suffix (str)
- commit(handle, meta, parents=())#
Publish the staged bytes atomically, register the row and lineage edges.
- Return type:
- Parameters:
handle (ObjectWriteHandle)
meta (ArtifactMeta)
parents (Sequence[str])
- class adapt.persistence.objects.ObjectWriteHandle(artifact_id, suffix, staging_path)#
Bases:
objectOne in-flight object write: id allocated, bytes staged, not yet visible.
- Parameters:
artifact_id (str)
suffix (str)
staging_path (Path)
- artifact_id: str#
- staging_path: Path#
- suffix: str#
Module-owned product tables with frozen first-frame schemas.
SchemaLedger freezes a table’s schema on its first non-empty frame —
declared primary key + payload columns — and writes identical snapshots to
products.db and catalog.db. Frozen types are deterministic: core identity
columns are contracted by name (_CORE_COLUMN_TYPES), every other numeric
column freezes as REAL, so the schema depends on the column set — never on
the values in whichever scan arrived first. TableWriter stamps scan identity from
PersistenceMeta, enforces the frozen schema (no new columns, no
incompatible dtypes, no duplicate keys, no cross-module writes), and upserts
on the declared key. There is no ALTER TABLE on the new path — ever.
Granularity derives from the declared primary key: valid_time in the key
marks a time-granular product (interpolations, forecasts; the row’s own time
is part of its identity), otherwise scan_id marks scan granularity,
otherwise the table is run-granular. Identity columns of scan-granular
per-scan writes are stamped by the writer — modules never pass them.
- class adapt.persistence.products.SchemaLedger(products_path, catalog)#
Bases:
objectFrozen first-frame schema registry with dual snapshots.
- Parameters:
products_path (str | Path)
catalog (Catalog)
- freeze(decl)#
Create the table + indexes and write identical snapshots to both dbs.
- Return type:
None- Parameters:
decl (TableDeclaration)
- frozen(table)#
The frozen declaration for
table, or None before first freeze.- Return type:
TableDeclaration|None- Parameters:
table (str)
- register_external(decl)#
Snapshot a table created by a bespoke writer (e.g. TrackStore DDL).
The table must already exist in products.db; only the dual snapshots are written here.
- Return type:
None- Parameters:
decl (TableDeclaration)
- class adapt.persistence.products.TableDeclaration(table, owner_module, granularity, primary_key, index_columns, columns)#
Bases:
objectOne frozen table schema: ownership, keys, and column types.
- Parameters:
table (str)
owner_module (str)
granularity (str)
primary_key (tuple[str, ...])
index_columns (tuple[str, ...])
columns (tuple[tuple[str, str], ...])
- columns: tuple[tuple[str, str], ...]#
- granularity: str#
- index_columns: tuple[str, ...]#
- owner_module: str#
- primary_key: tuple[str, ...]#
- table: str#
- class adapt.persistence.products.TableWriter(ledger, spec, owner_module)#
Bases:
objectWrites one module’s product table; freezes on the first non-empty frame.
- Parameters:
ledger (SchemaLedger)
spec (ProductTableWrite)
owner_module (str)
- write(df, meta)#
Stamp identity, enforce the frozen schema, and upsert on the declared key.
- Return type:
None- Parameters:
df (DataFrame)
meta (PersistenceMeta)
- adapt.persistence.products.table_granularity(primary_key)#
Derive a table’s granularity from its declared primary key.
- Return type:
str- Parameters:
primary_key (Sequence[str])
TrackStore — read/write the three track persistence tables.
Tables managed: - cells_by_scan : one row per active tracked cell per scan (wide canonical table) - cell_events : authoritative lineage edges (CONTINUE/SPLIT/MERGE/INITIATION/TERMINATION) - cell_tracks : convenience lifecycle summary per cell_uid
A “track” is a single connected chain of cell observations across scans identified by a stable cell_uid.
The tables live in the collection’s products.db: created with bespoke DDL (composite UNIQUE + retroactive-update SQL the generic writer cannot express) and frozen through the shared SchemaLedger on the first write — new columns are rejected forever after; there is no ALTER.
- class adapt.persistence.track_store.TrackStore(db_path, ledger)#
Bases:
objectRead/write track persistence tables in a collection’s products.db.
Thread-safe via SQLite WAL mode and an internal lock.
- Parameters:
db_path (Path)
ledger (SchemaLedger)
- close()#
- Return type:
None
- get_cell_events(run_id, cell_uid=None)#
- Return type:
DataFrame- Parameters:
run_id (str)
cell_uid (str | None)
- get_cell_tracks(run_id)#
- Return type:
DataFrame- Parameters:
run_id (str)
- get_cells_by_scan(run_id, scan_id)#
All cells_by_scan rows for one scan, keyed by identity.
- Return type:
DataFrame- Parameters:
run_id (str)
scan_id (str)
- get_track_history(run_id, cell_uid)#
- Return type:
DataFrame- Parameters:
run_id (str)
cell_uid (str)
- write_scan(run_id, scan_time, cell_stats_df, tracked_cells_df, cell_events_df, cell_adjacency_df, *, scan_id)#
Persist one scan’s track outputs to the three tables.
- Parameters:
run_id (
str)scan_time (
datetime)cell_stats_df (
DataFrame)tracked_cells_df (
DataFrame)cell_events_df (
DataFrame)cell_adjacency_df (
DataFrame)scan_id (
str)
- Return type:
None
Mechanical persistence of module-declared outputs.
The router knows spec TYPES, never module key names. Adding a module output means declaring a spec on the module — this file does not change.
Skip/raise rules: a key absent from the result means the module did not run this scan (skip); an empty DataFrame means no rows (skip); everything else that prevents a declared write is an error and raises.
- class adapt.persistence.output_router.StoreOutputRouter(collection)#
Bases:
objectPersist module-declared outputs into the store. No science names.
Dispatch: NetcdfArtifact → immutable object + catalog row + lineage to the scan’s raw volume + scan-product link; ProductTableWrite → products.db rows (frozen first-frame schema) + table link; TrackTablesWrite → TrackStore on products.db + auxiliary tracking link.
- Parameters:
collection (Collection)
- persist(modules, result, meta)#
- Return type:
None- Parameters:
modules (Iterable)
result (dict)
meta (PersistenceMeta)
Execution History — a permanent, queryable scientific metadata product.
Records each module execution (run_modules aggregates) and structured
warnings/errors (run_events) against the ONE run lifecycle in the store
registry, so questions like “which runs failed?”, “which module got slower?”,
“what config produced these outputs?” are answered with SQL, never by parsing
log files. Consumes only contracts DTOs — never the observability
implementation.
- class adapt.persistence.execution_history.StoreExecutionHistory(registry)#
Bases:
objectExecution history on the store registry (runs / run_modules / run_events).
The ONE run lifecycle lives in registry.runs (begun by the orchestrator via
StoreRegistry.begin_run); this class records per-module aggregates and warning/error events against it and maps the end-of-run summary onto the terminal run status.- Parameters:
registry (StoreRegistry)
- finalize_run(summary)#
Map the end-of-run summary onto the single registry run lifecycle.
- Return type:
None- Parameters:
summary (RunSummary)
- record_errors(run_id, errors)#
- Return type:
None- Parameters:
run_id (str)
errors (list[ErrorEvent])
- record_modules(run_id, scan_id, spans, recorded_at)#
Fold one scan’s module spans into the per-module run aggregates.
- Return type:
None- Parameters:
run_id (str)
scan_id (str)
spans (list[SpanRecord])
recorded_at (datetime)
- record_warnings(run_id, warnings)#
- Return type:
None- Parameters:
run_id (str)
warnings (list[WarningEvent])
Store-level exceptions, shared by every persistence component.
- exception adapt.persistence.errors.AlreadyInitializedError#
Bases:
StoreErrorThe root already contains an initialized store.
- exception adapt.persistence.errors.StoreError#
Bases:
ExceptionA store-level operation failed.