Overview

otto is an asyncio test orchestrator: a CLI and a Python library that drive labs of remote hosts — Unix machines over SSH/Telnet, embedded targets over serial consoles, Docker containers — to deploy products, run commands and test suites, and collect metrics and coverage. One process, one event loop; hosts are fanned out with asyncio, never threads.

The nine first-party commands

Everything a user does goes through one of nine first-party commands. Each is an ordinary entry in the CLI command registry — registered through the same public API a third-party command uses — and each has a lifecycle page explaining what it does once the shared machinery hands over control:

Command

What it is

otto run

Procedures: registered instructions with lab access

otto test

Verdicts: suites and pytest-native selection runs

otto host

Direct host verbs, synthesized from Python methods

otto monitor

Live metrics, dashboard, and replay

otto cov

Cross-compiled gcov coverage reports

otto docker

Images and compose stacks on lab hosts

otto reservation

The reservation gate, made inspectable

otto schema

The data contracts, exported for editors

otto init

Scaffold a new repo; doctor an existing one

These first-party commands stand on shared machinery, and the machinery stands on a small set of foundations:

bigpicture cluster_commands the nine first-party commands (otto <command>) cluster_shared shared lifecycle machinery cluster_subsystems subsystems cluster_foundation foundations run run cli CLI registry + lazy dispatch run->cli test test host host monitor monitor cov cov docker docker reservation reservation schema schema init init hosts host subsystem sessions · connections · transfer cli->hosts boot bootstrap (discovery + registration) ctx OttoContext + HostScope registry Registry engine hosts->registry suites suite subsystem + pytest plugin observers monitor + coverage pipelines data data boundary models · storage · settings result Result family logging three-sink logging

Read The command lifecycle for the shared path every invocation walks — bootstrap, dispatch, preamble, teardown — before its command takes over.

Layer map

The package splits into four layers. Dependencies point downward only — a lower layer never imports from a higher one.

Foundation — small, dependency-light modules everything else builds on:

Module

Responsibility

otto.registry

The generic named-registry engine behind every extension seam

otto.result

The Result family: statuses, payloads, exit codes

otto.utils

Status, small shared helpers, CLI signature overlays

otto.logger

The 'otto' logger tree, log levels, LogMode

otto.filesystem

Network-filesystem detection (drives WAL-vs-DELETE and rotation choices)

otto.console, otto.params, otto.version

Rich console singleton, options→CLI parameter expansion, version resolution

Boundary — where external data becomes trusted runtime objects:

Module

Responsibility

otto.models

Pydantic specs for hosts.json, settings.toml, env vars, monitor records

otto.storage

The lab-repository (host source) protocol and the default JSON backend

otto.configmodule

Repo/lab discovery, settings parsing, fleet accessors

Domain — the subsystems that do the actual work:

Package

Responsibility

otto.host

Host classes, sessions, connections, transfers, privilege, power

otto.suite

Test-suite base class, auto-registration, the pytest plugin, expect()

otto.monitor

Metric collection, parsers, SNMP, the live dashboard

otto.coverage

The embedded gcov pipeline: fetch, correlate, render, report

otto.docker

Image builds and compose lifecycles on parent hosts

otto.reservations

The reservation gate and its pluggable backends

Application — composition and the CLI edge:

Module

Responsibility

otto.bootstrap

Two-phase composition root: discovery, then contained user-code registration

otto.context

OttoContext: the per-invocation runtime and host lifecycle scope

otto.cli

The Typer app, the command registry, lazy dispatch, shell completion

Two support packages sit alongside: otto.testing (conformance helpers for third-party backends) and otto.examples (small, copyable reference implementations, conformance-verified in otto’s own suite).

Where the boundaries are enforced

  • The CLI edge is lazy. Importing otto.cli must never parse repo settings — a malformed settings.toml cannot brick otto --help. A bare import otto resolves its public names lazily (PEP 562), so library users pay only for what they touch.

  • The data edge is validated. Everything that enters from JSON, TOML, or the environment passes through a pydantic spec in otto.models exactly once; see Data at the boundary.

  • User code is contained. Bootstrap frames per-file failures; registries reject duplicate names loudly and attribute every entry to the module that registered it; see Registries and the pluggable CLI.

The lifecycle pages cover each first-party command in depth, the subsystem pages cover the machinery, and Design principles collects the recurring design rules the codebase holds itself to.