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.
Top-level entry point¶
_import
¶
Master import subpackage: project-folder → database in one transaction.
Public surface is :func:runner.import_project_from_dir. The
:mod:scripts.import_project CLI is a thin wrapper around it.
ProjectImportError
¶
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, so a failed import never leaves the database in a
half-written state. With force=True a re-run on the same
folder 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).
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
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 | |
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
¶
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, so a failed import never leaves the database in a
half-written state. With force=True a re-run on the same
folder 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).
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
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 | |
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
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 SQR / SQRP cell for import.
Uses the same canonicalization as
samples.create, so what the importer
accepts here is byte-identical to what gets stored — SQR+SQRP
plate 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
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]]
|
|