storage

The storage package provides a repository pattern for persisting and retrieving data (currently backed by JSON files).

class otto.storage.protocol.LabRepository(*args, **kwargs)

Bases: Protocol

Protocol defining DB-agnostic interface for loading labs.

load_lab(name, search_paths, defaults=None)

Load a lab by name from the repository.

Return type:

Lab

Parameters

namestr

Name of the lab to load

search_pathslist[Path]

Directories to search for the lab data

defaultsdict[str, dict[str, Any]] | None

Optional repo-level option defaults forwarded to the host factory. Backends should pass this through unchanged; the factory handles per-key merging beneath each host’s own *_options. None reproduces today’s behavior.

Returns

Lab

Fully constructed Lab object with all hosts

Raises

FileNotFoundError

If lab cannot be found in any search path

ValueError

If lab data is malformed

ImportError

If module loading fails (Python repository only)

supports_location(path)

Check if this repository can handle data at the given location.

Return type:

bool

Parameters

pathPath

Location to check

Returns

bool

True if this repository can load from this location

list_labs(search_paths)

List all valid lab names available in the search paths.

Return type:

list[str]

Parameters

search_pathslist[Path]

Directories to search for labs

Returns

list[str]

List of lab names found in the search paths

class otto.storage.json_repository.JsonFileLabRepository

Bases: object

Repository implementation for loading labs from a hosts.json file.

Each search-path directory may contain a hosts.json file holding all known hosts. Each host carries a ‘labs’ field listing the lab names it belongs to, mirroring a database row-with-membership design.

supports_location(path)

Check if path is a directory that could contain a hosts.json file.

Return type:

bool

load_lab(name, search_paths, defaults=None)

Load a lab by filtering hosts from hosts.json files.

Searches all search paths for hosts.json files, merges all hosts, then returns only those whose ‘labs’ field contains the requested name.

Return type:

Lab

Parameters

namestr

Name of the lab to load

search_pathslist[Path]

Directories to search for hosts.json files

defaultsdict[str, dict[str, Any]] | None

Optional repo-level option defaults forwarded to the host factory; merged per-key beneath each host’s own *_options.

Returns

Lab

Constructed Lab object with all matching hosts added

Raises

FileNotFoundError

If no hosts.json found in any search path, or no hosts belong to the requested lab

ValueError

If a hosts.json file doesn’t contain a JSON array or host data is invalid

json.JSONDecodeError

If a hosts.json file contains malformed JSON

list_labs(search_paths)

List all lab names referenced by hosts across all hosts.json files.

Return type:

list[str]

Parameters

search_pathslist[Path]

Directories to search for hosts.json files

Returns

list[str]

Sorted list of unique lab names found

otto.storage.factory.create_host_from_dict(host_data, defaults=None)

Create the appropriate RemoteHost subclass from a host dict.

os_type names a registered OsProfile, which selects the base host class and carries a bundle of default field values. The profile’s base resolves to a (host_class, host_spec) pair; the merged dict (host > profile > repo defaults, per-key for *_options) is validated once by the spec (extra='forbid', typed, with field-name suggestions on typos) and the spec builds the runtime host.

Field precedence, highest to lowest: the host’s own value; the profile’s defaults; repo-level *_options defaults (options only); the runtime class’s stock default.

Return type:

RemoteHost

Raises

ValueError

If os_type names no registered profile.

pydantic.ValidationError

If a field is missing, mistyped, misplaced, or unknown (a subclass of ValueError).

otto.storage.factory.validate_host_dict(host_data)

Validate a host dict without constructing the host.

os_type must name a registered profile; the profile’s base spec validates the merged dict (extra='forbid', required fields, typed coercion, family-specific field validators for command_frame / filesystem / transfer / docker_capable).

Return type:

None

Raises

ValueError

If os_type names no registered profile.

pydantic.ValidationError

On any structural problem (subclass of ValueError).