plesty-lib 0.3.5.dev1__tar.gz → 0.3.5.dev3__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 (163) hide show
  1. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/CHANGELOG.md +129 -0
  2. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/PKG-INFO +1 -1
  3. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/base_device.md +6 -1
  4. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/composite_device.md +51 -1
  5. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/test_helper.md +25 -1
  6. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/experiment.md +47 -0
  7. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/gl-secret-detection-report.json +5 -5
  8. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/composite_device.py +400 -26
  9. plesty_lib-0.3.5.dev3/plesty/lib/device/device_utils.py +231 -0
  10. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/funcs.py +18 -2
  11. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/experiment/__init__.py +3 -0
  12. plesty_lib-0.3.5.dev3/plesty/lib/experiment/assets/preflight.yaml +20 -0
  13. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/experiment/base_experiment.py +43 -3
  14. plesty_lib-0.3.5.dev3/plesty/lib/experiment/preflight.py +168 -0
  15. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/service/tcp_ip_client.py +25 -4
  16. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/client_field_test.py +88 -9
  17. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/field_test.py +330 -24
  18. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/report_artifact.py +7 -0
  19. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/__init__.py +17 -1
  20. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/apt.py +9 -0
  21. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/serial.py +5 -0
  22. plesty_lib-0.3.5.dev3/plesty/lib/traffic/serial_ports.py +86 -0
  23. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/qt/plot.py +2 -2
  24. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/qt/shell.py +1 -1
  25. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/logger.py +54 -20
  26. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_client_field_test.py +123 -0
  27. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_composite_config.py +280 -4
  28. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_base.py +4 -0
  29. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_funcs.py +64 -1
  30. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_experiment.py +30 -0
  31. plesty_lib-0.3.5.dev3/tests/test_experiment_preflight.py +114 -0
  32. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_field_test.py +250 -2
  33. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_report_artifact.py +7 -0
  34. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_traffic_drivers.py +101 -0
  35. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_utils.py +36 -0
  36. plesty_lib-0.3.5.dev1/plesty/lib/device/device_utils.py +0 -108
  37. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/.gitignore +0 -0
  38. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/.gitlab-ci.yml +0 -0
  39. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/LICENSE +0 -0
  40. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/LICENSES/LGPL-3.0-or-later.txt +0 -0
  41. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/README.md +0 -0
  42. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/REUSE.toml +0 -0
  43. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/analyzer.md +0 -0
  44. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/api_reference.md +0 -0
  45. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/architecture.md +0 -0
  46. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/contributing.md +0 -0
  47. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/data_schemas.md +0 -0
  48. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/cmd_solver.md +0 -0
  49. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/error_handling.md +0 -0
  50. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/func_system.md +0 -0
  51. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/index.md +0 -0
  52. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/logging_system.md +0 -0
  53. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/param_system.md +0 -0
  54. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/device/traffic_manager.md +0 -0
  55. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/imgs/device_layers.svg +0 -0
  56. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/imgs/device_standard.svg +0 -0
  57. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/imgs/plesty_framework.svg +0 -0
  58. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/index.md +0 -0
  59. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/monitor.md +0 -0
  60. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/quickstart.md +0 -0
  61. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/toc.yaml +0 -0
  62. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/docs/ui.md +0 -0
  63. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/__init__.py +0 -0
  64. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/analyzer/__init__.py +0 -0
  65. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/analyzer/base_analyzer.py +0 -0
  66. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/__init__.py +0 -0
  67. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/array.py +0 -0
  68. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/ctype_manager.py +0 -0
  69. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/io.py +0 -0
  70. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/table.py +0 -0
  71. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/types.py +0 -0
  72. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/data/units.py +0 -0
  73. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/__init__.py +0 -0
  74. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/async_wrapper.py +0 -0
  75. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/base_apt_device.py +0 -0
  76. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/base_device_sync.py +0 -0
  77. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/base_tcp_scpi_device.py +0 -0
  78. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/base_visa_scpi_device.py +0 -0
  79. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/doc.py +0 -0
  80. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/params.py +0 -0
  81. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/device/telemetry.py +0 -0
  82. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/experiment/journal.py +0 -0
  83. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/experiment/runs.py +0 -0
  84. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/experiment/schedule.py +0 -0
  85. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/monitor/__init__.py +0 -0
  86. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/monitor/assets/viz.yaml +0 -0
  87. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/monitor/base_monitor.py +0 -0
  88. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/monitor/sources.py +0 -0
  89. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/monitor/viz.py +0 -0
  90. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/service/__init__.py +0 -0
  91. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/service/resource_manager.py +0 -0
  92. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/service/tcp_ip_server.py +0 -0
  93. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/sim/__init__.py +0 -0
  94. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/sim/apt.py +0 -0
  95. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/sim/data_generator.py +0 -0
  96. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/sim/demo_device.py +0 -0
  97. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/solver/__init__.py +0 -0
  98. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/solver/iceblock.py +0 -0
  99. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/solver/scpi.py +0 -0
  100. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/__init__.py +0 -0
  101. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/analyzer_pipeline.py +0 -0
  102. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/device_func_system.py +0 -0
  103. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/device_param_system.py +0 -0
  104. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/device_pipeline.py +0 -0
  105. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/experiment_pipeline.py +0 -0
  106. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/field_test_concurrency.py +0 -0
  107. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/grouped_param_keys.py +0 -0
  108. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/resource_allocation.py +0 -0
  109. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/schema_params.py +0 -0
  110. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/test/schema_refresh.py +0 -0
  111. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/apt_protocol.py +0 -0
  112. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/tcp_ip.py +0 -0
  113. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/usb_utils.py +0 -0
  114. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/utils.py +0 -0
  115. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/traffic/visa.py +0 -0
  116. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/__init__.py +0 -0
  117. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/assets/palette.json +0 -0
  118. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/assets/shell.qss +0 -0
  119. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/monitor_panel.py +0 -0
  120. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/panel.py +0 -0
  121. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/qt/__init__.py +0 -0
  122. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/qt/app.py +0 -0
  123. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/qt/recorder.py +0 -0
  124. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/ui/theme.py +0 -0
  125. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/__init__.py +0 -0
  126. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/config.py +0 -0
  127. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/dll_utils.py +0 -0
  128. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/error_utils.py +0 -0
  129. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/registry.py +0 -0
  130. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/plesty/lib/utils/settings.py +0 -0
  131. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/pyproject.toml +0 -0
  132. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_analyzer.py +0 -0
  133. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_analyzer_pipeline.py +0 -0
  134. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_async_wrapper_threading.py +0 -0
  135. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_config.py +0 -0
  136. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_data_array.py +0 -0
  137. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_data_io_hdf5.py +0 -0
  138. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_data_types.py +0 -0
  139. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_demo_device.py +0 -0
  140. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_apt.py +0 -0
  141. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_data_path.py +0 -0
  142. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_params.py +0 -0
  143. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_pipeline.py +0 -0
  144. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_scpi.py +0 -0
  145. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_device_telemetry.py +0 -0
  146. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_experiment_pipeline.py +0 -0
  147. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_experiment_runs.py +0 -0
  148. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_misc.py +0 -0
  149. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_monitor.py +0 -0
  150. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_schema_params.py +0 -0
  151. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_schema_refresh.py +0 -0
  152. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_service_manager.py +0 -0
  153. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_settings.py +0 -0
  154. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_shipped_imports.py +0 -0
  155. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_sim.py +0 -0
  156. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_solver.py +0 -0
  157. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_tcp_ip_resources.py +0 -0
  158. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_traffic_orphan.py +0 -0
  159. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_traffic_utils.py +0 -0
  160. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_ui.py +0 -0
  161. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_visa_discovery.py +0 -0
  162. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/tests/test_viz.py +0 -0
  163. {plesty_lib-0.3.5.dev1 → plesty_lib-0.3.5.dev3}/uv.lock +0 -0
@@ -1,5 +1,134 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.5.dev3 — 2026-08-18
4
+
5
+ Development pre-release for field-testing operation kinds (#39): operations
6
+ declare what they do and the field test drives a motion out and back, a
7
+ lifecycle op once, a control op at rest; plus the pol-pl rig findings #35
8
+ and #38. Same line as 0.3.5.dev2 — the final release is 0.4.0.
9
+
10
+ - **Operations declare what they do, and the field test drives them by
11
+ kind** (#39). `@expose_to_api(kind=…)` and a schema `kind` block record
12
+ one of `read | motion | acquire | lifecycle | control | configure`
13
+ (`plesty.lib.device.device_utils.OpKind`; `FuncDoc.kind`, the describe
14
+ payload and `function_docs()` carry it). `FieldTestPipeline` acts on it
15
+ instead of asking the developer per operation: a `motion` op is driven
16
+ one `step` out from what its `position_key` reports and put back (the
17
+ functions gate checks it came back; the stability gate repeats out and
18
+ back), a `lifecycle` op runs once when allow-listed and never on repeat,
19
+ a `control` op once at rest after the motions (skipped when nothing
20
+ moved), a `configure` op is never called. `op_kinds={…}` overrides the
21
+ module's declaration. On plesty-k10cr1 the operation every pol-pl row
22
+ calls could not be tested at all: `move_absolute(position)` was called
23
+ bare and failed on the missing argument.
24
+ - **The client tier drives operations by kind too.** `ClientFieldTest`
25
+ takes `op_kinds=` (the generated `field_test_client.py` passes the host
26
+ tier's): a motion is queried through `position_key`, moved one step out
27
+ and back over the wire as many pairs as the host tier's call count, a
28
+ lifecycle op once, a control op once after the motions, a configure op
29
+ never; a motion without `position_key`/`target`/`step` fails the gate.
30
+ - **A provocation that breaks is a failed gate, not a forced timeout.**
31
+ The drain and recovery gates called the slow op with the payload as one
32
+ positional dict (`getattr(device, slow_op)(dict(payload))`), so an
33
+ `@expose_to_api` slow op raised `TypeError` before any transport
34
+ traffic — and both gates counted the exception as an abandoned call and
35
+ passed. The call now goes through the operation's own call form, only a
36
+ timeout-class error counts as forced, and a motion slow op is a delta
37
+ from where the part is, moved back afterwards (`motion_restored` in the
38
+ evidence). The slow-op payload is prepared before the timeout is
39
+ shortened, so reading the start position cannot itself time out.
40
+
41
+ - **A serial port that is held says so** (#35). pyserial reports a wrong
42
+ port name and a port open in another process with the same
43
+ `SerialException`, and on the K10CR1 rig `Access is denied` on the right
44
+ `COM4` — Kinesis had the stage loaded — cost the session to driver and
45
+ VCP checks. `TrafficManager.open` now asks the subclass to explain a
46
+ failure (`_explain_open_error`, `None` by default) and appends the answer
47
+ to the raised message. `AptTrafficManager` and `SerialTrafficManager`
48
+ answer for EACCES/EBUSY: on Windows and for EBUSY, the port exists but is
49
+ open elsewhere — close Kinesis/APT (a loaded stage locks the FTDI device
50
+ via D2XX and blocks the VCP), stray interpreters, serial terminals, replug
51
+ to drop a stale handle; on Linux EACCES, add the user to `dialout`. The
52
+ port's `list_ports` description and hwid follow, so the user sees at once
53
+ whether the address is the intended device. Windows sets no `errno` on
54
+ the exception, so the number is read out of the message text. Other
55
+ failures (`ENOENT`, timeouts) are reported as before.
56
+ - `ModuleInfo.from_project` and the field-test version lookup no longer
57
+ answer for whatever repository `GIT_DIR` names: a git hook exports it for
58
+ the repository being pushed, and every `git -C <root>` in `report_artifact`
59
+ followed it — a field test run from a pre-push hook reported the hook's
60
+ remote and version, and the pre-push suite failed on the tmp-dir module
61
+ test. `GIT_*` is scrubbed from the subprocess environment.
62
+
63
+ - **Client-tier `functions` gate no longer demands a dict of every
64
+ operation** (#38). It required `isinstance(answer, dict)` for each
65
+ allow-listed operation, while the host tier asks that only of
66
+ schema-declared ones; on lightfield_spectrometer `acquire` (`None`) and
67
+ `get_recent_file` (`str`) came back 21 times each with no failure and
68
+ the gate still failed. A call that returns is the pass; the answer's
69
+ type is recorded in the evidence.
70
+
71
+ ## 0.3.5.dev2 — 2026-08-17
72
+
73
+ Development pre-release for field-testing the rig start-up (#36, #37):
74
+ bounded, visible connects, the preflight, and default logging. Same line as
75
+ 0.3.5.dev1 — the final release is 0.4.0.
76
+
77
+ - **A run starts only on a rig that is really there** (pol-pl field
78
+ finding: a sweep went on with the powermeter server absent).
79
+ `CompositeDevice.preflight()` checks that every remote sub-device exposes
80
+ the methods listed under the new `requires` config key and answers
81
+ `identity()`; `Experiment.setup()` runs it and raises `RigNotReadyError`
82
+ on any problem. `plesty.lib.experiment.connect_rig(build, optional=…)`
83
+ is the start-up for every experiment's `__main__`: build, preflight, and
84
+ on a failure print which device, at which address, from which variable,
85
+ then ask `[r]etry / [a]bort / [s]kip` (skip only for optional
86
+ sub-devices, returned as `skipped`); non-interactive processes get the
87
+ diagnosis logged and `RigNotReadyError`. Text in
88
+ `experiment/assets/preflight.yaml`.
89
+ - **A composite that is given up closes its clients.** New
90
+ `CompositeDevice.release()` closes every remote client on its owning
91
+ thread (server-side disconnect with a 1 s window, linger 0) and stops the
92
+ threads; `disconnect_all()` does the same for remote clients, and a first
93
+ connect that fails closes the clients that did connect. A client left to
94
+ the garbage collector blocked the interpreter in its ZMQ context's
95
+ `__del__` — seen mid-run after `connect_rig` rebuilt a rig without the
96
+ powermeter. `connect_rig` releases a rig it gives up.
97
+ - **A first connect to a dead server now returns.** The client's connect
98
+ failure path closed its DEALER socket with the default infinite LINGER,
99
+ so `ctx.term()` blocked forever on the undelivered handshake — a rig built
100
+ against one absent server hung with no output, and no attempt ladder or
101
+ deadline could bound it. `_DeviceTCPIPClient` now drops undelivered
102
+ messages (linger 0) when a connect fails and on `close()`.
103
+ `CompositeDevice` also resolves every address before starting any connect
104
+ (a half-configured `.env` raises `ValueError` without touching a server)
105
+ and stops its worker threads when a first connect fails.
106
+ - **`CompositeDevice` signals sub-device connection state and bounds the first
107
+ connect** (#36). Each sub-device carries a `ConnectionState`
108
+ (`CONNECTING → CONNECTED → DEGRADED → DISCONNECTED`), read with
109
+ `status()` / `status(dev)` and observed with `on_status_change(callback)`;
110
+ every transition is logged (WARNING for `DEGRADED` / `DISCONNECTED`).
111
+ New config key `connect_deadline_s` (default 10 s): a server that does not
112
+ answer within it raises `DeviceUnreachableError(dev, address, elapsed_s)`
113
+ from the constructor instead of running attempts × timeout × backoff
114
+ (~40 s per dead server before). Under thread affinity the first connects
115
+ run in parallel on the sub-devices' own threads. `reconnect()` reuses the
116
+ sub-device's configured `timeout_ms` / `attempts` / `backoff_s` instead of
117
+ the 50 s defaults; a call timeout marks the sub-device `DEGRADED` before
118
+ the rebuild. `_DeviceTCPIPClient.set_default_timeout(ms)` added.
119
+ - **Default logging for experiments and composites** (#37): nothing is
120
+ silent anymore without module code. `Experiment` logs
121
+ `Run <id>: N step(s) (k already completed), run dir …`, `Setup: …`,
122
+ `Step i/N <id> (<op>)` / `Step i/N done in x s`, `Teardown: …`, and
123
+ `Run … completed (N steps) in x s`. `CompositeDevice` logs
124
+ `Connecting <dev> @ <addr>` / `Connected <dev> @ <addr> in x s`, each failed
125
+ attempt with the retry delay, `Reconnecting <dev> @ <addr>`, and the timeout
126
+ a retried call ran under. New `plesty.lib.utils.logger.ensure_logging()`
127
+ (root level + one console sink, idempotent) is called by both constructors;
128
+ `setup_logging(app)` builds on it and adds the per-process log file — the
129
+ first `app` names the file, so a rig built before its experiment no longer
130
+ steals the file name.
131
+
3
132
  ## 0.3.5.dev1 — 2026-08-17
4
133
 
5
134
  Development pre-release for field-testing the run convention (#34); the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: plesty-lib
3
- Version: 0.3.5.dev1
3
+ Version: 0.3.5.dev3
4
4
  Summary: A library for the Plesty ecosystem.
5
5
  Author: Plesty Development Team
6
6
  Author-email: Yunshuang Yuan <yunshuang.yuan@fkp.uni-hannover.de>
@@ -261,7 +261,12 @@ model.standard_methods # the common device API (connect/write/query/reset/...)
261
261
  `functions` unifies the two ways a device gains operations: schema-registered
262
262
  operations (`source="schema"`) and `@expose_to_api` methods
263
263
  (`source="custom"`); framework plumbing is kept out and surfaced separately
264
- under `standard_methods` (`source="standard"`). Per-parameter and return
264
+ under `standard_methods` (`source="standard"`). Each `FuncDoc` also carries
265
+ its `kind` — what the operation does to the world (`read`, `motion`,
266
+ `acquire`, `lifecycle`, `control`, `configure`), declared with
267
+ `@expose_to_api(kind=..., position_key=..., step=...)` or a `"kind"` block in
268
+ `schema_func.json` and used by the field test to drive the operation
269
+ (see *test_helper*, "Kinds"). Per-parameter and return
265
270
  descriptions are parsed from Google-style docstring `Args:`/`Returns:`
266
271
  sections — write those in your device methods and the generated documentation
267
272
  inherits them.
@@ -60,7 +60,9 @@ composite = CompositeDevice(
60
60
  |---|---|
61
61
  | `address` | Explicit ZMQ address, skipping the environment |
62
62
  | `env` | Environment variable to read, overriding the derived name |
63
- | `timeout_ms`, `attempts`, `backoff_s` | Forwarded to `connect_client` |
63
+ | `timeout_ms`, `attempts`, `backoff_s` | Forwarded to `connect_client` (first connect and every reconnect) |
64
+ | `connect_deadline_s` | Budget for the *first* connect (default 10 s); past it the constructor raises `DeviceUnreachableError` |
65
+ | `requires` | Remote methods the server must expose; checked by `preflight()` (a wrong address connects fine but is not this device) |
64
66
 
65
67
  The address comes from `address` if given, else the environment; an unset
66
68
  variable raises a `ValueError` naming the variable to set.
@@ -106,10 +108,48 @@ explicitly.
106
108
  `connect_client(address)` is the module-level builder used for the rebuilds:
107
109
  `build_client` plus a connection-handshake retry with linear backoff, which
108
110
  matters when several clients open back-to-back against the same server.
111
+ `reconnect(dev)` rebuilds with the sub-device's own `timeout_ms` /
112
+ `attempts` / `backoff_s`, not the defaults.
109
113
 
110
114
  Every multi-device composite gets this robustness for free instead of
111
115
  re-implementing it per module.
112
116
 
117
+ ## Connection state and a bounded first connect
118
+
119
+ The composite is never silent about its servers. Constructing it logs one
120
+ line per sub-device (`Connecting hwp @ tcp://… (timeout 5000 ms) …`,
121
+ `Connected hwp @ tcp://… in 0.4 s`, or `Connect to tcp://… failed, attempt
122
+ 1/4: … ; retrying in 2 s`) and a summary (`PolPlRig ready in 1.2 s:
123
+ hwp=tcp://…, spec=tcp://…`) — the console sink is switched on by the
124
+ composite itself (`ensure_logging()`), so this shows even when the composite
125
+ is built before the experiment that would configure logging.
126
+
127
+ The **first connect is bounded**: each configured sub-device gets
128
+ `connect_deadline_s` (default 10 s); a server that does not answer within it
129
+ raises `DeviceUnreachableError(dev, address, elapsed_s)` from the constructor
130
+ instead of grinding through attempts × timeout × backoff. Under thread
131
+ affinity the first connects run in parallel on the sub-devices' own threads,
132
+ so three dead servers cost one deadline, not three. Reconnects during a run
133
+ keep the attempt ladder — a server busy finishing a long move must be waited
134
+ for, not declared dead.
135
+
136
+ Each sub-device carries a `ConnectionState` — `CONNECTING → CONNECTED →
137
+ DEGRADED` (a call timed out, the client is being rebuilt) `→ DISCONNECTED`
138
+ (connect/reconnect failed, or `disconnect_all()`):
139
+
140
+ ```python
141
+ composite.status() # {"hwp": ConnectionState.CONNECTED, "spec": ...}
142
+ composite.status("hwp") # ConnectionState.CONNECTED
143
+
144
+ @composite.on_status_change
145
+ def _show(dev, old, new, detail):
146
+ ui.set_led(dev, new) # runs on the thread that changed the state
147
+ ```
148
+
149
+ Every transition is logged (INFO, or WARNING for `DEGRADED` /
150
+ `DISCONNECTED`) whether or not anyone subscribes; a subscriber that raises
151
+ is logged and ignored — a broken UI hook must not break the rig.
152
+
113
153
  ## One thread per sub-device
114
154
 
115
155
  ZMQ sockets are not thread-safe, and even migrating one between threads is
@@ -161,6 +201,16 @@ for a data directory either fails or means nothing:
161
201
  composite.set_data_path(frames_dir, devices=["spec"])
162
202
  ```
163
203
 
204
+ ## Preflight
205
+
206
+ `preflight()` proves each sub-device is the device it is supposed to be and
207
+ answers: a remote sub-device must expose every method in its `requires`
208
+ list and its `identity()` must reply; a local one must have the required
209
+ attributes. It returns `{name: problem}` for the failures (empty when the
210
+ rig is ready) and logs one line per sub-device either way. `Experiment`
211
+ runs it in `setup()`; `plesty.lib.experiment.connect_rig` runs it at
212
+ start-up and asks the operator what to do (see the experiment guide).
213
+
164
214
  ## Interplay with experiments
165
215
 
166
216
  `Experiment` accepts a `CompositeDevice` at construction; its default
@@ -455,7 +455,7 @@ if __name__ == "__main__":
455
455
  | 2 | `test_connect_lifecycle` | connect / identity / `check_errors` / disconnect / reconnect, each timed; identity stable across the reconnect |
456
456
  | 3 | `test_param_roundtrip` | every writable parameter round-trips; the value read back is written back, so device state is unchanged |
457
457
  | 4 | `test_param_constraints` | hardware MIN/MAX and option sets against the schema envelope — a schema wider than the hardware fails |
458
- | 5 | `test_functions` | every operation on the safe allow-list returns a `dict` |
458
+ | 5 | `test_functions` | every operation on the safe allow-list can be called — by kind: a motion one step out and back, a lifecycle op once, a control op once at rest after the motions |
459
459
  | 6 | `test_buffer_drain` | a query abandoned mid-flight must not leave its answer for the next query to consume |
460
460
  | 7 | `test_error_recovery` | the device recovers from a forced timeout, and the first command after a reconnect succeeds |
461
461
  | 8 | `test_stability` | every queryable parameter and safe op repeated `repetitions` times to collect the statistics |
@@ -464,6 +464,29 @@ if __name__ == "__main__":
464
464
  (a `"safe": true` entry in `schema_func.json` is forwarded automatically). Without an allow-list
465
465
  the function gate is skipped — no motion or emission operation ever runs unattended.
466
466
 
467
+ **Kinds.** *How* an allow-listed operation is called comes from what the module declares it
468
+ does — `@expose_to_api(kind=...)` or a `"kind"` block in `schema_func.json`, read back as
469
+ `OpKind` (`plesty.lib.device.device_utils`):
470
+
471
+ | kind | the gates |
472
+ |---|---|
473
+ | `read` (default) | called with its declared payload, repeated as it is |
474
+ | `motion` | driven from what `position_key` reports: `target = start ± step` (`relative=True` sends the step itself), then put back; the functions gate fails if it does not come back, the stability gate repeats out-and-back; a motion `slow_op` is a delta from the start and is moved back after the provocation |
475
+ | `acquire` | as `read`, but recorded as acting |
476
+ | `lifecycle` (home, reset) | once, when allow-listed; never repeated |
477
+ | `control` (stop, abort) | once, at rest, after the motions; skipped when nothing moved |
478
+ | `configure` | never called — the round-trip gate covers settings |
479
+
480
+ ```python
481
+ @expose_to_api(kind="motion", position_key="MO.Position", step=1.0)
482
+ def move_absolute(self, position: float) -> float: ...
483
+ ```
484
+
485
+ `op_kinds={"move_absolute": OpKind("motion", position_key="MO.Position", step=1.0)}` on the
486
+ pipeline overrides the declaration wholesale. A motion without `position_key`, `target` (the
487
+ first required argument by default) or `step` fails the gate rather than guessing how far a part
488
+ may move.
489
+
467
490
  **Key parameters:**
468
491
 
469
492
  | Parameter | Default | Description |
@@ -471,6 +494,7 @@ the function gate is skipped — no motion or emission operation ever runs unatt
471
494
  | `repetitions` | `20` | Per-command repeat count in the stability gate |
472
495
  | `report_path` | `None` | Base path; `run()` writes `<base>.jsonl` (appended) and `<base>.md` |
473
496
  | `safe_ops` / `unsafe_ops` | `None` | Operation allow-list and opt-out list |
497
+ | `op_kinds` | `None` | Per-operation `OpKind` overriding the module's declaration |
474
498
  | `op_payloads` | `None` | Explicit payload per operation instead of a generated one |
475
499
  | `ignore_keys` | `None` | Config keys excluded from every parameter gate |
476
500
  | `drain_probe` | auto | The two distinguishable keys used by the drain and recovery gates |
@@ -84,6 +84,53 @@ Step methods may be sync or async; results are awaited automatically. Each
84
84
  step is retried (`max_retries`, `retry_sleep` constructor arguments) before
85
85
  the run aborts.
86
86
 
87
+ ## Starting on a rig that is really there
88
+
89
+ An experiment's `__main__` builds the rig with `connect_rig` — the same
90
+ start for every experiment: connect the servers, run the composite's
91
+ `preflight()` (each sub-device exposes the methods its configuration
92
+ `requires` and answers `identity()`), and if anything is missing, tell the
93
+ operator which device, at which address, from which variable — and ask:
94
+
95
+ ```python
96
+ from plesty.lib.experiment import connect_rig
97
+
98
+ rig, skipped = connect_rig(
99
+ lambda skip: PolPlRig(with_powermeter="pm" not in skip),
100
+ optional={"pm"}, # the experiment can run without it
101
+ )
102
+ ```
103
+
104
+ ```text
105
+ The rig is not ready — 1 device(s) failed the preflight:
106
+ - pm: no answer from tcp://127.0.0.1:5557: TimeoutError('Server did not respond in time')
107
+ address tcp://127.0.0.1:5557 (from PM_ADDRESS in .env / the environment)
108
+ Check that the pm server(s) are running and that PM_ADDRESS point at them
109
+ (a wrong port connects fine but is not the expected device).
110
+ [r] retry (after fixing .env or starting the server) [a] abort [s] continue without pm
111
+ >
112
+ ```
113
+
114
+ `r` rebuilds the rig (an edited `.env` or a freshly started server is picked
115
+ up), `a` raises `RigNotReadyError`, `s` — offered only when every failing
116
+ sub-device is in `optional` — returns the rig without them and names them in
117
+ `skipped`, so the experiment can drop the matching part of its plan. Without
118
+ a terminal on stdin nothing waits: the diagnosis is logged and
119
+ `RigNotReadyError` raised.
120
+
121
+ The check also runs inside `Experiment.setup()`: a run never starts on a
122
+ composite whose `preflight()` reports a problem, whether or not `connect_rig`
123
+ was used. Declare what each sub-device must be able to do in its
124
+ configuration — that is what turns "the port answers" into "it is the
125
+ stage":
126
+
127
+ ```yaml
128
+ devices:
129
+ hwp: {timeout_ms: 5000, requires: [home_stage, move_absolute, get_position]}
130
+ spec: {timeout_ms: 5000, requires: [acquire, get_recent_file, write, query]}
131
+ pm: {timeout_ms: 5000, requires: [measure_power, write]}
132
+ ```
133
+
87
134
  ## Data on a shared disk
88
135
 
89
136
  Blobs a device writes itself (spectrometer frames, camera images) never
@@ -19,19 +19,19 @@
19
19
  "version": "8.30.1"
20
20
  },
21
21
  "type": "secret_detection",
22
- "start_time": "2026-08-17T12:45:23",
23
- "end_time": "2026-08-17T12:45:24",
22
+ "start_time": "2026-08-18T13:39:46",
23
+ "end_time": "2026-08-18T13:39:47",
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.469797198,
29
+ "time_s": 0.527730295,
30
30
  "exit_code": 0,
31
31
  "git_strategy": "FetchShallow",
32
- "repo_size_kb": 685,
32
+ "repo_size_kb": 709,
33
33
  "commit_count": 1,
34
- "bytes_scanned": 314,
34
+ "bytes_scanned": 317,
35
35
  "pipeline_type": "Tag"
36
36
  },
37
37
  {