bec-lib 4.1.7__tar.gz → 4.2.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.
- {bec_lib-4.1.7 → bec_lib-4.2.0}/PKG-INFO +1 -1
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/endpoints.py +17 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/messages.py +172 -5
- {bec_lib-4.1.7 → bec_lib-4.2.0}/pyproject.toml +1 -1
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_bec_messages.py +295 -1
- {bec_lib-4.1.7 → bec_lib-4.2.0}/.gitignore +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/README.md +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/__init__.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/_print_versions.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/acl_login.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/actors.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/alarm_handler.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/atlas_models.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/bec_errors.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/bec_service.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/bec_yaml_loader.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/bl_state_manager.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/bl_states.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/builtin_actor_hli.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/callback_handler.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/channel_monitor.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/client.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/codecs.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/config_helper.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/config_values.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/configs/__init__.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/configs/demo_config.yaml +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/configs/openapi_schema.json +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/connector.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/dap_plugin_objects.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/dap_plugins.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/device.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/devicemanager.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/file_utils.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/live_scan_data.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/lmfit_serializer.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/logger.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/macro_update_handler.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/messaging_hooks.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/messaging_services.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/metadata_schema.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/numpy_encoder.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/observer.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/pdf_writer.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/plugin_helper.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/procedures/__init__.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/procedures/helper.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/procedures/hli.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/procedures/status.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/queue_items.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/redis_connector/__init__.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/redis_connector/constants.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/redis_connector/hli.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/redis_connector/managed_redis_connection.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/redis_connector/streams.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/redis_connector/validation.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/request_context.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/request_items.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/scan_args.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/scan_data_container.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/scan_history.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/scan_input_validator.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/scan_items.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/scan_manager.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/scan_number_container.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/scan_repeat.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/scan_report.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/scans.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/scibec_validator.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/script_executor.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/serialization.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/serialization_registry.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/service_config.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/signature_serializer.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/tests/__init__.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/tests/fixtures.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/tests/test_config.yaml +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/tests/test_service_config.yaml +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/tests/utils.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/user_macros.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/__init__.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/copier_jinja_filters.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/deprecation.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/import_utils.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/json_extended.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/plugin_manager/__init__.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/plugin_manager/_constants.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/plugin_manager/_util.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/plugin_manager/create/__init__.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/plugin_manager/create/device.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/plugin_manager/create/scan.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/plugin_manager/create/templates/scan.py.jinja +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/plugin_manager/main.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/proxy.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/pydantic_pretty_print.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/rpc_utils.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/scan_utils.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/threading_utils.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/bec_lib/utils/user_acls_test.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/__init__.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/_additional_for_signature_serializer_test.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/conftest.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_acl_login.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_alarm_handler.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_atlas_models.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_beamline_states.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_bec_logger.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_bec_service.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_callback_handler.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_channel_monitor.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_client.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_config_helper.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_core_utils.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_dap_plugins.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_data_container.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_deprecation.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_device_hashing.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_device_manager.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_devices.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_file_utils.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_hli_connector_fakeredis.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_hli_redis_connector.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_import_utils.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_jinja_plugins.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_lmfit_serializer.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_managed_config_values.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_managed_redis_connection.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_messaging_hooks.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_messaging_service.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_metadata_schema.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_observer.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_pdf_writer.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_plugin_helper.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_plugin_manager.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_plugin_system.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_pydantic_pretty_print.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_queue_items.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_redis_connector_fakeredis.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_scan_context.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_scan_data.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_scan_history.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_scan_items.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_scan_manager.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_scan_object.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_scan_repeat.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_scan_report.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_scibec_validator.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_script_executor.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_serializer.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_signature_serializer.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_signature_serializer_with_future_import.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_stream_subs.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_user_macros.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_utils.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/tests/test_yaml_loader.py +0 -0
- {bec_lib-4.1.7 → bec_lib-4.2.0}/util_scripts/init_config.py +0 -0
|
@@ -361,6 +361,23 @@ class MessageEndpoints:
|
|
|
361
361
|
message_op=MessageOp.KEY_VALUE,
|
|
362
362
|
)
|
|
363
363
|
|
|
364
|
+
@staticmethod
|
|
365
|
+
def scan_device_info() -> EndpointInfo:
|
|
366
|
+
"""
|
|
367
|
+
Endpoint for scan device runtime information. The device server publishes
|
|
368
|
+
messages.ScanDeviceInfoMessage snapshots for all scans to this shared stream.
|
|
369
|
+
Consumers should use scan_id to associate device information with a scan.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
EndpointInfo: Shared stream endpoint for scan device runtime information.
|
|
373
|
+
"""
|
|
374
|
+
endpoint = f"{EndpointType.INFO.value}/scan_device_info"
|
|
375
|
+
return EndpointInfo(
|
|
376
|
+
endpoint=endpoint,
|
|
377
|
+
message_type=messages.ScanDeviceInfoMessage,
|
|
378
|
+
message_op=MessageOp.STREAM,
|
|
379
|
+
)
|
|
380
|
+
|
|
364
381
|
@staticmethod
|
|
365
382
|
def device_staged(device: str):
|
|
366
383
|
"""
|
|
@@ -24,9 +24,18 @@ from typing import (
|
|
|
24
24
|
from uuid import uuid4
|
|
25
25
|
|
|
26
26
|
import numpy as np
|
|
27
|
-
from pydantic import
|
|
27
|
+
from pydantic import (
|
|
28
|
+
AliasChoices,
|
|
29
|
+
BaseModel,
|
|
30
|
+
ConfigDict,
|
|
31
|
+
Field,
|
|
32
|
+
ValidationError,
|
|
33
|
+
field_validator,
|
|
34
|
+
model_validator,
|
|
35
|
+
)
|
|
28
36
|
from typing_extensions import deprecated
|
|
29
37
|
|
|
38
|
+
from bec_lib.endpoints import EndpointInfo, MessageOp
|
|
30
39
|
from bec_lib.metadata_schema import get_metadata_schema_for_scan
|
|
31
40
|
|
|
32
41
|
|
|
@@ -606,6 +615,7 @@ DeviceInstructionAction = Literal[
|
|
|
606
615
|
"baseline_reading",
|
|
607
616
|
"close_scan",
|
|
608
617
|
"publish_data_as_read",
|
|
618
|
+
"broadcast_scan_device_info",
|
|
609
619
|
]
|
|
610
620
|
|
|
611
621
|
|
|
@@ -629,6 +639,7 @@ class DeviceInstructionMessage(BECMessage):
|
|
|
629
639
|
"baseline_reading",
|
|
630
640
|
"close_scan",
|
|
631
641
|
"publish_data_as_read",
|
|
642
|
+
"broadcast_scan_device_info",
|
|
632
643
|
]) : Device action, note rpc calls can run any method of the device. The function name needs to be specified in parameters['func']
|
|
633
644
|
parameter (dict): Parameters required for the device action
|
|
634
645
|
metadata (dict, optional): Metadata to describe the conditions of the device instruction
|
|
@@ -880,10 +891,91 @@ class DeviceInfoMessage(BECMessage):
|
|
|
880
891
|
info: dict
|
|
881
892
|
|
|
882
893
|
|
|
894
|
+
class RedisSignalTransport(BaseModel):
|
|
895
|
+
"""Redis subscription described by a resolved ``EndpointInfo``.
|
|
896
|
+
|
|
897
|
+
Consumers use their existing Redis connection. Endpoint strings are obtained
|
|
898
|
+
from ``MessageEndpoints`` helpers rather than constructed by callers.
|
|
899
|
+
"""
|
|
900
|
+
|
|
901
|
+
transport: Literal["redis"] = "redis"
|
|
902
|
+
endpoint: str = Field(min_length=1, description="Resolved Redis endpoint.")
|
|
903
|
+
message_type: str = Field(min_length=1, description="Name of the BEC message class.")
|
|
904
|
+
message_op: MessageOp = Field(description="Supported Redis endpoint operations.")
|
|
905
|
+
|
|
906
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
907
|
+
|
|
908
|
+
@classmethod
|
|
909
|
+
def from_endpoint_info(cls, endpoint_info: EndpointInfo) -> Self:
|
|
910
|
+
"""Build a Redis transport descriptor from a message endpoint.
|
|
911
|
+
|
|
912
|
+
Args:
|
|
913
|
+
endpoint_info (EndpointInfo): Endpoint returned by a ``MessageEndpoints`` helper.
|
|
914
|
+
|
|
915
|
+
Returns:
|
|
916
|
+
Self: Transport descriptor containing the endpoint's subscription information.
|
|
917
|
+
"""
|
|
918
|
+
return cls(
|
|
919
|
+
endpoint=endpoint_info.endpoint,
|
|
920
|
+
message_type=endpoint_info.message_type.__name__,
|
|
921
|
+
message_op=endpoint_info.message_op,
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
class ZMQSignalTransport(BaseModel):
|
|
926
|
+
"""ZMQ image subscription described from the receiving client's perspective.
|
|
927
|
+
|
|
928
|
+
The address is a peer URI when connecting, or a local listening URI when
|
|
929
|
+
binding. The encoder identifies the complete wire format, including whether
|
|
930
|
+
a separate topic frame is present, rather than just JSON or CBOR encoding.
|
|
931
|
+
"""
|
|
932
|
+
|
|
933
|
+
transport: Literal["zmq"] = "zmq"
|
|
934
|
+
address: str = Field(min_length=1, description="ZMQ URI to connect to or bind locally.")
|
|
935
|
+
socket_type: Literal["sub", "pull"] = Field(
|
|
936
|
+
default="sub", description="Client socket type: SUB for broadcast or PULL for a pipeline."
|
|
937
|
+
)
|
|
938
|
+
connection_mode: Literal["connect", "bind"] = Field(
|
|
939
|
+
default="connect", description="Whether the client connects to a peer or binds locally."
|
|
940
|
+
)
|
|
941
|
+
topic: str | None = Field(
|
|
942
|
+
default=None,
|
|
943
|
+
description="UTF-8 subscription prefix for SUB sockets. None means no named topic "
|
|
944
|
+
"and clients subscribe with an empty filter; an empty string also selects all messages. "
|
|
945
|
+
"PULL sockets must leave this unset. The encoder defines the topic framing.",
|
|
946
|
+
)
|
|
947
|
+
encoder: str = Field(
|
|
948
|
+
min_length=1, description="Identifier of the complete payload wire format."
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
952
|
+
|
|
953
|
+
@model_validator(mode="before")
|
|
954
|
+
@classmethod
|
|
955
|
+
def validate_topic_socket_type(cls, value: Any) -> Any:
|
|
956
|
+
"""Reject subscription topics for PULL sockets, including on assignment."""
|
|
957
|
+
if (
|
|
958
|
+
isinstance(value, Mapping)
|
|
959
|
+
and value.get("socket_type") == "pull"
|
|
960
|
+
and value.get("topic") is not None
|
|
961
|
+
):
|
|
962
|
+
raise ValueError("Subscription topics are only supported for SUB sockets.")
|
|
963
|
+
return value
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
SignalTransport = Annotated[
|
|
967
|
+
RedisSignalTransport | ZMQSignalTransport, Field(discriminator="transport")
|
|
968
|
+
]
|
|
969
|
+
|
|
970
|
+
|
|
883
971
|
class SignalInfo(BaseModel):
|
|
884
972
|
"""
|
|
885
973
|
Base class for signal information.
|
|
886
974
|
This is used to store metadata about the signal.
|
|
975
|
+
|
|
976
|
+
Use ``correlation_group`` to group related signals. ``acquisition_group`` is
|
|
977
|
+
a deprecated input and attribute alias excluded from serialized messages.
|
|
978
|
+
If both names are supplied, ``correlation_group`` takes precedence.
|
|
887
979
|
"""
|
|
888
980
|
|
|
889
981
|
data_type: Literal["raw", "processed"] = Field(
|
|
@@ -919,20 +1011,95 @@ class SignalInfo(BaseModel):
|
|
|
919
1011
|
default=None,
|
|
920
1012
|
description="Metadata for the signal, which can include additional information about the signal's properties.",
|
|
921
1013
|
)
|
|
922
|
-
|
|
1014
|
+
correlation_group: Literal["baseline", "monitored"] | str | None = Field(
|
|
923
1015
|
default=None,
|
|
924
|
-
|
|
1016
|
+
validation_alias=AliasChoices("correlation_group", "acquisition_group"),
|
|
1017
|
+
description="""Specifies the correlation group of the signal.
|
|
925
1018
|
It can be in sync with 'baseline' or 'monitored' groups mapping readoutPriority.
|
|
926
|
-
Or mapped to a custom tag that allows grouping signals for
|
|
927
|
-
If None, the signal does not belong to any specific
|
|
1019
|
+
Or mapped to a custom tag that allows grouping signals for correlation and plotting.
|
|
1020
|
+
If None, the signal does not belong to any specific correlation group.
|
|
928
1021
|
""",
|
|
929
1022
|
)
|
|
1023
|
+
acquisition_group: Literal["baseline", "monitored"] | str | None = Field(
|
|
1024
|
+
default=None,
|
|
1025
|
+
deprecated=deprecated("acquisition_group is deprecated, use correlation_group instead"),
|
|
1026
|
+
description="Deprecated input and attribute alias for correlation_group.",
|
|
1027
|
+
repr=False,
|
|
1028
|
+
exclude=True,
|
|
1029
|
+
)
|
|
930
1030
|
use_alias: bool = Field(
|
|
931
1031
|
default=False, description="Whether the signal aliases one or more EPICS signals."
|
|
932
1032
|
)
|
|
933
1033
|
|
|
1034
|
+
transports: list[SignalTransport] | None = Field(
|
|
1035
|
+
default=None,
|
|
1036
|
+
description="Available subscriptions for this signal, populated by the publisher. "
|
|
1037
|
+
"None means transport metadata is unavailable; consumers retain their existing routing. "
|
|
1038
|
+
"Clients choose a supported transport according to their own preferences.",
|
|
1039
|
+
)
|
|
1040
|
+
|
|
934
1041
|
model_config = ConfigDict(validate_assignment=True)
|
|
935
1042
|
|
|
1043
|
+
@model_validator(mode="before")
|
|
1044
|
+
@classmethod
|
|
1045
|
+
def warn_deprecated_acquisition_group(cls, value: Any) -> Any:
|
|
1046
|
+
"""Warn for legacy input; the canonical name takes precedence when both are supplied."""
|
|
1047
|
+
if (
|
|
1048
|
+
isinstance(value, Mapping)
|
|
1049
|
+
and "acquisition_group" in value
|
|
1050
|
+
and "correlation_group" not in value
|
|
1051
|
+
):
|
|
1052
|
+
warnings.warn(
|
|
1053
|
+
"acquisition_group is deprecated, use correlation_group instead",
|
|
1054
|
+
DeprecationWarning,
|
|
1055
|
+
stacklevel=2,
|
|
1056
|
+
)
|
|
1057
|
+
return value
|
|
1058
|
+
|
|
1059
|
+
@model_validator(mode="after")
|
|
1060
|
+
def synchronize_acquisition_group(self) -> Self:
|
|
1061
|
+
"""Keep the deprecated attribute synchronized with the canonical value."""
|
|
1062
|
+
self.__dict__["acquisition_group"] = self.correlation_group
|
|
1063
|
+
return self
|
|
1064
|
+
|
|
1065
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
1066
|
+
if name == "acquisition_group":
|
|
1067
|
+
warnings.warn(
|
|
1068
|
+
"acquisition_group is deprecated, use correlation_group instead",
|
|
1069
|
+
DeprecationWarning,
|
|
1070
|
+
stacklevel=2,
|
|
1071
|
+
)
|
|
1072
|
+
name = "correlation_group"
|
|
1073
|
+
super().__setattr__(name, value)
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
class DeviceRuntimeInfo(BaseModel):
|
|
1077
|
+
"""Runtime information for a device, captured for a scan.
|
|
1078
|
+
|
|
1079
|
+
Args:
|
|
1080
|
+
signal_info (dict[str, SignalInfo]): BEC signal information keyed by dotted component
|
|
1081
|
+
name. Defaults to an empty dictionary for devices without BEC signals.
|
|
1082
|
+
disabled_signals (list[str]): Dotted component names of signals to ignore for this
|
|
1083
|
+
scan, relative to this device. Defaults to an empty list.
|
|
1084
|
+
"""
|
|
1085
|
+
|
|
1086
|
+
signal_info: dict[str, SignalInfo] = Field(default_factory=dict)
|
|
1087
|
+
disabled_signals: list[str] = Field(default_factory=list)
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
class ScanDeviceInfoMessage(BECMessage):
|
|
1091
|
+
"""Snapshot of device runtime information for a scan.
|
|
1092
|
+
|
|
1093
|
+
Args:
|
|
1094
|
+
scan_id (str): Scan ID that the snapshot belongs to.
|
|
1095
|
+
devices (dict[str, DeviceRuntimeInfo]): Runtime information keyed by device name.
|
|
1096
|
+
metadata (dict, optional): Additional metadata.
|
|
1097
|
+
"""
|
|
1098
|
+
|
|
1099
|
+
msg_type: ClassVar[str] = "scan_device_info_message"
|
|
1100
|
+
scan_id: str
|
|
1101
|
+
devices: dict[str, DeviceRuntimeInfo]
|
|
1102
|
+
|
|
936
1103
|
|
|
937
1104
|
class DeviceMonitor2DMessage(BECMessage):
|
|
938
1105
|
"""Message type for sending device monitor updates from the device server.
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import getpass
|
|
2
|
+
import json
|
|
3
|
+
import warnings
|
|
2
4
|
|
|
3
5
|
import numpy as np
|
|
4
6
|
import pydantic
|
|
5
7
|
import pytest
|
|
6
8
|
|
|
7
9
|
from bec_lib import messages
|
|
10
|
+
from bec_lib.endpoints import MessageEndpoints, MessageOp
|
|
8
11
|
from bec_lib.messaging_services import NotificationMessageObject
|
|
9
12
|
from bec_lib.serialization import MsgpackSerialization
|
|
10
13
|
|
|
@@ -82,6 +85,8 @@ def test_signal_info_defaults_and_roundtrip():
|
|
|
82
85
|
assert info.signals is None
|
|
83
86
|
assert info.signal_metadata is None
|
|
84
87
|
assert info.use_alias is False
|
|
88
|
+
assert info.correlation_group is None
|
|
89
|
+
assert info.transports is None
|
|
85
90
|
|
|
86
91
|
configured = messages.SignalInfo(
|
|
87
92
|
data_type="processed",
|
|
@@ -91,12 +96,242 @@ def test_signal_info_defaults_and_roundtrip():
|
|
|
91
96
|
role="preview",
|
|
92
97
|
signals=[("image", 1)],
|
|
93
98
|
signal_metadata={"units": "counts"},
|
|
94
|
-
|
|
99
|
+
correlation_group="monitored",
|
|
95
100
|
use_alias=True,
|
|
96
101
|
)
|
|
97
102
|
assert messages.SignalInfo.model_validate(configured.model_dump()) == configured
|
|
98
103
|
|
|
99
104
|
|
|
105
|
+
@pytest.mark.parametrize(
|
|
106
|
+
"transports",
|
|
107
|
+
[
|
|
108
|
+
None,
|
|
109
|
+
[],
|
|
110
|
+
[messages.RedisSignalTransport.from_endpoint_info(MessageEndpoints.scan_status())],
|
|
111
|
+
[
|
|
112
|
+
messages.RedisSignalTransport.from_endpoint_info(
|
|
113
|
+
MessageEndpoints.device_preview("detector", "preview")
|
|
114
|
+
).model_dump(mode="json"),
|
|
115
|
+
{
|
|
116
|
+
"transport": "zmq",
|
|
117
|
+
"address": "tcp://detector-host:5555",
|
|
118
|
+
"topic": "detector/preview",
|
|
119
|
+
"encoder": "bec-v1",
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
],
|
|
123
|
+
)
|
|
124
|
+
def test_signal_info_transports_roundtrip(transports):
|
|
125
|
+
info = messages.SignalInfo(transports=transports)
|
|
126
|
+
assert messages.SignalInfo.model_validate_json(info.model_dump_json()) == info
|
|
127
|
+
msg = messages.DeviceInfoMessage(device="detector", info=info.model_dump())
|
|
128
|
+
loaded = MsgpackSerialization.loads(MsgpackSerialization.dumps(msg))
|
|
129
|
+
assert messages.SignalInfo.model_validate(loaded.info) == info
|
|
130
|
+
if transports:
|
|
131
|
+
assert isinstance(info.transports[0], messages.RedisSignalTransport)
|
|
132
|
+
if len(transports) > 1:
|
|
133
|
+
assert isinstance(info.transports[1], messages.ZMQSignalTransport)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@pytest.mark.parametrize(
|
|
137
|
+
"transport",
|
|
138
|
+
[
|
|
139
|
+
{"transport": "unknown"},
|
|
140
|
+
{"transport": "redis"},
|
|
141
|
+
{"transport": "redis", "endpoint": ""},
|
|
142
|
+
{"transport": "redis", "endpoint": "preview", "message_op": MessageOp.STREAM},
|
|
143
|
+
{"transport": "redis", "endpoint": "preview", "message_type": "DevicePreviewMessage"},
|
|
144
|
+
{
|
|
145
|
+
"transport": "redis",
|
|
146
|
+
"endpoint": "preview",
|
|
147
|
+
"message_type": "DevicePreviewMessage",
|
|
148
|
+
"message_op": "invalid",
|
|
149
|
+
},
|
|
150
|
+
{"transport": "zmq", "address": "tcp://localhost:5555", "topic": "preview"},
|
|
151
|
+
{"transport": "zmq", "topic": "preview", "encoder": "bec-v1"},
|
|
152
|
+
{"transport": "zmq", "address": "", "topic": "preview", "encoder": "bec-v1"},
|
|
153
|
+
{"transport": "zmq", "address": "tcp://localhost:5555", "topic": "", "encoder": ""},
|
|
154
|
+
],
|
|
155
|
+
)
|
|
156
|
+
def test_signal_info_transports_reject_invalid_descriptors(transport):
|
|
157
|
+
with pytest.raises(pydantic.ValidationError):
|
|
158
|
+
messages.SignalInfo(transports=[transport])
|
|
159
|
+
info = messages.SignalInfo()
|
|
160
|
+
with pytest.raises(pydantic.ValidationError):
|
|
161
|
+
info.transports = [transport]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@pytest.mark.parametrize(
|
|
165
|
+
"endpoint_info",
|
|
166
|
+
[
|
|
167
|
+
MessageEndpoints.device_preview("detector", "preview"),
|
|
168
|
+
MessageEndpoints.device_readback("samx"),
|
|
169
|
+
MessageEndpoints.scan_status(),
|
|
170
|
+
],
|
|
171
|
+
)
|
|
172
|
+
def test_redis_signal_transport_from_endpoint_info(endpoint_info):
|
|
173
|
+
transport = messages.RedisSignalTransport.from_endpoint_info(endpoint_info)
|
|
174
|
+
assert transport.transport == "redis"
|
|
175
|
+
assert transport.endpoint == endpoint_info.endpoint
|
|
176
|
+
assert transport.message_type == endpoint_info.message_type.__name__
|
|
177
|
+
assert transport.message_op == endpoint_info.message_op
|
|
178
|
+
restored = messages.RedisSignalTransport.model_validate_json(transport.model_dump_json())
|
|
179
|
+
assert restored == transport
|
|
180
|
+
assert restored.message_op is endpoint_info.message_op
|
|
181
|
+
info = messages.SignalInfo(transports=[transport])
|
|
182
|
+
assert messages.SignalInfo.model_validate_json(info.model_dump_json()) == info
|
|
183
|
+
with pytest.raises(pydantic.ValidationError):
|
|
184
|
+
transport.endpoint = ""
|
|
185
|
+
with pytest.raises(pydantic.ValidationError):
|
|
186
|
+
transport.message_op = "invalid"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_zmq_signal_transport_assignment():
|
|
190
|
+
zmq = messages.ZMQSignalTransport(address="ipc:///tmp/bec-preview", topic="", encoder="bec-v1")
|
|
191
|
+
assert zmq.transport == "zmq"
|
|
192
|
+
with pytest.raises(pydantic.ValidationError):
|
|
193
|
+
zmq.encoder = ""
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_zmq_signal_transport_defaults():
|
|
197
|
+
transport = messages.ZMQSignalTransport(address="tcp://localhost:5555", encoder="bec-v1")
|
|
198
|
+
assert transport.socket_type == "sub"
|
|
199
|
+
assert transport.connection_mode == "connect"
|
|
200
|
+
assert transport.topic is None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@pytest.mark.parametrize("socket_type", ["sub", "pull"])
|
|
204
|
+
@pytest.mark.parametrize("connection_mode", ["connect", "bind"])
|
|
205
|
+
def test_zmq_signal_transport_modes_roundtrip(socket_type, connection_mode):
|
|
206
|
+
transport = messages.ZMQSignalTransport(
|
|
207
|
+
address="tcp://localhost:5555",
|
|
208
|
+
socket_type=socket_type,
|
|
209
|
+
connection_mode=connection_mode,
|
|
210
|
+
encoder="dectris-stream-v2" if socket_type == "pull" else "array-1.0",
|
|
211
|
+
)
|
|
212
|
+
info = messages.SignalInfo(transports=[transport])
|
|
213
|
+
assert messages.SignalInfo.model_validate_json(info.model_dump_json()) == info
|
|
214
|
+
msg = messages.DeviceInfoMessage(device="detector", info=info.model_dump())
|
|
215
|
+
loaded = MsgpackSerialization.loads(MsgpackSerialization.dumps(msg))
|
|
216
|
+
assert messages.SignalInfo.model_validate(loaded.info) == info
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@pytest.mark.parametrize("topic", [None, "", "camera/color"])
|
|
220
|
+
def test_zmq_signal_transport_sub_topics(topic):
|
|
221
|
+
transport = messages.ZMQSignalTransport(
|
|
222
|
+
address="tcp://localhost:5555", topic=topic, encoder="camera-stream-v1"
|
|
223
|
+
)
|
|
224
|
+
assert transport.topic == topic
|
|
225
|
+
assert messages.ZMQSignalTransport.model_validate_json(transport.model_dump_json()) == transport
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@pytest.mark.parametrize(
|
|
229
|
+
"invalid_fields",
|
|
230
|
+
[
|
|
231
|
+
{"socket_type": "pub"},
|
|
232
|
+
{"connection_mode": "listen"},
|
|
233
|
+
{"socket_type": "pull", "topic": "camera/color"},
|
|
234
|
+
{"socket_type": "pull", "topic": ""},
|
|
235
|
+
],
|
|
236
|
+
)
|
|
237
|
+
def test_zmq_signal_transport_rejects_invalid_modes(invalid_fields):
|
|
238
|
+
with pytest.raises(pydantic.ValidationError):
|
|
239
|
+
messages.SignalInfo(
|
|
240
|
+
transports=[
|
|
241
|
+
{
|
|
242
|
+
"transport": "zmq",
|
|
243
|
+
"address": "tcp://localhost:5555",
|
|
244
|
+
"encoder": "bec-v1",
|
|
245
|
+
**invalid_fields,
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_zmq_signal_transport_topic_assignment():
|
|
252
|
+
transport = messages.ZMQSignalTransport(
|
|
253
|
+
address="tcp://localhost:5555", topic="preview", encoder="bec-v1"
|
|
254
|
+
)
|
|
255
|
+
with pytest.raises(pydantic.ValidationError):
|
|
256
|
+
transport.socket_type = "pull"
|
|
257
|
+
assert transport.socket_type == "sub"
|
|
258
|
+
transport.topic = None
|
|
259
|
+
transport.socket_type = "pull"
|
|
260
|
+
with pytest.raises(pydantic.ValidationError):
|
|
261
|
+
transport.topic = "preview"
|
|
262
|
+
assert transport.topic is None
|
|
263
|
+
transport.connection_mode = "bind"
|
|
264
|
+
with pytest.raises(pydantic.ValidationError):
|
|
265
|
+
transport.connection_mode = "listen"
|
|
266
|
+
assert transport.connection_mode == "bind"
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@pytest.mark.parametrize("group", [None, "baseline", "monitored", "fly-scan"])
|
|
270
|
+
def test_signal_info_correlation_group_legacy_compatibility(group):
|
|
271
|
+
info = messages.SignalInfo(correlation_group=group)
|
|
272
|
+
assert info.correlation_group == group
|
|
273
|
+
with pytest.warns(DeprecationWarning, match="use correlation_group instead"):
|
|
274
|
+
legacy = messages.SignalInfo(acquisition_group=group)
|
|
275
|
+
assert legacy == info
|
|
276
|
+
with pytest.warns(DeprecationWarning, match="use correlation_group instead"):
|
|
277
|
+
assert info.acquisition_group == group
|
|
278
|
+
payload = info.model_dump()
|
|
279
|
+
assert payload["correlation_group"] == group
|
|
280
|
+
assert "acquisition_group" not in payload
|
|
281
|
+
assert legacy.model_dump() == payload
|
|
282
|
+
assert messages.SignalInfo.model_validate(payload) == info
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_signal_info_correlation_group_assignment():
|
|
286
|
+
info = messages.SignalInfo(correlation_group="baseline")
|
|
287
|
+
with pytest.warns(DeprecationWarning, match="use correlation_group instead"):
|
|
288
|
+
info.acquisition_group = "monitored"
|
|
289
|
+
assert info.correlation_group == "monitored"
|
|
290
|
+
info.correlation_group = "fly-scan"
|
|
291
|
+
with pytest.warns(DeprecationWarning, match="use correlation_group instead"):
|
|
292
|
+
assert info.acquisition_group == "fly-scan"
|
|
293
|
+
with pytest.raises(pydantic.ValidationError):
|
|
294
|
+
info.correlation_group = 42
|
|
295
|
+
with pytest.warns(DeprecationWarning, match="use correlation_group instead"):
|
|
296
|
+
with pytest.raises(pydantic.ValidationError):
|
|
297
|
+
info.acquisition_group = 42
|
|
298
|
+
assert info.correlation_group == "fly-scan"
|
|
299
|
+
with pytest.warns(DeprecationWarning, match="use correlation_group instead"):
|
|
300
|
+
info.acquisition_group = None
|
|
301
|
+
assert info.correlation_group is None
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def test_signal_info_correlation_group_takes_precedence():
|
|
305
|
+
info = messages.SignalInfo(correlation_group=None, acquisition_group="legacy")
|
|
306
|
+
assert info.correlation_group is None
|
|
307
|
+
assert "acquisition_group" not in info.model_dump()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def test_signal_info_acquisition_group_schema_is_deprecated():
|
|
311
|
+
properties = messages.SignalInfo.model_json_schema(mode="validation")["properties"]
|
|
312
|
+
assert properties["acquisition_group"]["deprecated"] is True
|
|
313
|
+
assert not properties["correlation_group"].get("deprecated", False)
|
|
314
|
+
serialized = messages.SignalInfo.model_json_schema(mode="serialization")["properties"]
|
|
315
|
+
assert "acquisition_group" not in serialized
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def test_signal_info_serialization_excludes_deprecated_alias_without_warnings():
|
|
319
|
+
info = messages.SignalInfo(correlation_group="monitored")
|
|
320
|
+
msg = messages.ScanDeviceInfoMessage(
|
|
321
|
+
scan_id="scan-1",
|
|
322
|
+
devices={"detector": messages.DeviceRuntimeInfo(signal_info={"preview": info})},
|
|
323
|
+
)
|
|
324
|
+
with warnings.catch_warnings(record=True) as caught:
|
|
325
|
+
warnings.simplefilter("always")
|
|
326
|
+
payload = info.model_dump()
|
|
327
|
+
assert json.loads(info.model_dump_json()) == payload
|
|
328
|
+
assert "acquisition_group" not in payload
|
|
329
|
+
nested = msg.model_dump()["devices"]["detector"]["signal_info"]["preview"]
|
|
330
|
+
assert nested == payload
|
|
331
|
+
assert MsgpackSerialization.loads(MsgpackSerialization.dumps(msg)) == msg
|
|
332
|
+
assert not caught
|
|
333
|
+
|
|
334
|
+
|
|
100
335
|
def test_signal_info_rejects_invalid_dimensions():
|
|
101
336
|
with pytest.raises(pydantic.ValidationError):
|
|
102
337
|
messages.SignalInfo(ndim=3)
|
|
@@ -112,6 +347,65 @@ def test_signal_info_rejects_invalid_dimension_assignment():
|
|
|
112
347
|
assert info.ndim == 2
|
|
113
348
|
|
|
114
349
|
|
|
350
|
+
def test_scan_device_info_message_roundtrip():
|
|
351
|
+
msg = messages.ScanDeviceInfoMessage(
|
|
352
|
+
scan_id="scan-1",
|
|
353
|
+
devices={
|
|
354
|
+
"eiger": messages.DeviceRuntimeInfo(
|
|
355
|
+
signal_info={
|
|
356
|
+
"preview": messages.SignalInfo(
|
|
357
|
+
data_type="processed",
|
|
358
|
+
saved=False,
|
|
359
|
+
ndim=2,
|
|
360
|
+
scope="continuous",
|
|
361
|
+
role="preview",
|
|
362
|
+
rpc_access=True,
|
|
363
|
+
signals=[("image", 1)],
|
|
364
|
+
signal_metadata={"units": "counts"},
|
|
365
|
+
correlation_group="monitored",
|
|
366
|
+
use_alias=True,
|
|
367
|
+
)
|
|
368
|
+
},
|
|
369
|
+
disabled_signals=["raw_image", "sub.diagnostic"],
|
|
370
|
+
),
|
|
371
|
+
"samx": messages.DeviceRuntimeInfo(),
|
|
372
|
+
},
|
|
373
|
+
metadata={"RID": "rid-1"},
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
res = MsgpackSerialization.dumps(msg)
|
|
377
|
+
res_loaded = MsgpackSerialization.loads(res)
|
|
378
|
+
|
|
379
|
+
assert res_loaded == msg
|
|
380
|
+
assert res_loaded.devices["eiger"].signal_info["preview"].ndim == 2
|
|
381
|
+
assert res_loaded.devices["eiger"].signal_info["preview"].role == "preview"
|
|
382
|
+
assert res_loaded.devices["eiger"].disabled_signals == ["raw_image", "sub.diagnostic"]
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def test_device_runtime_info_defaults_are_independent():
|
|
386
|
+
detector = messages.DeviceRuntimeInfo()
|
|
387
|
+
motor = messages.DeviceRuntimeInfo()
|
|
388
|
+
detector.signal_info["preview"] = messages.SignalInfo(role="preview")
|
|
389
|
+
detector.disabled_signals.append("raw_image")
|
|
390
|
+
assert motor.signal_info == {}
|
|
391
|
+
assert motor.disabled_signals == []
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def test_scan_device_info_validates_nested_signal_info():
|
|
395
|
+
with pytest.raises(pydantic.ValidationError):
|
|
396
|
+
messages.ScanDeviceInfoMessage(
|
|
397
|
+
scan_id="scan-1", devices={"eiger": {"signal_info": {"preview": {"ndim": 3}}}}
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def test_scan_device_info_endpoint_contract():
|
|
402
|
+
endpoint = MessageEndpoints.scan_device_info()
|
|
403
|
+
|
|
404
|
+
assert endpoint.endpoint == "info/scan_device_info"
|
|
405
|
+
assert endpoint.message_type is messages.ScanDeviceInfoMessage
|
|
406
|
+
assert endpoint.message_op == MessageOp.STREAM
|
|
407
|
+
|
|
408
|
+
|
|
115
409
|
def test_bundled_message():
|
|
116
410
|
sub_msg = messages.DeviceMessage(signals={"samx": {"value": 5.2}}, metadata={"RID": "1234"})
|
|
117
411
|
msg = messages.BundleMessage()
|
|
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
|