Project import¶
The master importer loads a whole project folder (project.yaml,
subjects.csv, visits.csv, samples.csv, files/manifest.csv)
into the database in a single transaction. See the CLI page
for the command-line wrapper.
Checking a bundle without importing¶
validate_bundle runs every check the import runs, without writing.
Without a cursor it needs no database, which suits a submission form;
with one it also compares the bundle with what is already stored.
from noxdb import init_pool, transaction
from noxdb._import import load_project_dir, validate_bundle
bundle = load_project_dir("IBD_Vienna/")
result = validate_bundle(bundle) # offline checks only
init_pool()
with transaction() as cur: # nothing is written
result = validate_bundle(bundle, cur=cur, force=True)
for problem in result.errors:
print("error:", problem)
for note in result.warnings:
print("warning:", note)
A bundle can also be built in memory from ProjectMeta, SubjectRow,
VisitRow, SampleRow and FileRow instead of read from a folder.
Adding to an existing project¶
A bundle that adds to an existing project (force=True) usually repeats
subjects, visits and samples the database already has. The import reuses
those rows as they are, so the bundle must agree with them:
- A different value for a stored field (a subject's sex, a visit's age or group, a sample's visit, type, plate coordinates, barcodes, library or antibody class) is an error, and the import is refused. Correct the bundle, or update the database explicitly first.
- A value for a field the database has empty (an age, a sample's
SQR or barcodes) is a warning: the import does not fill it in. A
sample's
SQR/SQRPand barcodes can be filled in withscripts/apply_run_sheet.py(seerun_sheet). - A metadata value that changes is a warning: the import overwrites it.
- An empty value in the bundle asserts nothing.
Top-level entry point¶
_import
¶
Master import subpackage: project-folder → database in one transaction.
Public surface:
- :func:
runner.import_project_from_dirvalidates and imports a folder. The :mod:scripts.import_projectCLI is a thin wrapper around it. - :func:
runner.validate_bundleruns the same checks without writing, on a bundle read by :func:loader.load_project_diror built in memory from the row dataclasses.
ProjectBundle
dataclass
¶
ProjectBundle(root: Path, project: ProjectMeta, subjects: list[SubjectRow] = list(), visits: list[VisitRow] = list(), samples: list[SampleRow] = list(), files: list[FileRow] = list(), warnings: list[str] = list())
Everything read from a project folder, pre-validation.
ProjectImportError
¶
ValidationResult
dataclass
¶
load_project_dir
¶
Read all required files from a project folder.
Loading is forgiving — does not check enum values or referential integrity, only structural things (required columns present, file readable, YAML parseable).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str | Path
|
Path to the project folder. |
required |
Returns:
| Type | Description |
|---|---|
ProjectBundle
|
A |
ProjectBundle
|
|
ProjectBundle
|
with the parsed contents. Unknown columns produce warnings |
ProjectBundle
|
stored on the bundle rather than raising. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
For missing required files. |
ValueError
|
For missing required columns. |
Source code in src/noxdb/_import/loader.py
import_project_from_dir
¶
import_project_from_dir(root: str | Path, *, dry_run: bool = False, force: bool = False, compute_md5: bool = False, skip_disk_check: bool = False, log_dir: str | Path | None = None) -> ImportReport
Validate and (unless dry_run) import the project under root.
The runner performs validation in a read-only pass before any
writes happen (see
validate_bundle), so a
failed import never leaves the database in a half-written state.
With force=True a re-run on the same folder, or a bundle adding
to an existing project, re-uses existing rows via the
get_or_create / get_or_register / set_* semantics of
the CRUD layer; the report distinguishes inserted from
existing (or, for metadata, inserted vs updated vs
unchanged). A reused subject, visit or sample must carry the
same values as the database, or the import is refused.
Cross-project collisions on sample_name or file_path are
refused even with force=True — those UNIQUEs are global by
design.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str | Path
|
Path to the project folder containing |
required |
dry_run
|
bool
|
Validate only; skip the commit phase. |
False
|
force
|
bool
|
Allow re-import of a project that already exists. |
False
|
compute_md5
|
bool
|
Hash files whose manifest entry has no
|
False
|
skip_disk_check
|
bool
|
Skip the per-file |
False
|
log_dir
|
str | Path | None
|
Directory to write the JSON report to. Defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
ImportReport
|
An |
Raises:
| Type | Description |
|---|---|
ProjectImportError
|
With the collected error list when
validation fails, or when the project already exists and
|
Source code in src/noxdb/_import/runner.py
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 | |
validate_bundle
¶
validate_bundle(bundle: ProjectBundle, *, cur=None, force: bool = False, skip_disk_check: bool = False) -> ValidationResult
Check a bundle without writing anything.
Every problem is collected, not just the first. Without cur only the checks that need no database run: allowed values, references between the files, duplicates and (unless skip_disk_check) whether the manifest's files exist. That is enough for a form that has no database access.
With cur the bundle is also checked against the database: the
project must not exist yet unless force is set, a file path must
not belong to another sample, and every subject, visit and sample
the database already holds must agree with the bundle (see
_validate_existing). The import runs exactly these checks before
it commits.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bundle
|
ProjectBundle
|
A bundle from
|
required |
cur
|
Cursor to read the database with, e.g. from
|
None
|
|
force
|
bool
|
Allow adding to a project that already exists. |
False
|
skip_disk_check
|
bool
|
Don't check that manifest files exist on this host. |
False
|
Returns:
| Type | Description |
|---|---|
ValidationResult
|
Source code in src/noxdb/_import/runner.py
Runner¶
runner
¶
Validate a :class:ProjectBundle and (optionally) commit it.
The runner is split into two distinct phases so import errors never leave the database in a half-written state:
- Validation — schema, referential, duplicate, on-disk path, and project-existence checks. Errors are collected exhaustively (not short-circuit) so the user sees every problem in one pass.
- Commit — a single :func:
transactionblock calling the existing CRUD wrappers in hierarchical order. An exception anywhere rolls back the entire import; partial states are impossible.
The split also gives --dry-run for free: skip phase 2.
ProjectImportError
¶
ValidationResult
dataclass
¶
validate_bundle
¶
validate_bundle(bundle: ProjectBundle, *, cur=None, force: bool = False, skip_disk_check: bool = False) -> ValidationResult
Check a bundle without writing anything.
Every problem is collected, not just the first. Without cur only the checks that need no database run: allowed values, references between the files, duplicates and (unless skip_disk_check) whether the manifest's files exist. That is enough for a form that has no database access.
With cur the bundle is also checked against the database: the
project must not exist yet unless force is set, a file path must
not belong to another sample, and every subject, visit and sample
the database already holds must agree with the bundle (see
_validate_existing). The import runs exactly these checks before
it commits.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bundle
|
ProjectBundle
|
A bundle from
|
required |
cur
|
Cursor to read the database with, e.g. from
|
None
|
|
force
|
bool
|
Allow adding to a project that already exists. |
False
|
skip_disk_check
|
bool
|
Don't check that manifest files exist on this host. |
False
|
Returns:
| Type | Description |
|---|---|
ValidationResult
|
Source code in src/noxdb/_import/runner.py
import_project_from_dir
¶
import_project_from_dir(root: str | Path, *, dry_run: bool = False, force: bool = False, compute_md5: bool = False, skip_disk_check: bool = False, log_dir: str | Path | None = None) -> ImportReport
Validate and (unless dry_run) import the project under root.
The runner performs validation in a read-only pass before any
writes happen (see
validate_bundle), so a
failed import never leaves the database in a half-written state.
With force=True a re-run on the same folder, or a bundle adding
to an existing project, re-uses existing rows via the
get_or_create / get_or_register / set_* semantics of
the CRUD layer; the report distinguishes inserted from
existing (or, for metadata, inserted vs updated vs
unchanged). A reused subject, visit or sample must carry the
same values as the database, or the import is refused.
Cross-project collisions on sample_name or file_path are
refused even with force=True — those UNIQUEs are global by
design.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str | Path
|
Path to the project folder containing |
required |
dry_run
|
bool
|
Validate only; skip the commit phase. |
False
|
force
|
bool
|
Allow re-import of a project that already exists. |
False
|
compute_md5
|
bool
|
Hash files whose manifest entry has no
|
False
|
skip_disk_check
|
bool
|
Skip the per-file |
False
|
log_dir
|
str | Path | None
|
Directory to write the JSON report to. Defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
ImportReport
|
An |
Raises:
| Type | Description |
|---|---|
ProjectImportError
|
With the collected error list when
validation fails, or when the project already exists and
|
Source code in src/noxdb/_import/runner.py
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 | |
Loader¶
loader
¶
Read a project folder into typed in-memory records.
The runner consumes the structures produced here and performs validation + commit. Loading itself is forgiving — it does not check enum values or referential integrity, only structural things (required columns present, file readable, YAML parseable).
ProjectBundle
dataclass
¶
ProjectBundle(root: Path, project: ProjectMeta, subjects: list[SubjectRow] = list(), visits: list[VisitRow] = list(), samples: list[SampleRow] = list(), files: list[FileRow] = list(), warnings: list[str] = list())
Everything read from a project folder, pre-validation.
load_project_dir
¶
Read all required files from a project folder.
Loading is forgiving — does not check enum values or referential integrity, only structural things (required columns present, file readable, YAML parseable).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str | Path
|
Path to the project folder. |
required |
Returns:
| Type | Description |
|---|---|
ProjectBundle
|
A |
ProjectBundle
|
|
ProjectBundle
|
with the parsed contents. Unknown columns produce warnings |
ProjectBundle
|
stored on the bundle rather than raising. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
For missing required files. |
ValueError
|
For missing required columns. |
Source code in src/noxdb/_import/loader.py
barcodes
¶
The optional barcode cells of a samples row, stripped; empty → None.
Schema¶
schema
¶
CSV / YAML schema declarations and value coercion helpers.
Each CSV file has a fixed set of required columns plus optional ones; any
column that starts with meta_ is treated as a typed metadata key (the
prefix is stripped to produce the stored key_name). Type inference
order: int → float → bool ('true'/'false'/'1'/'0') → str. Empty cells
are treated as "no metadata for this row" (no INSERT).
coerce_metadata_value
¶
Coerce a raw CSV cell to an int / float / bool / str.
Type inference order: bool ('true' / 'false') → int → float
→ str. '1' / '0' are NOT treated as bools (they'd otherwise
parse as int and lose their boolean intent at write time).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
str
|
The raw cell value. |
required |
Returns:
| Type | Description |
|---|---|
Any | None
|
The coerced value, or |
Any | None
|
cells (meaning "no metadata entry" — the caller should skip |
Any | None
|
the row rather than insert NULL, because |
Any | None
|
|
Any | None
|
|
Any | None
|
reject |
Source code in src/noxdb/_import/schema.py
coerce_int
¶
Parse an int with a labelled error on failure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
str
|
The raw cell value. |
required |
field
|
str
|
Human-readable identifier (e.g. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The parsed integer. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/noxdb/_import/schema.py
validate_plate_id
¶
Validate + canonicalize an IP or sequencing coordinate cell for import.
Uses the same canonicalization as
samples.create, so what the importer
accepts here is byte-identical to what gets stored and coordinate
matching can't drift between the two.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
str | None
|
The raw cell value. |
required |
field
|
str
|
Human-readable identifier (e.g.
|
required |
Returns:
| Type | Description |
|---|---|
str
|
|
str | None
|
be stored; warning is a human-readable string when |
tuple[str, str | None]
|
canonicalization changed the input (whitespace stripped or an |
tuple[str, str | None]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the canonical value exceeds the
|
Source code in src/noxdb/_import/schema.py
barcode_errors
¶
Problems with a samples row's barcode cells.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
dict[str, str | None]
|
The row's |
required |
where
|
str
|
Location to prefix each message with, e.g.
|
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
One message per index sequence with characters other than |
list[str]
|
A, C, G, T and N, and per value too long for its column. |
Source code in src/noxdb/_import/schema.py
check_no_subject_metadata
¶
Refuse meta_* columns in a subjects CSV.
There is no subject-level metadata table, so these columns used to be
dropped without a word. Subject-level facts (a diagnosis, a score)
belong in visits.csv, which stores them per visit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
header
|
list[str]
|
Column names from the CSV header row. |
required |
filename
|
str
|
File name to report in the error. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If any column starts with |
Source code in src/noxdb/_import/schema.py
split_columns
¶
split_columns(header: list[str], required: tuple[str, ...], optional: tuple[str, ...]) -> tuple[list[str], list[str]]
Split a CSV header into known, metadata, and extra columns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
header
|
list[str]
|
List of column names from the CSV header row. |
required |
required
|
tuple[str, ...]
|
Column names that must be present. |
required |
optional
|
tuple[str, ...]
|
Column names allowed but not required. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
|
list[str]
|
|
tuple[list[str], list[str]]
|
|