plesty.lib.experiment.cli ========================= .. py:module:: plesty.lib.experiment.cli .. autoapi-nested-parse:: 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. :class:`ExperimentCLI` owns the former; the module writes two functions and is done — the same division :class:`~plesty.lib.monitor.Viz` makes for watching a run. .. code-block:: python 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()) .. code-block:: bash 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 ---------- .. autoapisummary:: 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 plesty.lib.experiment.cli._EXIT_CODES Classes ------- .. autoapisummary:: plesty.lib.experiment.cli.ExperimentCLI Functions --------- .. autoapisummary:: plesty.lib.experiment.cli._text Module Contents --------------- .. py:data:: RigFunc .. py:data:: BuildFunc .. py:data:: ConfigureFunc .. py:data:: DefaultConfigFunc .. py:data:: CheckFunc .. py:data:: FinishedFunc .. py:data:: NeedsFunc .. py:data:: _TEXT :type: Optional[dict[str, Any]] :value: None .. py:data:: _EXIT_CODES .. py:function:: _text() -> dict[str, Any] Return the command-line text catalog (loaded once from the assets). .. py:class:: ExperimentCLI(manifest: plesty.lib.experiment.manifest.Manifest) The standard ``python -m `` command line over one experiment. Describe the experiment this command line runs. :param manifest: What the module declares — the configuration schema, the environment it reads, its default configuration file and run root. Options added with :meth:`add_argument` are folded in, so the printed manifest and the parser cannot drift apart. .. py:attribute:: manifest .. py:attribute:: _rig :type: Optional[RigFunc] :value: None .. py:attribute:: _build :type: Optional[BuildFunc] :value: None .. py:attribute:: _configure :type: Optional[ConfigureFunc] :value: None .. py:attribute:: _default_config :type: Optional[DefaultConfigFunc] :value: None .. py:attribute:: _check :type: Optional[CheckFunc] :value: None .. py:attribute:: _finished :type: Optional[FinishedFunc] :value: None .. py:attribute:: _needs :type: Optional[NeedsFunc] :value: None .. py:attribute:: _optional :type: frozenset[str] .. py:attribute:: _extra :type: list[tuple[tuple[Any, Ellipsis], dict[str, Any]]] :value: [] .. py:attribute:: _options :type: list[plesty.lib.experiment.manifest.Option] .. py:method:: 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. .. py:method:: 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. .. py:method:: 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. .. py:method:: 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. .. py:method:: 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. .. py:method:: 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. .. py:method:: 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. .. code-block:: python @cli.needs def needs(config): return ["hwp", "spec"] if not config.get("dots") else None .. py:method:: devices(args: argparse.Namespace) -> Optional[list[str]] The devices of ``setup.yaml`` this run uses; ``None`` for all of them. :param args: The parsed arguments; their configuration is what decides. .. py:method:: 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. :param \*names: Sub-device names. .. py:method:: add_argument(*args: Any, sets: Optional[str] = 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. :param \*args: As :meth:`argparse.ArgumentParser.add_argument`. :param sets: The configuration key this option overrides, written as a renderer addresses it (``analysis.fit_options.prominence``); ``None`` when it changes something else. :param \*\*kwargs: As :meth:`argparse.ArgumentParser.add_argument`. .. py:method:: declared() -> plesty.lib.experiment.manifest.Manifest Return the manifest including the options added to the parser. .. py:method:: build_parser() -> argparse.ArgumentParser Return the argument parser: the standard options plus the module's. .. py:method:: run_root(args: argparse.Namespace) -> Optional[str] 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`` → ``/pol-pl``), as it is when not. :param args: The parsed arguments. :returns: The run root, as a path string. .. py:method:: 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 :meth:`default_config`, and otherwise it is the declared one. :param args: The parsed arguments. :returns: The file to read. .. py:method:: 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. :param args: The parsed arguments. :returns: The configuration to freeze into the plan. .. py:method:: main(argv: Optional[Sequence[str]] = None) -> int Run the command line and return the exit code. :param argv: 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. .. py:method:: _execute(args: argparse.Namespace) -> int :async: Load, plan, connect and run, printing what each outcome means. :param args: The parsed arguments. :returns: The exit code.