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.OPTIONS_KEYS : frozenset[str] = frozenset({'ftp_options', 'nc_options', 'scp_options', 'sftp_options', 'ssh_options', 'telnet_options'})

Names of the per-protocol option tables accepted on host dicts and as repo-level [host_defaults.<key>] tables.

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

Create the appropriate RemoteHost subclass from a host dict.

The osType field names a registered OsProfile, which selects the base host class to build and carries a bundle of default field values: :rtype: RemoteHost

  • unix (the default when osType is absent) → UnixHost

  • embeddedEmbeddedHost

  • any custom profile registered via register_os_profile or an [os_profiles.<name>] settings table → its declared base class

Field precedence, highest to lowest:

  1. the host’s own value in host_data;

  2. the profile’s defaults;

  3. repo-level *_options defaults from defaults (per-key, options only);

  4. the base class’s stock dataclass default.

Parameters

host_datadict[str, Any]

Dictionary containing host configuration. The accepted keys depend on the profile’s base family; see validate_host_dict() for the required set. Common keys: ip, ne, creds, user, board, slot, neId, resources, hop, log, log_stdout, name, osType, osName, osVersion. Unix hosts additionally accept docker_capable, toolchain, and the *_options tables; embedded hosts accept telnet_options only.

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

Optional repo-level option defaults, keyed by *_options table name. When supplied, each table is merged per-key beneath the profile’s and the host’s own *_options (host keys win, then profile keys). None (the default) applies no repo-level defaults.

Returns

RemoteHost

A UnixHost or EmbeddedHost, selected by the profile’s base family.

Raises

ValueError

If osType names no registered profile, or if an embedded host declares docker_capable.

TypeError

If required fields are missing or field types are incorrect.

otto.storage.factory.validate_host_dict(host_data)

Validate host dictionary structure without creating a Host object.

osType must name a registered OsProfile; the profile’s base family determines the required-field set and the family-specific checks. The checks run against the effective dict — the host’s fields layered over the profile’s defaults — so a value supplied by the profile (e.g. creds or filesystem) satisfies/validates the same as a host field. :rtype: None

  • unix base (the default when osType is absent): ip, creds, ne required.

  • embedded base: ip, ne required — creds is optional, since the RTOS telnet shell typically has no login step.

Parameters

host_datadict[str, Any]

Host data dictionary to validate

Raises

ValueError

If osType names no registered profile, a required field is missing, a field has the wrong type, an embedded host declares docker_capable, or an embedded host’s transfer value is not console or tftp.