bec-lib 3.151.2__tar.gz → 3.152.0__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.151.2 → bec_lib-3.152.0}/PKG-INFO +1 -1
  2. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/devicemanager.py +3 -1
  3. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/messages.py +4 -0
  4. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/queue_items.py +40 -0
  5. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/redis_connector/managed_redis_connection.py +13 -4
  6. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/scan_input_validator.py +1 -1
  7. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/scans.py +17 -3
  8. {bec_lib-3.151.2 → bec_lib-3.152.0}/pyproject.toml +1 -1
  9. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_queue_items.py +67 -0
  10. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_redis_connector_fakeredis.py +29 -0
  11. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_scan_object.py +10 -0
  12. {bec_lib-3.151.2 → bec_lib-3.152.0}/.gitignore +0 -0
  13. {bec_lib-3.151.2 → bec_lib-3.152.0}/README.md +0 -0
  14. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/__init__.py +0 -0
  15. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/_print_versions.py +0 -0
  16. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/acl_login.py +0 -0
  17. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/actors.py +0 -0
  18. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/alarm_handler.py +0 -0
  19. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/atlas_models.py +0 -0
  20. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/bec_errors.py +0 -0
  21. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/bec_service.py +0 -0
  22. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/bec_yaml_loader.py +0 -0
  23. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/bl_state_manager.py +0 -0
  24. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/bl_states.py +0 -0
  25. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/builtin_actor_hli.py +0 -0
  26. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/callback_handler.py +0 -0
  27. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/channel_monitor.py +0 -0
  28. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/client.py +0 -0
  29. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/codecs.py +0 -0
  30. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/config_helper.py +0 -0
  31. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/config_values.py +0 -0
  32. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/configs/__init__.py +0 -0
  33. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/configs/demo_config.yaml +0 -0
  34. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/configs/openapi_schema.json +0 -0
  35. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/connector.py +0 -0
  36. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/dap_plugin_objects.py +0 -0
  37. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/dap_plugins.py +0 -0
  38. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/device.py +0 -0
  39. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/endpoints.py +0 -0
  40. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/file_utils.py +0 -0
  41. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/live_scan_data.py +0 -0
  42. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/lmfit_serializer.py +0 -0
  43. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/logger.py +0 -0
  44. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/macro_update_handler.py +0 -0
  45. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/messaging_hooks.py +0 -0
  46. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/messaging_services.py +0 -0
  47. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/metadata_schema.py +0 -0
  48. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/numpy_encoder.py +0 -0
  49. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/observer.py +0 -0
  50. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/pdf_writer.py +0 -0
  51. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/plugin_helper.py +0 -0
  52. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/procedures/__init__.py +0 -0
  53. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/procedures/helper.py +0 -0
  54. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/procedures/hli.py +0 -0
  55. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/procedures/status.py +0 -0
  56. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/redis_connector/__init__.py +0 -0
  57. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/redis_connector/constants.py +0 -0
  58. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/redis_connector/hli.py +0 -0
  59. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/redis_connector/streams.py +0 -0
  60. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/redis_connector/validation.py +0 -0
  61. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/request_context.py +0 -0
  62. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/request_items.py +0 -0
  63. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/scan_args.py +0 -0
  64. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/scan_data_container.py +0 -0
  65. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/scan_history.py +0 -0
  66. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/scan_items.py +0 -0
  67. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/scan_manager.py +0 -0
  68. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/scan_number_container.py +0 -0
  69. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/scan_repeat.py +0 -0
  70. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/scan_report.py +0 -0
  71. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/scibec_validator.py +0 -0
  72. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/script_executor.py +0 -0
  73. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/serialization.py +0 -0
  74. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/serialization_registry.py +0 -0
  75. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/service_config.py +0 -0
  76. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/signature_serializer.py +0 -0
  77. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/tests/__init__.py +0 -0
  78. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/tests/fixtures.py +0 -0
  79. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/tests/test_config.yaml +0 -0
  80. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/tests/test_service_config.yaml +0 -0
  81. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/tests/utils.py +0 -0
  82. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/user_macros.py +0 -0
  83. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/__init__.py +0 -0
  84. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/copier_jinja_filters.py +0 -0
  85. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/import_utils.py +0 -0
  86. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/json_extended.py +0 -0
  87. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/plugin_manager/__init__.py +0 -0
  88. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/plugin_manager/_constants.py +0 -0
  89. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/plugin_manager/_util.py +0 -0
  90. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/plugin_manager/create/__init__.py +0 -0
  91. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/plugin_manager/create/device.py +0 -0
  92. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/plugin_manager/create/scan.py +0 -0
  93. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/plugin_manager/create/templates/v4_scan.py.jinja +0 -0
  94. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/plugin_manager/main.py +0 -0
  95. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/proxy.py +0 -0
  96. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/pydantic_pretty_print.py +0 -0
  97. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/rpc_utils.py +0 -0
  98. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/scan_utils.py +0 -0
  99. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/threading_utils.py +0 -0
  100. {bec_lib-3.151.2 → bec_lib-3.152.0}/bec_lib/utils/user_acls_test.py +0 -0
  101. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/__init__.py +0 -0
  102. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/_additional_for_signature_serializer_test.py +0 -0
  103. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/conftest.py +0 -0
  104. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_acl_login.py +0 -0
  105. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_alarm_handler.py +0 -0
  106. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_atlas_models.py +0 -0
  107. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_beamline_states.py +0 -0
  108. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_bec_logger.py +0 -0
  109. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_bec_messages.py +0 -0
  110. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_bec_service.py +0 -0
  111. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_callback_handler.py +0 -0
  112. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_channel_monitor.py +0 -0
  113. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_client.py +0 -0
  114. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_config_helper.py +0 -0
  115. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_core_utils.py +0 -0
  116. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_dap_plugins.py +0 -0
  117. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_data_container.py +0 -0
  118. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_device_hashing.py +0 -0
  119. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_device_manager.py +0 -0
  120. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_devices.py +0 -0
  121. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_file_utils.py +0 -0
  122. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_hli_connector_fakeredis.py +0 -0
  123. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_hli_redis_connector.py +0 -0
  124. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_import_utils.py +0 -0
  125. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_jinja_plugins.py +0 -0
  126. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_lmfit_serializer.py +0 -0
  127. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_managed_config_values.py +0 -0
  128. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_managed_redis_connection.py +0 -0
  129. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_messaging_hooks.py +0 -0
  130. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_messaging_service.py +0 -0
  131. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_metadata_schema.py +0 -0
  132. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_observer.py +0 -0
  133. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_pdf_writer.py +0 -0
  134. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_plugin_helper.py +0 -0
  135. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_plugin_manager.py +0 -0
  136. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_plugin_system.py +0 -0
  137. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_pydantic_pretty_print.py +0 -0
  138. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_scan_context.py +0 -0
  139. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_scan_data.py +0 -0
  140. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_scan_history.py +0 -0
  141. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_scan_items.py +0 -0
  142. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_scan_manager.py +0 -0
  143. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_scan_repeat.py +0 -0
  144. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_scan_report.py +0 -0
  145. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_scibec_validator.py +0 -0
  146. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_script_executor.py +0 -0
  147. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_serializer.py +0 -0
  148. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_signature_serializer.py +0 -0
  149. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_signature_serializer_with_future_import.py +0 -0
  150. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_stream_subs.py +0 -0
  151. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_user_macros.py +0 -0
  152. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_utils.py +0 -0
  153. {bec_lib-3.151.2 → bec_lib-3.152.0}/tests/test_yaml_loader.py +0 -0
  154. {bec_lib-3.151.2 → bec_lib-3.152.0}/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.151.2
3
+ Version: 3.152.0
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
@@ -315,7 +315,9 @@ class DeviceContainer(dict):
315
315
  return list(tags)
316
316
 
317
317
  def get_software_triggered_devices(self) -> list:
318
- """get a list of all devices that should receive a software trigger detectors"""
318
+ """
319
+ Get a list of all enabled devices that should receive a software trigger during a scan.
320
+ """
319
321
  # pylint: disable=protected-access
320
322
  devices = [
321
323
  dev for _, dev in self.items() if dev._config.get("softwareTrigger", False) is True
@@ -415,6 +415,8 @@ class RequestBlock(BaseModel):
415
415
  scan_number (int | None): Scan number if applicable
416
416
  scan_id (str | None): Scan ID if applicable
417
417
  report_instructions (list[dict] | None): List of report instructions for the scan, if any
418
+ owned_device_locks (list[str]): List of device locks owned by the request
419
+ pending_device_locks (list[str]): List of device locks pending for the request
418
420
 
419
421
  """
420
422
 
@@ -427,6 +429,8 @@ class RequestBlock(BaseModel):
427
429
  scan_number: int | None
428
430
  scan_id: str | None
429
431
  report_instructions: list[dict] | None = None
432
+ owned_device_locks: list[str] = Field(default_factory=list)
433
+ pending_device_locks: list[str] = Field(default_factory=list)
430
434
 
431
435
 
432
436
  class QueueInfoEntry(BaseModel):
@@ -54,6 +54,7 @@ class QueueItem:
54
54
  self.active_request_block = active_request_block
55
55
  self.scan_ids = scan_id
56
56
  self.reason = reason
57
+ self._lock = threading.RLock()
57
58
  if client_messages is None:
58
59
  client_messages = []
59
60
  self.client_messages = client_messages
@@ -85,6 +86,44 @@ class QueueItem:
85
86
  def status(self):
86
87
  return self._status
87
88
 
89
+ @threadlocked
90
+ def _get_device_lock_state(self) -> tuple[list[str], list[str]]:
91
+ """
92
+ Return a snapshot of owned and pending device locks.
93
+
94
+ Returns:
95
+ tuple[list[str], list[str]]: A tuple containing two lists:
96
+ - The first list contains the names of devices whose locks are currently owned.
97
+ - The second list contains the names of devices whose locks are pending.
98
+ """
99
+ owned_device_locks = sorted(
100
+ {
101
+ device
102
+ for request_block in self.request_blocks
103
+ for device in request_block.owned_device_locks
104
+ }
105
+ )
106
+ pending_device_locks = sorted(
107
+ {
108
+ device
109
+ for request_block in self.request_blocks
110
+ for device in request_block.pending_device_locks
111
+ }
112
+ )
113
+ return owned_device_locks, pending_device_locks
114
+
115
+ def get_device_lock_state(self) -> tuple[list[str], list[str]]:
116
+ """
117
+ Refresh and return a consistent snapshot of device-lock state.
118
+
119
+ Returns:
120
+ tuple[list[str], list[str]]: A tuple containing two lists:
121
+ - The first list contains the names of devices whose locks are currently owned.
122
+ - The second list contains the names of devices whose locks are pending.
123
+ """
124
+ self._update_with_buffer()
125
+ return self._get_device_lock_state()
126
+
88
127
  def _update_with_buffer(self):
89
128
  current_queue = self.scan_manager.queue_storage.current_scan_queue
90
129
  if not current_queue:
@@ -103,6 +142,7 @@ class QueueItem:
103
142
  self.update_queue_item(queue_item.info)
104
143
  return
105
144
 
145
+ @threadlocked
106
146
  def update_queue_item(self, queue_item: messages.QueueInfoEntry):
107
147
  """update the queue item"""
108
148
  self.request_blocks = queue_item.request_blocks
@@ -595,7 +595,9 @@ class ManagedRedisConnection:
595
595
  unsubscribe_list = []
596
596
  with self._topics_cb_lock:
597
597
  for topic in topics:
598
- topics_cb = self._topics_cb[topic]
598
+ topics_cb = self._topics_cb.get(topic)
599
+ if topics_cb is None:
600
+ continue
599
601
  # remove callback from list
600
602
  self._topics_cb[topic] = list(
601
603
  filter(lambda item: cb and item[0]() != cb, topics_cb)
@@ -631,9 +633,16 @@ class ManagedRedisConnection:
631
633
  self._pubsub_conn.unsubscribe(unsubscribe_list)
632
634
  else:
633
635
  with self._topics_cb_lock:
634
- topics = list(self._topics_cb.keys())
635
- self.unregister(topics, cb)
636
- self.unregister(self._stream_subs.all_topics, cb)
636
+ registered_topics = list(self._topics_cb.keys())
637
+ direct_topics = [
638
+ topic for topic in registered_topics if not set("*?[]").intersection(topic)
639
+ ]
640
+ pattern_topics = [topic for topic in registered_topics if topic not in direct_topics]
641
+ if direct_topics:
642
+ self.unregister(topics=direct_topics, cb=cb)
643
+ if pattern_topics:
644
+ self.unregister(patterns=pattern_topics, cb=cb)
645
+ self.unregister(topics=self._stream_subs.all_topics, cb=cb)
637
646
 
638
647
  def _unregister_stream(self, topics: list[str], cb: Callable | None = None) -> bool:
639
648
  """Unregister callbacks from a list of topics. Returns true if any were removed"""
@@ -13,7 +13,7 @@ from bec_lib.scan_args import ScanArgument
13
13
  from bec_lib.signature_serializer import deserialize_dtype, dict_to_signature
14
14
 
15
15
  # Internal scan kwargs that are automatically added to the scan request
16
- INTERNAL_SCAN_KWARGS = {"user_metadata", "system_config", "scan_queue"}
16
+ INTERNAL_SCAN_KWARGS = {"user_metadata", "system_config", "scan_queue", "monitored", "on_request"}
17
17
 
18
18
 
19
19
  class ScanInputValidator:
@@ -58,6 +58,7 @@ class ScanObject:
58
58
  hide_report: bool = False,
59
59
  metadata: dict | None = None,
60
60
  monitored: list[str | DeviceBase] | None = None,
61
+ on_request: list[str | DeviceBase] | None = None,
61
62
  file_suffix: str | None = None,
62
63
  file_directory: str | None = None,
63
64
  scan_queue: str | None = None,
@@ -72,7 +73,8 @@ class ScanObject:
72
73
  async_callback: Asynchronous callback function
73
74
  hide_report: Hide the report
74
75
  metadata: Metadata dictionary
75
- monitored: List of monitored devices
76
+ monitored: List of devices to be put on the readout priority `monitored`
77
+ on_request: List of devices to be put on the readout priority `on_request`
76
78
  file_suffix: File suffix for the scan data
77
79
  file_directory: File directory for the scan data
78
80
  scan_queue: Scan queue name. If None, the default queue will be used.
@@ -110,13 +112,25 @@ class ScanObject:
110
112
  monitored = [monitored]
111
113
  for mon_device in monitored:
112
114
  if isinstance(mon_device, str):
113
- mon_device = self.client.device_manager.devices.get(mon_device)
114
- if not mon_device:
115
+ mon_device_obj = self.client.device_manager.devices.get(mon_device)
116
+ if not mon_device_obj:
115
117
  raise RuntimeError(
116
118
  f"Specified monitored device {mon_device} does not exist in the current device configuration."
117
119
  )
118
120
  kwargs["monitored"] = monitored
119
121
 
122
+ if on_request is not None:
123
+ if not isinstance(on_request, list):
124
+ on_request = [on_request]
125
+ for or_device in on_request:
126
+ if isinstance(or_device, str):
127
+ or_device_obj = self.client.device_manager.devices.get(or_device)
128
+ if not or_device_obj:
129
+ raise RuntimeError(
130
+ f"Specified on_request device {or_device} does not exist in the current device configuration."
131
+ )
132
+ kwargs["on_request"] = on_request
133
+
120
134
  sys_config = sys_config.model_dump()
121
135
  # pylint: disable=protected-access
122
136
  if scans._scan_group:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_lib"
7
- version = "3.151.2"
7
+ version = "3.152.0"
8
8
  description = "BEC library"
9
9
  requires-python = ">=3.11"
10
10
  classifiers = [
@@ -520,6 +520,73 @@ def test_queue_item_status_property(queue_item, scan_queue_status_message):
520
520
  assert queue_item._status == "pending"
521
521
 
522
522
 
523
+ def test_queue_item_device_lock_properties(queue_item):
524
+ queue_item.request_blocks = [
525
+ messages.RequestBlock(
526
+ msg=messages.ScanQueueMessage(
527
+ queue="primary",
528
+ scan_type="step",
529
+ parameter={"args": {}, "kwargs": {}},
530
+ metadata={"RID": "rid_1"},
531
+ ),
532
+ RID="rid_1",
533
+ readout_priority={"monitored": []},
534
+ is_scan=True,
535
+ scan_number=1,
536
+ scan_id="scan_1",
537
+ owned_device_locks=["samx", "samy"],
538
+ pending_device_locks=["bpm4i"],
539
+ ),
540
+ messages.RequestBlock(
541
+ msg=messages.ScanQueueMessage(
542
+ queue="primary",
543
+ scan_type="step",
544
+ parameter={"args": {}, "kwargs": {}},
545
+ metadata={"RID": "rid_2"},
546
+ ),
547
+ RID="rid_2",
548
+ readout_priority={"monitored": []},
549
+ is_scan=True,
550
+ scan_number=2,
551
+ scan_id="scan_2",
552
+ owned_device_locks=["samy", "samz"],
553
+ pending_device_locks=["bpm4i", "bpm5i"],
554
+ ),
555
+ ]
556
+ queue_item.scan_manager.queue_storage.current_scan_queue = None
557
+
558
+ owned_device_locks, pending_device_locks = queue_item.get_device_lock_state()
559
+
560
+ assert owned_device_locks == ["samx", "samy", "samz"]
561
+ assert pending_device_locks == ["bpm4i", "bpm5i"]
562
+
563
+
564
+ def test_queue_item_get_device_lock_state(queue_item):
565
+ queue_item.request_blocks = [
566
+ messages.RequestBlock(
567
+ msg=messages.ScanQueueMessage(
568
+ queue="primary",
569
+ scan_type="step",
570
+ parameter={"args": {}, "kwargs": {}},
571
+ metadata={"RID": "rid_1"},
572
+ ),
573
+ RID="rid_1",
574
+ readout_priority={"monitored": []},
575
+ is_scan=True,
576
+ scan_number=1,
577
+ scan_id="scan_1",
578
+ owned_device_locks=["samx", "samy"],
579
+ pending_device_locks=["bpm4i"],
580
+ )
581
+ ]
582
+ queue_item.scan_manager.queue_storage.current_scan_queue = None
583
+
584
+ owned_device_locks, pending_device_locks = queue_item.get_device_lock_state()
585
+
586
+ assert owned_device_locks == ["samx", "samy"]
587
+ assert pending_device_locks == ["bpm4i"]
588
+
589
+
523
590
  def test_queue_item_scans_property(queue_item, scan_queue_status_message):
524
591
  """Test scans property retrieves scan items."""
525
592
  queue_item.scan_ids = ["scan_1", "scan_2"]
@@ -148,6 +148,35 @@ def test_redis_connector_unregister(connected_connector):
148
148
  assert len(connector._topics_cb) == 0
149
149
 
150
150
 
151
+ def test_redis_connector_unregister_all_for_callback(connected_connector):
152
+ connector = connected_connector
153
+ cb_mock = mock.Mock(spec=[])
154
+
155
+ connector.register(topics=["topic1"], cb=cb_mock, start_thread=False)
156
+ connector.register(patterns=["topic2:*"], cb=cb_mock, start_thread=False)
157
+ connector.register(TestStreamEndpoint, cb=cb_mock, start_thread=False)
158
+
159
+ connector.send("topic1", TestMessage(msg="topic1"))
160
+ connector.poll_messages(timeout=1)
161
+ connector.send("topic2:a", TestMessage(msg="topic2"))
162
+ connector.poll_messages(timeout=1)
163
+ connector.xadd("test", {"data": 1})
164
+ connector.poll_messages(timeout=1)
165
+ assert cb_mock.call_count == 3
166
+
167
+ cb_mock.reset_mock()
168
+ connector.unregister(cb=cb_mock)
169
+
170
+ connector.send("topic1", TestMessage(msg="topic1"))
171
+ connector.send("topic2:a", TestMessage(msg="topic2"))
172
+ connector.xadd("test", {"data": 2})
173
+ with pytest.raises(TimeoutError):
174
+ connector.poll_messages(timeout=1)
175
+ assert cb_mock.call_count == 0
176
+ assert len(connector._topics_cb) == 0
177
+ assert connector._stream_subs.all_topics == []
178
+
179
+
151
180
  def test_redis_connector_register_identical(connected_connector):
152
181
  connector = connected_connector
153
182
 
@@ -115,6 +115,16 @@ def test_scan_object_v4_rejects_additional_kwargs(scan_obj_no_args, dev):
115
115
  assert "Unknown keyword argument(s) for scan: 'additional_device'" in str(exc.value)
116
116
 
117
117
 
118
+ def test_scan_object_v4_accepts_monitored_kwarg(scan_obj_no_args):
119
+ scan_obj_no_args.scan_info["base_class"] = "ScanBaseV4"
120
+
121
+ with mock.patch.object(scan_obj_no_args, "_send_scan_request") as send_scan_request:
122
+ scan_obj_no_args._run(points=10, interval=1.5, monitored=["samx"])
123
+
124
+ sent_msg = send_scan_request.call_args.args[0]
125
+ assert sent_msg.parameter["kwargs"]["monitored"] == ["samx"]
126
+
127
+
118
128
  def test_scan_object_raises_signature_kwargs_type_for_arg_input_scan(scan_obj, dev):
119
129
  with pytest.raises(ScanInputValidationError, match="step': .*float or int"):
120
130
  scan_obj._run(dev.samx, -5, 5, dev.samy, -5, 5, step="bad", exp_time=0.1, relative=False)
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