store
CRUD for the runs record (v1).
Records the lifecycle of each flow execution and provides the key queries
used by BatchDiscovery and trigger flows.
Module
Functions
get_last_refreshed
def get_last_refreshed( cache: CacheProtocol, task_hash: str, run_type: str, tags: dict[str, Any] | None = None,) ‑> datetime.datetime | None:Return the completed_at of the most recent successful run of run_type.
Scoped by type, not by task_hash alone: every run of a datasource shares
one task_hash — the indexing runtimes, the background DAG and each of its
nodes — so an unscoped query answers "has anything at all completed for this
datasource", which is never the question a caller means.
Arguments
cache: Cache instance.task_hash: Pipeline task hash.run_type: Thetypecolumn value to match, e.g."file_metadata"or a DAG or node name. Seeruns.v1.schema.tags: When set, only runs whosetagshold every one of these key/value pairs are considered.
Returns
The timestamp, or None if no matching run has ever completed.
get_last_run_started
def get_last_run_started( cache: CacheProtocol, task_hash: str, run_type: str, tags: dict[str, Any] | None = None,) ‑> datetime.datetime | None:Return the started_at of the most recent successful run of run_type.
The same row get_last_refreshed selects, read at the other end. A run
observes the filesystem from its start, so started_at is the latest moment
everything that run saw is known to be recorded; anything changed after it
may have been missed by that run.
Arguments
cache: Cache instance.task_hash: Pipeline task hash.run_type: Thetypecolumn value to match, e.g."file_metadata".tags: When set, only runs whosetagshold every one of these key/value pairs are considered.
Returns
The timestamp, or None if no matching run has ever completed.
get_latest_finished_run_status
def get_latest_finished_run_status( cache: CacheProtocol, task_hash: str, run_type: str, tags: dict[str, Any] | None = None,) ‑> RunStatus | None:Return the status of the most recently started finished run of run_type.
RUNNING rows are ignored: whether a run is live is Prefect's to answer,
and a crashed run's row stays running until recovery reaps it.
Arguments
cache: Cache instance.task_hash: Pipeline task hash.run_type: Thetypecolumn value to match.tags: When set, only runs whosetagshold every one of these key/value pairs are considered.
Returns
The status, or None if no matching run has finished.
get_run_by_run_id
def get_run_by_run_id( cache: CacheProtocol, run_id: str,) ‑> RunRecord | None:Return a single run record by its UUID, or None if not found.
Arguments
cache: Cache instance.run_id: UUID of the run to retrieve.
Returns
The matching RunRecord, or None if no run with that ID exists.
get_runs_by_task
def get_runs_by_task( cache: CacheProtocol, task_hash: str,) ‑> CollectedRunQueryResult:Return all run records for task_hash, most recent first.
Arguments
cache: Cache instance.task_hash: Pipeline task hash.
Returns
A CollectedRunQueryResult containing all run records ordered by
started_at descending. The result is falsy when no runs exist.
get_task_hashes_for_run_type
def get_task_hashes_for_run_type(cache: CacheProtocol, run_type: str) ‑> set[str]:Return every task hash with at least one run of run_type.
Arguments
cache: Cache instance.run_type: Thetypecolumn value to match.
has_run_with_status
def has_run_with_status( cache: CacheProtocol, task_hash: str, run_type: str, status: RunStatus, tags: dict[str, Any] | None = None,) ‑> bool:Return whether any run of run_type for task_hash ended with status.
Arguments
cache: Cache instance.task_hash: Pipeline task hash.run_type: Thetypecolumn value to match.status: The status to look for.tags: When set, only runs whosetagshold every one of these key/value pairs are considered.
mark_run_complete
def mark_run_complete(cache: CacheProtocol, run_id: str, files_processed: int) ‑> None:Record successful completion of a running flow.
Arguments
cache: Cache instance.run_id: UUID of the run to mark complete (returned bymark_run_started).files_processed: Number of files processed.
mark_run_failed
def mark_run_failed(cache: CacheProtocol, run_id: str, error: str | None = None) ‑> None:Record a failed flow.
Arguments
cache: Cache instance.run_id: UUID of the run to mark failed (returned bymark_run_started).error: Optional error message.
mark_run_partial
def mark_run_partial( cache: CacheProtocol, run_id: str, files_processed: int, reason: str | None = None,) ‑> None:Record a flow that finished without covering all of its input.
Arguments
cache: Cache instance.run_id: UUID of the run to close (returned bymark_run_started).files_processed: Number of files processed.reason: Why the run is partial, stored in theerrorcolumn.
mark_run_started
def mark_run_started( cache: CacheProtocol, task_hash: str, run_type: str, stale_threshold_hours: float = 1.0, tags: dict[str, Any] | None = None, parent_run_id: str | None = None, project_id: str | None = None, dedup: bool = True, run_id: str | None = None,) ‑> str | None:Record that a flow has started for task_hash, or return None.
Uses a single atomic INSERT ... SELECT [WHERE NOT EXISTS] statement so
that the "is there already a live run?" check and the "insert this run"
step are never separated by a window where another writer can sneak in.
This eliminates the TOCTOU race that would exist with a plain
SELECT-then-INSERT approach.
A "running" row is considered stale (i.e. orphaned by a crash) when
it is older than stale_threshold_hours. Stale rows are ignored by the
guard — they should be reaped by _is_run_active in the refresh path.
The guard is scoped by (task_hash, run_type, project_id) so that a
running DAG-level run (run_type=dag.name) does not block concurrent
node-level runs (run_type=node.name), and a run for one project does
not block a run of the same (task_hash, run_type) for a different
project linked to the same datasource.
When dedup is False the guard is bypassed and a bookkeeping row is
always inserted (the function then never returns None). This is the mode
the file_metadata / scan_metadata runtimes use: mutual exclusion now lives
at the trigger layer backed by the heartbeat
- zombie automation, so the
runsrow is pure bookkeeping. Keeping the row's own guard for those runtimes would re-introduce the crash-lockout it was demoted to fix — a crashed run's stalerunningrow would block a fresh run for up tostale_threshold_hours. All other callers (DAG / node / interactive runs) keep the defaultdedup=True.
Arguments
cache: Cache instance.task_hash: Pipeline task hash.run_type: Required, free-form run-kind discriminator (e.g."file_metadata","interactive", a DAG name, or a node name). The dedup guard is scoped by(task_hash, run_type), so this must be specific enough that two runs sharing it are genuinely mutually-exclusive duplicates. There is deliberately no default — a coarse default previously broke the contract.stale_threshold_hours: How old (in hours) arunningrow must be before it is considered stale and no longer blocks a new run. Defaults to 1 hour.tags: Optional flat metadata dict to attach to this run record.parent_run_id: Optional UUID of the parent run that spawned this run (e.g. the DAG-level run for a node-level run).project_id: Project that owns this run, folded into the dedup scope.Nonefor project-independent runs (e.g. file-metadata indexing). Two projects sharing a(task_hash, run_type)get independent runs because theirproject_iddiffers.dedup: WhenTrue(default) theINSERT … WHERE NOT EXISTSguard is applied, so a second concurrent start returnsNone. WhenFalsethe guard is bypassed and a bookkeeping row is always inserted (the function never returnsNone); the metadata runtimes use this because mutual exclusion lives at the trigger layer.run_id: The UUID to give this row, when the caller must know it before the row exists. A background DAG runs in a child process, so its parent mints the id and passes it down: that is what lets the parent close out this run, and only this run, if the child dies without closing it. Defaults to a fresh UUID.
Returns
The UUID string assigned to this run as run_id, or None when
a non-stale "running" row already exists for the same
(task_hash, run_type, project_id) (only possible when dedup=True).
Classes
CollectedRunQueryResult
class CollectedRunQueryResult(rows: Iterable[RunRecord]):Materialised run query results.
Eagerly collects all run rows for a task hash into memory. Rows are
ordered by started_at descending so records[0] is always the
most recent run.
Methods
as_df
def as_df(self) ‑> pandas.core.frame.DataFrame:Return all run records as a flat DataFrame.