plesty.lib.experiment.cli

The command line that runs an experiment — a module supplies only its rig.

Everything about starting a run is the same for every experiment: which configuration, where the runs go, whether this is a new run or the resumption of an interrupted one, and whether to build the plan without touching an instrument. What differs is the rig and how the experiment is constructed on it. ExperimentCLI owns the former; the module writes two functions and is done — the same division Viz makes for watching a run.

cli = ExperimentCLI(Manifest(module="plesty.pol_pl", title="Polarization PL", ...))
cli.add_argument("--prominence", type=float, sets="analysis.fit_options.prominence",
                 help="Line-finding threshold in counts.")

@cli.rig
def rig(args, config, skip):
    return PolPlRig(devices={n: d for n, d in DEVICES.items() if n not in skip})

@cli.build
def build(args, config, run_root, devices):
    return Experiment(devices=devices, config=config, run_root=run_root)

if __name__ == "__main__":
    sys.exit(cli.main())
python -m plesty.pol_pl                          # run with config/default.yaml
python -m plesty.pol_pl --resume pol_pl_2026…    # continue an interrupted run
python -m plesty.pol_pl --dry-run                # the plan, nothing connected
python -m plesty.pol_pl --manifest               # what this module declares, as JSON

--manifest is the contract a caller reads: it connects to nothing, imports no toolkit and needs no configuration, so a console can ask a freshly installed module what settings it takes before anything is set up.

Stopping is interrupting. A run has no stop switch of its own and does not need one: SIGINT reaches the process, the journal records run_canceled, and the run id it names is what --resume takes. That is what a caller sends to stop a run it started, and why it must not kill the process outright — a killed run leaves no journal entry and so no point to resume from.

Attributes

RigFunc

BuildFunc

ConfigureFunc

DefaultConfigFunc

CheckFunc

FinishedFunc

NeedsFunc

_TEXT

_EXIT_CODES

Classes

ExperimentCLI

The standard python -m <module> command line over one experiment.

Functions

_text(→ dict[str, Any])

Return the command-line text catalog (loaded once from the assets).

Module Contents

plesty.lib.experiment.cli.RigFunc
plesty.lib.experiment.cli.BuildFunc
plesty.lib.experiment.cli.ConfigureFunc
plesty.lib.experiment.cli.DefaultConfigFunc
plesty.lib.experiment.cli.CheckFunc
plesty.lib.experiment.cli.FinishedFunc
plesty.lib.experiment.cli.NeedsFunc
plesty.lib.experiment.cli._TEXT: dict[str, Any] | None = None
plesty.lib.experiment.cli._EXIT_CODES
plesty.lib.experiment.cli._text() → dict[str, Any]

Return the command-line text catalog (loaded once from the assets).

Return type:

dict[str, Any]

class plesty.lib.experiment.cli.ExperimentCLI(manifest: plesty.lib.experiment.manifest.Manifest)

The standard python -m <module> command line over one experiment.

Describe the experiment this command line runs.

Parameters:

manifest (plesty.lib.experiment.manifest.Manifest) – What the module declares — the configuration schema, the environment it reads, its default configuration file and run root. Options added with add_argument() are folded in, so the printed manifest and the parser cannot drift apart.

manifest
_rig: RigFunc | None = None
_build: BuildFunc | None = None
_configure: ConfigureFunc | None = None
_default_config: DefaultConfigFunc | None = None
_check: CheckFunc | None = None
_finished: FinishedFunc | None = None
_needs: NeedsFunc | None = None
_optional: frozenset[str]
_extra: list[tuple[tuple[Any, Ellipsis], dict[str, Any]]] = []
_options: list[plesty.lib.experiment.manifest.Option]
rig(func: RigFunc) → RigFunc

Register the function that builds the rig. Use as a decorator.

It is called again on every preflight retry, so an edited .env or a server started in the meantime is picked up. Returning None declares an experiment that drives no devices.

Parameters:

func (RigFunc)

Return type:

RigFunc

build(func: BuildFunc) → BuildFunc

Register the function that builds the experiment. Use as a decorator.

It receives the rig — None for --dry-run, where nothing is connected but the plan must still be built.

Parameters:

func (BuildFunc)

Return type:

BuildFunc

default_config(func: DefaultConfigFunc) → DefaultConfigFunc

Register which configuration file to read when none was named.

Use as a decorator, for a module whose own options imply a different configuration — a --demo that replays a recording is configured differently from a run against instruments. Returning None takes the declared file. --config still wins over both: whoever passed it said which file.

Parameters:

func (DefaultConfigFunc)

Return type:

DefaultConfigFunc

configure(func: ConfigureFunc) → ConfigureFunc

Register the configuration override hook. Use as a decorator.

Called once with the loaded configuration and the parsed arguments, before the plan is built; what it returns is the configuration the run freezes into its plan. Optional — without it the file is used as read.

Parameters:

func (ConfigureFunc)

Return type:

ConfigureFunc

finished(func: FinishedFunc) → FinishedFunc

Register what to say when a run completes. Use as a decorator.

Called with the run directory once the plan is through, for a module that can summarise what it measured — the fitted results, a count, a path worth naming. It runs only on a completed run: a canceled or aborted one has a summary that would be a half-truth.

Parameters:

func (FinishedFunc)

Return type:

FinishedFunc

check(func: CheckFunc) → CheckFunc

Register the pre-connection check. Use as a decorator.

Called before the rig is built, to refuse an incomplete configuration while nothing has been connected and nothing measured. Optional.

Parameters:

func (CheckFunc)

Return type:

CheckFunc

needs(func: NeedsFunc) → NeedsFunc

Register which devices of setup.yaml a configuration uses. Use as a decorator.

One module can measure in configurations that use different devices — in place with four, or navigating with four more on another bench. The bench serves only what this returns, and the run needs only those devices’ benches set up (#62). None means all of them, as without this function. Optional.

@cli.needs
def needs(config):
    return ["hwp", "spec"] if not config.get("dots") else None
Parameters:

func (NeedsFunc)

Return type:

NeedsFunc

devices(args: argparse.Namespace) → list[str] | None

The devices of setup.yaml this run uses; None for all of them.

Parameters:

args (argparse.Namespace) – The parsed arguments; their configuration is what decides.

Return type:

Optional[list[str]]

optional(*names: str) → None

Declare sub-devices the run can do without.

When only these fail the preflight, the operator may continue without them; anything else is a rig that is not what the plan assumes.

Parameters:

*names (str) – Sub-device names.

Return type:

None

add_argument(*args: Any, sets: str | None = None, **kwargs: Any) → None

Add one option beyond the standard set, and declare it.

The option reaches both the parser and the printed manifest, so a caller offering the experiment’s settings sees exactly what the command line accepts.

Parameters:
  • *args (Any) – As argparse.ArgumentParser.add_argument().

  • sets (Optional[str]) – The configuration key this option overrides, written as a renderer addresses it (analysis.fit_options.prominence); None when it changes something else.

  • **kwargs (Any) – As argparse.ArgumentParser.add_argument().

Return type:

None

declared() → plesty.lib.experiment.manifest.Manifest

Return the manifest including the options added to the parser.

Return type:

plesty.lib.experiment.manifest.Manifest

build_parser() → argparse.ArgumentParser

Return the argument parser: the standard options plus the module’s.

Return type:

argparse.ArgumentParser

run_root(args: argparse.Namespace) → str | None

Return where this run’s checkpoint directory goes.

--run-root wins, as given; otherwise the module’s declared root — under the shared point when one is configured (pol-pl → <PLESTY_DATA>/pol-pl), as it is when not.

Parameters:

args (argparse.Namespace) – The parsed arguments.

Returns:

The run root, as a path string.

Return type:

Optional[str]

config_path(args: argparse.Namespace) → pathlib.Path

Return the configuration file this run reads.

--config always wins: whoever passed it said which file, and a module second-guessing that would make the option a suggestion. With nothing passed, a module that chooses its own — one whose --demo implies a different configuration — decides through default_config(), and otherwise it is the declared one.

Parameters:

args (argparse.Namespace) – The parsed arguments.

Returns:

The file to read.

Return type:

pathlib.Path

load(args: argparse.Namespace) → dict[str, Any]

Return the configuration this run uses, overrides applied.

A missing file is an empty configuration, so an experiment whose defaults are all internal runs without one.

Parameters:

args (argparse.Namespace) – The parsed arguments.

Returns:

The configuration to freeze into the plan.

Return type:

dict[str, Any]

main(argv: Sequence[str] | None = None) → int

Run the command line and return the exit code.

Parameters:

argv (Optional[Sequence[str]]) – Argument list; None uses sys.argv.

Returns:

0 when the run completed, the plan printed or the manifest was written; non-zero when the rig was not ready.

Raises:

RuntimeError – If no rig or build function was registered.

Return type:

int

async _execute(args: argparse.Namespace) → int

Load, plan, connect and run, printing what each outcome means.

Parameters:

args (argparse.Namespace) – The parsed arguments.

Returns:

The exit code.

Return type:

int