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 |
|---|---|
Procedures: registered instructions with lab access |
|
Verdicts: suites and pytest-native selection runs |
|
Direct host verbs, synthesized from Python methods |
|
Live metrics, dashboard, and replay |
|
Cross-compiled gcov coverage reports |
|
Images and compose stacks on lab hosts |
|
The reservation gate, made inspectable |
|
The data contracts, exported for editors |
|
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:
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 |
|---|---|
The generic named-registry engine behind every extension seam |
|
The |
|
|
|
|
The |
|
Network-filesystem detection (drives WAL-vs-DELETE and rotation choices) |
|
Rich console singleton, options→CLI parameter expansion, version resolution |
Boundary — where external data becomes trusted runtime objects:
Module |
Responsibility |
|---|---|
|
Pydantic specs for hosts.json, settings.toml, env vars, monitor records |
|
The lab-repository (host source) protocol and the default JSON backend |
|
Repo/lab discovery, settings parsing, fleet accessors |
Domain — the subsystems that do the actual work:
Package |
Responsibility |
|---|---|
|
Host classes, sessions, connections, transfers, privilege, power |
|
Test-suite base class, auto-registration, the pytest plugin, |
|
Metric collection, parsers, SNMP, the live dashboard |
|
The embedded gcov pipeline: fetch, correlate, render, report |
Image builds and compose lifecycles on parent hosts |
|
The reservation gate and its pluggable backends |
Application — composition and the CLI edge:
Module |
Responsibility |
|---|---|
Two-phase composition root: discovery, then contained user-code registration |
|
|
|
|
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.climust never parse repo settings — a malformedsettings.tomlcannot brickotto --help. A bareimport ottoresolves 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.modelsexactly 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.