exlab_wizard.config.models#

Pydantic models that mirror config.yaml. Backend Spec §9.

These models are the typed schema for the on-disk config.yaml. The loader (exlab_wizard.config.loader) parses YAML into a plain dict, hands it to Config.model_validate, and converts any Pydantic ValidationError into a ConfigError at the boundary; nothing here raises ConfigError directly except for cases that need a custom message before the model layer sees the input.

Style: - model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) on

every model so unknown keys raise a clear validation error.

  • StrEnum values are accepted in either string or enum form; Pydantic v2 lax mode coerces raw strings to enum members, and the spec stores the string value verbatim on dump (via StrEnum.value or explicit field_serializer).

  • All cross-field invariants from §9 are encoded as ``model_validator``s.

Functions

config_with_equipment_appended(config, equipment)

Return a copy of config with equipment appended.

Classes

BandwidthConfig(**data)

bandwidth: sub-block on a transport.

BandwidthWindow(**data)

One {days, from, to} window.

Config(**data)

Top-level config.yaml model.

EquipmentConfig(**data)

One equipment: list entry.

FileStabilityConfig(**data)

sync.stability: sub-block.

LIMSConfig(**data)

lims: block.

LoggingConfig(**data)

logging: block.

NASCleanupConfig(**data)

nas_cleanup: block.

NasConfig(**data)

nas: block — the single rclone remote + base root for NAS sync.

OperatorsConfig(**data)

operators: block.

OrchestratorConfig(**data)

orchestrator: block.

OrchestratorStagingCleanup(**data)

orchestrator.staging_cleanup: sub-block.

PathsConfig(**data)

paths: block.

PluginsConfig(**data)

plugins: block.

READMEConfig(**data)

readme: block.

READMEDefaultField(**data)

One operator-defined extra README field.

RclonePerf(**data)

Parallelism knobs forwarded to rclone (--transfers / --checkers).

SyncConfig(**data)

sync: block.

UpdateCheckConfig(**data)

update_check: block.

ValidatorConfig(**data)

validator: block.

class exlab_wizard.config.models.BandwidthConfig(**data)[source]#

Bases: BaseModel

bandwidth: sub-block on a transport.

Parameters:

data (Any)

model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

schedule: list[BandwidthWindow]#
upload_mbps: float | None#
class exlab_wizard.config.models.BandwidthWindow(**data)[source]#

Bases: BaseModel

One {days, from, to} window. Backend Spec §9.

Parameters:

data (Any)

days: list[BandwidthDay]#
from_: str#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'populate_by_name': True, 'str_strip_whitespace': True, 'validate_by_alias': True, 'validate_by_name': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

to: str#
class exlab_wizard.config.models.Config(**data)[source]#

Bases: BaseModel

Top-level config.yaml model. Mirrors §9 verbatim.

Parameters:

data (Any)

equipment: list[EquipmentConfig]#
lims: LIMSConfig#
logging: LoggingConfig#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

nas: NasConfig#
nas_cleanup: NASCleanupConfig#
operators: OperatorsConfig#
orchestrator: OrchestratorConfig#
paths: PathsConfig#
plugins: PluginsConfig#
readme: READMEConfig#
sync: SyncConfig#
update_check: UpdateCheckConfig#
validator: ValidatorConfig#
class exlab_wizard.config.models.EquipmentConfig(**data)[source]#

Bases: BaseModel

One equipment: list entry. Backend Spec §9.

sync_mode (Redesign Spec §3.2) is the per-equipment role this device plays for the equipment: nas means this device acquires runs and syncs them directly to the NAS; stage means this device acquires runs and pushes them to a connected staging PC’s staging area instead.

rclone.conf NAS-sync migration: neither mode carries a per-equipment connection block. The NAS connection is defined once by the nas: block (a single rclone remote); the staging hop is defined once by orchestrator.staging_remote / orchestrator.staging_base_root (a second rclone remote in the same rclone.conf). The push target is selected by sync_mode at sync time, not by a per-equipment block.

Parameters:

data (Any)

id: str#
label: str#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

nas_root: str#
sync_mode: SyncMode#
class exlab_wizard.config.models.FileStabilityConfig(**data)[source]#

Bases: BaseModel

sync.stability: sub-block. Pre-rclone size-stability guard.

A complete file passes in (checks - 1) * interval_seconds (~4 s with the defaults); timeout_seconds bounds a still-growing file before it is deferred to a later sweep. On NFS-mounted sources raise interval_seconds to at least the mount’s actimeo (typically >= 30 s) so attribute-cache staleness cannot mask an in-progress write.

Parameters:

data (Any)

checks: int#
enabled: bool#
interval_seconds: float#
max_workers: int#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

timeout_seconds: float#
class exlab_wizard.config.models.LIMSConfig(**data)[source]#

Bases: BaseModel

lims: block. Read-only LIMS endpoint plus offline catalogue path.

Parameters:

data (Any)

cache_ttl_hours: int#
email: str#
endpoint: str#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

offline_catalogue_path: str#
class exlab_wizard.config.models.LoggingConfig(**data)[source]#

Bases: BaseModel

logging: block. Central app-log rotation + level.

Parameters:

data (Any)

central_log_keep: int#
central_log_max_mb: int#
level: str#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class exlab_wizard.config.models.NASCleanupConfig(**data)[source]#

Bases: BaseModel

nas_cleanup: block. Local-copy retention after NAS verify.

Parameters:

data (Any)

delete_ignored: bool#
enabled: bool#
min_age_hours: int#
min_verify_passes: int#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

retain_cache: bool#
class exlab_wizard.config.models.NasConfig(**data)[source]#

Bases: BaseModel

nas: block — the single rclone remote + base root for NAS sync.

remote is the name of a remote defined in the operator’s rclone.conf (set up separately with rclone config). Equipment run folders live under <remote>:<base_root>/<equipment_id>/…. rclone_config_path optionally pins rclone --config <path> for when the app runs as a different OS user than the one who created the config; blank means rclone’s default discovery.

Parameters:

data (Any)

bandwidth: BandwidthConfig#
base_root: str#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

mtime_tolerance_s: int#
perf: RclonePerf#
rclone_config_path: str#
remote: str#
class exlab_wizard.config.models.OperatorsConfig(**data)[source]#

Bases: BaseModel

operators: block. Optional case-sensitive allowlist.

Parameters:

data (Any)

allowlist: list[str]#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class exlab_wizard.config.models.OrchestratorConfig(**data)[source]#

Bases: BaseModel

orchestrator: block. Backend Spec §9, §13.

GUI/Orchestrator Redesign §3.1 collapsed the single-equipment / orchestrator distinction (no enabled toggle). label is required by the setup-state gate – it identifies this workstation in every run’s creation.json. staging_root is opt-in: a blank value means this device is not a staging PC, so it does not gate setup and no staging directory is created until the operator saves a non-empty path.

rclone.conf NAS-sync migration (Phase 8): staging_remote / staging_base_root define the orchestrator’s stage-mode hop as a named rclone remote (a second remote in the same rclone.conf as the nas: remote). stage-mode equipment push run folders to <staging_remote>:<staging_base_root>/<equipment_id>/<run-leaf> using the same RcloneDriver ops as the NAS leg. staging_perf is the parallelism dial for that hop.

Parameters:

data (Any)

label: str#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

staging_base_root: str#
staging_cleanup: OrchestratorStagingCleanup#
staging_perf: RclonePerf#
staging_remote: str#
staging_root: str#
class exlab_wizard.config.models.OrchestratorStagingCleanup(**data)[source]#

Bases: BaseModel

orchestrator.staging_cleanup: sub-block. Backend Spec §13.7.

Parameters:

data (Any)

mode: StagingCleanupMode#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

retain_hours: int#
class exlab_wizard.config.models.PathsConfig(**data)[source]#

Bases: BaseModel

paths: block. A single app root with derived working subdirectories.

Only app_root is stored (and serialized); templates/, plugins/ and the experiment data/ root are derived read-only properties so the operator configures exactly one location. app_root defaults under the OS Documents folder (<Documents>/ExLabWizard via exlab_wizard.paths.default_app_root()) so a fresh install needs no manual path entry.

The derived names templates_dir / plugin_dir / local_root are kept so existing read-only consumers (run creation, browse, template resolution) keep reading config.paths.local_root unchanged – it now resolves to <app_root>/data. local_root is an alias of data_root; new code should prefer data_root.

Parameters:

data (Any)

app_root: str#
property data_root: str#

The experiment data root, <app_root>/data.

property local_root: str#

Alias of data_root (kept for existing consumers).

model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

property plugin_dir: str#

The lab plugin directory, <app_root>/plugins.

property templates_dir: str#

The global Copier template library, <app_root>/templates.

class exlab_wizard.config.models.PluginsConfig(**data)[source]#

Bases: BaseModel

plugins: block. Master opt-in for network-declaring plugins.

Parameters:

data (Any)

allow_network: bool#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class exlab_wizard.config.models.READMEConfig(**data)[source]#

Bases: BaseModel

readme: block. Lab-policy fields layered on top of the core set.

Parameters:

data (Any)

defaults: list[READMEDefaultField]#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class exlab_wizard.config.models.READMEDefaultField(**data)[source]#

Bases: BaseModel

One operator-defined extra README field. Backend Spec §9, §10.

Parameters:

data (Any)

default: Any#
hint: str | None#
id: str#
label: str#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

options: list[str] | None#
required: bool#
type: FieldType#
class exlab_wizard.config.models.RclonePerf(**data)[source]#

Bases: BaseModel

Parallelism knobs forwarded to rclone (--transfers / --checkers).

These double as the memory dial on space- and RAM-constrained acquisition machines: peak memory scales with these counts times rclone’s per-stream buffer.

Parameters:

data (Any)

checkers: int#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

transfers: int#
class exlab_wizard.config.models.SyncConfig(**data)[source]#

Bases: BaseModel

sync: block. NAS sync engine kill-switch + retry / quiescence policy.

Parameters:

data (Any)

enabled: bool#
ignore_globs: list[str]#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

poll_interval_seconds: int#
quiescence_minutes: int#
retry_attempts: int#
stability: FileStabilityConfig#
class exlab_wizard.config.models.UpdateCheckConfig(**data)[source]#

Bases: BaseModel

update_check: block. Design Spec §15.6 / §15.8 item 3.

Kill-switch for the startup update notifier (the §15.8 item 3 self-update channel, notifier-only stage). Default-ON; set enabled: false to stop the launch-time GitHub Releases probe on air-gapped or policy-locked hosts.

Parameters:

data (Any)

enabled: bool#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class exlab_wizard.config.models.ValidatorConfig(**data)[source]#

Bases: BaseModel

validator: block. Content-scan tuning. Backend Spec §8.1.1, §11.8.

Parameters:

data (Any)

content_scan_extensions: list[str]#
content_scan_max_mib: int#
model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'str_strip_whitespace': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].