exlab_wizard.lims#
LIMS integration package. Backend Spec §7.2 + §7.4.
Public surface:
LIMSClient– read-only REST client (Mapping B; §7.2).LIMSCache– aiosqlite TTL cache for project rows (§7.2.4).OfflineCatalogue+read_catalogue()/write_catalogue()– NAS-shared offline catalogue (§7.2.9).KeyringStore– OS keyring with encrypted-at-rest fallback for credential storage (§7.4).LIMSProject,LIMSUser,HealthStatus– the typed values exchanged across the package boundary.
- class exlab_wizard.lims.HealthStatus(ok, latency_ms, reason=None)[source]#
Bases:
objectResult of
LIMSClient.health_check(). Backend Spec §7.2.3.okis True iff the LIMS responded toGET /mewith 2xx.latency_msis the wall-clock duration in milliseconds (rounded to the nearest int).reasonis None on success and carries a short human-readable failure summary on failure.
- class exlab_wizard.lims.KeyringStore(*, state_dir, passphrase_provider=None)[source]#
Bases:
objectOS keyring with encrypted-at-rest fallback.
Backend Spec §7.4. Tries the OS keyring first via the
keyringmodule. When that backend raiseskeyring.errors.KeyringError(or one of its subclasses for “no backend installed”), falls back to<state_dir>/exlab-wizard/secrets.encencrypted with Fernet (AES-128-CBC + HMAC-SHA256), keyed by an Argon2id KDF over a user-supplied master passphrase.The fallback path requires a
passphrase_providercallable; if the keyring is available this argument may beNone– it is only invoked when the fallback engages.- delete_password(*, username)[source]#
Remove the secret stored under
(KEYRING_SERVICE, username).Missing entries are silent in both backends. Keyring errors defer to the fallback.
- get_password(*, username)[source]#
Look up the secret for
(KEYRING_SERVICE, username).Returns
Nonewhen the entry is absent in both backends. Errors from the OS keyring are caught and the fallback is consulted; errors from the fallback propagate asexlab_wizard.errors.KeyringUnavailableError.
- is_keyring_available()[source]#
Best-effort probe of the OS keyring backend.
Implemented as a round-trip
set+deleteof a sentinel value. A backend that raises on either side is considered unavailable. Per §7.4.4 thekeyringpackage’s “fail” backends raisekeyring.errors.KeyringErrorfrom these calls.- Return type:
- class exlab_wizard.lims.LIMSCache(db_path, *, ttl_hours=24)[source]#
Bases:
objectSQLite TTL cache for LIMS project rows. Backend Spec §7.2.4.
The cache is async-native via aiosqlite so that
LIMSClientcan interleave cache reads with httpx network calls without blocking the event loop.- async get_project(endpoint, uid_or_short_id)[source]#
Return one project by uid or short_id, or None if absent.
- Parameters:
- Return type:
- async is_fresh(endpoint)[source]#
True iff the most recent
last_refreshedis withinttl_hoursof the wizard’s current UTC time. False when the cache has no rows forendpoint.
- async list_projects(endpoint, *, status_filter=None)[source]#
Return every cached project for
endpoint, optionally filtered bystatus_filter(an OR of allowed status values).
- async upsert_many(endpoint, projects)[source]#
Insert or update every row.
last_refreshedis taken from eachLIMSProject.fetched_at; the caller stamps that value before invoking this method so a single refresh batch shares one timestamp.- Parameters:
endpoint (
str)projects (
list[LIMSProject])
- Return type:
- class exlab_wizard.lims.LIMSClient(*, endpoint, email, keyring_password_provider)[source]#
Bases:
objectRead-only LIMS client. Backend Spec §7.2 Mapping B.
Cookie-session auth:
login()establishes the session; subsequent list/get methods reuse the cookie. On 401, the client refreshes the cookie vialogin()once before failing.The
keyring_password_providercallable is invoked fromlogin()when no explicit password is passed. Callers wire this toexlab_wizard.lims.keyring_store.KeyringStore.get_passwordin production; tests can pass a lambda that returns a static value.- async get_project(uid_or_short_id)[source]#
GET /api/v1/projects/<id>; returns None on 404.uid_or_short_idmay be either a UUID (uidcolumn) or aPROJ-NNNNstring (short_idcolumn). The LIMS resolves both at the same endpoint.- Parameters:
uid_or_short_id (
str)- Return type:
- async health_check()[source]#
Return a
HealthStatussnapshot. Backend Spec §7.2.3.Calls
GET /api/v1/meand times the response. On any error (network, 4xx, 5xx) returnsok=Falsewith a short reason rather than raising – the Settings “Test connection” UX needs a value to render.- Return type:
- async list_projects(*, status_filter=None)[source]#
GET /api/v1/projects; returns one LIMSProject per row.status_filteris an optional list of allowed status values; rows whosestatusis not in the set are dropped on the client side. Filtering happens after deserialization so the wire format stays uniform.Wire envelope: upstream returns
{"data": [...], "count": N}; a missingdatakey is treated as an empty list rather than propagating aKeyErrorto the caller.- Parameters:
- Return type:
- async login(*, password=None)[source]#
POST
/api/v1/loginwith email + password.On success the underlying
httpx.AsyncClientretains the session cookie automatically; subsequent reads reuse it.Raises
exlab_wizard.errors.ConfigErrorwhen the keyring provider returns no password and none was supplied – that is a configuration condition, not a transient network failure.
- class exlab_wizard.lims.LIMSProject(*, uid: str, short_id: str, name: str, status: LIMSProjectStatus, owner: str, fetched_at: str, description: str | None = None, contact_name: str | None = None, metadata: dict = <factory>)[source]#
Bases:
StructOne LIMS project row. Backend Spec §7.2.3.
The
metadatafield is a JSONB blob the LIMS owns; ExLab-Wizard does not mutate it.fetched_atis a UTC ISO 8601 timestamp set when the wizard pulled the row – the local cache uses it for freshness bookkeeping (§7.2.4).- status: LIMSProjectStatus#
- class exlab_wizard.lims.LIMSUser(*, uid: str, email: str, role: str)[source]#
Bases:
StructOne LIMS user row. Backend Spec §7.2.3.
Mirrors the upstream
safe_usercontract returned byGET /api/v1/me. Only the fields ExLab-Wizard surfaces are typed; everything else is dropped by msgspec.
- class exlab_wizard.lims.OfflineCatalogue(schema_version, produced_by, produced_at, lims_endpoint, projects)[source]#
Bases:
objectDecoded offline catalogue. Backend Spec §7.2.9.1.
schema_versionis pinned to the constant declared inexlab_wizard.constants.schema_versions; mismatches surface asexlab_wizard.errors.ConfigError.lims_endpointis verified byread_catalogue()against the consumer’s configured LIMS endpoint; mismatches are rejected per §7.2.9.3 to defend against accidentally pointing at a different lab’s LIMS.- Parameters:
- projects: list[LIMSProject]#
- exlab_wizard.lims.read_catalogue(path, *, expected_endpoint)[source]#
Read and validate the catalogue file.
Raises
exlab_wizard.errors.ConfigErroron any of:file missing / unreadable
JSON parse error
schema_versionis not the constantexlab_wizard.constants.OFFLINE_CATALOGUE_VERSIONlims_endpointdiffers fromexpected_endpoint(per §7.2.9.3 the producer’s LIMS must match the consumer’s configuration; cross-lab leakage is rejected, not warned).
- Parameters:
- Return type:
- exlab_wizard.lims.write_catalogue(path, catalogue)[source]#
Atomically write
cataloguetopath. Backend Spec §7.2.9.2.Protocol: serialize, write to
<path>.tmp.<pid>, fsync, thenos.replaceto the final path. Concurrent producers do not corrupt the file – each rename is atomic; the last writer wins.- Parameters:
path (
Path)catalogue (
OfflineCatalogue)
- Return type:
Modules
aiosqlite-backed cache for LIMS project rows. |
|
Offline LIMS project catalogue read/write. |
|
Read-only LIMS client (Mapping B). |
|
OS keyring storage with encrypted-at-rest fallback. |
|
msgspec.Struct types for the LIMS read-only client. |