host.host¶
Async host abstraction: the Host protocol, BaseHost ABC, and run helpers.
- otto.host.host.get_logging_command_output_enabled() bool¶
Return True if command-output logging is enabled on the active context.
-
class otto.host.host.ShellCommand(cmd: str, expects: tuple[str | Pattern[str], str] | list[tuple[str | Pattern[str], str]] | None =
None, timeout: float | None =None, log: LogMode | None =None)¶ Bases:
objectA command plus the per-command options that should be used to run it.
Fields left as
Noneinherit from the run-level kwargs onHost.run(). A scalarExpectvalue is accepted forexpectsfor ergonomics; it is normalized to a one-element list before execution.- expects : tuple[str | Pattern[str], str] | list[tuple[str | Pattern[str], str]] | None¶
Per-command expects.
Noneinherits the run-levelexpectsvalue.
- class otto.host.host.RunResult(status: Status, statuses: list[CommandStatus])¶
Bases:
objectUnified result of
Host.run()regardless of how many commands ran.statusesalways has one entry per issued command.statusis the aggregate:Status.Successwhen every entry is ok, otherwise the first non-ok status encountered (matching the old tuple-form semantics).- statuses : list[CommandStatus]¶
Per-command statuses in execution order.
- property only : CommandStatus¶
Return the sole
CommandStatuswhen exactly one command ran.Raises
ValueErrorotherwise — useful for single-command call sites that want to read fields directly without unpacking.
- class otto.host.host.Host(*args, **kwargs)¶
Bases:
ProtocolStructural protocol defining the public interface every otto host must satisfy.
Implementations of
Hostconnect otto to a specific target type (SSH, serial console, QEMU, etc.).BaseHostprovides concrete default implementations for the shared mechanics; individual host classes such asUnixHostorEmbeddedHostinherit fromBaseHostand implement the family-specific hooks.- log : LogMode¶
Standing per-host logging disposition. Composed with the per-command mode via
effective_modeat the emit seam.
- power_control : PowerController | None¶
Pluggable power backend, or None when this host can’t be power-controlled.
-
async run(cmds: str | ShellCommand | Sequence[str | ShellCommand], expects: tuple[str | Pattern[str], str] | list[tuple[str | Pattern[str], str]] | None =
None, timeout: float | None =None, log: LogMode =LogMode.NORMAL, sudo: bool =False) RunResult¶ Run one or more commands on the host and collect their results.
- Parameters:¶
cmds – A single command or a sequence of commands to run in order. Strings and
ShellCommandobjects may be mixed.expects – Optional
(pattern, response)pair(s) for interactive prompts. Inherited by each command unless overridden per-command.timeout – Per-command timeout for a single command, or a cumulative budget shared across all commands in a sequence.
Nonemeans no limit.log – Whether to log command output for this call.
sudo – If
True, each command is run with elevated privileges. Implementations that do not support elevation raiseNotImplementedError.
- Returns:¶
A
RunResultaggregating each command’s status and output.
-
async oneshot(cmd: str, timeout: float | None =
None, log: LogMode =LogMode.NORMAL) CommandStatus¶ Run a single command outside the typical stateful
runworkflow.Concurrency safety is implementation-dependent. Host families with an independent exec primitive (e.g.
UnixHost,LocalHost) open a fresh connection or subprocess per call, sooneshotis safe to use concurrently from multiple coroutines. Families exposing only a single console (e.g.EmbeddedHost) share the persistent session and are not concurrency-safe — see the concrete class.Returns the
CommandStatusfor the command.
- async open_session(name: str) HostSession¶
Open a named auxiliary session on this host.
Named sessions are independent of the host’s default persistent session and of each other, allowing concurrent shell interactions. The caller is responsible for closing the returned
HostSessionwhen done.
-
async send(text: str, log: LogMode =
LogMode.NORMAL) None¶ Send raw text to the host’s persistent session without waiting for a response.
Useful for driving interactive prompts or menu-driven interfaces where a full
run()round-trip is not appropriate.
-
async expect(pattern: str | Pattern[str], timeout: float =
30.0) str¶ Wait for pattern to appear in the host’s session output.
- async get(src_files: list[Path] | Path, dest_dir: Path) tuple[Status, str]¶
Download one or more files from the host to a local directory.
Returns a
(Status, message)tuple:Successwith an empty message on success; a non-ok status with a diagnostic on failure.
- async put(src_files: list[Path] | Path, dest_dir: Path) tuple[Status, str]¶
Upload one or more local files to a directory on the host.
Returns a
(Status, message)tuple:Successwith an empty message on success; a non-ok status with a diagnostic on failure.
-
async power(state: str | None =
None) tuple[Status, str]¶ Power this host on, off, or toggle (when state is
None).Returns a
(Status, message)tuple.
-
async reboot(hard: bool =
False, wait: bool =False, timeout: float =600.0) tuple[Status, str]¶ Reboot this host.
hard=Falseissues an in-shell reboot;hard=Truepower-cycles via thePowerController. When wait isTrue, blocks until the host is reachable again or timeout seconds have elapsed. Returns a(Status, message)tuple.
- async shutdown() tuple[Status, str]¶
Power this host off from its own shell.
Distinct from
power()('off'), which uses an external power controller. Returns a(Status, message)tuple.
-
async is_reachable(timeout: float =
10.0) bool¶ Return
Trueif the host responds to a connection probe within timeout seconds.
-
async wait_until_up(timeout: float, interval: float =
2.0) bool¶ Poll until the host is reachable or timeout seconds elapse.
Returns
Trueif reachable before the deadline,Falseotherwise.
-
async wait_until_down(timeout: float, interval: float =
2.0) bool¶ Poll until the host is unreachable or timeout seconds elapse.
Returns
Trueif unreachable before the deadline,Falseotherwise.
- async stage() tuple[Status, str]¶
Stage every product onto this host (transfer/place, no install).
Returns a
(Status, message)tuple.
-
async install(stage_only: bool =
False) tuple[Status, str]¶ Stage and then install every product on this host.
When stage_only is
True, stops after staging without installing. Returns a(Status, message)tuple, short-circuiting on the first failure.
- async uninstall() tuple[Status, str]¶
Uninstall every product from this host (best-effort).
Returns a
(Status, message)tuple.
- async is_uninstalled() bool¶
Return
Trueiffis_installed()returnsFalse.
- class otto.host.host.BaseHost¶
Bases:
ABCAbstract base class providing shared mechanics for all host implementations.
BaseHostimplements the cross-cutting concerns that every host family needs — command budgeting, dry-run stubs, product lifecycle, power/reboot orchestration, and the repeat-command scheduler. Concrete host classes (UnixHost,EmbeddedHost, etc.) inherit fromBaseHost, implement the family-specific hooks (_run_one,oneshot,_soft_reboot, …), and satisfy theHostprotocol.- power_control : PowerController | None¶
-
async switch_user(user: str =
'', password: str | None =None) None¶ Switch the persistent session to another user via
su.Default raises — only posix-shell hosts (via
PosixPrivilege) supportsu.
-
as_user(user: str =
'root', password: str | None =None) NoReturn¶ Async context manager to run a block as user.
Default raises — only posix-shell hosts (via
PosixPrivilege) supportsu-based user switching.
- property current_user : str¶
User this host’s default shell session is currently running as.
Seeded from the login user; changes only through
switch_user()/as_user(). Seecurrent_userfor named sessions.
- async interact() None¶
Open an interactive shell bridged to the local terminal.
Subclasses implement
_interactto do the actual protocol work. This wrapper exists so CLI and SDK callers have a single public entry point.stdin and stdout are bridged directly to the remote terminal and the session is recorded to the otto log. Press
Ctrl+]to disconnect locally without ending the remote session; typeexitorlogoutto end the session normally.
- async run(cmds: ~types.Annotated[str | ~otto.host.host.ShellCommand | ~collections.abc.Sequence[str | ~otto.host.host.ShellCommand], ~otto.utils.Arg(variadic=True, elem_type=str, name=None, help=Command(s) to run.)], expects: ~types.Annotated[tuple[str | ~re.Pattern[str], str] | list[tuple[str | ~re.Pattern[str], str]] | None, <otto.utils._Exclude object at 0x7c9c99ff98c0>] | None = None, timeout: ~types.Annotated[float | None, ~otto.utils.Opt(elem_type=None, name=None, help=Per-command/cumulative timeout (seconds).)] | None = None, log: ~otto.logger.mode.Annotated[~otto.logger.mode.LogMode, <otto.utils._Exclude object at 0x7c9c99ff98c0>] = LogMode.NORMAL, sudo: bool = False) RunResult¶
Execute one or more commands on the host via the persistent shell session.
The session is stateful: working directory changes (
cd), exported environment variables, and other shell state persist between calls, just as they would in an interactive terminal.- Parameters:¶
cmds – A single command (
strorShellCommand) or a sequence of commands. Strings andShellCommandobjects may be mixed. For single-command calls, read the result viaresult.only(orresult.statuses[0]).expects – Default
(pattern, response)pair(s) for interactive prompts. Accepts a singleExpecttuple or a list of them. Each command inherits this value unless its ownShellCommand.expectsis set.timeout – For a single command, the per-command timeout. For a sequence, a cumulative timeout shared across all commands — each command receives the remaining budget; when exhausted, remaining commands are skipped with
Status.Error.ShellCommand.timeoutcaps the per-command value but is still bounded by the remaining budget.sudo – If
True, each command is rewritten through_elevatebefore execution. Hosts that do not support elevation (e.g. embedded/RTOS) raiseNotImplementedError— see_elevate.
- Returns:¶
RunResultwith the aggregateStatusand a list of per-commandCommandStatusentries.
See also
oneshot(): stateless, concurrent-safe alternative for one-off commands.
-
async oneshot(cmd: str, timeout: float | None =
None, log: LogMode =LogMode.NORMAL) CommandStatus¶ Run a single command outside the persistent shell session. Subclasses must override.
- async open_session(name: str) HostSession¶
Open a named auxiliary session on this host. Subclasses must override.
-
async send(text: str, log: LogMode =
LogMode.NORMAL) None¶ Send raw text to the host’s persistent session. Subclasses must override.
-
async expect(pattern: str | Pattern[str], timeout: float =
30.0) str¶ Wait for pattern in the session output. Subclasses must override.
- async get(src_files: list[Path] | Path, dest_dir: Path) tuple[Status, str]¶
Download files from the host to a local directory. Subclasses must override.
- async put(src_files: list[Path] | Path, dest_dir: Path) tuple[Status, str]¶
Upload local files to a directory on the host. Subclasses must override.
- async close() None¶
Close the persistent session and release held resources. Subclasses must override.
- async stage() tuple[Status, str]¶
Stage every product onto this host (transfer/place, no install).
Iterates
productsin declaration order, returning the first non-ok(Status, str); an empty list is a successful no-op.
-
async install(stage_only: bool =
False) tuple[Status, str]¶ Stage, then install every product.
Calls
stage()first; returns early ifstage_onlyis set or the stage step failed. Otherwise installs each product in declaration order, short-circuiting on the first failure. Projects may override for cross-product ordering/dependencies.
- async uninstall() tuple[Status, str]¶
Uninstall every product (best-effort).
Attempts every product even if one fails, returning the first non-ok result seen (so cleanup is not abandoned halfway).
- async is_installed() bool¶
Return True iff there is at least one product and all are installed.
An empty
productslist is not installed (avoids the vacuous-truth surprise ofall([])).
- async is_uninstalled() bool¶
Inverse of
is_installed().
-
async power(state: Annotated[str | None, Arg(variadic=False, elem_type=None, name=None, help=None)] | None =
None) tuple[Status, str]¶ Power this host
'on'/'off', or toggle when state is None.Toggling reads the controller’s
status(); if the controller can’t report state, pass an explicitstate.
-
async reboot(hard: bool =
False, wait: bool =False, timeout: float =600.0) tuple[Status, str]¶ Reboot this host.
hard=False(default) issues the in-shell reboot command (_soft_reboot);hard=Truepower-cycles via thePowerController. When wait, block onwait_until_up()(up to timeout, default 10 minutes); if the host is still unreachable when timeout expires, the result is downgraded toFailed.
- async shutdown() tuple[Status, str]¶
Power this host off from its own shell (distinct from external
power('off')).Per-family override; default raises.
-
async is_reachable(timeout: float =
10.0) bool¶ Whether this host answers a lightweight connection probe.
Per-family override; default raises (no generic probe).
-
async wait_until_up(timeout: float, interval: float =
2.0) bool¶ Poll
is_reachable()until reachable or timeout. Returns success.
-
async wait_until_down(timeout: float, interval: float =
2.0) bool¶ Poll
is_reachable()until not reachable or timeout.
-
start_repeat(name: str, cmds: list[str] | str, interval: timedelta, times: int =
-1, duration: timedelta =datetime.timedelta(days=999999999, seconds=86399, microseconds=999999), until: datetime =datetime.datetime(9999, 12, 31, 23, 59, 59, 999999, tzinfo=datetime.timezone.utc), on_result: Callable[[str, datetime, list[CommandStatus]], None] | None =None, max_history: int =1000) None¶ Start a named background task that runs cmds repeatedly at interval.
- Parameters:¶
name – Unique label for this repeat task. Raises
RuntimeErrorif a task with the same name is already running.cmds – Command string or list of command strings to run each cycle.
interval – Time between the start of successive runs.
times – Maximum number of cycles, or
-1for unlimited.duration – Stop after this wall-clock duration, or
timedelta.maxfor unlimited.until – Stop at this UTC datetime, or the module sentinel for unlimited.
on_result – Optional callback invoked after each cycle with the task name, timestamp, and per-command statuses from that run.
max_history – Maximum number of past run results to retain (ring buffer).
- repeat_results(name: str) list[tuple[datetime, list[CommandStatus]]]¶
Return the stored run history for the named repeat task.
Each entry is a
(timestamp, statuses)pair recorded at the end of one cycle. At mostmax_historyentries are kept (oldest discarded first, as set instart_repeat()).
-
class otto.host.host.HostFilter(name=
'')¶ Bases:
FilterConsole-side suppress filter: drops QUIET/NEVER records and honors the global flag.
Attached to the console +
console.loghandlers only —verbose.logkeeps the records (seemanagement).The per-host standing mode is now folded into each record’s
log_modeviaBaseHost._effective_logat the emit seam, so the filter decides purely onrecord.log_modeplus the global command-output flag.
-
class otto.host.host.SuppressCommandOutput(host: Host | None =
None)¶ Bases:
objectSuppress command/output logging for one host or globally.
On enter, the prior state is snapshotted; on exit it is restored. That makes nesting safe — an inner context cannot clobber an outer one — and makes concurrent per-host suppressions race-free, since each context only touches its own host’s
logattribute.The no-host (global) path mutates
log_command_outputon the activeOttoContextwhen one is present. When no context is active the call is a no-op (there is nothing to suppress). Prefer the per-host form when suppressing work that runs concurrently.