bec-lib 4.3.0__tar.gz → 4.3.2__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 (164) hide show
  1. {bec_lib-4.3.0 → bec_lib-4.3.2}/PKG-INFO +1 -1
  2. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/bec_service.py +2 -2
  3. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/endpoints.py +33 -3
  4. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/messages.py +6 -1
  5. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/procedures/helper.py +34 -2
  6. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/service_config.py +14 -15
  7. {bec_lib-4.3.0 → bec_lib-4.3.2}/pyproject.toml +1 -1
  8. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_bec_service.py +2 -2
  9. bec_lib-4.3.2/tests/test_procedure_submission.py +39 -0
  10. bec_lib-4.3.2/tests/test_service_config.py +55 -0
  11. {bec_lib-4.3.0 → bec_lib-4.3.2}/.gitignore +0 -0
  12. {bec_lib-4.3.0 → bec_lib-4.3.2}/README.md +0 -0
  13. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/__init__.py +0 -0
  14. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/_print_versions.py +0 -0
  15. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/acl_login.py +0 -0
  16. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/actors.py +0 -0
  17. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/alarm_handler.py +0 -0
  18. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/atlas_models.py +0 -0
  19. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/bec_errors.py +0 -0
  20. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/bec_yaml_loader.py +0 -0
  21. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/bl_state_manager.py +0 -0
  22. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/bl_states.py +0 -0
  23. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/builtin_actor_hli.py +0 -0
  24. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/callback_handler.py +0 -0
  25. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/channel_monitor.py +0 -0
  26. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/client.py +0 -0
  27. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/codecs.py +0 -0
  28. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/config_helper.py +0 -0
  29. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/config_values.py +0 -0
  30. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/configs/__init__.py +0 -0
  31. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/configs/demo_config.yaml +0 -0
  32. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/configs/openapi_schema.json +0 -0
  33. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/connector.py +0 -0
  34. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/dap_plugin_objects.py +0 -0
  35. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/dap_plugins.py +0 -0
  36. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/device.py +0 -0
  37. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/devicemanager.py +0 -0
  38. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/file_utils.py +0 -0
  39. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/live_scan_data.py +0 -0
  40. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/lmfit_serializer.py +0 -0
  41. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/logger.py +0 -0
  42. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/macro_update_handler.py +0 -0
  43. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/messaging_hooks.py +0 -0
  44. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/messaging_services.py +0 -0
  45. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/metadata_schema.py +0 -0
  46. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/numpy_encoder.py +0 -0
  47. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/observer.py +0 -0
  48. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/path_optimization.py +0 -0
  49. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/pdf_writer.py +0 -0
  50. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/plugin_helper.py +0 -0
  51. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/position_generators.py +0 -0
  52. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/procedures/__init__.py +0 -0
  53. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/procedures/hli.py +0 -0
  54. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/procedures/status.py +0 -0
  55. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/queue_items.py +0 -0
  56. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/__init__.py +0 -0
  57. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/constants.py +0 -0
  58. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/hli.py +0 -0
  59. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/managed_redis_connection.py +0 -0
  60. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/streams.py +0 -0
  61. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/validation.py +0 -0
  62. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/request_context.py +0 -0
  63. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/request_items.py +0 -0
  64. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_args.py +0 -0
  65. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_data_container.py +0 -0
  66. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_history.py +0 -0
  67. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_input_validator.py +0 -0
  68. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_items.py +0 -0
  69. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_manager.py +0 -0
  70. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_number_container.py +0 -0
  71. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_preview.py +0 -0
  72. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_repeat.py +0 -0
  73. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_report.py +0 -0
  74. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scans.py +0 -0
  75. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scibec_validator.py +0 -0
  76. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/script_executor.py +0 -0
  77. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/serialization.py +0 -0
  78. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/serialization_registry.py +0 -0
  79. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/signature_serializer.py +0 -0
  80. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/tests/__init__.py +0 -0
  81. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/tests/fixtures.py +0 -0
  82. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/tests/test_config.yaml +0 -0
  83. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/tests/test_service_config.yaml +0 -0
  84. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/tests/utils.py +0 -0
  85. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/user_macros.py +0 -0
  86. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/__init__.py +0 -0
  87. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/copier_jinja_filters.py +0 -0
  88. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/deprecation.py +0 -0
  89. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/import_utils.py +0 -0
  90. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/json_extended.py +0 -0
  91. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/__init__.py +0 -0
  92. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/_constants.py +0 -0
  93. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/_util.py +0 -0
  94. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/create/__init__.py +0 -0
  95. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/create/device.py +0 -0
  96. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/create/scan.py +0 -0
  97. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/create/templates/scan.py.jinja +0 -0
  98. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/main.py +0 -0
  99. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/proxy.py +0 -0
  100. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/pydantic_pretty_print.py +0 -0
  101. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/rpc_utils.py +0 -0
  102. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/scan_utils.py +0 -0
  103. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/threading_utils.py +0 -0
  104. {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/user_acls_test.py +0 -0
  105. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/__init__.py +0 -0
  106. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/_additional_for_signature_serializer_test.py +0 -0
  107. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/conftest.py +0 -0
  108. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_acl_login.py +0 -0
  109. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_alarm_handler.py +0 -0
  110. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_atlas_models.py +0 -0
  111. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_beamline_states.py +0 -0
  112. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_bec_logger.py +0 -0
  113. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_bec_messages.py +0 -0
  114. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_callback_handler.py +0 -0
  115. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_channel_monitor.py +0 -0
  116. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_client.py +0 -0
  117. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_config_helper.py +0 -0
  118. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_core_utils.py +0 -0
  119. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_dap_plugins.py +0 -0
  120. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_data_container.py +0 -0
  121. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_deprecation.py +0 -0
  122. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_device_hashing.py +0 -0
  123. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_device_manager.py +0 -0
  124. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_devices.py +0 -0
  125. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_file_utils.py +0 -0
  126. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_hli_connector_fakeredis.py +0 -0
  127. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_hli_redis_connector.py +0 -0
  128. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_import_utils.py +0 -0
  129. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_jinja_plugins.py +0 -0
  130. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_lmfit_serializer.py +0 -0
  131. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_managed_config_values.py +0 -0
  132. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_managed_redis_connection.py +0 -0
  133. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_messaging_hooks.py +0 -0
  134. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_messaging_service.py +0 -0
  135. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_metadata_schema.py +0 -0
  136. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_observer.py +0 -0
  137. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_path_optimization.py +0 -0
  138. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_pdf_writer.py +0 -0
  139. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_plugin_helper.py +0 -0
  140. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_plugin_manager.py +0 -0
  141. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_plugin_system.py +0 -0
  142. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_position_generators.py +0 -0
  143. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_pydantic_pretty_print.py +0 -0
  144. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_queue_items.py +0 -0
  145. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_redis_connector_fakeredis.py +0 -0
  146. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_context.py +0 -0
  147. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_data.py +0 -0
  148. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_history.py +0 -0
  149. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_items.py +0 -0
  150. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_manager.py +0 -0
  151. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_object.py +0 -0
  152. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_preview.py +0 -0
  153. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_repeat.py +0 -0
  154. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_report.py +0 -0
  155. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scibec_validator.py +0 -0
  156. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_script_executor.py +0 -0
  157. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_serializer.py +0 -0
  158. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_signature_serializer.py +0 -0
  159. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_signature_serializer_with_future_import.py +0 -0
  160. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_stream_subs.py +0 -0
  161. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_user_macros.py +0 -0
  162. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_utils.py +0 -0
  163. {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_yaml_loader.py +0 -0
  164. {bec_lib-4.3.0 → bec_lib-4.3.2}/util_scripts/init_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: bec_lib
3
- Version: 4.3.0
3
+ Version: 4.3.2
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
@@ -83,8 +83,8 @@ def parse_cmdline_args(parser=None, config_name: Literal["client", "server"] | s
83
83
  parser.add_argument(
84
84
  "--use-subprocess-proc-worker",
85
85
  action="store_true",
86
- default=False,
87
- help="Use the in process procedure worker for local testing",
86
+ default=None,
87
+ help="Use the subprocess procedure worker for local testing",
88
88
  )
89
89
 
90
90
  args, extra_args = parser.parse_known_args()
@@ -1470,15 +1470,19 @@ class MessageEndpoints:
1470
1470
  )
1471
1471
 
1472
1472
  @staticmethod
1473
- def procedure_request():
1473
+ def procedure_request(username: str) -> EndpointInfo:
1474
1474
  """
1475
- Endpoint for requesting new procedures.
1475
+ Endpoint for requesting new procedures, scoped to the submitting user through Redis ACLs.
1476
1476
  The request is sent using a messages.ProcedureRequestMessage message.
1477
+ Its metadata.client_info.acl_user must match the username in this endpoint.
1478
+
1479
+ Args:
1480
+ username (str): Username of the user making the request.
1477
1481
 
1478
1482
  Returns:
1479
1483
  EndpointInfo: Endpoint for procedure request.
1480
1484
  """
1481
- endpoint = f"{EndpointType.USER.value}/procedures/procedure_request"
1485
+ endpoint = f"{EndpointType.PERSONAL.value}/{username}/procedures/procedure_request"
1482
1486
  return EndpointInfo(
1483
1487
  endpoint=endpoint,
1484
1488
  message_type=messages.ProcedureRequestMessage,
@@ -1503,6 +1507,32 @@ class MessageEndpoints:
1503
1507
  message_op=MessageOp.SEND,
1504
1508
  )
1505
1509
 
1510
+ @staticmethod
1511
+ def procedure_workers() -> EndpointInfo:
1512
+ """Return the public registry of workers and their owning queues.
1513
+
1514
+ Returns:
1515
+ EndpointInfo: Worker registry.
1516
+ """
1517
+ return EndpointInfo(
1518
+ endpoint=f"{EndpointType.INFO.value}/procedures/workers",
1519
+ message_type=messages.AvailableResourceMessage,
1520
+ message_op=MessageOp.KEY_VALUE,
1521
+ )
1522
+
1523
+ @staticmethod
1524
+ def procedure_queues() -> EndpointInfo:
1525
+ """Return the registry of logical queues, including empty queues.
1526
+
1527
+ Returns:
1528
+ EndpointInfo: Queue registry.
1529
+ """
1530
+ return EndpointInfo(
1531
+ endpoint=f"{EndpointType.INFO.value}/procedures/queues",
1532
+ message_type=messages.AvailableResourceMessage,
1533
+ message_op=MessageOp.KEY_VALUE,
1534
+ )
1535
+
1506
1536
  @staticmethod
1507
1537
  def procedure_execution(queue_id: str):
1508
1538
  """
@@ -1937,9 +1937,11 @@ class ProcedureWorkerStatusMessage(BECMessage):
1937
1937
  """Message type for sending procedure worker status updates
1938
1938
 
1939
1939
  Args:
1940
- worker_queue (str): Worker queue ID
1940
+ worker_queue (str): Worker identifier; legacy actor workers use their queue ID.
1941
1941
  status (str): Worker status
1942
1942
  current_execution_id (str | None): ID of the current job, only allowed for RUNNING
1943
+ completed_execution_id (str | None): Completed pooled execution, if any.
1944
+ error (str | None): Error from the completed execution.
1943
1945
  """
1944
1946
 
1945
1947
  msg_type: ClassVar[str] = "procedure_worker_status_message"
@@ -1947,6 +1949,9 @@ class ProcedureWorkerStatusMessage(BECMessage):
1947
1949
  status: ProcedureWorkerStatus
1948
1950
  current_execution_id: str | None = None
1949
1951
 
1952
+ completed_execution_id: str | None = None
1953
+ error: str | None = None
1954
+
1950
1955
  @model_validator(mode="after")
1951
1956
  def check_id(self) -> Self:
1952
1957
  if self.current_execution_id is not None and self.status != ProcedureWorkerStatus.RUNNING:
@@ -87,6 +87,11 @@ class ProcedureStatus:
87
87
  return
88
88
  # The request can be accepted or denied
89
89
  if self._state is ProcedureState.REQUESTED:
90
+ # Cancellation can finish an admission before its acknowledgement arrives.
91
+ if state == ProcedureState.FAILED:
92
+ self._state = state
93
+ self._done.set()
94
+ return
90
95
  if state not in [ProcedureState.REJECTED, ProcedureState.SCHEDULED]:
91
96
  raise ValueError(f"Improper transition from {self._state} to {state}")
92
97
  self._state = state
@@ -185,9 +190,14 @@ class _Request(_HelperBase):
185
190
  return self._procedure(msg)
186
191
 
187
192
  def _procedure(self, msg):
193
+ from bec_lib.acl_login import BECAccess
194
+
195
+ credentials = BECAccess(self._conn).current_credentials()
196
+ username = credentials["username"] or "default"
197
+ msg.metadata["client_info"] = {"acl_user": username, "acl_token": credentials["token"]}
188
198
  st = ProcedureStatus(self._conn, msg.execution_id, msg.identifier)
189
199
  self._set_callbacks(msg.execution_id, st)
190
- self._conn.send(ME.procedure_request(), msg)
200
+ self._conn.send(ME.procedure_request(username), msg)
191
201
  return st
192
202
 
193
203
  def abort_execution(self, execution_id: str):
@@ -256,7 +266,29 @@ class _Get(_HelperBase):
256
266
  else ME.unhandled_procedure_execution
257
267
  )
258
268
  raw: list[str] = [s.decode() for s in self._conn.keys(ep("*"))]
259
- return [s.split("/")[-1] for s in raw]
269
+ names = {s.split("/")[-1] for s in raw}
270
+ if queue_type == "execution":
271
+ registry = self._conn.get(ME.procedure_queues())
272
+ if registry is not None:
273
+ names.update(registry.resource)
274
+ return sorted(names)
275
+
276
+ def workers(self, queue: str | None = None) -> dict[str, dict]:
277
+ """Return workers, optionally filtered by their owning logical queue.
278
+
279
+ Args:
280
+ queue (str | None): Queue name, or None for all queues.
281
+ Returns:
282
+ dict[str, dict]: Worker IDs mapped to queue, status, and current execution.
283
+ """
284
+ registry = self._conn.get(ME.procedure_workers())
285
+ if registry is None:
286
+ return {}
287
+ return {
288
+ identifier: worker
289
+ for identifier, worker in registry.resource.items()
290
+ if queue is None or worker["queue"] == queue
291
+ }
260
292
 
261
293
  def log_queue_names(self) -> list[str]:
262
294
  """Get the names of queues currently containing logs from procedures."""
@@ -7,7 +7,7 @@ import os
7
7
  import re
8
8
  from getpass import getuser
9
9
  from pathlib import Path
10
- from typing import Any, ClassVar, Self
10
+ from typing import Any, ClassVar
11
11
 
12
12
  import yaml
13
13
  from pydantic import BaseModel, Field, model_validator
@@ -114,21 +114,20 @@ class ServiceConfigModel(BaseModel):
114
114
  procedures: ProcedureConfig = Field(default_factory=ProcedureConfig)
115
115
 
116
116
  @model_validator(mode="before")
117
- def apply_cmdline_args(self, data: Any) -> Self:
118
- if isinstance(data, dict):
119
- if cmdline_args := data.get("cmdline_args"):
120
- for arg in cmdline_args.items():
121
- self._update_data_for_arg(arg, data)
122
- return self
123
-
124
- def _update_data_for_arg(self, arg: tuple[str, Any], data: dict):
125
- argname, argval = arg
126
- if argname not in self._CMDLINE_ARGS:
117
+ @classmethod
118
+ def apply_cmdline_args(cls, data: Any) -> Any:
119
+ """Apply supplied CLI overrides before validating the configuration."""
120
+ if not isinstance(data, dict):
127
121
  return data
128
- subconfig, variable = self._CMDLINE_ARGS[argname]
129
- if subconfig not in data:
130
- data[subconfig] = {}
131
- data[subconfig][variable] = argval
122
+ data = data.copy()
123
+ for argname, argval in (data.get("cmdline_args") or {}).items():
124
+ if argname not in cls._CMDLINE_ARGS or argval is None:
125
+ continue
126
+ subconfig, variable = cls._CMDLINE_ARGS[argname]
127
+ values = data.get(subconfig, {})
128
+ if isinstance(values, BaseModel):
129
+ values = values.model_dump()
130
+ data[subconfig] = {**values, variable: argval}
132
131
  return data
133
132
 
134
133
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_lib"
7
- version = "4.3.0"
7
+ version = "4.3.2"
8
8
  description = "BEC library"
9
9
  requires-python = ">=3.11"
10
10
  classifiers = [
@@ -408,7 +408,7 @@ def test_parse_cmdline_args_with_config():
408
408
  "file_log_level": None,
409
409
  "redis_log_level": None,
410
410
  "bec_server": None,
411
- "use_subprocess_proc_worker": False,
411
+ "use_subprocess_proc_worker": None,
412
412
  },
413
413
  config_name="server",
414
414
  )
@@ -429,7 +429,7 @@ def test_parse_cmdline_args_with_user():
429
429
  "file_log_level": None,
430
430
  "redis_log_level": None,
431
431
  "bec_server": None,
432
- "use_subprocess_proc_worker": False,
432
+ "use_subprocess_proc_worker": None,
433
433
  },
434
434
  acl={"user": "admin"},
435
435
  config_name="server",
@@ -0,0 +1,39 @@
1
+ """Procedure requests use the authenticated user's personal endpoint."""
2
+
3
+ from unittest.mock import MagicMock
4
+
5
+ from bec_lib.endpoints import MessageEndpoints
6
+ from bec_lib.messages import ProcedureRequestMessage
7
+ from bec_lib.procedures.helper import FrontendProcedureHelper
8
+
9
+
10
+ def test_procedure_submission_tracks_current_acl_user():
11
+ conn = MagicMock()
12
+ conn._managed_connection._redis_conn.connection_pool.connection_kwargs = {"password": "secret"}
13
+ helper = FrontendProcedureHelper(conn)
14
+ for username in ["alice", "bob", "default"]:
15
+ conn.username = username
16
+ status = helper.request.procedure("sleep", args_kwargs=((), {"time_s": 1}))
17
+ endpoint, message = conn.send.call_args.args
18
+ assert endpoint == MessageEndpoints.procedure_request(username)
19
+ assert endpoint.endpoint == f"personal/{username}/procedures/procedure_request"
20
+ assert message.metadata["client_info"]["acl_user"] == username
21
+ assert message.execution_id == status._id
22
+ assert message.args_kwargs == ((), {"time_s": 1})
23
+
24
+
25
+ def test_submission_replaces_untrusted_acl_identity():
26
+ conn = MagicMock()
27
+ conn._managed_connection._redis_conn.connection_pool.connection_kwargs = {"password": "secret"}
28
+ conn.username = "alice"
29
+ helper = FrontendProcedureHelper(conn)
30
+ message = ProcedureRequestMessage(
31
+ identifier="sleep", metadata={"client_info": {"acl_user": "bob"}, "custom": "preserved"}
32
+ )
33
+ helper.request._procedure(message)
34
+ endpoint, submitted = conn.send.call_args.args
35
+ assert endpoint == MessageEndpoints.procedure_request("alice")
36
+ assert submitted.metadata == {
37
+ "client_info": {"acl_user": "alice", "acl_token": "secret"},
38
+ "custom": "preserved",
39
+ }
@@ -0,0 +1,55 @@
1
+ """Regression tests for service configuration CLI overrides."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ import pytest
8
+
9
+ from bec_lib.bec_service import parse_cmdline_args
10
+ from bec_lib.service_config import ProcedureConfig, ServiceConfig, ServiceConfigModel
11
+
12
+
13
+ @pytest.mark.parametrize("configured", [True, False])
14
+ @pytest.mark.parametrize("override", [True, False, None])
15
+ def test_procedure_cli_override(configured, override):
16
+ config = ServiceConfig(
17
+ config={"procedures": {"use_subprocess_worker": configured}},
18
+ cmdline_args={"use_subprocess_proc_worker": override},
19
+ )
20
+ assert config.model.procedures.use_subprocess_worker is (
21
+ configured if override is None else override
22
+ )
23
+
24
+
25
+ def test_cli_override_preserves_config_input():
26
+ data = {"procedures": {"enable_procedures": False, "use_subprocess_worker": False}}
27
+ model = ServiceConfigModel.model_validate(
28
+ data | {"cmdline_args": {"use_subprocess_proc_worker": True, "unknown": True}}
29
+ )
30
+ assert model.procedures.use_subprocess_worker
31
+ assert not model.procedures.enable_procedures
32
+ assert data["procedures"]["use_subprocess_worker"] is False
33
+
34
+
35
+ def test_cli_override_without_procedure_section():
36
+ config = ServiceConfigModel(cmdline_args={"use_subprocess_proc_worker": True})
37
+ assert config.procedures.use_subprocess_worker
38
+
39
+
40
+ def test_cli_override_with_model_section():
41
+ config = ServiceConfigModel(
42
+ procedures=ProcedureConfig(enable_procedures=False),
43
+ cmdline_args={"use_subprocess_proc_worker": True},
44
+ )
45
+ assert config.procedures.use_subprocess_worker
46
+ assert not config.procedures.enable_procedures
47
+
48
+
49
+ @pytest.mark.parametrize("flag", [[], ["--use-subprocess-proc-worker"]])
50
+ def test_procedure_worker_cli_selection(monkeypatch, tmp_path, flag):
51
+ path = tmp_path / "server.yaml"
52
+ path.write_text("procedures:\n use_subprocess_worker: true\n")
53
+ monkeypatch.setattr(sys, "argv", ["scan-server", "--config", str(path), *flag])
54
+ _, _, config = parse_cmdline_args()
55
+ assert config.model.procedures.use_subprocess_worker
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
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
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