labs¶
The labs package provides a DB-agnostic host-source (LabRepository)
backend, selected by name and constructed via otto.labs.build_lab_repository().
The built-in json backend reads lab.json files; custom backends
register a name via otto.labs.register_lab_repository() from an
init module.
-
otto.labs.build_lab_repository(settings: dict[str, Any], repo_dir: Path, *, search_paths: list[Path] | None =
None) LabRepository¶ Construct a host-source backend from a parsed
[lab]section.Parameters¶
- settingsdict[str, Any]
The
[lab]sub-dict parsed from.otto/settings.toml.backendselects a registered name (defaults to"json");[lab.<name>]holds the backend’s keyword arguments.- repo_dirPath
The SUT repo root, forwarded as
repo_dir=to a custom backend’s constructor. The built-injsonbackend ignores it and usessearch_pathsinstead.- search_pathslist[Path] | None
The aggregated
labsdirectories. Passed to the built-injsonbackend (preserving today’s multi-repo path merge); custom backends carry their own config and do not receive it.
Returns¶
- LabRepository
A ready-to-query backend instance.
Raises¶
- ValueError
If the
[lab]envelope is malformed.- LabRepositoryError
If
backendnames an unknown (unregistered) backend.
-
otto.labs.register_lab_repository(name: str, cls: type, *, overwrite: bool =
False) None¶ Make a custom host-source backend selectable as
backend = "<name>".Call from an
initmodule listed in.otto/settings.toml. The class must satisfy theLabRepositoryprotocol.overwrite replaces an existing registration under name deliberately (e.g. a built-in); by default a duplicate name raises.
- exception otto.labs.LabNotFoundError¶
Bases:
LabRepositoryErrorload_labwas asked for a lab name the backend does not know.A missing lab must raise this — not return
Noneor raise a bareKeyError/FileNotFoundError— so callers can distinguish “unknown lab” from “backend is broken”.
LabRepository protocol — the DB-agnostic interface all lab-repository backends satisfy.
- class otto.labs.protocol.LabRepository(*args, **kwargs)¶
Bases:
ProtocolDB-agnostic interface for loading labs.
A backend is configured at construction time (the built-in JSON backend takes its
search_pathsin__init__), then queried through the two methods below. Selection and construction happen inotto.labs.build_lab_repository().-
load_lab(name: str, preferences: dict[str, dict[str, Any]] | None =
None) Lab¶ Load a lab by name.
Parameters¶
- namestr
Name of the lab to load.
- preferencesdict[str, dict[str, Any]] | None
The unified
{selector: {capability: [...] | option_table: {key: val}}}product-preference table forwarded to the factory, which matches each host’sidand applies the result.Nonereproduces today’s behavior.
Returns¶
- Lab
Fully constructed lab.
Raises¶
- LabNotFoundError
If no lab named
nameexists.- LabRepositoryError
If the backend fails to satisfy the query (I/O, parse, network).
-
load_lab(name: str, preferences: dict[str, dict[str, Any]] | None =
Built-in "json" LabRepository backend: loads labs from lab.json files.
- otto.labs.json_repository.parse_lab_sections(data: object, source: str) dict[str, list[Any]]¶
Validate a parsed
lab.jsonobject’s section shape; return its sections.The single source of truth for the
lab.jsonobject contract — shared by the runtime loader (JsonFileLabRepository._load_lab_file) and theotto initdoctor (otto.cli.init._validate_lab) so the doctor cannot drift from what otto actually accepts (there is no second validator to drift). data is the already-parsed JSON value; source names its origin (a file path) for error messages.Top-level
_-prefixed keys are comment space (same idiom as host entries); unknown sections fail loud. Returns a dict carrying every known section as a (possibly empty) list.Raises¶
- LabRepositoryError
If data is not a JSON object, carries an unknown top-level section, or a section’s value is not a JSON array.
-
class otto.labs.json_repository.JsonFileLabRepository(search_paths: list[Path] | None =
None)¶ Bases:
objectLoad labs from
lab.jsonfiles under a fixed set of search paths.Each
lab.jsonis a JSON object with array sections —{"hosts": [...], "links": [...]}. The search paths are supplied once at construction — this is the built-in"json"backend, andotto.labs.build_lab_repository()feeds it the aggregatedlabsdirectories. Thehostssection holds all known hosts; a host’slabsfield lists the labs it belongs to, mirroring a row-with-membership database design. Top-level_-prefixed keys are comment space; unknown sections fail loud.-
load_lab(name: str, preferences: dict[str, dict[str, Any]] | None =
None) Lab¶ Load a lab by filtering hosts from the configured lab.json files.
Raises¶
- LabNotFoundError
If no lab.json exists in any search path, or no host belongs to the requested lab.
- LabRepositoryError
If a lab.json is malformed or a host’s data is invalid.
-
load_lab(name: str, preferences: dict[str, dict[str, Any]] | None =
Name → class registry for host-source (LabRepository) backends.
Mirrors otto.reservations.registry and otto’s other extension
registries (register_term_backend / register_transfer_backend /
register_host_class): a custom backend registers a bare name from an
init module, and [lab] backend = "<name>" selects it. The built-in
json backend is pre-registered at import so it resolves through the same
path.
-
otto.labs.registry.register_lab_repository(name: str, cls: type, *, overwrite: bool =
False) None¶ Make a custom host-source backend selectable as
backend = "<name>".Call from an
initmodule listed in.otto/settings.toml. The class must satisfy theLabRepositoryprotocol.overwrite replaces an existing registration under name deliberately (e.g. a built-in); by default a duplicate name raises.
- otto.labs.registry.get_lab_repository_class(name: str) type¶
Return the backend class registered under name.
Raises¶
- LabRepositoryError
If name is not registered; the message lists the registered names.
Error contract for the host-source (LabRepository) backend interface.
Mirrors the reservation backend’s error contract
(ReservationBackendError): a backend signals
trouble through these types so callers and the conformance suite can rely on a
stable surface instead of backend-specific exceptions.
- exception otto.labs.errors.LabRepositoryError¶
Bases:
ExceptionA host-source backend failed to satisfy a query.
Raised for I/O, network, parse, or credential failures while loading or listing labs — anything other than “the named lab does not exist”, which raises the more specific
LabNotFoundError.
- exception otto.labs.errors.LabNotFoundError¶
Bases:
LabRepositoryErrorload_labwas asked for a lab name the backend does not know.A missing lab must raise this — not return
Noneor raise a bareKeyError/FileNotFoundError— so callers can distinguish “unknown lab” from “backend is broken”.