workflows¶
workflows
¶
High-level composite operations built on the per-table CRUD modules.
Each workflow takes an optional cursor (cur=None). When the caller
provides a cursor, the workflow piggybacks on the caller's transaction —
all writes are part of the same atomic unit, and a later exception in
the caller's block rolls them back. When cur is None, the
workflow opens its own :func:noxdb.transaction block, so the
multi-step operation is still atomic when called standalone (e.g. from a
notebook):
# Notebook style — workflow owns the transaction:
sid, vid = workflows.register_subject_with_visit(
subject_code="S1", sex="F",
timepoint="baseline", group_test="ctrl", age=42,
visit_metadata={"bmi": 24.1, "smoker": False},
)
# Composed inside a larger transaction:
with transaction() as cur:
sid, vid = workflows.register_subject_with_visit(cur, ...)
for spec in sample_specs:
workflows.register_sample_with_files(cur, visit_id=vid, **spec)
register_subject_with_visit
¶
register_subject_with_visit(cur=None, *, subject_code: str, sex: str, origin: str | None = None, timepoint: str, group_test: str, age: int, visit_metadata: dict[str, Any] | None = None) -> tuple[int, int]
Idempotently create a subject and its first visit. Atomic.
Uses subjects.get_or_create
and visits.get_or_create, so
re-running with the same natural keys yields the same IDs without
duplicating rows. Existing rows are NOT updated by this helper —
call the CRUD update directly to change attributes of a row
that already exists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cur
|
Optional cursor from |
None
|
|
subject_code
|
str
|
Globally unique subject code. |
required |
sex
|
str
|
|
required |
origin
|
str | None
|
Used only when inserting a new subject. |
None
|
timepoint
|
str
|
Visit timepoint. Must be non-NULL — the underlying
|
required |
group_test
|
str
|
Used only when inserting a new visit. |
required |
age
|
int
|
Used only when inserting a new visit. |
required |
visit_metadata
|
dict[str, Any] | None
|
Optional |
None
|
Returns:
| Type | Description |
|---|---|
tuple[int, int]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/noxdb/workflows.py
register_sample_with_files
¶
register_sample_with_files(cur=None, *, visit_id: int, sample_name: str, sample_type: str, sqr: str, sqrp: str, library: str, antibody_class: str | None = None, sample_metadata: dict[str, Any] | None = None, files_spec: list[dict[str, Any]] | None = None, compute_md5: bool = False) -> tuple[int, list[int]]
Idempotently create a sample, its metadata, and its files. Atomic.
The sample is upserted via
samples.get_or_create keyed
on the globally-UNIQUE sample_name; existing samples keep their
original visit_id even when called with a different one.
Any disk/path validation error from
files is raised inside the transaction,
which rolls back the sample insert too — the unit is the
sample-plus-files bundle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cur
|
Optional cursor from |
None
|
|
visit_id
|
int
|
Parent visit (used only when inserting a new sample). |
required |
sample_name
|
str
|
Globally unique sample name. |
required |
sample_type
|
str
|
See |
required |
sqr
|
str
|
Used only on insert. |
required |
sqrp
|
str
|
Used only on insert. |
required |
library
|
str
|
Used only on insert. |
required |
antibody_class
|
str | None
|
Used only on insert. |
None
|
sample_metadata
|
dict[str, Any] | None
|
Optional |
None
|
files_spec
|
list[dict[str, Any]] | None
|
Optional list of dicts forwarded to
|
None
|
compute_md5
|
bool
|
Default for entries in |
False
|
Returns:
| Type | Description |
|---|---|
int
|
|
list[int]
|
|