plesty-sdk 0.5.0.dev1__tar.gz → 0.5.1__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.
Files changed (113) hide show
  1. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/.gitlab-ci.yml +2 -1
  2. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/CHANGELOG.md +39 -0
  3. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/PKG-INFO +1 -1
  4. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/docs/architecture.md +4 -11
  5. plesty_sdk-0.5.1/docs/commands/init-field-test.md +128 -0
  6. plesty_sdk-0.5.1/docs/commands/init-mock-test.md +86 -0
  7. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/docs/commands/init.md +7 -190
  8. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/docs/getting-started.md +43 -14
  9. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/docs/index.md +15 -0
  10. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/docs/toc.yaml +5 -0
  11. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/gl-secret-detection-report.json +6 -6
  12. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/hub-theme/plesty-logo.svg +4 -2
  13. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/hub-theme/plesty-mark.svg +4 -2
  14. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/extras/device/preflight.py +2 -2
  15. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/init.py +9 -10
  16. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/pyproject.toml +1 -1
  17. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/uv.lock +18 -18
  18. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/.coverage +0 -0
  19. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/.gitignore +0 -0
  20. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/.mailmap +0 -0
  21. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/LICENSE +0 -0
  22. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/LICENSES/LGPL-3.0-or-later.txt +0 -0
  23. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/README.md +0 -0
  24. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/REUSE.toml +0 -0
  25. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/docs/commands/check.md +0 -0
  26. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/docs/commands/docs.md +0 -0
  27. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/docs/commands/license.md +0 -0
  28. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/docs/commands/manual.md +0 -0
  29. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/docs/commands/update.md +0 -0
  30. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/packaging/versioning.py +0 -0
  31. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/__init__.py +0 -0
  32. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/check/guidance.yaml +0 -0
  33. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/docs-build-gitlab-ci.yml +0 -0
  34. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/hub-theme/plesty-hub.css +0 -0
  35. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/hub-theme/plesty-versions.css +0 -0
  36. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/hub-theme/plesty-versions.js +0 -0
  37. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/init/client-field-test.py.template +0 -0
  38. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/init/field-test-env.example +0 -0
  39. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/init/field-test-questions.yaml +0 -0
  40. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/init/field-test.py.template +0 -0
  41. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/init/mock-test.py.template +0 -0
  42. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/init/next-steps.yaml +0 -0
  43. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/manual/pdf-common.css +0 -0
  44. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/manual/pdf-page-landscape.css +0 -0
  45. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/manual/pdf-page-portrait.css +0 -0
  46. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/ruff.toml +0 -0
  47. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/copier.yml +0 -0
  48. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/extras/analyzer/analyzer.py +0 -0
  49. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/extras/device/base_device.py +0 -0
  50. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/extras/device/device.py +0 -0
  51. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/extras/experiment/experiment.py +0 -0
  52. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/.gitignore +0 -0
  53. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/CHANGELOG.md.jinja +0 -0
  54. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/LICENSE +0 -0
  55. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/LICENSES/LGPL-3.0-or-later.txt +0 -0
  56. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/README.md.jinja +0 -0
  57. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/REUSE.toml.jinja +0 -0
  58. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/docs/index.md.jinja +0 -0
  59. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/docs/toc.yaml +0 -0
  60. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/packaging/versioning.py +0 -0
  61. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/plesty/{{module_name}}/__init__.py.jinja +0 -0
  62. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/plesty/{{module_name}}/config.yaml.jinja +0 -0
  63. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/plesty/{{module_name}}/{% if project_type in ['device', 'experiment'] %}__main__.py{% endif %}.jinja +0 -0
  64. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/pyproject.toml.jinja +0 -0
  65. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/tests/__init__.py.jinja +0 -0
  66. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/tests/test_{{module_name}}.py.jinja +0 -0
  67. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/tests/{% if project_type == 'device' %}test_preflight.py{% endif %}.jinja +0 -0
  68. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/{% if project_type == 'experiment' %}config{% endif %}/default.yaml +0 -0
  69. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/{% if project_type == 'experiment' %}setup.yaml{% endif %} +0 -0
  70. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/assets/templates/template/{% if project_type in ['device', 'experiment'] %}.env.example{% endif %}.jinja +0 -0
  71. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/authorship.py +0 -0
  72. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/cache.py +0 -0
  73. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/cli.py +0 -0
  74. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/__init__.py +0 -0
  75. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/check.py +0 -0
  76. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/docs/__init__.py +0 -0
  77. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/docs/deploy.py +0 -0
  78. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/docs/generate.py +0 -0
  79. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/docs/picker.py +0 -0
  80. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/docs/serve.py +0 -0
  81. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/docs/sphinx_builder.py +0 -0
  82. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/field_test.py +0 -0
  83. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/license.py +0 -0
  84. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/manual.py +0 -0
  85. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/mock_test.py +0 -0
  86. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/commands/update.py +0 -0
  87. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/context.py +0 -0
  88. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/report.py +0 -0
  89. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/semver.py +0 -0
  90. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/plesty/sdk/tui.py +0 -0
  91. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/__init__.py +0 -0
  92. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_authorship.py +0 -0
  93. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_cache.py +0 -0
  94. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_check.py +0 -0
  95. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_check_experiment_manifest.py +0 -0
  96. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_cli.py +0 -0
  97. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_context.py +0 -0
  98. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_device_preflight_template.py +0 -0
  99. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_docs_deploy.py +0 -0
  100. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_docs_generate.py +0 -0
  101. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_docs_picker.py +0 -0
  102. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_docs_serve.py +0 -0
  103. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_field_test_scaffold.py +0 -0
  104. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_init.py +0 -0
  105. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_license.py +0 -0
  106. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_manual.py +0 -0
  107. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_mock_test_scaffold.py +0 -0
  108. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_report.py +0 -0
  109. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_semver.py +0 -0
  110. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_sphinx_builder.py +0 -0
  111. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_tui.py +0 -0
  112. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_update.py +0 -0
  113. {plesty_sdk-0.5.0.dev1 → plesty_sdk-0.5.1}/tests/test_versioning_rule.py +0 -0
@@ -2,9 +2,10 @@ workflow:
2
2
  rules:
3
3
  # Create pipelines for merge requests
4
4
  - if: $CI_PIPELINE_SOURCE == "merge_request_event"
5
- # Create branch pipelines for main and exp
5
+ # Create branch pipelines for main, exp and every supported maintain/* line
6
6
  - if: $CI_COMMIT_BRANCH == "main"
7
7
  - if: $CI_COMMIT_BRANCH == "exp"
8
+ - if: $CI_COMMIT_BRANCH =~ /^maintain\//
8
9
  # Create pipelines for tags (dev and stable releases)
9
10
  - if: $CI_COMMIT_TAG
10
11
  # Otherwise do not create a pipeline
@@ -2,6 +2,34 @@
2
2
 
3
3
  The format is based on [Keep a Changelog](https://keepachangelog.com).
4
4
 
5
+ ## [0.5.1] — 2026-10-15
6
+
7
+ Fixes on the 0.5 line, the same as in 0.4.1. Supported lines: 0.5 and 0.4
8
+ (`maintain/0.4`).
9
+
10
+ ### Fixed
11
+ - The logo in the generated docs pages no longer animates forever. The photon
12
+ in the PLESTY mark looped indefinitely through a blur filter, so an idle docs
13
+ page kept the browser redrawing every frame (~25% CPU in Safari). It now
14
+ travels three times and stops.
15
+
16
+ ### Security
17
+ - urllib3 2.8.0 and tornado 6.5.10 in the lockfile: 2.7.0 and 6.5.8 carry six
18
+ published advisories (PYSEC-2026-4175/4176/4177, GHSA-chx6-46f5-w4vp,
19
+ GHSA-c2m8-h5v5-343r, GHSA-3hv7-mjh2-fv65), which failed the audit gate.
20
+
21
+ ## [0.5.0] — 2026-09-29
22
+
23
+ Opens the 0.5 line, scaffolding against the released plesty-lib 0.5.0: every
24
+ change listed under 0.5.0.dev1 below, and the pin that follows. Supported
25
+ lines: 0.5 and 0.4 (`maintain/0.4`); 0.3 is retired.
26
+
27
+ ### Changed
28
+ - **Scaffolds pin the released plesty-lib 0.5.0, not a snapshot.** The floor
29
+ `plesty init` writes when PyPI cannot be reached (`PLESTY_LIB_VERSION`), the
30
+ version a scaffolded device's preflight names when its library is too old,
31
+ and the dev-group pin are `0.5.0`, where they named `0.5.0.dev7`.
32
+
5
33
  ## [0.5.0.dev1] — 2026-09-25
6
34
 
7
35
  Development pre-release, the first of the 0.5 line: scaffolds generated against
@@ -98,6 +126,17 @@ writes them before 0.5.0 is ready to be called finished.
98
126
  `major.minor`, taken from the line the checkout can reach by tag. Pre-release
99
127
  and yanked files are skipped. Nothing changes on the newest line.
100
128
 
129
+ ### Added
130
+ - **Gate 8 baselines against the line being built.** It compared the working
131
+ tree against the newest release of the whole project, which is right only
132
+ while there is one line. A `maintain/x.y` branch takes fixes to its own
133
+ features and merges upward only, so it never gains what a later line added:
134
+ once that line publishes, every function it introduced would read as a
135
+ removal from the older tree, on every run, for as long as the line is
136
+ supported. The baseline is now the newest release at or below the tree's own
137
+ `major.minor`, taken from the line the checkout can reach by tag. Pre-release
138
+ and yanked files are skipped. Nothing changes on the newest line.
139
+
101
140
  ### Fixed
102
141
  - **A scaffolded experiment ships the `setup.yaml` plesty-lib requires**
103
142
  (plesty-sdk#43). plesty-lib 0.5.0.dev6 made every experiment module declare
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: plesty-sdk
3
- Version: 0.5.0.dev1
3
+ Version: 0.5.1
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
@@ -124,17 +124,10 @@ needs network access (OSV queries), so pushing requires connectivity under `quan
124
124
  ### Version-derived default standard
125
125
 
126
126
  When `[tool.plesty] standard` is unset, `derive_standard()` maps the release version to a
127
- level, so shared prototypes ramp up automatically:
128
-
129
- | Version | Standard |
130
- |---|---|
131
- | `< 0.1.0`, or a dynamic version with no release tag yet | `pixel` |
132
- | `>= 0.1.0, < 1.0.0` | `nebula` |
133
- | `>= 1.0.0` | `quantum` |
134
-
135
- An explicit `standard` always wins; a project with no version information at all falls back
136
- to `quantum`, the conservative default. `plesty check` echoes which rule applied, so the
137
- resolution is never silent.
127
+ level, so shared prototypes ramp up automatically — pixel before `0.1.0`, nebula before
128
+ `1.0.0`, quantum from then on. The mapping and its fallbacks are specified once, under
129
+ [Standard resolution](commands/check.md#standard-resolution) on the `plesty check` page.
130
+ `plesty check` echoes which rule applied, so the resolution is never silent.
138
131
 
139
132
  ## Design Decisions
140
133
 
@@ -0,0 +1,128 @@
1
+ # plesty init field-test
2
+
3
+ Adds a field test — the gate sequence run against the real instrument — to an
4
+ **existing** device module. Unlike [`plesty init mock-test`](init-mock-test.md),
5
+ it interviews you.
6
+
7
+ ```bash
8
+ cd plesty-pm100d
9
+ plesty init field-test # interactive
10
+ plesty init field-test --force # replace an existing tests/field_test.py
11
+ ```
12
+
13
+ `plesty.lib.test.field_test.FieldTestPipeline` gives every module the same gate
14
+ sequence. The gates are the easy part — they are generic and already shared.
15
+ What takes the time, and what three modules each got wrong at least once, is
16
+ the **configuration**, because every line of it is a judgement about the
17
+ instrument that no schema inspection can make:
18
+
19
+ - which operations are safe to call unattended — the wrong answer moved a
20
+ stage, fired an exposure, and rewrote a sensor's zero calibration
21
+ - which parameters survive being written back their own value — one crashed
22
+ the controlling application outright, killing every call after it
23
+ - what is slow enough to be timed out on demand, since a call returning in two
24
+ milliseconds cannot be abandoned and its gate silently skips
25
+ - which two parameters are distinguishable enough that a stale reply is
26
+ unambiguous
27
+ - whether connecting is cheap, or starts an application and costs half a minute
28
+
29
+ ## Candidates from the device, judgement from you
30
+
31
+ So the command asks the **device** for the *candidates*, and you for the
32
+ *judgement*. It constructs the module hardware-free — exactly as `plesty docs`
33
+ does, which gate d3 already requires — and reads `doc_model()`.
34
+
35
+ Not the JSON schema files, and the difference is not cosmetic. A module
36
+ registers parameters and operations in Python too (`register_config` in
37
+ `__init__`, an `@expose_to_api` method), and none of that is in a file:
38
+ `plesty-pm100d` alone has two such operations, which a file-reading generator
39
+ would never offer. And for a grouped schema the keys differ —
40
+ `get_config_list()` answers `group_prefix.param_key` while the file is keyed by
41
+ the bare name, so an `only_keys` list built from the file would name parameters
42
+ the pipeline cannot resolve. That is the group-name against command-prefix
43
+ mismatch three modules hit independently.
44
+
45
+ There is no fallback to the files. A device module that cannot build its doc
46
+ model already fails gate d3, and generating from a partial picture would
47
+ produce a file that looks complete and tests less than it claims.
48
+
49
+ ## The interview
50
+
51
+ Each question states what the answer is used for before offering the choices,
52
+ and each answer is followed by "why?" — the reason is written into the
53
+ generated file as a comment, so the next reader sees why and not only what.
54
+
55
+ Choices are a checkable list: arrow keys move, space selects, `a` selects all,
56
+ `n` clears, Enter confirms, Escape backs out. Each entry shows what it is —
57
+ a parameter's type and range, an operation's description — because choosing
58
+ which operations may run unsupervised from a list of bare names is how the
59
+ wrong one gets picked.
60
+
61
+ Nothing is ticked to begin with, and Enter confirms whatever *is* ticked, so
62
+ the cheapest keypress gives the most conservative answer. `a` exists but has
63
+ to be typed. While a list is empty it says what empty means **for that
64
+ question**, which is not the same everywhere: no operation selected means none
65
+ is called, while no parameter selected means every one is written back.
66
+
67
+ A run with no keyboard — piped input, a script, CI — falls back to typing
68
+ comma-separated numbers rather than waiting for a keypress that never comes.
69
+
70
+ **Nothing unclassified is treated as safe.** An operation you do not select is
71
+ in neither list: it is never called, and the gate that would have called it
72
+ skips saying so. A generated allow-list that quietly included a motion command
73
+ would be worse than no generator at all.
74
+
75
+ **What the module declares is not asked again.** An operation exposed with a
76
+ kind — `@expose_to_api(kind="motion", position_key="MO.Position", step=1.0)`,
77
+ `kind="lifecycle"`, `kind="control"`, or a `"kind"` block in
78
+ `schema_func.json` (plesty-lib#39) — is shown as `[motion]`, `[lifecycle]`,
79
+ `[control]` in the chooser, and the "acts on the world" question is asked only
80
+ about operations that declared nothing. A `configure` operation is not offered
81
+ at all: it is recorded as excluded, because a setting that outlives the call is
82
+ verified by the round-trip gate. For an allow-listed motion the interview
83
+ confirms how far the test may move it (`step`; the declared value is the
84
+ default) and, if the module left it open, which parameter reports the position
85
+ — a motion with neither is dropped, since the pipeline drives a motion one step
86
+ out from that parameter and back and will not guess how far. A motion chosen as
87
+ the slow operation is asked only how far the long move goes: the pipeline
88
+ starts it from wherever the part is and moves it back afterwards. Both
89
+ generated files carry the result as `OP_KINDS`.
90
+
91
+ ## The generated files
92
+
93
+ The result is two files from one interview. `tests/field_test.py` runs on the
94
+ bench against the instrument: hardware-gated (it defines no pytest test
95
+ functions, so CI collects nothing), wired to publish
96
+ `reports/<instrument>/field-test.json`, and runnable against the simulator
97
+ first:
98
+
99
+ ```bash
100
+ uv run python tests/field_test.py --address mock
101
+ uv run python tests/field_test.py --address <address>
102
+ ```
103
+
104
+ `tests/field_test_client.py` runs the same checks over the wire, against the
105
+ served module. It inherits the answers rather than asking again: an operation
106
+ unsafe on the bench is unsafe over the network too, and a second allow-list
107
+ would be a second chance to disagree.
108
+
109
+ Neither file is replaced without `--force`: they hold answers somebody thought
110
+ about, and a hardware run may since have corrected them.
111
+
112
+ ## The answers are kept
113
+
114
+ Each answer is written to `.plesty/field-test.json` as it is given, and every
115
+ run of the command starts from what that file holds — so Ctrl-C loses nothing,
116
+ and running `plesty init field-test` again carries on from where the interview
117
+ stopped. A question that was displayed but not answered is asked again; it is
118
+ marked done only once an answer is in hand. The file is the source of truth:
119
+ edit it to change an answer without repeating the interview, because an answer
120
+ hand-edited in the generated `.py` is overwritten by the next run. Answers left
121
+ by an older SDK in `.plesty-field-test.partial.json` are moved into `.plesty/`
122
+ on the first run.
123
+
124
+ The run ends by saying which constant holds which answer.
125
+
126
+ The interview text ships in the wheel
127
+ (`plesty/sdk/assets/init/field-test-questions.yaml`) and the generated file's
128
+ skeleton alongside it (`field-test.py.template`).
@@ -0,0 +1,86 @@
1
+ # plesty init mock-test
2
+
3
+ Adds the hardware-free test gate d1 requires to an **existing** device module.
4
+ Unlike [`plesty init field-test`](init-field-test.md), it asks nothing.
5
+
6
+ ```bash
7
+ cd plesty-pm100d
8
+ plesty init mock-test # writes tests/test_mock_pipeline.py
9
+ plesty init mock-test --force # regenerate over its own previous output
10
+ ```
11
+
12
+ The field-test interview exists because every one of its questions is a safety
13
+ judgement about a real instrument. Nothing in the mock pipeline can move a
14
+ stage or fire an exposure, and everything it needs is either declared by the
15
+ device or settled by trying it:
16
+
17
+ | Source | What it gives |
18
+ |---|---|
19
+ | the device's `doc_model()` | the parameters and operations, read through the same reader the field test uses |
20
+ | the package name | the module and the import |
21
+ | a construction probe | how to reach the device without hardware |
22
+
23
+ ## The probe
24
+
25
+ **The probe is the design.** A device module is hardware-free in one of
26
+ several ways — `address="mock"` selects an in-memory simulator in one
27
+ reference module, `use_mock_solver=True` does it in the other — and which one
28
+ applies is knowable by constructing the device and looking. Each candidate is
29
+ verified *the way the gates will verify it*: connect, read `is_operatable`,
30
+ call `identity()`, and call `check_errors()`, which must return a list — empty
31
+ on a fresh device. A strategy is written into the file only once it has passed
32
+ the checks the file will make, which is why a generated test that gets written
33
+ is a generated test that passes those checks.
34
+
35
+ When neither applies, the last resort is the module's own `DocDevice` — the
36
+ hardware-free construction gate d3 already requires. A module scaffolded by
37
+ `plesty init device` exports it as `partial(Device, id=...)`, and because
38
+ `DevicePipeline` constructs from a class, the file passes `Device` and
39
+ forwards the partial's own arguments:
40
+
41
+ ```python
42
+ PIPELINE = DevicePipeline(Device, *DocDevice.args, **DocDevice.keywords)
43
+ ```
44
+
45
+ A `DocDevice` that is neither a class nor a partial of `Device` is refused
46
+ with that reason.
47
+
48
+ Where no strategy works the command refuses and lists what it tried. It does
49
+ not fall back to asking: a developer handed a question the probe could not
50
+ answer would be guessing at the same thing, and a file built on a guess looks
51
+ complete and tests less than it claims. Give the device a hardware-free path —
52
+ `address="mock"` is the convention, and
53
+ `plesty.lib.sim.demo_device.DemoDevice` is the reference implementation of it —
54
+ then run the command again.
55
+
56
+ The probe covers three of the eight gates. On a fresh scaffold the file is
57
+ written and gate 2 (parameter key resolution) still fails until the device
58
+ declares its schema — the first of the next steps `plesty init device` prints.
59
+
60
+ ## The generated file
61
+
62
+ The generated file is short, because `run_mock_pipeline()` runs all eight
63
+ gates and raises with everything that failed:
64
+
65
+ ```python
66
+ from plesty.lib.test.device_pipeline import DevicePipeline
67
+
68
+ from plesty.pm100d import Device
69
+
70
+ PIPELINE = DevicePipeline(Device, address="mock")
71
+
72
+
73
+ def test_mock_pipeline() -> None:
74
+ """Run the eight mock gates and fail with everything that broke."""
75
+ PIPELINE.run_mock_pipeline()
76
+ ```
77
+
78
+ No schema path appears in it. The gates read what the device declares at run
79
+ time, so nothing here restates a schema and nothing goes stale when the device
80
+ gains a parameter.
81
+
82
+ The file is always `tests/test_mock_pipeline.py`, never `tests/test_<module>.py`
83
+ — that is where a module's hand-written unit tests already live, and one
84
+ reference module keeps 487 lines of them. `--force` regenerates over this
85
+ command's own output and refuses anything else, checked by a marker in the
86
+ file, so a regeneration cannot destroy work.
@@ -19,8 +19,9 @@ plesty init [PROJECT_TYPE] [OPTIONS]
19
19
  and `experiment` the plan/journal experiment skeleton with the E1 gate tests.
20
20
 
21
21
  Two further types, `mock-test` and `field-test`, add a test to an **existing** device
22
- module instead of creating a project — see [`plesty init mock-test`](#plesty-init-mock-test)
23
- and [`plesty init field-test`](#plesty-init-field-test).
22
+ module instead of creating a project; each has its own page:
23
+ [`plesty init mock-test`](init-mock-test.md) and
24
+ [`plesty init field-test`](init-field-test.md).
24
25
 
25
26
  ## Naming conventions
26
27
 
@@ -63,9 +64,10 @@ After copying the template, `plesty init` automatically:
63
64
  2. **Creates the initial git commit** — required by `versioningit` to resolve a package
64
65
  version from git history.
65
66
  3. **Installs the pre-push hook** — writes `.git/hooks/pre-push` to run
66
- `plesty check` automatically before every `git push` (standard read from
67
- `[tool.plesty] standard` in `pyproject.toml`, or derived from the release
68
- version when unset — `<0.1.0` pixel, `<1.0.0` nebula, `>=1.0.0` quantum).
67
+ `plesty check` automatically before every `git push`, at the project's
68
+ resolved standard (see [Standard resolution](check.md#standard-resolution)).
69
+ Hooks are not cloned: a contributor who clones the project installs it once
70
+ with `plesty update --install-hook`.
69
71
 
70
72
  ## Examples
71
73
 
@@ -316,188 +318,3 @@ the global `--quiet` flag to print only the result line:
316
318
  ```bash
317
319
  plesty --quiet --project-dir plesty-foo-bar init device
318
320
  ```
319
-
320
- ## `plesty init mock-test`
321
-
322
- Adds to an **existing** device module, like `field-test` — and unlike it, asks
323
- nothing.
324
-
325
- ```bash
326
- cd plesty-pm100d
327
- plesty init mock-test # writes tests/test_mock_pipeline.py
328
- plesty init mock-test --force # regenerate over its own previous output
329
- ```
330
-
331
- The field-test interview exists because every one of its questions is a safety
332
- judgement about a real instrument. Nothing in the mock pipeline can move a
333
- stage or fire an exposure, and everything it needs is either declared by the
334
- device or settled by trying it:
335
-
336
- | Source | What it gives |
337
- |---|---|
338
- | the device's `doc_model()` | the parameters and operations, read through the same reader the field test uses |
339
- | the package name | the module and the import |
340
- | a construction probe | how to reach the device without hardware |
341
-
342
- **The probe is the design.** A device module is hardware-free in one of
343
- several ways — `address="mock"` selects an in-memory simulator in one
344
- reference module, `use_mock_solver=True` does it in the other — and which one
345
- applies is knowable by constructing the device and looking. Each candidate is
346
- verified *the way the gates will verify it*: connect, read `is_operatable`,
347
- call `identity()` and `check_errors()`. A strategy is written into the file
348
- only once it has passed the checks the file will make, which is why a
349
- generated test that gets written is a generated test that passes.
350
-
351
- Where no strategy works the command refuses and lists what it tried. It does
352
- not fall back to asking: a developer handed a question the probe could not
353
- answer would be guessing at the same thing, and a file built on a guess looks
354
- complete and tests less than it claims. Give the device a hardware-free path —
355
- `address="mock"` is the convention, and
356
- `plesty.lib.sim.demo_device.DemoDevice` is the reference implementation of it —
357
- then run the command again.
358
-
359
- The generated file is short, because `run_mock_pipeline()` runs all eight
360
- gates and raises with everything that failed:
361
-
362
- ```python
363
- from plesty.lib.test.device_pipeline import DevicePipeline
364
-
365
- from plesty.pm100d import Device
366
-
367
- PIPELINE = DevicePipeline(Device, address="mock")
368
-
369
-
370
- def test_mock_pipeline() -> None:
371
- """Run the eight mock gates and fail with everything that broke."""
372
- PIPELINE.run_mock_pipeline()
373
- ```
374
-
375
- No schema path appears in it. The gates read what the device declares at run
376
- time, so nothing here restates a schema and nothing goes stale when the device
377
- gains a parameter.
378
-
379
- The file is always `tests/test_mock_pipeline.py`, never `tests/test_<module>.py`
380
- — that is where a module's hand-written unit tests already live, and one
381
- reference module keeps 487 lines of them. `--force` regenerates over this
382
- command's own output and refuses anything else, checked by a marker in the
383
- file, so a regeneration cannot destroy work.
384
-
385
- ## `plesty init field-test`
386
-
387
- Different from the other project types: it adds to an **existing** device
388
- module rather than creating a project.
389
-
390
- ```bash
391
- cd plesty-pm100d
392
- plesty init field-test # interactive
393
- plesty init field-test --force # replace an existing tests/field_test.py
394
- ```
395
-
396
- `plesty.lib.test.field_test.FieldTestPipeline` gives every module the same gate
397
- sequence. The gates are the easy part — they are generic and already shared.
398
- What takes the time, and what three modules each got wrong at least once, is
399
- the **configuration**, because every line of it is a judgement about the
400
- instrument that no schema inspection can make:
401
-
402
- - which operations are safe to call unattended — the wrong answer moved a
403
- stage, fired an exposure, and rewrote a sensor's zero calibration
404
- - which parameters survive being written back their own value — one crashed
405
- the controlling application outright, killing every call after it
406
- - what is slow enough to be timed out on demand, since a call returning in two
407
- milliseconds cannot be abandoned and its gate silently skips
408
- - which two parameters are distinguishable enough that a stale reply is
409
- unambiguous
410
- - whether connecting is cheap, or starts an application and costs half a minute
411
-
412
- So the command asks the **device** for the *candidates*, and you for the
413
- *judgement*. It constructs the module hardware-free — exactly as `plesty docs`
414
- does, which gate d3 already requires — and reads `doc_model()`.
415
-
416
- Not the JSON schema files, and the difference is not cosmetic. A module
417
- registers parameters and operations in Python too (`register_config` in
418
- `__init__`, an `@expose_to_api` method), and none of that is in a file:
419
- `plesty-pm100d` alone has two such operations, which a file-reading generator
420
- would never offer. And for a grouped schema the keys differ —
421
- `get_config_list()` answers `group_prefix.param_key` while the file is keyed by
422
- the bare name, so an `only_keys` list built from the file would name parameters
423
- the pipeline cannot resolve. That is the group-name against command-prefix
424
- mismatch three modules hit independently.
425
-
426
- There is no fallback to the files. A device module that cannot build its doc
427
- model already fails gate d3, and generating from a partial picture would
428
- produce a file that looks complete and tests less than it claims.
429
-
430
- Each question states what the answer is used for before offering the choices,
431
- and each answer is followed by "why?" — the reason is written into the
432
- generated file as a comment, so the next reader sees why and not only what.
433
-
434
- Choices are a checkable list: arrow keys move, space selects, `a` selects all,
435
- `n` clears, Enter confirms, Escape backs out. Each entry shows what it is —
436
- a parameter's type and range, an operation's description — because choosing
437
- which operations may run unsupervised from a list of bare names is how the
438
- wrong one gets picked.
439
-
440
- Nothing is ticked to begin with, and Enter confirms whatever *is* ticked, so
441
- the cheapest keypress gives the most conservative answer. `a` exists but has
442
- to be typed. While a list is empty it says what empty means **for that
443
- question**, which is not the same everywhere: no operation selected means none
444
- is called, while no parameter selected means every one is written back.
445
-
446
- A run with no keyboard — piped input, a script, CI — falls back to typing
447
- comma-separated numbers rather than waiting for a keypress that never comes.
448
-
449
- **Nothing unclassified is treated as safe.** An operation you do not select is
450
- in neither list: it is never called, and the gate that would have called it
451
- skips saying so. A generated allow-list that quietly included a motion command
452
- would be worse than no generator at all.
453
-
454
- **What the module declares is not asked again.** An operation exposed with a
455
- kind — `@expose_to_api(kind="motion", position_key="MO.Position", step=1.0)`,
456
- `kind="lifecycle"`, `kind="control"`, or a `"kind"` block in
457
- `schema_func.json` (plesty-lib#39) — is shown as `[motion]`, `[lifecycle]`,
458
- `[control]` in the chooser, and the "acts on the world" question is asked only
459
- about operations that declared nothing. A `configure` operation is not offered
460
- at all: it is recorded as excluded, because a setting that outlives the call is
461
- verified by the round-trip gate. For an allow-listed motion the interview
462
- confirms how far the test may move it (`step`; the declared value is the
463
- default) and, if the module left it open, which parameter reports the position
464
- — a motion with neither is dropped, since the pipeline drives a motion one step
465
- out from that parameter and back and will not guess how far. A motion chosen as
466
- the slow operation is asked only how far the long move goes: the pipeline
467
- starts it from wherever the part is and moves it back afterwards. Both
468
- generated files carry the result as `OP_KINDS`.
469
-
470
- The result is two files from one interview. `tests/field_test.py` runs on the
471
- bench against the instrument: hardware-gated (it defines no pytest test
472
- functions, so CI collects nothing), wired to publish
473
- `reports/<instrument>/field-test.json`, and runnable against the simulator
474
- first:
475
-
476
- ```bash
477
- uv run python tests/field_test.py --address mock
478
- uv run python tests/field_test.py --address <address>
479
- ```
480
-
481
- `tests/field_test_client.py` runs the same checks over the wire, against the
482
- served module. It inherits the answers rather than asking again: an operation
483
- unsafe on the bench is unsafe over the network too, and a second allow-list
484
- would be a second chance to disagree.
485
-
486
- Neither file is replaced without `--force`: they hold answers somebody thought
487
- about, and a hardware run may since have corrected them.
488
-
489
- **The answers are kept.** Each one is written to `.plesty/field-test.json` as
490
- it is given, and every run of the command starts from what that file holds —
491
- so Ctrl-C loses nothing, and running `plesty init field-test` again carries on
492
- from where the interview stopped. A question that was displayed but not
493
- answered is asked again; it is marked done only once an answer is in hand. The
494
- file is the source of truth: edit it to change an answer without repeating the
495
- interview, because an answer hand-edited in the generated `.py` is overwritten
496
- by the next run. Answers left by an older SDK in
497
- `.plesty-field-test.partial.json` are moved into `.plesty/` on the first run.
498
-
499
- The run ends by saying which constant holds which answer.
500
-
501
- The interview text ships in the wheel
502
- (`plesty/sdk/assets/init/field-test-questions.yaml`) and the generated file's
503
- skeleton alongside it (`field-test.py.template`).
@@ -27,9 +27,16 @@ plesty --project-dir ~/projects/plesty-power-meter init device \
27
27
  This generates a project pre-configured with the expected repository structure,
28
28
  `pyproject.toml`, example source files, CI pipeline (`.gitlab-ci.yml`), documentation
29
29
  stubs, and a `CHANGELOG.md`. A pre-push git hook is also installed automatically —
30
- it runs `plesty check` before every `git push`, using the standard declared in
31
- `[tool.plesty]` or, when unset, derived from the release version (`<0.1.0` pixel,
32
- `<1.0.0` nebula, `>=1.0.0` quantum; no tag yet = pixel).
30
+ it runs `plesty check` before every `git push`, at the project's resolved standard
31
+ (see [Standard resolution](commands/check.md#standard-resolution)).
32
+
33
+ ```{note}
34
+ Git hooks live in `.git/` and are never cloned. Anyone who clones an existing
35
+ project — every contributor after the one who scaffolded it — installs the hook
36
+ once:
37
+
38
+ plesty update --install-hook
39
+ ```
33
40
 
34
41
  Use `--standard` to choose the compliance standard the generated CI pipeline enforces
35
42
  (default `pixel` — a fresh scaffold is a prototype; bump it alongside your releases), or
@@ -61,15 +68,37 @@ cd plesty-power-meter
61
68
  plesty check
62
69
  ```
63
70
 
64
- By default this checks against the standard declared in `[tool.plesty]` or,
65
- when unset, derived from the release version — a fresh scaffold without a
66
- release tag starts at **pixel** and ramps up to **quantum** with `v1.0.0`
67
- (see `plesty check`'s standard-resolution docs). Add `--standard pixel` for a fast metadata +
68
- code hygiene check, or `--standard nebula` for all static gates (1–6)
69
- without the slower quantum-only ones (7–11 and the module-type gates). Fix any reported
70
- issues and re-run until the check passes.
71
+ By default this checks against the project's resolved standard — a fresh scaffold
72
+ without a release tag starts at **pixel** and ramps up to **quantum** with `v1.0.0`
73
+ (see [Standard resolution](commands/check.md#standard-resolution)). Add
74
+ `--standard pixel` for a fast metadata + code hygiene check, or `--standard nebula` for
75
+ all static gates (1–6) without the slower quantum-only ones (7–11 and the module-type
76
+ gates). Fix any reported issues and re-run until the check passes.
77
+
78
+ ## 3. Test the device, without and then with hardware
79
+
80
+ Under `quantum`, a device module must pass gate d1: a test that runs plesty-lib's eight
81
+ mock gates without an instrument attached. Once the device declares its schema, generate
82
+ that test — the command works out by itself how to reach the device without hardware:
83
+
84
+ ```bash
85
+ plesty init mock-test # writes tests/test_mock_pipeline.py
86
+ ```
87
+
88
+ When the instrument is on the bench, add the field test. It interviews you about what is
89
+ safe to run unattended, then writes a test that runs against the real device and
90
+ publishes a report:
91
+
92
+ ```bash
93
+ plesty init field-test
94
+ uv run python tests/field_test.py --address mock # rehearse against the simulator
95
+ uv run python tests/field_test.py --address <address> # then the instrument
96
+ ```
97
+
98
+ See [`plesty init mock-test`](commands/init-mock-test.md) and
99
+ [`plesty init field-test`](commands/init-field-test.md) for what each asks and writes.
71
100
 
72
- ## 3. Preview the documentation
101
+ ## 4. Preview the documentation
73
102
 
74
103
  Start a local documentation server with live reload:
75
104
 
@@ -80,7 +109,7 @@ plesty docs serve
80
109
  Open [http://localhost:8000](http://localhost:8000) in your browser. Any changes to `.md`
81
110
  or `.rst` files in the `docs/` directory are reflected immediately.
82
111
 
83
- ## 4. Apply license headers
112
+ ## 5. Apply license headers
84
113
 
85
114
  Keep all Python files covered by the SPDX license header:
86
115
 
@@ -91,7 +120,7 @@ plesty license update
91
120
  This reads author and maintainer information from `pyproject.toml` and writes the correct
92
121
  header to every `.py` file in the project.
93
122
 
94
- ## 5. Update project metadata
123
+ ## 6. Update project metadata
95
124
 
96
125
  Use `plesty update` to change author info, the PyPI distribution name, or the CI pipeline
97
126
  at any time without re-scaffolding the project:
@@ -107,7 +136,7 @@ plesty update --name plesty-pm100d
107
136
  plesty update --standard quantum
108
137
  ```
109
138
 
110
- ## 6. Deploy documentation
139
+ ## 7. Deploy documentation
111
140
 
112
141
  Documentation deployment is handled automatically by the `hub-deploy-docs` job of the
113
142
  `plesty-standard-ci` component when commits land on the default branch or a release tag is
@@ -26,6 +26,21 @@ plesty -d ../my-device docs serve
26
26
  Every command reads `pyproject.toml`, the `.plesty` cache, and all source paths relative to
27
27
  this directory.
28
28
 
29
+ ## The `--quiet` option
30
+
31
+ The second global option, `--quiet` (`-q`), suppresses progress and guidance output —
32
+ `plesty check`'s per-gate progress lines, the numbered **Next steps** `plesty init` ends
33
+ with. Failures, their guidance, and the result line always print, so a quiet run still
34
+ says what broke and whether it passed:
35
+
36
+ ```bash
37
+ plesty --quiet check
38
+ plesty -q --project-dir plesty-foo-bar init device
39
+ ```
40
+
41
+ Global options go before the command name: `plesty --quiet check`, not
42
+ `plesty check --quiet`.
43
+
29
44
  ## Commands at a glance
30
45
 
31
46
  | Command | Description |
@@ -8,6 +8,11 @@ entries:
8
8
  title: check
9
9
  - file: commands/init.md
10
10
  title: init
11
+ entries:
12
+ - file: commands/init-mock-test.md
13
+ title: init mock-test
14
+ - file: commands/init-field-test.md
15
+ title: init field-test
11
16
  - file: commands/docs.md
12
17
  title: docs
13
18
  - file: commands/license.md