Skip to content

Commit a5c4a85

Browse files
committed
f
1 parent 0d03379 commit a5c4a85

5 files changed

Lines changed: 124 additions & 10 deletions

File tree

bec_lib/bec_lib/utils/scan_utils.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
import csv
88
import datetime
99
from collections import defaultdict
10-
from typing import TYPE_CHECKING
10+
from collections.abc import Mapping
11+
from typing import TYPE_CHECKING, Any
1112

1213
from typeguard import typechecked
1314

@@ -62,6 +63,46 @@ def scan_to_csv(
6263
_write_csv(output_name=output_name, delimiter=delimiter, dialect=dialect, output=data_output)
6364

6465

66+
def compose_cli_input_from_scan_info(scan_info: Mapping[str, Any] | Any) -> str:
67+
"""Compose a user-facing scan call string from a scan info object."""
68+
scan_name = _get_scan_info_value(scan_info, "scan_name")
69+
if not scan_name:
70+
return "scans.unknown()"
71+
72+
request_inputs = _get_scan_info_value(scan_info, "request_inputs") or {}
73+
arg_bundle = list(request_inputs.get("arg_bundle") or [])
74+
inputs = request_inputs.get("inputs") or {}
75+
kwargs = request_inputs.get("kwargs") or {}
76+
77+
call_parts: list[str] = [_render_scan_cli_value(value) for value in arg_bundle]
78+
call_parts.extend(f"{name}={_render_scan_cli_value(value)}" for name, value in inputs.items())
79+
call_parts.extend(f"{name}={_render_scan_cli_value(value)}" for name, value in kwargs.items())
80+
return f"scans.{scan_name}({', '.join(call_parts)})"
81+
82+
83+
def _get_scan_info_value(scan_info: Mapping[str, Any] | Any, key: str) -> Any:
84+
if isinstance(scan_info, Mapping):
85+
return scan_info.get(key)
86+
return getattr(scan_info, key, None)
87+
88+
89+
def _render_scan_cli_value(value: Any) -> str:
90+
if isinstance(value, Mapping):
91+
rendered_items = ", ".join(
92+
f"{_render_scan_cli_value(key)}: {_render_scan_cli_value(val)}"
93+
for key, val in value.items()
94+
)
95+
return "{" + rendered_items + "}"
96+
if isinstance(value, tuple):
97+
inner = ", ".join(_render_scan_cli_value(item) for item in value)
98+
if len(value) == 1:
99+
inner += ","
100+
return f"({inner})"
101+
if isinstance(value, list):
102+
return "[" + ", ".join(_render_scan_cli_value(item) for item in value) + "]"
103+
return repr(value)
104+
105+
65106
def _write_csv(output_name: str, delimiter: str, output: list, dialect: str = None) -> None:
66107
"""Write csv file.
67108

bec_server/bec_server/scan_server/generator_scan_worker.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from bec_lib.file_utils import compile_file_components
1313
from bec_lib.logger import bec_logger
1414
from bec_lib.messaging_hooks import MessagingEvent
15+
from bec_lib.utils.scan_utils import compose_cli_input_from_scan_info
1516

1617
from .errors import DeviceInstructionError, ScanAbortion, UserScanInterruption
1718
from .scan_queue import InstructionQueueItem, InstructionQueueStatus, RequestBlock
@@ -342,13 +343,20 @@ def _send_scan_status(
342343
self.worker.device_manager.connector.set_and_publish(
343344
MessageEndpoints.scan_status(), msg, pipe=pipe
344345
)
346+
cli_input = compose_cli_input_from_scan_info(self.current_scan_info)
347+
scan_number = self.current_scan_info.get("scan_number")
348+
scan_id = self.current_scan_id
345349
if status == "open":
346350
self.worker.device_manager.connector.notify(
347-
MessagingEvent.SCAN, f"Scan started: {self.current_scan_id}", pipe=pipe
351+
MessagingEvent.SCAN,
352+
f"Scan started: scan_number={scan_number} ({cli_input}, scan_id={scan_id})",
353+
pipe=pipe,
348354
)
349355
elif status in {"closed", "user_completed"}:
350356
self.worker.device_manager.connector.notify(
351-
MessagingEvent.SCAN_COMPLETED, f"Scan completed: {self.current_scan_id}", pipe=pipe
357+
MessagingEvent.SCAN_COMPLETED,
358+
f"Scan completed: scan_number={scan_number} ({cli_input}, scan_id={scan_id})",
359+
pipe=pipe,
352360
)
353361
pipe.execute()
354362

bec_server/bec_server/scan_server/scans/scan_actions.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from bec_lib.file_utils import compile_file_components
1616
from bec_lib.logger import bec_logger
1717
from bec_lib.messaging_hooks import MessagingEvent
18+
from bec_lib.utils.scan_utils import compose_cli_input_from_scan_info
1819
from bec_server.scan_server.scan_stubs import ScanStubStatus
1920

2021
if TYPE_CHECKING:
@@ -1063,14 +1064,19 @@ def _send_scan_status(
10631064
MessageEndpoints.public_scan_info(scan.scan_info.scan_id), msg, pipe=pipe, expire=expire
10641065
)
10651066
self._connector.set_and_publish(MessageEndpoints.scan_status(), msg, pipe=pipe)
1067+
cli_input = compose_cli_input_from_scan_info(scan.scan_info)
1068+
scan_number = scan.scan_info.scan_number
1069+
scan_id = scan.scan_info.scan_id
10661070
if status == "open":
10671071
self._connector.notify(
1068-
MessagingEvent.SCAN, f"Scan started: {scan.scan_info.scan_id}", pipe=pipe
1072+
MessagingEvent.SCAN,
1073+
f"Scan started: scan_number={scan_number} ({cli_input}, scan_id={scan_id})",
1074+
pipe=pipe,
10691075
)
10701076
elif status in {"closed", "user_completed"}:
10711077
self._connector.notify(
10721078
MessagingEvent.SCAN_COMPLETED,
1073-
f"Scan completed: {scan.scan_info.scan_id}",
1079+
f"Scan completed: scan_number={scan_number} ({cli_input}, scan_id={scan_id})",
10741080
pipe=pipe,
10751081
)
10761082
pipe.execute()

bec_server/tests/tests_scan_server/scans_v4/test_scan_actions.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from bec_lib.messaging_hooks import MessagingEvent
1212
from bec_lib.tests.fixtures import dm_with_devices # noqa: F401
1313
from bec_lib.tests.utils import ConnectorMock
14+
from bec_lib.utils.scan_utils import compose_cli_input_from_scan_info
1415
from bec_server.scan_server.instruction_handler import InstructionHandler
1516
from bec_server.scan_server.scan_stubs import ScanStubStatus
1617
from bec_server.scan_server.scans.scan_base import ScanBase, ScanInfo
@@ -107,6 +108,40 @@ def test_scan_info_stores_scan_report_device_objects_as_names(dm_with_devices):
107108
assert scan_info.scan_report_devices == ["samz"]
108109

109110

111+
def test_compose_cli_input_from_scan_info_uses_named_inputs():
112+
scan_info = ScanInfo(
113+
scan_name="_v4_test_scan",
114+
scan_id="scan-id-test",
115+
scan_type=None,
116+
request_inputs={
117+
"arg_bundle": [],
118+
"inputs": {"device": "samx", "target": 1},
119+
"kwargs": {"relative": True},
120+
},
121+
)
122+
123+
assert (
124+
compose_cli_input_from_scan_info(scan_info)
125+
== "scans._v4_test_scan(device='samx', target=1, relative=True)"
126+
)
127+
128+
129+
def test_compose_cli_input_from_scan_info_uses_arg_bundle():
130+
scan_info = {
131+
"scan_name": "line_scan",
132+
"request_inputs": {
133+
"arg_bundle": ["samx", -5, 5, 3],
134+
"inputs": {},
135+
"kwargs": {"exp_time": 0.1},
136+
},
137+
}
138+
139+
assert (
140+
compose_cli_input_from_scan_info(scan_info)
141+
== "scans.line_scan('samx', -5, 5, 3, exp_time=0.1)"
142+
)
143+
144+
110145
def _last_device_instruction(ctx, action):
111146
return _sent_device_instructions(ctx, action)[-1]
112147

@@ -531,6 +566,7 @@ def test_send_scan_status_publishes_message(action_context):
531566
ctx.connector.notify = mock.MagicMock()
532567
status_msg = messages.ScanStatusMessage(scan_id="scan-id-test", status="closed", info={})
533568
ctx.actions._build_scan_status_message = mock.MagicMock(return_value=status_msg)
569+
ctx.scan.scan_info.request_inputs = {"arg_bundle": [], "inputs": {}, "kwargs": {}}
534570

535571
ctx.actions._send_scan_status("closed", reason="alarm")
536572

@@ -542,7 +578,9 @@ def test_send_scan_status_publishes_message(action_context):
542578
MessageEndpoints.scan_status(), status_msg, pipe=pipe
543579
)
544580
ctx.connector.notify.assert_called_once_with(
545-
MessagingEvent.SCAN_COMPLETED, "Scan completed: scan-id-test", pipe=pipe
581+
MessagingEvent.SCAN_COMPLETED,
582+
"Scan completed: scan_number=1 (scans._v4_test_scan(), scan_id=scan-id-test)",
583+
pipe=pipe,
546584
)
547585
pipe.execute.assert_called_once_with()
548586

@@ -556,11 +594,18 @@ def test_send_scan_status_publishes_new_scan_notification(action_context):
556594
ctx.connector.notify = mock.MagicMock()
557595
status_msg = messages.ScanStatusMessage(scan_id="scan-id-test", status="open", info={})
558596
ctx.actions._build_scan_status_message = mock.MagicMock(return_value=status_msg)
597+
ctx.scan.scan_info.request_inputs = {
598+
"arg_bundle": [],
599+
"inputs": {"device": "samx"},
600+
"kwargs": {},
601+
}
559602

560603
ctx.actions._send_scan_status("open")
561604

562605
ctx.connector.notify.assert_called_once_with(
563-
MessagingEvent.SCAN, "Scan started: scan-id-test", pipe=pipe
606+
MessagingEvent.SCAN,
607+
"Scan started: scan_number=1 (scans._v4_test_scan(device='samx'), scan_id=scan-id-test)",
608+
pipe=pipe,
564609
)
565610

566611

bec_server/tests/tests_scan_server/test_generator_scan_worker.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,15 @@ def test_send_scan_status(generator_worker_mock, status, expire):
403403
worker = generator_worker_mock
404404
worker.worker.device_manager.connector = ConnectorMock()
405405
worker.current_scan_id = str(uuid.uuid4())
406-
worker.current_scan_info = {"scan_number": 5}
406+
worker.current_scan_info = {
407+
"scan_number": 5,
408+
"scan_name": "line_scan",
409+
"request_inputs": {
410+
"arg_bundle": ["samx", -5, 5, 3],
411+
"inputs": {},
412+
"kwargs": {"exp_time": 0.1},
413+
},
414+
}
407415
worker._send_scan_status(status)
408416
scan_info_msgs = [
409417
msg
@@ -423,10 +431,16 @@ def test_send_scan_status(generator_worker_mock, status, expire):
423431
]
424432
if status == "open":
425433
assert len(notification_msgs) == 1
426-
assert notification_msgs[0]["msg"].message == f"Scan started: {worker.current_scan_id}"
434+
assert (
435+
notification_msgs[0]["msg"].message == "Scan started: scan_number=5 "
436+
f"(scans.line_scan('samx', -5, 5, 3, exp_time=0.1), scan_id={worker.current_scan_id})"
437+
)
427438
elif status == "closed":
428439
assert len(notification_msgs) == 1
429-
assert notification_msgs[0]["msg"].message == f"Scan completed: {worker.current_scan_id}"
440+
assert (
441+
notification_msgs[0]["msg"].message == "Scan completed: scan_number=5 "
442+
f"(scans.line_scan('samx', -5, 5, 3, exp_time=0.1), scan_id={worker.current_scan_id})"
443+
)
430444
else:
431445
assert notification_msgs == []
432446

0 commit comments

Comments
 (0)