plesty-sdk 0.3.5.dev1__tar.gz → 0.3.6__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 (104) hide show
  1. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/CHANGELOG.md +102 -0
  2. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/PKG-INFO +1 -1
  3. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/docs/commands/check.md +1 -1
  4. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/gl-secret-detection-report.json +7 -7
  5. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/check/guidance.yaml +3 -1
  6. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/docs-build-gitlab-ci.yml +6 -2
  7. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/hub-theme/plesty-hub.css +2 -0
  8. plesty_sdk-0.3.6/plesty/sdk/assets/hub-theme/plesty-versions.css +49 -0
  9. plesty_sdk-0.3.6/plesty/sdk/assets/hub-theme/plesty-versions.js +110 -0
  10. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/plesty/{{module_name}}/{% if project_type in ['device', 'experiment'] %}__main__.py{% endif %}.jinja +11 -5
  11. plesty_sdk-0.3.6/plesty/sdk/commands/docs/deploy.py +288 -0
  12. plesty_sdk-0.3.6/plesty/sdk/commands/docs/picker.py +141 -0
  13. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/docs/sphinx_builder.py +19 -3
  14. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/field_test.py +5 -1
  15. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/init.py +2 -1
  16. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/pyproject.toml +11 -0
  17. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_docs_deploy.py +135 -32
  18. plesty_sdk-0.3.6/tests/test_docs_picker.py +148 -0
  19. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_field_test_scaffold.py +87 -0
  20. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_init.py +21 -0
  21. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_sphinx_builder.py +1 -1
  22. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/uv.lock +189 -13
  23. plesty_sdk-0.3.5.dev1/plesty/sdk/commands/docs/deploy.py +0 -198
  24. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/.coverage +0 -0
  25. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/.gitignore +0 -0
  26. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/.gitlab-ci.yml +0 -0
  27. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/.mailmap +0 -0
  28. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/LICENSE +0 -0
  29. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/LICENSES/LGPL-3.0-or-later.txt +0 -0
  30. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/README.md +0 -0
  31. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/REUSE.toml +0 -0
  32. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/docs/architecture.md +0 -0
  33. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/docs/commands/docs.md +0 -0
  34. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/docs/commands/init.md +0 -0
  35. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/docs/commands/license.md +0 -0
  36. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/docs/commands/manual.md +0 -0
  37. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/docs/commands/update.md +0 -0
  38. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/docs/getting-started.md +0 -0
  39. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/docs/index.md +0 -0
  40. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/docs/toc.yaml +0 -0
  41. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/__init__.py +0 -0
  42. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/hub-theme/plesty-logo.svg +0 -0
  43. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/hub-theme/plesty-mark.svg +0 -0
  44. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/init/client-field-test.py.template +0 -0
  45. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/init/field-test-env.example +0 -0
  46. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/init/field-test-questions.yaml +0 -0
  47. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/init/field-test.py.template +0 -0
  48. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/init/mock-test.py.template +0 -0
  49. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/init/next-steps.yaml +0 -0
  50. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/manual/pdf-common.css +0 -0
  51. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/manual/pdf-page-landscape.css +0 -0
  52. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/manual/pdf-page-portrait.css +0 -0
  53. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/ruff.toml +0 -0
  54. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/copier.yml +0 -0
  55. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/extras/analyzer/analyzer.py +0 -0
  56. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/extras/device/base_device.py +0 -0
  57. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/extras/device/device.py +0 -0
  58. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/extras/experiment/experiment.py +0 -0
  59. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/.gitignore +0 -0
  60. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/CHANGELOG.md.jinja +0 -0
  61. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/LICENSE +0 -0
  62. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/LICENSES/LGPL-3.0-or-later.txt +0 -0
  63. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/README.md.jinja +0 -0
  64. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/REUSE.toml.jinja +0 -0
  65. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/docs/index.md.jinja +0 -0
  66. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/docs/toc.yaml +0 -0
  67. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/plesty/{{module_name}}/__init__.py.jinja +0 -0
  68. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/plesty/{{module_name}}/config.yaml.jinja +0 -0
  69. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/pyproject.toml.jinja +0 -0
  70. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/tests/__init__.py.jinja +0 -0
  71. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/tests/test_{{module_name}}.py.jinja +0 -0
  72. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/{% if project_type == 'experiment' %}config{% endif %}/default.yaml +0 -0
  73. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/assets/templates/template/{% if project_type in ['device', 'experiment'] %}.env.example{% endif %}.jinja +0 -0
  74. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/authorship.py +0 -0
  75. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/cache.py +0 -0
  76. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/cli.py +0 -0
  77. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/__init__.py +0 -0
  78. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/check.py +0 -0
  79. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/docs/__init__.py +0 -0
  80. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/docs/generate.py +0 -0
  81. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/docs/serve.py +0 -0
  82. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/license.py +0 -0
  83. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/manual.py +0 -0
  84. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/mock_test.py +0 -0
  85. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/commands/update.py +0 -0
  86. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/context.py +0 -0
  87. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/report.py +0 -0
  88. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/semver.py +0 -0
  89. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/plesty/sdk/tui.py +0 -0
  90. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/__init__.py +0 -0
  91. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_authorship.py +0 -0
  92. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_cache.py +0 -0
  93. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_check.py +0 -0
  94. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_cli.py +0 -0
  95. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_context.py +0 -0
  96. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_docs_generate.py +0 -0
  97. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_docs_serve.py +0 -0
  98. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_license.py +0 -0
  99. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_manual.py +0 -0
  100. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_mock_test_scaffold.py +0 -0
  101. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_report.py +0 -0
  102. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_semver.py +0 -0
  103. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_tui.py +0 -0
  104. {plesty_sdk-0.3.5.dev1 → plesty_sdk-0.3.6}/tests/test_update.py +0 -0
@@ -2,6 +2,108 @@
2
2
 
3
3
  The format is based on [Keep a Changelog](https://keepachangelog.com).
4
4
 
5
+ ## [0.3.6] — 2026-09-03
6
+
7
+ Everything the scaffolds generate now passes the gates the scaffolds ask for.
8
+ Two generated files failed a check the SDK itself enforces — a field-test
9
+ client with an import nothing used, and entry points on an event loop that
10
+ Python 3.16 will not give them — and the version picker no longer strands a
11
+ reader on a release it cannot leave. Generated projects pin `plesty-lib>=0.3.6`.
12
+
13
+ ### Fixed
14
+ - Generated device and experiment entry points start through
15
+ `plesty.lib.service.run_server` instead of a bare `asyncio.run`. The bare
16
+ call worked on Windows only because importing `plesty.lib.service` installed
17
+ a global event-loop policy — deprecated in Python 3.14 (two warnings per
18
+ server start on benches already there) and removed in 3.16, where the entry
19
+ point would get a `ProactorEventLoop` and fail inside pyzmq for want of
20
+ `add_reader`. The `plesty-lib` pin floor moves to 0.3.6, the first release
21
+ carrying `run_server`. Existing modules keep working on 3.14 and 3.15 and
22
+ pick the swap up when they regenerate.
23
+ - `plesty init field-test` no longer writes a client whose imports fail the
24
+ module's own lint gate. A module reached through its `DocDevice` — or through
25
+ any construction taking no arguments — gets a bare factory, and the generated
26
+ `tests/field_test_client.py` imported `functools.partial` anyway: F401, so
27
+ `plesty check` could not pass on a module that had just been field-tested.
28
+ The import is now emitted only in the branch that calls `partial`. Both
29
+ branches are linted in the tests; neither was before, because every fixture
30
+ probed into the "no hardware-free construction" path.
31
+ Generating against released 0.3.3 has a worse form of the same block — it
32
+ emits the `partial` import but not the device import, leaving `SPAWN_FACTORY`
33
+ an undefined name (F821). Add the `from plesty.<module> import <factory>`
34
+ line by hand, or regenerate once this ships.
35
+ - The version picker no longer offers a version the reader cannot switch back
36
+ out of. A release built before the Read-the-Docs theme has no sidebar for the
37
+ picker to mount on and cannot be stamped, yet the manifest still listed it —
38
+ on the SDK's own docs, selecting 0.1 stranded the reader. The manifest now
39
+ advertises only releases that carry the picker; such a release stays
40
+ published and reachable at its own URL, and `/releases/` still redirects to
41
+ the newest one that exists. Stamping consequently runs before the manifest is
42
+ written, so the manifest sees the final state of every tree.
43
+ - A module with no releases rendered a dropdown holding a single entry. The
44
+ picker now appears only when there is something to pick between.
45
+
46
+ ## [0.3.5] — 2026-08-24
47
+
48
+ Documentation gains a version picker, and the two things standing between it
49
+ and the published sites are fixed: the docs-deploy gate had been skipped since
50
+ 2026-08-21 behind a failing test collection, and releases built before the
51
+ picker existed had no way back to the current docs.
52
+
53
+ ### Added
54
+ - Every module's documentation carries a **version picker** in the sidebar.
55
+ Each module already deployed `latest/` from its default branch and
56
+ `releases/<major>.<minor>/` from each tag, but nothing linked them: a reader
57
+ on the release docs could not reach any other version. The picker is fed by
58
+ `versions.json`, fetched at page load rather than baked in — a page built
59
+ months ago cannot know which versions exist today. Switching versions keeps
60
+ the current page when the target has it, and falls back to that version's
61
+ index when it does not (pages come and go between releases). A deployment
62
+ without a manifest — a local `plesty docs serve` preview — renders no picker
63
+ at all.
64
+
65
+ ### Fixed
66
+ - The version picker is no longer a one-way trip. Releases published before it
67
+ existed are never rebuilt, so switching to one landed the reader on pages
68
+ with no picker and no way back. Each deploy now stamps the picker's two
69
+ assets and the tags referencing them into any release tree that lacks them.
70
+ Only the `<head>` gains anything — the prose, the API reference and the
71
+ stylesheets a historical release was built with are untouched, which is why
72
+ the picker's styling moved out of `plesty-hub.css` into its own
73
+ `plesty-versions.css`. Idempotent, so re-running over a stamped tree does
74
+ nothing. A release predating the Read-the-Docs theme (plesty-sdk 0.1) has no
75
+ sidebar to mount on and is skipped.
76
+ - The test suite no longer fails to collect in CI. `plesty-lib` is declared in
77
+ a `dev` dependency group: `tests/test_mock_test_scaffold.py` drives the
78
+ mock-test scaffold against a real `plesty.lib.sim.demo_device.DemoDevice`,
79
+ which it subclasses and runs, but nothing installed the library on a runner —
80
+ so `hub-check` had been failing with `ModuleNotFoundError: No module named
81
+ 'plesty.lib'`, which in turn skipped `hub-deploy-docs` and froze the
82
+ published documentation. It is a group rather than a runtime dependency:
83
+ installing plesty-sdk must not pull the library it scaffolds modules for.
84
+ - `versions.json` entries were origin-absolute (`"/0.3/"`), so they resolved
85
+ against the host rather than the module root, and the `releases/` redirect
86
+ stub built from them has been broken since it was written — on
87
+ docs.plesty.net it pointed at `docs.plesty.net/0.3/`. The manifest moves to
88
+ the module root, lists `latest` alongside the releases, and its URLs are
89
+ relative to that root, which is `/<slug>/` on docs.plesty.net and
90
+ `/<group>/<project>/` on GitLab Pages.
91
+
92
+ ### Changed
93
+ - Both deploy modes rewrite the manifest, not only `--release`. This is what
94
+ gives a module that has never been tagged a picker, and what migrates
95
+ branches deployed by an older SDK — including their `.gitlab-ci.yml`, which
96
+ was previously written once at branch creation and never updated again.
97
+ - The Pages job publishes the whole `html/` tree instead of `html/latest`, so
98
+ the releases the picker lists are reachable there too, and `html/index.html`
99
+ redirects the Pages root to `latest/`. The layout now matches what
100
+ docs.plesty.net already serves, so one picker runs unchanged on both.
101
+ **Deep links to `plesty.gitlab.io/<group>/<project>/<page>.html` move under
102
+ `/latest/`**; docs.plesty.net URLs are unaffected. The reference link
103
+ `plesty check` prints on a failing gate followed that move, so it now points
104
+ at docs.plesty.net — the site readers are sent to, and the one whose path is
105
+ independent of how Pages lays the tree out.
106
+
5
107
  ## [0.3.5.dev1] — 2026-08-21
6
108
 
7
109
  Development pre-release: the tutorial needs the pin option before 0.3.5 is
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: plesty-sdk
3
- Version: 0.3.5.dev1
3
+ Version: 0.3.6
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
@@ -146,7 +146,7 @@ failures and their guidance always print:
146
146
  Error: [Coverage] total coverage 62% below threshold
147
147
  This gate verifies: the pytest suite passes with at least 80% line coverage.
148
148
  How to fix: Reproduce with 'uv run pytest --cov=plesty' and add tests ...
149
- Reference: https://plesty.gitlab.io/core/plesty-sdk/commands/check.html
149
+ Reference: https://docs.plesty.net/plesty-sdk/latest/commands/check.html
150
150
  ```
151
151
 
152
152
  ## Examples
@@ -7,7 +7,7 @@
7
7
  "vendor": {
8
8
  "name": "GitLab"
9
9
  },
10
- "version": "7.38.1"
10
+ "version": "7.39.0"
11
11
  },
12
12
  "scanner": {
13
13
  "id": "gitleaks",
@@ -19,19 +19,19 @@
19
19
  "version": "8.30.1"
20
20
  },
21
21
  "type": "secret_detection",
22
- "start_time": "2026-08-21T18:31:49",
23
- "end_time": "2026-08-21T18:31:49",
22
+ "start_time": "2026-09-03T08:02:36",
23
+ "end_time": "2026-09-03T08:02:36",
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.480570925,
29
+ "time_s": 0.448937143,
30
30
  "exit_code": 0,
31
31
  "git_strategy": "FetchShallow",
32
- "repo_size_kb": 468,
32
+ "repo_size_kb": 503,
33
33
  "commit_count": 1,
34
- "bytes_scanned": 136,
34
+ "bytes_scanned": 415,
35
35
  "pipeline_type": "Tag"
36
36
  },
37
37
  {
@@ -40,7 +40,7 @@
40
40
  "passthrough_types": null,
41
41
  "passthrough_count": 0,
42
42
  "passthrough_auth_used": false,
43
- "rules_version": "0.25.0"
43
+ "rules_version": "0.25.1"
44
44
  }
45
45
  ]
46
46
  }
@@ -1,6 +1,8 @@
1
1
  # Per-gate failure guidance echoed by `plesty check` (issue sdk#26).
2
2
  # Keys match the gate keys used in check_command; text is plain CLI prose.
3
- docs_url: https://plesty.gitlab.io/core/plesty-sdk/commands/check.html
3
+ # docs.plesty.net, not the Pages mirror: it is the site readers are sent to,
4
+ # and its /<slug>/latest/ path is unaffected by how Pages lays the tree out.
5
+ docs_url: https://docs.plesty.net/plesty-sdk/latest/commands/check.html
4
6
 
5
7
  gates:
6
8
  metadata:
@@ -4,7 +4,7 @@ pages:
4
4
  stage: deploy
5
5
  rules:
6
6
  # The gate for creating the job, not a selector of what is published —
7
- # `publish: html/latest` below uploads the whole directory. exists
7
+ # `publish: html` below uploads the whole directory. exists
8
8
  # matches FILE globs: the bare directory path "html/latest/" stopped
9
9
  # matching on gitlab.com between 2026-07-21 and 2026-08-10, every
10
10
  # docs-build pipeline failed with "no jobs", and Pages froze while the
@@ -14,4 +14,8 @@ pages:
14
14
  script:
15
15
  - echo "Serving documentation on GitLab pages..."
16
16
  pages:
17
- publish: html/latest
17
+ # The whole tree, not just html/latest: the sidebar version picker needs
18
+ # versions.json and the releases/ it lists to be reachable from the same
19
+ # site. html/index.html forwards the Pages root to latest/, and the layout
20
+ # then matches docs.plesty.net, where the same picker runs unchanged.
21
+ publish: html
@@ -24,6 +24,8 @@
24
24
 
25
25
  /* The sidebar header keeps the stock Read-the-Docs styling. */
26
26
 
27
+ /* The version picker styles itself from plesty-versions.css. */
28
+
27
29
  /* Schema shields → soft rounded pills. */
28
30
  .rst-content .sd-badge {
29
31
  border-radius: 999px;
@@ -0,0 +1,49 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2026 Plesty Development Team
3
+ * SPDX-FileContributor: Yunshuang Yuan
4
+ * SPDX-License-Identifier: LGPL-3.0-or-later
5
+ *
6
+ * Styling for the sidebar version picker (plesty-versions.js).
7
+ *
8
+ * Separate from plesty-hub.css because it is also stamped into release docs
9
+ * built before the picker existed. Those trees carry their own copy of
10
+ * plesty-hub.css, and replacing it would change how a historical page looks
11
+ * beyond adding the dropdown; dropping one more file in beside it does not.
12
+ */
13
+
14
+ /* Sits at the foot of the sidebar header, under the search box. Added by the
15
+ * script, so a deployment without a versions.json shows no empty slot. */
16
+ .wy-side-nav-search .plesty-versions {
17
+ margin: 0.75rem 0 0;
18
+ text-align: left;
19
+ }
20
+ .wy-side-nav-search .plesty-versions label {
21
+ display: block;
22
+ margin-bottom: 0.3rem;
23
+ color: #d9d9d9;
24
+ font-size: 80%;
25
+ text-transform: uppercase;
26
+ letter-spacing: 0.06em;
27
+ }
28
+ .wy-side-nav-search .plesty-versions select {
29
+ width: 100%;
30
+ padding: 0.3rem 0.5rem;
31
+ border: 1px solid rgba(255, 255, 255, 0.25);
32
+ border-radius: 4px;
33
+ background: rgba(0, 0, 0, 0.2);
34
+ color: #fff;
35
+ font-size: 90%;
36
+ cursor: pointer;
37
+ }
38
+ .wy-side-nav-search .plesty-versions select:focus {
39
+ /* Plesty cyan, spelled out: the historical trees this file is stamped into
40
+ carry a plesty-hub.css from before the custom property existed. */
41
+ border-color: #0891b2;
42
+ outline: none;
43
+ }
44
+ /* The dropdown list itself renders in the OS palette, not the sidebar's —
45
+ without this the options are white-on-white on most platforms. */
46
+ .wy-side-nav-search .plesty-versions option {
47
+ color: #404040;
48
+ background: #fff;
49
+ }
@@ -0,0 +1,110 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2026 Plesty Development Team
3
+ * SPDX-FileContributor: Yunshuang Yuan
4
+ * SPDX-License-Identifier: LGPL-3.0-or-later
5
+ *
6
+ * Version picker for the Read-the-Docs sidebar.
7
+ *
8
+ * Every module is deployed as a tree of independently built docs — `latest/`
9
+ * from the default branch, `releases/<major>.<minor>/` from each tag — under
10
+ * one module root, and `versions.json` at that root lists them. A page built
11
+ * months ago cannot know which versions exist today, so the list is read at
12
+ * page load rather than baked in at build time.
13
+ *
14
+ * The module root is not derivable from the URL: docs.plesty.net serves the
15
+ * tree at /<slug>/ and GitLab Pages at /<group>/<project>/. It is derived
16
+ * from this script's own location instead — the file is always at
17
+ * <docs-root>/_static/plesty-versions.js, and `data-root-prefix` (set by
18
+ * sphinx_builder) is the hop from the docs root up to the module root.
19
+ */
20
+ (() => {
21
+ // Read synchronously: document.currentScript is null once the DOM is ready.
22
+ const script = document.currentScript;
23
+ if (!script) return;
24
+ const rootPrefix = script.dataset.rootPrefix;
25
+ const current = script.dataset.version || "latest";
26
+ if (!rootPrefix) return;
27
+
28
+ const docsRoot = new URL("../", script.src);
29
+ const moduleRoot = new URL(rootPrefix, docsRoot);
30
+ // Page path within this version's docs, carried across when the target
31
+ // version has the same page.
32
+ const pagePath = decodeURIComponent(location.href.slice(docsRoot.href.length));
33
+
34
+ const label = (version) =>
35
+ version === "latest" ? "latest (development)" : version;
36
+
37
+ /* Navigate to `path` under `target`, falling back to the target's index
38
+ when the page does not exist there — pages come and go between versions,
39
+ and a picker that lands on a 404 is worse than one that lands on a
40
+ landing page. A HEAD request that cannot run at all (file:// origin)
41
+ falls back the same way. */
42
+ const goTo = async (target, path) => {
43
+ const home = new URL(target, moduleRoot);
44
+ if (!path) {
45
+ location.assign(home);
46
+ return;
47
+ }
48
+ const exact = new URL(path, home);
49
+ try {
50
+ const response = await fetch(exact, { method: "HEAD" });
51
+ location.assign(response.ok ? exact : home);
52
+ } catch {
53
+ location.assign(home);
54
+ }
55
+ };
56
+
57
+ const render = (versions) => {
58
+ const container = document.querySelector(".wy-side-nav-search");
59
+ if (!container) return;
60
+
61
+ const picker = document.createElement("div");
62
+ picker.className = "plesty-versions";
63
+
64
+ const caption = document.createElement("label");
65
+ caption.textContent = "Version";
66
+ caption.htmlFor = "plesty-version-select";
67
+ picker.appendChild(caption);
68
+
69
+ const select = document.createElement("select");
70
+ select.id = "plesty-version-select";
71
+ for (const entry of versions) {
72
+ const option = document.createElement("option");
73
+ option.value = entry.url;
74
+ option.textContent = label(entry.version);
75
+ option.selected = entry.version === current;
76
+ select.appendChild(option);
77
+ }
78
+ // The running version is missing from versions.json when this build has
79
+ // not been indexed yet (a local preview, a tag mid-deploy). Show it so the
80
+ // picker never reads as if the reader were somewhere else.
81
+ if (!versions.some((entry) => entry.version === current)) {
82
+ const option = document.createElement("option");
83
+ option.textContent = label(current);
84
+ option.selected = true;
85
+ option.disabled = true;
86
+ select.insertBefore(option, select.firstChild);
87
+ }
88
+ select.addEventListener("change", () => goTo(select.value, pagePath));
89
+ picker.appendChild(select);
90
+
91
+ // Below the search box, so the picker does not push the search field out
92
+ // of its usual place under the logo.
93
+ container.appendChild(picker);
94
+ };
95
+
96
+ const ready = (run) =>
97
+ document.readyState === "loading"
98
+ ? document.addEventListener("DOMContentLoaded", run)
99
+ : run();
100
+
101
+ fetch(new URL("versions.json", moduleRoot))
102
+ .then((response) => (response.ok ? response.json() : null))
103
+ .then((versions) => {
104
+ // Nothing to pick between — no manifest (a local `plesty docs serve`
105
+ // preview), or a module with no releases yet, whose manifest holds only
106
+ // `latest`. A dropdown with one entry is furniture, not navigation.
107
+ if (Array.isArray(versions) && versions.length > 1) ready(() => render(versions));
108
+ })
109
+ .catch(() => {});
110
+ })();
@@ -4,9 +4,8 @@
4
4
  """Entry point for running the {{ module_name | replace('_', ' ') }} {{ project_type }} as a standalone process."""
5
5
  {% if project_type == 'device' %}
6
6
  import argparse
7
- import asyncio
8
7
 
9
- from plesty.lib.service import build_server
8
+ from plesty.lib.service import build_server, run_server
10
9
  from plesty.lib.utils import EnvSettings
11
10
 
12
11
  from .device import Device
@@ -64,17 +63,21 @@ async def main() -> None: # pragma: no cover
64
63
 
65
64
  if __name__ == "__main__": # pragma: no cover
66
65
  try:
67
- asyncio.run(main())
66
+ # Not a bare asyncio.run: pyzmq needs a loop with add_reader, which on
67
+ # Windows is the selector loop and not the default one. run_server asks
68
+ # for it explicitly — the loop policy that used to arrange it process-wide
69
+ # is deprecated in Python 3.14 and gone in 3.16.
70
+ run_server(main())
68
71
  except KeyboardInterrupt:
69
72
  print("Server stopped by user.")
70
73
  {% elif project_type == 'experiment' %}
71
74
  import argparse
72
- import asyncio
73
75
  from pathlib import Path
74
76
  from typing import Optional
75
77
 
76
78
  from plesty.lib.device.composite_device import CompositeDevice
77
79
  from plesty.lib.experiment import RigNotReadyError, connect_rig
80
+ from plesty.lib.service import run_server
78
81
  from plesty.lib.utils.config import load_yaml
79
82
 
80
83
  from .experiment import Experiment
@@ -200,7 +203,10 @@ async def main() -> None: # pragma: no cover
200
203
 
201
204
  if __name__ == "__main__": # pragma: no cover
202
205
  try:
203
- asyncio.run(main())
206
+ # The rig's clients are pyzmq sockets, so this run needs the same loop
207
+ # a device server does — see run_server. A bare asyncio.run gets the
208
+ # Windows default one, which has no add_reader, from Python 3.16 on.
209
+ run_server(main())
204
210
  except RigNotReadyError as error:
205
211
  # The diagnosis is already out (connect_rig printed and logged it);
206
212
  # this only says how the process ended, and with a non-zero status.