cli.main¶
Top-level otto CLI: callback, subcommand dispatch, and eager option handlers.
- otto.cli.main.version_callback(version: bool) None¶
Print the otto version string and exit when
--versionis passed.
- otto.cli.main.clear_autocomplete_cache_callback(value: bool) None¶
Delete the shell-completion cache file and exit when the flag is set.
- otto.cli.main.list_labs_callback(value: bool) None¶
Print all available lab names (one panel per repo) and exit when the flag is set.
- otto.cli.main.log_level_callback(value: str) str¶
Normalise the
--log-levelvalue to upper-case before Typer stores it.
- otto.cli.main.parse_lab_selection(value: list[str] | None) list[str] | None¶
Typer callback for
--lab: split each value on+.Typer hands this
Nonewhen neither--labnorOTTO_LABis set — and also whenOTTO_LABis set but empty (verified empirically). Both pass straight through asNone, so the preamble’s no-lab path is unchanged. Repeats accumulate and each value is itself split, so--lab a+b --lab cselectsa,b, andc. Malformed input is a usage error (exit 2).The grammar itself lives in
otto.config.lab.split_lab_names()— this is only the CLI adapter, translating itsValueErrorinto a Typer usage error. The import is function-local to match this module’s existing convention for narrowly-used helpers (see_lab_completerabove andreservation.py).
- otto.cli.main.main(ctx: ~typer.models.Context, labs: ~types.Annotated[list[str] | None, <typer.models.OptionInfo object at 0x738001a715d0>] | None = None, xdir: ~pathlib.Annotated[~pathlib.Path, <typer.models.OptionInfo object at 0x738001a71510>] = PosixPath('.'), debug: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738001a71870>] = False, log_days: ~typing.Annotated[int, <typer.models.OptionInfo object at 0x738001a71930>] = 30, log_level: ~typing.Annotated[str, <typer.models.OptionInfo object at 0x738001a719f0>] = 'INFO', rich_log_file: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738001a71c00>] = False, show_time: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738001a71b40>] = False, lab_depth: ~typing.Annotated[int, <typer.models.OptionInfo object at 0x738001a70b80>] = 3, list_labs: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738001a70370>] = False, show_lab: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738001a709d0>] = False, list_hosts: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738001a71ab0>] = False, dry_run: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738001a70e80>] = False, version: ~types.Annotated[bool | None, <typer.models.OptionInfo object at 0x738001a70ca0>] | None = None, clear_autocomplete_cache: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738001a72e90>] = False, as_user: ~types.Annotated[str | None, <typer.models.OptionInfo object at 0x738001a71030>] | None = None, skip_reservation_check: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x738001a714b0>] = False) None¶
Record root options for lazy lab loading; handle inline root-flag actions.
This is the Typer root callback executed before every
ottosubcommand. It no longer loads the lab or initialises logging: it stashes the root options onctx.metaand returns. The real work (lab load, session setup, output dir, reservation gate) runs lazily in the leaf-invokecommand_preamble(), so--help/ discovery paths are structurally incapable of touching host state. The only exceptions are--show-lab/--list-hosts, which inspect live lab state and so load it inline here before printing and exiting.
- otto.cli.main.entry() None¶
Console-script entry: composition root, then the Typer app.
Completion invocations take the cache fast path (zero user code); everything else runs
otto.bootstrap.bootstrap()before argv parsing so registered third-party commands exist when the root group is consulted. Contained user-code failures print one framed warning line each; real command dispatch fails loud in the invoke preamble.