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.
- {bec_lib-4.3.0 → bec_lib-4.3.2}/PKG-INFO +1 -1
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/bec_service.py +2 -2
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/endpoints.py +33 -3
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/messages.py +6 -1
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/procedures/helper.py +34 -2
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/service_config.py +14 -15
- {bec_lib-4.3.0 → bec_lib-4.3.2}/pyproject.toml +1 -1
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_bec_service.py +2 -2
- bec_lib-4.3.2/tests/test_procedure_submission.py +39 -0
- bec_lib-4.3.2/tests/test_service_config.py +55 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/.gitignore +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/README.md +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/__init__.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/_print_versions.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/acl_login.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/actors.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/alarm_handler.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/atlas_models.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/bec_errors.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/bec_yaml_loader.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/bl_state_manager.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/bl_states.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/builtin_actor_hli.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/callback_handler.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/channel_monitor.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/client.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/codecs.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/config_helper.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/config_values.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/configs/__init__.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/configs/demo_config.yaml +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/configs/openapi_schema.json +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/connector.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/dap_plugin_objects.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/dap_plugins.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/device.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/devicemanager.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/file_utils.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/live_scan_data.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/lmfit_serializer.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/logger.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/macro_update_handler.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/messaging_hooks.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/messaging_services.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/metadata_schema.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/numpy_encoder.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/observer.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/path_optimization.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/pdf_writer.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/plugin_helper.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/position_generators.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/procedures/__init__.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/procedures/hli.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/procedures/status.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/queue_items.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/__init__.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/constants.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/hli.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/managed_redis_connection.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/streams.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/redis_connector/validation.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/request_context.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/request_items.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_args.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_data_container.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_history.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_input_validator.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_items.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_manager.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_number_container.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_preview.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_repeat.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scan_report.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scans.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/scibec_validator.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/script_executor.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/serialization.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/serialization_registry.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/signature_serializer.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/tests/__init__.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/tests/fixtures.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/tests/test_config.yaml +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/tests/test_service_config.yaml +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/tests/utils.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/user_macros.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/__init__.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/copier_jinja_filters.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/deprecation.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/import_utils.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/json_extended.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/__init__.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/_constants.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/_util.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/create/__init__.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/create/device.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/create/scan.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/create/templates/scan.py.jinja +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/plugin_manager/main.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/proxy.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/pydantic_pretty_print.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/rpc_utils.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/scan_utils.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/threading_utils.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/bec_lib/utils/user_acls_test.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/__init__.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/_additional_for_signature_serializer_test.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/conftest.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_acl_login.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_alarm_handler.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_atlas_models.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_beamline_states.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_bec_logger.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_bec_messages.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_callback_handler.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_channel_monitor.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_client.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_config_helper.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_core_utils.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_dap_plugins.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_data_container.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_deprecation.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_device_hashing.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_device_manager.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_devices.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_file_utils.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_hli_connector_fakeredis.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_hli_redis_connector.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_import_utils.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_jinja_plugins.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_lmfit_serializer.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_managed_config_values.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_managed_redis_connection.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_messaging_hooks.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_messaging_service.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_metadata_schema.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_observer.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_path_optimization.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_pdf_writer.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_plugin_helper.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_plugin_manager.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_plugin_system.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_position_generators.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_pydantic_pretty_print.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_queue_items.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_redis_connector_fakeredis.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_context.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_data.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_history.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_items.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_manager.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_object.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_preview.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_repeat.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scan_report.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_scibec_validator.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_script_executor.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_serializer.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_signature_serializer.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_signature_serializer_with_future_import.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_stream_subs.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_user_macros.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_utils.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/tests/test_yaml_loader.py +0 -0
- {bec_lib-4.3.0 → bec_lib-4.3.2}/util_scripts/init_config.py +0 -0
|
@@ -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=
|
|
87
|
-
help="Use the
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
|
@@ -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":
|
|
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":
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|