bec-lib 3.150.2__tar.gz → 3.151.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 (154) hide show
  1. {bec_lib-3.150.2 → bec_lib-3.151.1}/PKG-INFO +1 -1
  2. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/bl_state_manager.py +22 -1
  3. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/bl_states.py +0 -27
  4. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/messages.py +2 -2
  5. {bec_lib-3.150.2 → bec_lib-3.151.1}/pyproject.toml +1 -1
  6. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_beamline_states.py +128 -58
  7. {bec_lib-3.150.2 → bec_lib-3.151.1}/.gitignore +0 -0
  8. {bec_lib-3.150.2 → bec_lib-3.151.1}/README.md +0 -0
  9. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/__init__.py +0 -0
  10. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/_print_versions.py +0 -0
  11. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/acl_login.py +0 -0
  12. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/actors.py +0 -0
  13. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/alarm_handler.py +0 -0
  14. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/atlas_models.py +0 -0
  15. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/bec_errors.py +0 -0
  16. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/bec_service.py +0 -0
  17. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/bec_yaml_loader.py +0 -0
  18. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/builtin_actor_hli.py +0 -0
  19. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/callback_handler.py +0 -0
  20. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/channel_monitor.py +0 -0
  21. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/client.py +0 -0
  22. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/codecs.py +0 -0
  23. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/config_helper.py +0 -0
  24. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/config_values.py +0 -0
  25. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/configs/__init__.py +0 -0
  26. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/configs/demo_config.yaml +0 -0
  27. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/configs/openapi_schema.json +0 -0
  28. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/connector.py +0 -0
  29. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/dap_plugin_objects.py +0 -0
  30. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/dap_plugins.py +0 -0
  31. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/device.py +0 -0
  32. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/devicemanager.py +0 -0
  33. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/endpoints.py +0 -0
  34. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/file_utils.py +0 -0
  35. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/live_scan_data.py +0 -0
  36. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/lmfit_serializer.py +0 -0
  37. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/logger.py +0 -0
  38. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/macro_update_handler.py +0 -0
  39. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/messaging_hooks.py +0 -0
  40. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/messaging_services.py +0 -0
  41. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/metadata_schema.py +0 -0
  42. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/numpy_encoder.py +0 -0
  43. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/observer.py +0 -0
  44. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/pdf_writer.py +0 -0
  45. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/plugin_helper.py +0 -0
  46. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/procedures/__init__.py +0 -0
  47. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/procedures/helper.py +0 -0
  48. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/procedures/hli.py +0 -0
  49. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/procedures/status.py +0 -0
  50. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/queue_items.py +0 -0
  51. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/redis_connector/__init__.py +0 -0
  52. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/redis_connector/constants.py +0 -0
  53. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/redis_connector/hli.py +0 -0
  54. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/redis_connector/managed_redis_connection.py +0 -0
  55. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/redis_connector/streams.py +0 -0
  56. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/redis_connector/validation.py +0 -0
  57. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/request_context.py +0 -0
  58. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/request_items.py +0 -0
  59. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/scan_args.py +0 -0
  60. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/scan_data_container.py +0 -0
  61. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/scan_history.py +0 -0
  62. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/scan_input_validator.py +0 -0
  63. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/scan_items.py +0 -0
  64. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/scan_manager.py +0 -0
  65. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/scan_number_container.py +0 -0
  66. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/scan_repeat.py +0 -0
  67. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/scan_report.py +0 -0
  68. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/scans.py +0 -0
  69. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/scibec_validator.py +0 -0
  70. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/script_executor.py +0 -0
  71. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/serialization.py +0 -0
  72. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/serialization_registry.py +0 -0
  73. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/service_config.py +0 -0
  74. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/signature_serializer.py +0 -0
  75. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/tests/__init__.py +0 -0
  76. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/tests/fixtures.py +0 -0
  77. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/tests/test_config.yaml +0 -0
  78. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/tests/test_service_config.yaml +0 -0
  79. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/tests/utils.py +0 -0
  80. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/user_macros.py +0 -0
  81. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/__init__.py +0 -0
  82. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/copier_jinja_filters.py +0 -0
  83. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/import_utils.py +0 -0
  84. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/json_extended.py +0 -0
  85. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/plugin_manager/__init__.py +0 -0
  86. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/plugin_manager/_constants.py +0 -0
  87. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/plugin_manager/_util.py +0 -0
  88. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/plugin_manager/create/__init__.py +0 -0
  89. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/plugin_manager/create/device.py +0 -0
  90. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/plugin_manager/create/scan.py +0 -0
  91. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/plugin_manager/create/templates/v4_scan.py.jinja +0 -0
  92. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/plugin_manager/main.py +0 -0
  93. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/proxy.py +0 -0
  94. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/pydantic_pretty_print.py +0 -0
  95. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/rpc_utils.py +0 -0
  96. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/scan_utils.py +0 -0
  97. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/threading_utils.py +0 -0
  98. {bec_lib-3.150.2 → bec_lib-3.151.1}/bec_lib/utils/user_acls_test.py +0 -0
  99. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/__init__.py +0 -0
  100. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/_additional_for_signature_serializer_test.py +0 -0
  101. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/conftest.py +0 -0
  102. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_acl_login.py +0 -0
  103. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_alarm_handler.py +0 -0
  104. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_atlas_models.py +0 -0
  105. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_bec_logger.py +0 -0
  106. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_bec_messages.py +0 -0
  107. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_bec_service.py +0 -0
  108. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_callback_handler.py +0 -0
  109. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_channel_monitor.py +0 -0
  110. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_client.py +0 -0
  111. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_config_helper.py +0 -0
  112. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_core_utils.py +0 -0
  113. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_dap_plugins.py +0 -0
  114. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_data_container.py +0 -0
  115. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_device_hashing.py +0 -0
  116. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_device_manager.py +0 -0
  117. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_devices.py +0 -0
  118. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_file_utils.py +0 -0
  119. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_hli_connector_fakeredis.py +0 -0
  120. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_hli_redis_connector.py +0 -0
  121. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_import_utils.py +0 -0
  122. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_jinja_plugins.py +0 -0
  123. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_lmfit_serializer.py +0 -0
  124. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_managed_config_values.py +0 -0
  125. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_managed_redis_connection.py +0 -0
  126. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_messaging_hooks.py +0 -0
  127. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_messaging_service.py +0 -0
  128. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_metadata_schema.py +0 -0
  129. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_observer.py +0 -0
  130. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_pdf_writer.py +0 -0
  131. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_plugin_helper.py +0 -0
  132. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_plugin_manager.py +0 -0
  133. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_plugin_system.py +0 -0
  134. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_pydantic_pretty_print.py +0 -0
  135. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_queue_items.py +0 -0
  136. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_redis_connector_fakeredis.py +0 -0
  137. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_scan_context.py +0 -0
  138. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_scan_data.py +0 -0
  139. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_scan_history.py +0 -0
  140. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_scan_items.py +0 -0
  141. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_scan_manager.py +0 -0
  142. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_scan_object.py +0 -0
  143. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_scan_repeat.py +0 -0
  144. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_scan_report.py +0 -0
  145. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_scibec_validator.py +0 -0
  146. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_script_executor.py +0 -0
  147. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_serializer.py +0 -0
  148. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_signature_serializer.py +0 -0
  149. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_signature_serializer_with_future_import.py +0 -0
  150. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_stream_subs.py +0 -0
  151. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_user_macros.py +0 -0
  152. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_utils.py +0 -0
  153. {bec_lib-3.150.2 → bec_lib-3.151.1}/tests/test_yaml_loader.py +0 -0
  154. {bec_lib-3.150.2 → bec_lib-3.151.1}/util_scripts/init_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_lib
3
- Version: 3.150.2
3
+ Version: 3.151.1
4
4
  Summary: BEC library
5
5
  Project-URL: Bug Tracker, https://github.com/bec-project/bec/issues
6
6
  Project-URL: Homepage, https://github.com/bec-project/bec
@@ -44,6 +44,26 @@ def build_signature_from_model(model: BaseModel, skip: set[str] | None = None) -
44
44
  return Signature(parameters)
45
45
 
46
46
 
47
+ def _state_class_for_state_type(state_type: str) -> type[bl_states.BeamlineState]:
48
+ """
49
+ Resolve and validate a serialized beamline state type.
50
+
51
+ The state type identifies the concrete runtime state class that will be
52
+ started by the scan server.
53
+ """
54
+ state_class = getattr(bl_states, state_type, None)
55
+ if (
56
+ not inspect.isclass(state_class)
57
+ or not issubclass(state_class, bl_states.BeamlineState)
58
+ or inspect.isabstract(state_class)
59
+ ):
60
+ raise ValueError(f"State type {state_type!r} is not a concrete beamline state.")
61
+ if getattr(state_class, "CONFIG_CLASS", None) is None:
62
+ raise ValueError(f"State type {state_type!r} does not define a config class.")
63
+
64
+ return state_class
65
+
66
+
47
67
  class BeamlineStateGet(TypedDict):
48
68
  """
49
69
  TypedDict for the return value of the get method of a beamline state client.
@@ -154,10 +174,11 @@ class BeamlineStateManager:
154
174
  self, state: messages.BeamlineStateConfig | bl_states.BeamlineStateConfig
155
175
  ) -> None:
156
176
  if isinstance(state, messages.BeamlineStateConfig):
157
- state_class = getattr(bl_states, state.state_type)
177
+ state_class = _state_class_for_state_type(state.state_type)
158
178
  model_cls = state_class.CONFIG_CLASS
159
179
  model_instance = model_cls(**state.parameters)
160
180
  else:
181
+ _state_class_for_state_type(state.state_type)
161
182
  model_instance = state
162
183
  instance = BeamlineStateClientBase(manager=self, state=model_instance)
163
184
  setattr(self, state.name, instance)
@@ -408,33 +408,6 @@ class DeviceBeamlineState(BeamlineState[D], Generic[D]):
408
408
  return self.evaluate(msg)
409
409
 
410
410
 
411
- class ShutterState(DeviceBeamlineState[DeviceStateConfig]):
412
- """
413
- A state that checks if the shutter is open.
414
-
415
- Example:
416
- shutter_state = DeviceStateConfig(
417
- name="shutter_open",
418
- device="shutter1",
419
- )
420
- bec.beamline_states.add(shutter_state)
421
- """
422
-
423
- CONFIG_CLASS = DeviceStateConfig
424
-
425
- def evaluate(
426
- self, msg: messages.DeviceMessage, *args, **kwargs
427
- ) -> messages.BeamlineStateMessage:
428
- val = msg.signals.get(self.signal_name, {}).get("value", "").lower()
429
- if val == "open":
430
- return messages.BeamlineStateMessage(
431
- name=self.config.name, status="valid", label="Shutter is open."
432
- )
433
- return messages.BeamlineStateMessage(
434
- name=self.config.name, status="invalid", label="Shutter is closed."
435
- )
436
-
437
-
438
411
  class DeviceWithinLimitsState(DeviceBeamlineState[DeviceWithinLimitsStateConfig]):
439
412
  """
440
413
  A state that checks if a device signal is within limits.
@@ -2093,7 +2093,7 @@ class FeedbackMessage(BECMessage):
2093
2093
 
2094
2094
  Args:
2095
2095
  feedback (str): User feedback
2096
- rating (int): User rating (1-5)
2096
+ rating (int): User rating (0-5)
2097
2097
  feedback_type (Literal["bug_report", "feature_request", "general_feedback"]): Type of feedback. Defaults to "general_feedback"
2098
2098
  contact (str | None): User contact information, if they want to be contacted regarding their feedback
2099
2099
  realm_id (str | None): Realm ID, if applicable
@@ -2109,7 +2109,7 @@ class FeedbackMessage(BECMessage):
2109
2109
 
2110
2110
  feedback: str
2111
2111
 
2112
- rating: int = Field(ge=1, le=5)
2112
+ rating: int = Field(ge=0, le=5)
2113
2113
  feedback_type: Literal["bug_report", "feature_request", "general_feedback"] = "general_feedback"
2114
2114
  contact: str | None = None
2115
2115
  realm_id: str | None = None
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_lib"
7
- version = "3.150.2"
7
+ version = "3.151.1"
8
8
  description = "BEC library"
9
9
  requires-python = ">=3.11"
10
10
  classifiers = [
@@ -44,8 +44,8 @@ class TestHelpers:
44
44
 
45
45
  class TestConfigModels:
46
46
  def test_beamline_state_config_valid_name(self):
47
- config = bl_states.BeamlineStateConfig(name="shutter_open")
48
- assert config.name == "shutter_open"
47
+ config = bl_states.BeamlineStateConfig(name="sample_x_limits")
48
+ assert config.name == "sample_x_limits"
49
49
 
50
50
  @pytest.mark.parametrize("invalid_name", ["state-name", "class", "add", "remove", "show_all"])
51
51
  def test_beamline_state_config_invalid_name(self, invalid_name):
@@ -91,18 +91,25 @@ class TestBeamlineStateBase:
91
91
 
92
92
  class TestDeviceBeamlineState:
93
93
  def test_start_requires_connector(self, dm_with_devices):
94
- state = bl_states.ShutterState(
95
- name="shutter_open", device="samy", signal="samy", device_manager=dm_with_devices
94
+ state = bl_states.DeviceWithinLimitsState(
95
+ name="sample_y_limits",
96
+ device="samy",
97
+ signal="samy",
98
+ low_limit=0.0,
99
+ high_limit=10.0,
100
+ device_manager=dm_with_devices,
96
101
  )
97
102
 
98
103
  with pytest.raises(RuntimeError, match="Redis connector is not set"):
99
104
  state.start()
100
105
 
101
106
  def test_start_registers_device_callback(self, connected_connector, dm_with_devices):
102
- state = bl_states.ShutterState(
103
- name="shutter_open",
107
+ state = bl_states.DeviceWithinLimitsState(
108
+ name="sample_x_limits",
104
109
  device="samx",
105
110
  signal="samx",
111
+ low_limit=0.0,
112
+ high_limit=10.0,
106
113
  redis_connector=connected_connector,
107
114
  device_manager=dm_with_devices,
108
115
  )
@@ -115,10 +122,12 @@ class TestDeviceBeamlineState:
115
122
  )
116
123
 
117
124
  def test_stop_unregisters_device_callback(self, connected_connector, dm_with_devices):
118
- state = bl_states.ShutterState(
119
- name="shutter_open",
125
+ state = bl_states.DeviceWithinLimitsState(
126
+ name="sample_x_limits",
120
127
  device="samx",
121
128
  signal="samx",
129
+ low_limit=0.0,
130
+ high_limit=10.0,
122
131
  redis_connector=connected_connector,
123
132
  device_manager=dm_with_devices,
124
133
  )
@@ -134,50 +143,55 @@ class TestDeviceBeamlineState:
134
143
  def test_update_device_state_publishes_when_state_changes(
135
144
  self, connected_connector, dm_with_devices
136
145
  ):
137
- state = bl_states.ShutterState(
138
- name="shutter_open",
146
+ state = bl_states.DeviceWithinLimitsState(
147
+ name="sample_x_limits",
139
148
  device="samx",
140
149
  signal="samx",
150
+ low_limit=0.0,
151
+ high_limit=10.0,
141
152
  redis_connector=connected_connector,
142
153
  device_manager=dm_with_devices,
143
154
  )
144
155
 
145
156
  msg = messages.DeviceMessage(
146
- signals={"samx": {"value": "open", "timestamp": 1.0}}, metadata={"stream": "primary"}
157
+ signals={"samx": {"value": 5.0, "timestamp": 1.0}}, metadata={"stream": "primary"}
147
158
  )
148
- msg_obj = MessageObject(value=msg, topic="test")
149
159
 
150
160
  state._update_device_state(MessageObject(value=msg, topic="test"))
151
161
 
152
162
  assert state._last_state is not None
153
163
  assert state._last_state.status == "valid"
154
164
  out = connected_connector.xread(
155
- MessageEndpoints.beamline_state("shutter_open"), from_start=True
165
+ MessageEndpoints.beamline_state("sample_x_limits"), from_start=True
156
166
  )
157
167
  assert out is not None
158
168
  assert out[0]["data"].status == "valid"
159
169
 
160
170
 
161
171
  class TestConcreteStates:
162
- def test_shutter_state_open_and_closed(self, connected_connector, dm_with_devices):
163
- state = bl_states.ShutterState(
164
- name="shutter_open",
172
+ def test_device_within_limits_state_valid_and_invalid(
173
+ self, connected_connector, dm_with_devices
174
+ ):
175
+ state = bl_states.DeviceWithinLimitsState(
176
+ name="sample_x_limits",
165
177
  device="samx",
166
178
  signal="samx",
179
+ low_limit=0.0,
180
+ high_limit=10.0,
167
181
  redis_connector=connected_connector,
168
182
  device_manager=dm_with_devices,
169
183
  )
170
184
  state.start()
171
185
 
172
- open_msg = messages.DeviceMessage(
173
- signals={"samx": {"value": "OPEN", "timestamp": 1.0}}, metadata={"stream": "primary"}
186
+ valid_msg = messages.DeviceMessage(
187
+ signals={"samx": {"value": 5.0, "timestamp": 1.0}}, metadata={"stream": "primary"}
174
188
  )
175
- closed_msg = messages.DeviceMessage(
176
- signals={"samx": {"value": "closed", "timestamp": 2.0}}, metadata={"stream": "primary"}
189
+ invalid_msg = messages.DeviceMessage(
190
+ signals={"samx": {"value": 11.0, "timestamp": 2.0}}, metadata={"stream": "primary"}
177
191
  )
178
192
 
179
- assert state.evaluate(open_msg).status == "valid"
180
- assert state.evaluate(closed_msg).status == "invalid"
193
+ assert state.evaluate(valid_msg).status == "valid"
194
+ assert state.evaluate(invalid_msg).status == "invalid"
181
195
 
182
196
  def test_device_within_limits_state(self, connected_connector, dm_with_devices):
183
197
  state = bl_states.DeviceWithinLimitsState(
@@ -253,9 +267,14 @@ class TestBeamlineStateManager:
253
267
 
254
268
  def test_manager_loads_existing_state_update_on_init(self, connected_connector):
255
269
  config = messages.BeamlineStateConfig(
256
- name="shutter_open",
257
- state_type="ShutterState",
258
- parameters={"name": "shutter_open", "device": "samy"},
270
+ name="sample_y_limits",
271
+ state_type="DeviceWithinLimitsState",
272
+ parameters={
273
+ "name": "sample_y_limits",
274
+ "device": "samy",
275
+ "low_limit": 0.0,
276
+ "high_limit": 10.0,
277
+ },
259
278
  )
260
279
  connected_connector.xadd(
261
280
  MessageEndpoints.available_beamline_states(),
@@ -268,22 +287,46 @@ class TestBeamlineStateManager:
268
287
  manager = BeamlineStateManager(client)
269
288
 
270
289
  assert manager.ready is True
271
- assert "shutter_open" in manager._states
272
- assert isinstance(getattr(manager, "shutter_open"), BeamlineStateClientBase)
290
+ assert "sample_y_limits" in manager._states
291
+ assert isinstance(getattr(manager, "sample_y_limits"), BeamlineStateClientBase)
292
+
293
+ def test_manager_rejects_abstract_state_type_on_init(self, connected_connector):
294
+ config = messages.BeamlineStateConfig(
295
+ name="generic_device_state",
296
+ state_type="DeviceBeamlineState",
297
+ parameters={"name": "generic_device_state", "device": "samy"},
298
+ )
299
+ connected_connector.xadd(
300
+ MessageEndpoints.available_beamline_states(),
301
+ {"data": messages.AvailableBeamlineStatesMessage(states=[config])},
302
+ max_size=1,
303
+ )
304
+ client = mock.MagicMock()
305
+ client.connector = connected_connector
306
+
307
+ with pytest.raises(ValueError, match="not a concrete beamline state"):
308
+ BeamlineStateManager(client)
273
309
 
274
310
  def test_on_state_update_creates_client_attribute(self, state_manager):
275
311
  config = messages.BeamlineStateConfig(
276
- name="shutter_open",
277
- state_type="ShutterState",
278
- parameters={"name": "shutter_open", "device": "samy"},
312
+ name="sample_y_limits",
313
+ state_type="DeviceWithinLimitsState",
314
+ parameters={
315
+ "name": "sample_y_limits",
316
+ "device": "samy",
317
+ "low_limit": 0.0,
318
+ "high_limit": 10.0,
319
+ },
279
320
  )
280
321
  update = messages.AvailableBeamlineStatesMessage(states=[config])
281
322
 
282
323
  state_manager._on_state_update({"data": update}, parent=state_manager)
283
324
 
284
- assert "shutter_open" in state_manager._states
285
- assert isinstance(state_manager._states["shutter_open"], bl_states.DeviceStateConfig)
286
- assert isinstance(getattr(state_manager, "shutter_open"), BeamlineStateClientBase)
325
+ assert "sample_y_limits" in state_manager._states
326
+ assert isinstance(
327
+ state_manager._states["sample_y_limits"], bl_states.DeviceWithinLimitsStateConfig
328
+ )
329
+ assert isinstance(getattr(state_manager, "sample_y_limits"), BeamlineStateClientBase)
287
330
 
288
331
  def test_update_parameters_from_client_updates_state_and_publishes(self, state_manager):
289
332
  config = messages.BeamlineStateConfig(
@@ -338,48 +381,60 @@ class TestBeamlineStateManager:
338
381
 
339
382
  def test_client_get_returns_unknown_without_status_message(self, state_manager):
340
383
  config = messages.BeamlineStateConfig(
341
- name="shutter_open",
342
- state_type="ShutterState",
343
- parameters={"name": "shutter_open", "device": "samy"},
384
+ name="sample_y_limits",
385
+ state_type="DeviceWithinLimitsState",
386
+ parameters={
387
+ "name": "sample_y_limits",
388
+ "device": "samy",
389
+ "low_limit": 0.0,
390
+ "high_limit": 10.0,
391
+ },
344
392
  )
345
393
  update = messages.AvailableBeamlineStatesMessage(states=[config])
346
394
  state_manager._on_state_update({"data": update}, parent=state_manager)
347
395
 
348
- result = state_manager.shutter_open.get()
396
+ result = state_manager.sample_y_limits.get()
349
397
  assert result == {"status": "unknown", "label": "No state information available."}
350
398
 
351
399
  def test_client_get_returns_latest_status_message(self, state_manager):
352
400
  config = messages.BeamlineStateConfig(
353
- name="shutter_open",
354
- state_type="ShutterState",
355
- parameters={"name": "shutter_open", "device": "samy"},
401
+ name="sample_y_limits",
402
+ state_type="DeviceWithinLimitsState",
403
+ parameters={
404
+ "name": "sample_y_limits",
405
+ "device": "samy",
406
+ "low_limit": 0.0,
407
+ "high_limit": 10.0,
408
+ },
356
409
  )
357
410
  update = messages.AvailableBeamlineStatesMessage(states=[config])
358
411
  state_manager._on_state_update({"data": update}, parent=state_manager)
359
412
 
360
413
  state_manager._connector.xadd(
361
- MessageEndpoints.beamline_state("shutter_open"),
414
+ MessageEndpoints.beamline_state("sample_y_limits"),
362
415
  {
363
416
  "data": messages.BeamlineStateMessage(
364
- name="shutter_open", status="valid", label="ok"
417
+ name="sample_y_limits", status="valid", label="ok"
365
418
  )
366
419
  },
367
420
  max_size=1,
368
421
  )
369
422
 
370
- result = state_manager.shutter_open.get()
423
+ result = state_manager.sample_y_limits.get()
371
424
  assert result == {"status": "valid", "label": "ok"}
372
425
 
373
426
  def test_add_waits_for_initial_state_message(self, state_manager):
374
- state = bl_states.DeviceStateConfig(name="shutter_open", device="samy")
427
+ state = bl_states.DeviceWithinLimitsStateConfig(
428
+ name="sample_y_limits", device="samy", low_limit=0.0, high_limit=10.0
429
+ )
375
430
 
376
431
  def publish_initial_state():
377
432
  time.sleep(0.05)
378
433
  state_manager._connector.xadd(
379
- MessageEndpoints.beamline_state("shutter_open"),
434
+ MessageEndpoints.beamline_state("sample_y_limits"),
380
435
  {
381
436
  "data": messages.BeamlineStateMessage(
382
- name="shutter_open", status="valid", label="ok"
437
+ name="sample_y_limits", status="valid", label="ok"
383
438
  )
384
439
  },
385
440
  max_size=1,
@@ -392,37 +447,52 @@ class TestBeamlineStateManager:
392
447
  finally:
393
448
  publisher.join()
394
449
 
395
- assert state_manager.shutter_open.get() == {"status": "valid", "label": "ok"}
450
+ assert state_manager.sample_y_limits.get() == {"status": "valid", "label": "ok"}
451
+
452
+ def test_add_rejects_abstract_device_state_config(self, state_manager):
453
+ state = bl_states.DeviceStateConfig(name="generic_device_state", device="samy")
454
+
455
+ with pytest.raises(ValueError, match="not a concrete beamline state"):
456
+ state_manager.add(state)
396
457
 
397
458
  def test_add_and_delete_publish_updates(self, state_manager):
398
- state = bl_states.DeviceStateConfig(name="shutter_open", device="samy")
459
+ state = bl_states.DeviceWithinLimitsStateConfig(
460
+ name="sample_y_limits", device="samy", low_limit=0.0, high_limit=10.0
461
+ )
399
462
 
400
463
  with mock.patch.object(state_manager, "_wait_for_initial_state"):
401
464
  state_manager.add(state)
402
- assert "shutter_open" in state_manager._states
465
+ assert "sample_y_limits" in state_manager._states
403
466
 
404
- state_manager.delete("shutter_open")
405
- assert "shutter_open" not in state_manager._states
467
+ state_manager.delete("sample_y_limits")
468
+ assert "sample_y_limits" not in state_manager._states
406
469
 
407
470
  def test_client_remove_state(self, state_manager):
408
471
  config = messages.BeamlineStateConfig(
409
- name="shutter_open",
410
- state_type="ShutterState",
411
- parameters={"name": "shutter_open", "device": "samy"},
472
+ name="sample_y_limits",
473
+ state_type="DeviceWithinLimitsState",
474
+ parameters={
475
+ "name": "sample_y_limits",
476
+ "device": "samy",
477
+ "low_limit": 0.0,
478
+ "high_limit": 10.0,
479
+ },
412
480
  )
413
481
  update = messages.AvailableBeamlineStatesMessage(states=[config])
414
482
  state_manager._on_state_update({"data": update}, parent=state_manager)
415
483
 
416
- state_manager.shutter_open.remove()
484
+ state_manager.sample_y_limits.remove()
417
485
 
418
- assert "shutter_open" not in state_manager._states
486
+ assert "sample_y_limits" not in state_manager._states
419
487
 
420
488
  def test_show_all_prints_table(self, state_manager, capsys):
421
- state = bl_states.DeviceStateConfig(name="shutter_open", device="samy")
489
+ state = bl_states.DeviceWithinLimitsStateConfig(
490
+ name="sample_y_limits", device="samy", low_limit=0.0, high_limit=10.0
491
+ )
422
492
  with mock.patch.object(state_manager, "_wait_for_initial_state"):
423
493
  state_manager.add(state)
424
494
 
425
495
  state_manager.show_all()
426
496
 
427
497
  captured = capsys.readouterr()
428
- assert "shutter_open" in (captured.out + captured.err)
498
+ assert "sample_y_limits" in (captured.out + captured.err)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes