storage¶
The storage package provides a DB-agnostic host-source (LabRepository)
backend, selected by name and constructed via otto.storage.build_lab_repository().
The built-in json backend reads hosts.json files; custom backends
register a name via otto.storage.register_lab_repository() from an
init module.
-
otto.storage.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.storage.register_lab_repository(name: str, cls: type) 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.
-
otto.storage.create_host_from_dict(host_data: dict[str, Any], preferences: dict[str, dict[str, Any]] | None =
None) RemoteHost¶ Create the appropriate
RemoteHostsubclass from a host dict.os_typeselects the profile / class / spec.preferencesis the unified{selector: {capability_list | option_table}}table; for each host the factory cascades it byidinto capability selections (forwarded toto_host) and option-value defaults (merged per-key, product-wins). Withpreferences=Nonethe result is identical to a bare host dict.
- exception otto.storage.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”.
- class otto.storage.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.storage.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 =
-
class otto.storage.json_repository.JsonFileLabRepository(search_paths: list[Path] | None =
None)¶ Bases:
objectLoad labs from
hosts.jsonfiles under a fixed set of search paths.The search paths are supplied once at construction — this is the built-in
"json"backend, andotto.storage.build_lab_repository()feeds it the aggregatedlabsdirectories. Eachhosts.jsonholds all known hosts; a host’slabsfield lists the labs it belongs to, mirroring a row-with-membership database design.-
load_lab(name: str, preferences: dict[str, dict[str, Any]] | None =
None) Lab¶ Load a lab by filtering hosts from the configured hosts.json files.
Raises¶
- LabNotFoundError
If no hosts.json exists in any search path, or no host belongs to the requested lab.
- LabRepositoryError
If a hosts.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.storage.registry.register_lab_repository(name: str, cls: type) 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.
- otto.storage.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.storage.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.storage.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”.
-
otto.storage.factory.create_host_from_dict(host_data: dict[str, Any], preferences: dict[str, dict[str, Any]] | None =
None) RemoteHost¶ Create the appropriate
RemoteHostsubclass from a host dict.os_typeselects the profile / class / spec.preferencesis the unified{selector: {capability_list | option_table}}table; for each host the factory cascades it byidinto capability selections (forwarded toto_host) and option-value defaults (merged per-key, product-wins). Withpreferences=Nonethe result is identical to a bare host dict.
- otto.storage.factory.validate_host_dict(host_data: dict[str, Any]) None¶
Validate a host dict without constructing the host.
os_typemust name a registered profile; the profile’s base spec validates the merged dict (extra='forbid', required fields, typed coercion, family-specific field validators forcommand_frame/filesystem/transfer/docker_capable).Raises¶
- ValueError
If
os_typenames no registered profile.- pydantic.ValidationError
On any structural problem (subclass of
ValueError).