plesty-sdk 0.3.3__tar.gz → 0.3.5.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/CHANGELOG.md +94 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/PKG-INFO +1 -1
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/docs/commands/init.md +68 -1
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/gl-secret-detection-report.json +5 -5
- plesty_sdk-0.3.5.dev1/plesty/sdk/assets/docs-build-gitlab-ci.yml +17 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/init/client-field-test.py.template +4 -2
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/init/field-test-questions.yaml +59 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/init/field-test.py.template +2 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/init/next-steps.yaml +3 -1
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/extras/device/base_device.py +4 -1
- plesty_sdk-0.3.5.dev1/plesty/sdk/assets/templates/extras/device/device.py +60 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/extras/experiment/experiment.py +30 -6
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/plesty/{{module_name}}/{% if project_type in ['device', 'experiment'] %}__main__.py{% endif %}.jinja +66 -30
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/pyproject.toml.jinja +8 -3
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/tests/test_{{module_name}}.py.jinja +30 -11
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/{% if project_type in ['device', 'experiment'] %}.env.example{% endif %}.jinja +10 -1
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/field_test.py +362 -23
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/init.py +131 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/tui.py +77 -17
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_field_test_scaffold.py +301 -2
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_init.py +185 -2
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_tui.py +68 -35
- plesty_sdk-0.3.3/plesty/sdk/assets/docs-build-gitlab-ci.yml +0 -11
- plesty_sdk-0.3.3/plesty/sdk/assets/templates/extras/device/device.py +0 -37
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/.coverage +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/.gitignore +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/.gitlab-ci.yml +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/.mailmap +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/LICENSE +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/LICENSES/LGPL-3.0-or-later.txt +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/README.md +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/REUSE.toml +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/docs/architecture.md +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/docs/commands/check.md +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/docs/commands/docs.md +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/docs/commands/license.md +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/docs/commands/manual.md +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/docs/commands/update.md +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/docs/getting-started.md +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/docs/index.md +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/docs/toc.yaml +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/__init__.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/check/guidance.yaml +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/hub-theme/plesty-hub.css +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/hub-theme/plesty-logo.svg +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/hub-theme/plesty-mark.svg +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/init/field-test-env.example +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/init/mock-test.py.template +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/manual/pdf-common.css +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/manual/pdf-page-landscape.css +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/manual/pdf-page-portrait.css +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/ruff.toml +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/copier.yml +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/extras/analyzer/analyzer.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/.gitignore +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/CHANGELOG.md.jinja +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/LICENSE +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/LICENSES/LGPL-3.0-or-later.txt +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/README.md.jinja +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/REUSE.toml.jinja +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/docs/index.md.jinja +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/docs/toc.yaml +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/plesty/{{module_name}}/__init__.py.jinja +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/plesty/{{module_name}}/config.yaml.jinja +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/tests/__init__.py.jinja +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/template/{% if project_type == 'experiment' %}config{% endif %}/default.yaml +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/authorship.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/cache.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/cli.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/__init__.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/check.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/docs/__init__.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/docs/deploy.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/docs/generate.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/docs/serve.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/docs/sphinx_builder.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/license.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/manual.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/mock_test.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/commands/update.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/context.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/report.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/semver.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/pyproject.toml +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/__init__.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_authorship.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_cache.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_check.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_cli.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_context.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_docs_deploy.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_docs_generate.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_docs_serve.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_license.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_manual.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_mock_test_scaffold.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_report.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_semver.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_sphinx_builder.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/tests/test_update.py +0 -0
- {plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/uv.lock +0 -0
|
@@ -2,6 +2,100 @@
|
|
|
2
2
|
|
|
3
3
|
The format is based on [Keep a Changelog](https://keepachangelog.com).
|
|
4
4
|
|
|
5
|
+
## [0.3.5.dev1] — 2026-08-21
|
|
6
|
+
|
|
7
|
+
Development pre-release: the tutorial needs the pin option before 0.3.5 is
|
|
8
|
+
ready to be called finished.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `plesty init --lib-version VERSION` pins `plesty-lib` to what the caller
|
|
12
|
+
names instead of to the newest release on PyPI, and skips the lookup. A bare
|
|
13
|
+
version becomes a lower bound (`0.3.5.dev3` → `>=0.3.5.dev3`); a complete
|
|
14
|
+
specifier is written as given (`==0.4.0`, `>=0.3.5.dev3,<0.4`). One that PEP
|
|
15
|
+
440 cannot read is refused at the command line, before a directory is
|
|
16
|
+
created. For the tutorial: a lesson quoting a `pyproject.toml` and the file
|
|
17
|
+
its reader ends up with have to keep saying the same thing however far the
|
|
18
|
+
library has moved since the page was written — and a scaffold on an
|
|
19
|
+
air-gapped machine now pins what was asked for rather than a fallback.
|
|
20
|
+
|
|
21
|
+
## [0.3.4] — 2026-08-21
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- The scaffold is generated against the **newest published `plesty-lib`**, not
|
|
25
|
+
the one that existed when the SDK was released: `plesty init` reads the
|
|
26
|
+
version list from PyPI (pre-releases included — the development line is where
|
|
27
|
+
the features the templates use land first) and pins `plesty-lib>=<newest>`.
|
|
28
|
+
The lookup has a 3 s budget and every way it can fail — no route, a proxy, a
|
|
29
|
+
body that is not the expected JSON, a fully yanked release — falls back to
|
|
30
|
+
`PLESTY_LIB_VERSION` (currently 0.3.5.dev3), the library the templates are
|
|
31
|
+
written against; the pin is never lower than that floor. The dev group pins
|
|
32
|
+
`plesty-sdk>=0.3.4` — this release, the one the scaffold is written against —
|
|
33
|
+
instead of `>=0.2.0`.
|
|
34
|
+
- The experiment scaffold starts its rig the way plesty-lib 0.3.5.dev2 asks it
|
|
35
|
+
to. `__main__.py` declares a `RIG` — one sub-device per instrument, naming
|
|
36
|
+
the operations the experiment calls on its server — and hands it to
|
|
37
|
+
`connect_rig()`, which builds the composite, preflights it, and on a failure
|
|
38
|
+
names the sub-device, its address and the variable that set it before
|
|
39
|
+
offering retry / abort / skip. Addresses are resolved from `<NAME>_ADDRESS`
|
|
40
|
+
by the composite itself, so `build_client` is gone; the rig is `release()`d
|
|
41
|
+
after the run, `--run-root` defaults to `PLESTY_DATA_MOUNT` (the run lands on
|
|
42
|
+
the share), and `.env.example` carries `PLESTY_DATA_DIR`/`PLESTY_DATA_MOUNT`.
|
|
43
|
+
The run config is read with `plesty.lib.utils.config.load_yaml` instead of a
|
|
44
|
+
hand-written `yaml.safe_load` — the last copy of a loader the library owns.
|
|
45
|
+
- The device scaffold exposes its example operation with `@expose_to_api` and
|
|
46
|
+
shows how an operation that acts on the world declares what it does
|
|
47
|
+
(`kind="motion"`, `position_key`, `step`, `kind="lifecycle"`) — what the
|
|
48
|
+
field-test interview and `FieldTestPipeline` now drive operations by.
|
|
49
|
+
`base_device.py` re-exports `expose_to_api`, as the hub modules do.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
- `plesty init field-test` reads the kind each operation declares
|
|
53
|
+
(plesty-lib#39: `@expose_to_api(kind=…)` / a schema `kind` block) and
|
|
54
|
+
asks only what it leaves open. A declared kind is shown as `[motion]`,
|
|
55
|
+
`[lifecycle]`, `[control]` beside the operation and pre-fills the
|
|
56
|
+
"acts on the world" question, which is asked only for undeclared
|
|
57
|
+
operations; a `configure` operation is not offered and is recorded as
|
|
58
|
+
excluded (the round-trip gate covers settings). For an allow-listed
|
|
59
|
+
motion the interview confirms `step` (the declared value is the default)
|
|
60
|
+
and asks `position_key` from the readable parameters when the module did
|
|
61
|
+
not declare it; a motion left without either is dropped, not guessed. A
|
|
62
|
+
motion slow-op skips the argument chooser and asks how far — a distance
|
|
63
|
+
the pipeline drives from wherever the part is and undoes. Both generated
|
|
64
|
+
files carry `OP_KINDS` and pass it to the host and client tiers.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
- Generated `tests/field_test_client.py` imported the device class at the
|
|
68
|
+
top and, for a module with no hardware-free construction, left it unused
|
|
69
|
+
— failing the module's own lint gate. The import now lives with the
|
|
70
|
+
`SPAWN_FACTORY` that needs it.
|
|
71
|
+
- `plesty init field-test`: an arrow key no longer answers the question
|
|
72
|
+
(#32). The key reader took the first byte of `ESC [ B` through
|
|
73
|
+
`sys.stdin`, whose buffer swallowed the rest, so the descriptor looked
|
|
74
|
+
idle and the press was read as a lone Escape — which confirmed the
|
|
75
|
+
question empty and moved on; three presses skipped three questions.
|
|
76
|
+
Keys are read from the descriptor itself, control sequences are consumed
|
|
77
|
+
whole, and Escape no longer counts as an answer.
|
|
78
|
+
- `plesty init field-test`: a list longer than ten entries is shown but not
|
|
79
|
+
asked (#32). Drawn as a checkable list, a spectrometer's 262 parameters
|
|
80
|
+
were 500 lines per redraw — taller than any terminal, so nothing could be
|
|
81
|
+
erased and every keypress appended the list again. The interview now says
|
|
82
|
+
so first, keeps whatever `.plesty/field-test.json` already holds for the
|
|
83
|
+
question (an empty list on a first run), names the entry to edit, lists
|
|
84
|
+
the first ten with `… and N more`, and waits for Enter — otherwise two
|
|
85
|
+
such questions scroll past together.
|
|
86
|
+
- A confirmed selection leaves a one-line record above the next question;
|
|
87
|
+
the list itself is erased on Enter and the transcript used to show every
|
|
88
|
+
question and none of the answers.
|
|
89
|
+
- `answered` in `.plesty/field-test.json` records each question once
|
|
90
|
+
instead of growing on every interrupted run.
|
|
91
|
+
- The generated docs-build Pages job gates on `html/latest/index.html`
|
|
92
|
+
instead of the directory path `"html/latest/"`: `rules.exists` matches
|
|
93
|
+
file globs, and the directory form stopped matching on gitlab.com
|
|
94
|
+
between 2026-07-21 and 2026-08-10 — every docs-build pipeline failed
|
|
95
|
+
with "no jobs" and the Pages sites froze while the deploys kept
|
|
96
|
+
succeeding. Existing repos pick the fix up on their next docs deploy
|
|
97
|
+
(the deploy rewrites the branch's `.gitlab-ci.yml` from this asset).
|
|
98
|
+
|
|
5
99
|
## [0.3.3] — 2026-08-14
|
|
6
100
|
|
|
7
101
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: plesty-sdk
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5.dev1
|
|
4
4
|
Summary: Command-line application for developing Python modules compliant with the Plesty standard.
|
|
5
5
|
Author: Plesty Development Team, Yunshuang Yuan, Christopher Borchers, fvrehlinger, Maximilian Heller, Michael Zopf
|
|
6
6
|
License-Expression: LGPL-3.0-or-later
|
|
@@ -41,6 +41,7 @@ name, so `plesty-foo-bar` → `foo_bar` without needing `--name`.
|
|
|
41
41
|
| `--author` | `John Doe` | Author name written into `pyproject.toml` and `REUSE.toml` |
|
|
42
42
|
| `--email` | `john.doe@example.com` | Author email written into `pyproject.toml` |
|
|
43
43
|
| `--standard` | `pixel` | Compliance standard for the generated CI pipeline: `pixel`, `nebula`, or `quantum`. A fresh scaffold is a prototype, so CI starts at `pixel`; bump it alongside your releases. |
|
|
44
|
+
| `--lib-version` | newest on PyPI | Pin `plesty-lib` to this version instead of looking it up. A bare version becomes a lower bound (`0.3.5.dev3` → `>=0.3.5.dev3`); a complete specifier is written as given (`==0.4.0`, `'>=0.3.5.dev3,<0.4'`). PyPI is not consulted, so the scaffold is reproducible. |
|
|
44
45
|
| `--no-ci` | off | Skip generating `.gitlab-ci.yml` entirely. |
|
|
45
46
|
|
|
46
47
|
## Directory rules
|
|
@@ -120,7 +121,23 @@ The `pyproject.toml` is pre-configured with:
|
|
|
120
121
|
- The PyPI distribution name `plesty-foo-bar` (kebab-case)
|
|
121
122
|
- `packages = ["plesty"]` so the module installs under the `plesty` namespace (`import plesty.foo_bar`)
|
|
122
123
|
- Dynamic versioning via `versioningit`
|
|
123
|
-
- `plesty-lib` as a runtime dependency
|
|
124
|
+
- `plesty-lib` as a runtime dependency, pinned to the **newest version published
|
|
125
|
+
on PyPI** when the project is scaffolded (pre-releases included — that is where
|
|
126
|
+
the features the templates use land first). The lookup has a 3 s budget; with
|
|
127
|
+
no route to PyPI the pin falls back to `PLESTY_LIB_VERSION` in
|
|
128
|
+
`plesty/sdk/commands/init.py`, the library the templates are written against.
|
|
129
|
+
The pin is never lower than that floor: the scaffold uses that library's
|
|
130
|
+
operation kinds and rig start-up, and an older one would not run it.
|
|
131
|
+
`--lib-version` overrides all of that and skips the lookup — for a tutorial,
|
|
132
|
+
whose lesson text and reader's `pyproject.toml` have to keep agreeing however
|
|
133
|
+
far the library has moved since the page was written:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
plesty init experiment --name scan_exp --lib-version 0.3.5.dev3
|
|
137
|
+
# → "plesty-lib>=0.3.5.dev3"
|
|
138
|
+
plesty init experiment --name scan_exp --lib-version '>=0.3.5.dev3,<0.4'
|
|
139
|
+
# → "plesty-lib>=0.3.5.dev3,<0.4"
|
|
140
|
+
```
|
|
124
141
|
- `pytest`, `pytest-cov`, `mypy`, and `plesty-sdk` under `[dependency-groups] dev`
|
|
125
142
|
(included automatically by `uv run` — no `--extra dev` needed)
|
|
126
143
|
- Sphinx docs settings under `[tool.plesty.docs]`
|
|
@@ -167,6 +184,40 @@ python -m plesty.foo_bar --host 10.0.0.5 --tcp-port 6000 # CLI overrides env
|
|
|
167
184
|
variables, so one `.env` drives both the server and the tests. It is skipped unless
|
|
168
185
|
`DEVICE_HOST` is set.
|
|
169
186
|
|
|
187
|
+
## Rig start-up and the shared disk (experiment only)
|
|
188
|
+
|
|
189
|
+
The generated `__main__.py` does not open device clients itself. It declares
|
|
190
|
+
the rig — one entry per sub-device, naming the operations the experiment calls
|
|
191
|
+
on that server — and hands it to
|
|
192
|
+
[`plesty.lib.experiment.connect_rig`](https://gitlab.com/plesty/core/plesty-lib):
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
RIG = {"device": ["identity"]}
|
|
196
|
+
OPTIONAL: frozenset[str] = frozenset()
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`connect_rig` builds the composite, preflights it (every listed operation must
|
|
200
|
+
be exposed and `identity()` must answer), and on a failure prints which
|
|
201
|
+
sub-device, at which address, from which variable, then offers
|
|
202
|
+
**retry / abort / skip** — skip only for the names in `OPTIONAL`. A retry calls
|
|
203
|
+
the builder again, so a `.env` corrected or a server started in the meantime is
|
|
204
|
+
picked up. Non-interactive processes get the diagnosis logged and
|
|
205
|
+
`RigNotReadyError`; nothing is measured on a rig that is not what the plan
|
|
206
|
+
assumes.
|
|
207
|
+
|
|
208
|
+
Addresses are never written into the module: each sub-device reads
|
|
209
|
+
`<NAME>_ADDRESS` from the environment (`device` → `DEVICE_ADDRESS`), overridable
|
|
210
|
+
with `--device-address`.
|
|
211
|
+
|
|
212
|
+
| Variable | Meaning |
|
|
213
|
+
|---|---|
|
|
214
|
+
| `<NAME>_ADDRESS` | ZMQ address of one sub-device's server |
|
|
215
|
+
| `PLESTY_DATA_DIR` | Shared disk as the acquiring host writes it — devices write frames to `<PLESTY_DATA_DIR>/<run_id>/raw` and return only paths |
|
|
216
|
+
| `PLESTY_DATA_MOUNT` | The same disk as this machine reads it; also the default `run_root`, so runs land on the share |
|
|
217
|
+
|
|
218
|
+
Both data variables are optional: without them runs go to `./runs` and no
|
|
219
|
+
device writes blobs. `--run-root` overrides the default per call.
|
|
220
|
+
|
|
170
221
|
## Next steps after scaffolding
|
|
171
222
|
|
|
172
223
|
`plesty init` ends with a numbered, project-type-specific **Next steps** list —
|
|
@@ -312,6 +363,22 @@ in neither list: it is never called, and the gate that would have called it
|
|
|
312
363
|
skips saying so. A generated allow-list that quietly included a motion command
|
|
313
364
|
would be worse than no generator at all.
|
|
314
365
|
|
|
366
|
+
**What the module declares is not asked again.** An operation exposed with a
|
|
367
|
+
kind — `@expose_to_api(kind="motion", position_key="MO.Position", step=1.0)`,
|
|
368
|
+
`kind="lifecycle"`, `kind="control"`, or a `"kind"` block in
|
|
369
|
+
`schema_func.json` (plesty-lib#39) — is shown as `[motion]`, `[lifecycle]`,
|
|
370
|
+
`[control]` in the chooser, and the "acts on the world" question is asked only
|
|
371
|
+
about operations that declared nothing. A `configure` operation is not offered
|
|
372
|
+
at all: it is recorded as excluded, because a setting that outlives the call is
|
|
373
|
+
verified by the round-trip gate. For an allow-listed motion the interview
|
|
374
|
+
confirms how far the test may move it (`step`; the declared value is the
|
|
375
|
+
default) and, if the module left it open, which parameter reports the position
|
|
376
|
+
— a motion with neither is dropped, since the pipeline drives a motion one step
|
|
377
|
+
out from that parameter and back and will not guess how far. A motion chosen as
|
|
378
|
+
the slow operation is asked only how far the long move goes: the pipeline
|
|
379
|
+
starts it from wherever the part is and moves it back afterwards. Both
|
|
380
|
+
generated files carry the result as `OP_KINDS`.
|
|
381
|
+
|
|
315
382
|
The result is `tests/field_test.py`: hardware-gated (it defines no pytest test
|
|
316
383
|
functions, so CI collects nothing), wired to publish
|
|
317
384
|
`reports/<instrument>/field-test.json`, and runnable against the simulator
|
|
@@ -19,19 +19,19 @@
|
|
|
19
19
|
"version": "8.30.1"
|
|
20
20
|
},
|
|
21
21
|
"type": "secret_detection",
|
|
22
|
-
"start_time": "2026-08-
|
|
23
|
-
"end_time": "2026-08-
|
|
22
|
+
"start_time": "2026-08-21T18:31:49",
|
|
23
|
+
"end_time": "2026-08-21T18:31:49",
|
|
24
24
|
"status": "success",
|
|
25
25
|
"observability": {
|
|
26
26
|
"events": [
|
|
27
27
|
{
|
|
28
28
|
"event": "collect_secrets_analyzer_scan_metrics_from_pipeline",
|
|
29
|
-
"time_s": 0.
|
|
29
|
+
"time_s": 0.480570925,
|
|
30
30
|
"exit_code": 0,
|
|
31
31
|
"git_strategy": "FetchShallow",
|
|
32
|
-
"repo_size_kb":
|
|
32
|
+
"repo_size_kb": 468,
|
|
33
33
|
"commit_count": 1,
|
|
34
|
-
"bytes_scanned":
|
|
34
|
+
"bytes_scanned": 136,
|
|
35
35
|
"pipeline_type": "Tag"
|
|
36
36
|
},
|
|
37
37
|
{
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
stages: [deploy]
|
|
2
|
+
|
|
3
|
+
pages:
|
|
4
|
+
stage: deploy
|
|
5
|
+
rules:
|
|
6
|
+
# The gate for creating the job, not a selector of what is published —
|
|
7
|
+
# `publish: html/latest` below uploads the whole directory. exists
|
|
8
|
+
# matches FILE globs: the bare directory path "html/latest/" stopped
|
|
9
|
+
# matching on gitlab.com between 2026-07-21 and 2026-08-10, every
|
|
10
|
+
# docs-build pipeline failed with "no jobs", and Pages froze while the
|
|
11
|
+
# deploys kept succeeding. Name a file Sphinx always emits.
|
|
12
|
+
- exists:
|
|
13
|
+
- "html/latest/index.html"
|
|
14
|
+
script:
|
|
15
|
+
- echo "Serving documentation on GitLab pages..."
|
|
16
|
+
pages:
|
|
17
|
+
publish: html/latest
|
{plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/init/client-field-test.py.template
RENAMED
|
@@ -57,8 +57,6 @@ from plesty.lib.test.client_field_test import ClientFieldTest
|
|
|
57
57
|
from plesty.lib.test.report_artifact import Instrument
|
|
58
58
|
from plesty.lib.utils.settings import EnvSettings
|
|
59
59
|
|
|
60
|
-
from {{DEVICE_IMPORT}} import {{DEVICE_CLASS}}
|
|
61
|
-
|
|
62
60
|
#: The instrument address comes from ``.env`` — the same file the device
|
|
63
61
|
#: server reads. Never written here: an address carries a serial number and
|
|
64
62
|
#: this file is committed.
|
|
@@ -83,6 +81,7 @@ BASELINE = PROJECT_ROOT / "reports" / "{{INSTRUMENT_SLUG}}" / "field-test.json"
|
|
|
83
81
|
{{TYPES_BLOCK}}
|
|
84
82
|
{{RESOURCES_BLOCK}}
|
|
85
83
|
{{SAFE_OPS_BLOCK}}
|
|
84
|
+
{{OP_KINDS_BLOCK}}
|
|
86
85
|
{{SPAWN_BLOCK}}
|
|
87
86
|
|
|
88
87
|
def build(address: str | None, spawn: bool) -> ClientFieldTest:
|
|
@@ -100,6 +99,9 @@ def build(address: str | None, spawn: bool) -> ClientFieldTest:
|
|
|
100
99
|
expected_types=EXPECTED_TYPES,
|
|
101
100
|
resources=RESOURCES,
|
|
102
101
|
safe_ops=SAFE_OPS,
|
|
102
|
+
# How each of them is driven — the host tier's kinds, handed over
|
|
103
|
+
# because describe() cannot say which method moves a stage.
|
|
104
|
+
op_kinds=OP_KINDS,
|
|
103
105
|
# The host tier's report decides what this tier touches. Its coverage
|
|
104
106
|
# block records what that run actually reached on this instrument,
|
|
105
107
|
# already filtered by what the fitted sensor supports — so the two
|
|
@@ -34,6 +34,17 @@ intro: |
|
|
|
34
34
|
|
|
35
35
|
Ctrl-C stops without writing anything.
|
|
36
36
|
|
|
37
|
+
# Shown instead of a choice when a list is longer than the interview offers
|
|
38
|
+
# at the terminal (sdk#32). {count} items, {shown} listed, {key} is the entry
|
|
39
|
+
# in the answers file at {answers}, {state} says what is written for it now.
|
|
40
|
+
too_long: |
|
|
41
|
+
{count} entries — more than the {shown} this interview can offer at once.
|
|
42
|
+
|
|
43
|
+
The answer is written {state}: "{key}" in {answers}.
|
|
44
|
+
To choose, put the entries you want in that list and run this command
|
|
45
|
+
again; the list is then taken as the answer. The first {shown} of the
|
|
46
|
+
{count} follow, for orientation.
|
|
47
|
+
|
|
37
48
|
questions:
|
|
38
49
|
safe_ops:
|
|
39
50
|
when_empty: "nothing selected — no operation will be called or timed"
|
|
@@ -62,6 +73,12 @@ questions:
|
|
|
62
73
|
· changes a setting or calibration that outlives the call
|
|
63
74
|
· takes a long time, or writes files
|
|
64
75
|
· needs arguments you would have to invent
|
|
76
|
+
|
|
77
|
+
An operation shown as [motion], [lifecycle], [control] or [acquire]
|
|
78
|
+
was declared so by the module, and the test already knows how to
|
|
79
|
+
drive it: a motion one step out from its position parameter and back,
|
|
80
|
+
a lifecycle op once, a control op once after the motions. Pick those
|
|
81
|
+
when the bench allows it — the two questions above still apply.
|
|
65
82
|
reason_prompt: "Why is '{item}' safe to run 20 times unsupervised?"
|
|
66
83
|
after: >-
|
|
67
84
|
Nothing selected. The test will not call any operation, and will say so
|
|
@@ -85,6 +102,41 @@ questions:
|
|
|
85
102
|
reason_prompt: "Which safeguard makes '{item}' safe to repeat?"
|
|
86
103
|
after: "nothing selected — taken as: all of these only read"
|
|
87
104
|
|
|
105
|
+
motion_position_key:
|
|
106
|
+
when_empty: "nothing selected — the motion is dropped from the allow-list"
|
|
107
|
+
prompt: "Which parameter reports where '{item}' moves the part to?"
|
|
108
|
+
why: >-
|
|
109
|
+
A motion is driven from where the part is: the test reads its position,
|
|
110
|
+
moves it one step out, moves it back and checks the position returned.
|
|
111
|
+
The module did not say which parameter that is, so it is asked here.
|
|
112
|
+
decide: |
|
|
113
|
+
Pick the parameter that reports the moving part's position in the same
|
|
114
|
+
units the operation takes — a stage angle in degrees against a move in
|
|
115
|
+
degrees, a piezo position in µm against a step in µm.
|
|
116
|
+
|
|
117
|
+
Pick nothing if no parameter reports it: the motion is then left out,
|
|
118
|
+
because the test will not move a part it cannot place.
|
|
119
|
+
after: >-
|
|
120
|
+
No position parameter named. The operation is left out of the
|
|
121
|
+
allow-list; declare position_key in the module and re-run.
|
|
122
|
+
|
|
123
|
+
motion_step:
|
|
124
|
+
prompt: "How far may the test move '{item}'?"
|
|
125
|
+
why: >-
|
|
126
|
+
Every repetition moves the part this far out and straight back, twenty
|
|
127
|
+
times in a row with nobody watching. The size is the safeguard: small
|
|
128
|
+
enough to be harmless whatever is mounted, large enough to be a real
|
|
129
|
+
move rather than a no-op below the stage's resolution.
|
|
130
|
+
decide: |
|
|
131
|
+
Give a distance in the position parameter's units. A stage that resolves
|
|
132
|
+
26 µdeg is fine with 1 deg; a piezo with a 100 µm range with 1 µm.
|
|
133
|
+
Press Enter to accept the value the module declared, if it did.
|
|
134
|
+
|
|
135
|
+
Give nothing (or 0) if no distance is harmless: the motion is then left
|
|
136
|
+
out of the allow-list.
|
|
137
|
+
value_prompt: "Step for '{item}', in the units of {position_key}"
|
|
138
|
+
after: "no step given — the motion is dropped from the allow-list"
|
|
139
|
+
|
|
88
140
|
only_keys:
|
|
89
141
|
when_empty: "nothing selected — EVERY setting will be written back"
|
|
90
142
|
prompt: "Which settings can be safely written back their own value?"
|
|
@@ -168,6 +220,13 @@ questions:
|
|
|
168
220
|
Pick nothing if none of them work that way — a mode or a unit does not
|
|
169
221
|
make a call slower, and the check is better skipped than fooled.
|
|
170
222
|
value_prompt: "What value of '{item}' makes the call take longer than a tenth of a second?"
|
|
223
|
+
motion_value_prompt: >-
|
|
224
|
+
How far should the slow move of '{item}' go — a distance from wherever
|
|
225
|
+
the part is, in the units of {position_key} — to take longer than a
|
|
226
|
+
tenth of a second?
|
|
227
|
+
motion_restored: >-
|
|
228
|
+
'{item}' is a motion: the test moves the part that far from where it is,
|
|
229
|
+
interrupts the call, and afterwards moves it back to where it started.
|
|
171
230
|
after: >-
|
|
172
231
|
No argument chosen. The recovery check will be skipped: it can name the
|
|
173
232
|
call but not make it slow enough to interrupt.
|
|
@@ -74,6 +74,7 @@ INSTRUMENT = Instrument(model="{{INSTRUMENT_MODEL}}", sensor={{INSTRUMENT_SENSOR
|
|
|
74
74
|
|
|
75
75
|
{{SAFE_OPS_BLOCK}}
|
|
76
76
|
{{UNSAFE_OPS_BLOCK}}
|
|
77
|
+
{{OP_KINDS_BLOCK}}
|
|
77
78
|
{{ONLY_KEYS_BLOCK}}
|
|
78
79
|
{{DRAIN_PROBE_BLOCK}}
|
|
79
80
|
{{SLOW_OP_BLOCK}}
|
|
@@ -104,6 +105,7 @@ def build_pipeline(
|
|
|
104
105
|
"report_path": report_path,
|
|
105
106
|
"safe_ops": SAFE_OPS,
|
|
106
107
|
"unsafe_ops": UNSAFE_OPS,
|
|
108
|
+
"op_kinds": OP_KINDS,
|
|
107
109
|
"only_keys": ONLY_KEYS,
|
|
108
110
|
"drain_probe": DRAIN_PROBE,
|
|
109
111
|
"slow_op": SLOW_OP,
|
|
@@ -10,6 +10,7 @@ default:
|
|
|
10
10
|
device:
|
|
11
11
|
- "Define the device schema and wire it into plesty/{module_name}/base_device.py."
|
|
12
12
|
- "Implement the vendor communication in plesty/{module_name}/device.py."
|
|
13
|
+
- "Expose your custom operations with @expose_to_api and declare what each one does (kind=motion/acquire/lifecycle/control/configure) — the field test drives them by kind instead of asking you about every call."
|
|
13
14
|
- "Copy .env.example to .env and set DEVICE_HOST/DEVICE_PORT (never commit .env)."
|
|
14
15
|
- "Try it live: uv run python -m plesty.{module_name} --tcp-port 5555."
|
|
15
16
|
|
|
@@ -20,7 +21,8 @@ analyzer:
|
|
|
20
21
|
|
|
21
22
|
experiment:
|
|
22
23
|
- "Implement build_plan() and the step methods in plesty/{module_name}/experiment.py."
|
|
23
|
-
- "
|
|
24
|
+
- "Name your rig in the RIG of plesty/{module_name}/__main__.py: one sub-device per instrument, listing the operations you call on it — the preflight checks them before the first step."
|
|
25
|
+
- "Copy .env.example to .env and set one <NAME>_ADDRESS per sub-device (plus PLESTY_DATA_DIR/PLESTY_DATA_MOUNT if runs go to the shared disk); tune config/default.yaml."
|
|
24
26
|
- "Preview the plan without hardware: uv run python -m plesty.{module_name} --dry-run."
|
|
25
27
|
|
|
26
28
|
verify:
|
{plesty_sdk-0.3.3 → plesty_sdk-0.3.5.dev1}/plesty/sdk/assets/templates/extras/device/base_device.py
RENAMED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
"""Base device implementation template."""
|
|
7
7
|
|
|
8
|
-
from plesty.lib.device.base_device_sync import BaseDeviceSyncModel
|
|
8
|
+
from plesty.lib.device.base_device_sync import BaseDeviceSyncModel, expose_to_api
|
|
9
|
+
|
|
10
|
+
# Re-exported so device.py imports both from here, as the hub modules do.
|
|
11
|
+
__all__ = ["BaseDevice", "expose_to_api"]
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
class BaseDevice(BaseDeviceSyncModel):
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025-2026 Plesty Development Team
|
|
2
|
+
# SPDX-FileContributor: Yunshuang Yuan
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
"""Concrete device implementation."""
|
|
7
|
+
|
|
8
|
+
from .base_device import BaseDevice, expose_to_api
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Device(BaseDevice):
|
|
12
|
+
"""Concrete device class extending BaseDevice with custom functionality."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
id: str,
|
|
17
|
+
host: str | None = None,
|
|
18
|
+
port: int | None = None,
|
|
19
|
+
param_schema=None,
|
|
20
|
+
op_schema=None,
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Initialize the device with an id, connection info, and optional schemas."""
|
|
23
|
+
super().__init__(id, op_schema=op_schema, param_schema=param_schema)
|
|
24
|
+
## Connection credentials (from the environment via EnvSettings); use them
|
|
25
|
+
## in `connect()` to reach the hardware.
|
|
26
|
+
self.host = host
|
|
27
|
+
self.port = port
|
|
28
|
+
## Device-specific initialization can be added here
|
|
29
|
+
|
|
30
|
+
@expose_to_api
|
|
31
|
+
def custom_method(self) -> dict:
|
|
32
|
+
"""Implement a custom device-specific operation.
|
|
33
|
+
|
|
34
|
+
Custom methods are not required by the base model; they add workflows
|
|
35
|
+
on top of the registered configuration parameters and schema
|
|
36
|
+
operations. `@expose_to_api` is what puts one on the describe
|
|
37
|
+
endpoint, so a remote client and the field test can call it. Bare, it
|
|
38
|
+
declares a `read`: the operation answers and changes nothing.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Whatever the operation reports; a mapping keeps the reply
|
|
42
|
+
self-describing over the wire.
|
|
43
|
+
"""
|
|
44
|
+
## Device-specific logic can be added here
|
|
45
|
+
return {}
|
|
46
|
+
|
|
47
|
+
## An operation that acts on the world declares what it does, so the field
|
|
48
|
+
## test (`plesty init field-test`) can drive it unattended instead of
|
|
49
|
+
## asking about every call:
|
|
50
|
+
##
|
|
51
|
+
## @expose_to_api(kind="motion", position_key="position", step=1.0)
|
|
52
|
+
## def move_absolute(self, position: float) -> float: ...
|
|
53
|
+
##
|
|
54
|
+
## @expose_to_api(kind="lifecycle")
|
|
55
|
+
## def home(self) -> bool: ...
|
|
56
|
+
##
|
|
57
|
+
## Kinds are read | motion | acquire | lifecycle | control | configure. A
|
|
58
|
+
## motion is driven one `step` out from what `position_key` reports and put
|
|
59
|
+
## back, a lifecycle operation runs once, a control operation once at rest
|
|
60
|
+
## after the motions, and a configure operation is never called.
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Plesty Development Team
|
|
2
|
+
# SPDX-FileContributor: Yunshuang Yuan
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
5
|
+
|
|
1
6
|
"""Experiment implementation.
|
|
2
7
|
|
|
3
8
|
Replace the example step operation with real measurement logic. The plan/
|
|
4
9
|
journal/resume lifecycle is inherited from ``plesty.lib.experiment`` — keep
|
|
5
10
|
``build_plan()`` deterministic and device-free so interrupted runs can be
|
|
6
11
|
resumed with ``run(resume=<run_id>)``.
|
|
12
|
+
|
|
13
|
+
Each run owns a directory under ``run_root`` holding ``plan.json``,
|
|
14
|
+
``journal.jsonl``, one ``records.jsonl`` line per completed step, and the
|
|
15
|
+
blobs those records reference. Live views over the same records are written
|
|
16
|
+
against ``plesty.lib.monitor`` (``Viz``), so watching a run needs nothing
|
|
17
|
+
from this file.
|
|
7
18
|
"""
|
|
8
19
|
|
|
9
20
|
from pathlib import Path
|
|
@@ -27,7 +38,7 @@ class Experiment(BaseExperiment):
|
|
|
27
38
|
self,
|
|
28
39
|
devices: Optional[CompositeDevice] = None,
|
|
29
40
|
config: Optional[dict[str, Any]] = None,
|
|
30
|
-
run_root: str | Path =
|
|
41
|
+
run_root: str | Path | None = None,
|
|
31
42
|
**kwargs: Any,
|
|
32
43
|
) -> None:
|
|
33
44
|
"""Initialize the experiment.
|
|
@@ -39,9 +50,12 @@ class Experiment(BaseExperiment):
|
|
|
39
50
|
config: Measurement configuration (tuning values, no
|
|
40
51
|
credentials); recorded in the plan.
|
|
41
52
|
run_root: Directory under which each run creates its checkpoint
|
|
42
|
-
directory.
|
|
53
|
+
directory. ``None`` takes ``PLESTY_DATA_MOUNT`` from the
|
|
54
|
+
environment — runs land on the shared disk — and falls
|
|
55
|
+
back to ``runs`` when no share is configured.
|
|
43
56
|
**kwargs: Forwarded to the base ``Experiment`` (``name``,
|
|
44
|
-
``max_retries``, ``retry_sleep``
|
|
57
|
+
``max_retries``, ``retry_sleep``, ``data_dir``,
|
|
58
|
+
``data_mount``).
|
|
45
59
|
"""
|
|
46
60
|
super().__init__(devices=devices, run_root=run_root, **kwargs)
|
|
47
61
|
self.config: dict[str, Any] = dict(config or {})
|
|
@@ -62,15 +76,25 @@ class Experiment(BaseExperiment):
|
|
|
62
76
|
"""Example step operation — replace with a real measurement.
|
|
63
77
|
|
|
64
78
|
Access instruments through ``self.devices``. Return a result (e.g. a
|
|
65
|
-
``plesty.lib.data.PlestyArray``) to have it
|
|
66
|
-
``
|
|
79
|
+
``plesty.lib.data.PlestyArray``) to have it appended to the run's
|
|
80
|
+
``records.jsonl`` (large arrays land beside it under ``data/``);
|
|
81
|
+
returning ``None`` records nothing for the step. A device that writes
|
|
82
|
+
its own files writes them to ``self.raw_dir`` on the shared disk —
|
|
83
|
+
never back over the network.
|
|
67
84
|
|
|
68
85
|
Args:
|
|
69
86
|
x: Example sweep position taken from the step params.
|
|
70
87
|
"""
|
|
71
88
|
|
|
72
89
|
async def setup(self) -> None:
|
|
73
|
-
"""Prepare instruments before the first step; extend as needed.
|
|
90
|
+
"""Prepare instruments before the first step; extend as needed.
|
|
91
|
+
|
|
92
|
+
The inherited setup connects the composite and preflights it — every
|
|
93
|
+
remote sub-device must expose the methods its ``requires`` names and
|
|
94
|
+
answer ``identity()``, or ``RigNotReadyError`` stops the run before
|
|
95
|
+
anything is measured. Route the writing devices here, e.g.
|
|
96
|
+
``self.devices.set_data_path(self.raw_dir, devices=["cam"])``.
|
|
97
|
+
"""
|
|
74
98
|
await super().setup()
|
|
75
99
|
|
|
76
100
|
async def teardown(self) -> None:
|