bec-ipython-client 3.142.0__tar.gz → 3.142.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_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/PKG-INFO +1 -1
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/callbacks/device_progress.py +17 -26
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/callbacks/ipython_live_updates.py +7 -7
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/callbacks/move_device.py +15 -5
- bec_ipython_client-3.142.2/bec_ipython_client/callbacks/utils.py +273 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/pyproject.toml +3 -1
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_device_progress.py +51 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_ipython_live_updates.py +87 -1
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_move_callback.py +164 -0
- bec_ipython_client-3.142.0/bec_ipython_client/callbacks/utils.py +0 -165
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/.gitignore +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/__init__.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/beamline_mixin.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/bec_magics.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/bec_startup.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/callbacks/__init__.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/callbacks/live_table.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/high_level_interfaces/__init__.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/high_level_interfaces/bec_hli.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/high_level_interfaces/spec_hli.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/main.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/plugins/SLS/__init__.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/plugins/SLS/sls_info.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/plugins/XTreme/__init__.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/plugins/XTreme/x-treme.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/plugins/__init__.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/plugins/flomni/flomni_config.yaml +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/prettytable.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/progressbar.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/signals.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/demo.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/conftest.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_beamline_mixins.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_bec_client.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_live_table.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_pretty_table.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_signals.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/conftest.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/end-2-end/_ensure_requirements_container.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/end-2-end/test_actors_e2e.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/end-2-end/test_procedures_e2e.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/end-2-end/test_scans_e2e.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/end-2-end/test_scans_lib_e2e.py +0 -0
- {bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/end-2-end/test_scans_v4_lib_e2e.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import time
|
|
2
2
|
|
|
3
3
|
from bec_ipython_client.progressbar import ScanProgressBar
|
|
4
|
-
from bec_lib.bec_errors import ScanInterruption, ScanRestart
|
|
5
4
|
from bec_lib.endpoints import MessageEndpoints
|
|
6
5
|
from bec_lib.logger import bec_logger
|
|
7
6
|
|
|
8
7
|
from .live_table import LiveUpdatesTable
|
|
8
|
+
from .utils import ScanState, evaluate_scan_state
|
|
9
9
|
|
|
10
10
|
logger = bec_logger.logger
|
|
11
11
|
|
|
@@ -15,33 +15,14 @@ class LiveUpdatesDeviceProgress(LiveUpdatesTable):
|
|
|
15
15
|
|
|
16
16
|
REPORT_TYPE = "device_progress"
|
|
17
17
|
|
|
18
|
-
def _check_scan_state(self) ->
|
|
18
|
+
def _check_scan_state(self) -> ScanState:
|
|
19
19
|
"""Check whether the scan has reached a terminal or exceptional state.
|
|
20
20
|
|
|
21
21
|
Returns:
|
|
22
|
-
|
|
22
|
+
The current scan state outcome for the callback loop.
|
|
23
23
|
"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
restarted_msg = getattr(self.scan_item, "restarted_msg", None)
|
|
28
|
-
if restarted_msg:
|
|
29
|
-
raise ScanRestart(new_scan_msg=restarted_msg)
|
|
30
|
-
|
|
31
|
-
if getattr(self.scan_item, "status", None) == "user_completed":
|
|
32
|
-
print("Scan was set to 'completed' by user.")
|
|
33
|
-
return True
|
|
34
|
-
|
|
35
|
-
status_message = getattr(self.scan_item, "status_message", None)
|
|
36
|
-
if status_message and getattr(status_message, "reason", None) == "user":
|
|
37
|
-
scan_number = getattr(self.scan_item, "scan_number", None)
|
|
38
|
-
if scan_number is None:
|
|
39
|
-
msg = "Scan was aborted by user."
|
|
40
|
-
else:
|
|
41
|
-
msg = f"Scan {scan_number} was aborted by user."
|
|
42
|
-
raise ScanInterruption(msg)
|
|
43
|
-
|
|
44
|
-
return False
|
|
24
|
+
queue = self.scan_item.queue if self.scan_item else None
|
|
25
|
+
return evaluate_scan_state(scan_item=self.scan_item, queue=queue)
|
|
45
26
|
|
|
46
27
|
def core(self):
|
|
47
28
|
"""core function to run the live updates for the table"""
|
|
@@ -73,8 +54,13 @@ class LiveUpdatesDeviceProgress(LiveUpdatesTable):
|
|
|
73
54
|
bool: True if the scan is finished.
|
|
74
55
|
"""
|
|
75
56
|
self.check_alarms()
|
|
76
|
-
|
|
57
|
+
scan_state = self._check_scan_state()
|
|
58
|
+
if scan_state == ScanState.DONE:
|
|
59
|
+
print("Scan was set to 'completed' by user.")
|
|
77
60
|
return True
|
|
61
|
+
if scan_state == ScanState.WAIT:
|
|
62
|
+
time.sleep(0.05)
|
|
63
|
+
return False
|
|
78
64
|
status = self.bec.connector.get(MessageEndpoints.device_progress(device_names[0]))
|
|
79
65
|
if not status:
|
|
80
66
|
logger.trace("waiting for new data point")
|
|
@@ -99,10 +85,15 @@ class LiveUpdatesDeviceProgress(LiveUpdatesTable):
|
|
|
99
85
|
# process sync callbacks
|
|
100
86
|
self.bec.callbacks.poll()
|
|
101
87
|
self.scan_item.poll_callbacks()
|
|
102
|
-
|
|
88
|
+
scan_state = self._check_scan_state()
|
|
89
|
+
if scan_state == ScanState.DONE:
|
|
90
|
+
print("Scan was set to 'completed' by user.")
|
|
103
91
|
return True
|
|
92
|
+
if scan_state == ScanState.WAIT:
|
|
93
|
+
return False
|
|
104
94
|
|
|
105
95
|
done = status.content.get("done")
|
|
106
96
|
if point_id == max_value or done:
|
|
107
97
|
return True
|
|
98
|
+
time.sleep(0.05) # small sleep to avoid busy waiting
|
|
108
99
|
return False
|
|
@@ -16,7 +16,7 @@ from bec_lib.request_context import ActiveRequestContext, active_request_context
|
|
|
16
16
|
|
|
17
17
|
from .live_table import LiveUpdatesTable
|
|
18
18
|
from .move_device import LiveUpdatesReadbackProgressbar
|
|
19
|
-
from .utils import ScanRequestMixin, check_alarms
|
|
19
|
+
from .utils import ScanRequestMixin, ScanState, check_alarms, evaluate_scan_state
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from bec_lib import messages
|
|
@@ -202,7 +202,7 @@ class IPythonLiveUpdates:
|
|
|
202
202
|
self._reset()
|
|
203
203
|
|
|
204
204
|
except ScanRestart as scan_restart:
|
|
205
|
-
self.
|
|
205
|
+
self._reset()
|
|
206
206
|
Console().print("[yellow]Scan restarted[/yellow]")
|
|
207
207
|
request = scan_restart.new_scan_msg
|
|
208
208
|
if request.allow_restart:
|
|
@@ -299,11 +299,11 @@ class IPythonLiveUpdates:
|
|
|
299
299
|
check_alarms(self.client)
|
|
300
300
|
if not queue.request_blocks or not queue.status:
|
|
301
301
|
return False
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
302
|
+
queue_state = evaluate_scan_state(queue=queue)
|
|
303
|
+
if queue_state == ScanState.DONE:
|
|
304
|
+
return True
|
|
305
|
+
if queue_state == ScanState.WAIT:
|
|
306
|
+
return False
|
|
307
307
|
|
|
308
308
|
if queue.queue_position is None:
|
|
309
309
|
return False
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import threading
|
|
4
|
+
import time
|
|
4
5
|
from collections.abc import Callable
|
|
5
6
|
from typing import TYPE_CHECKING, cast
|
|
6
7
|
|
|
@@ -8,11 +9,10 @@ import numpy as np
|
|
|
8
9
|
|
|
9
10
|
from bec_ipython_client.progressbar import DeviceProgressBar
|
|
10
11
|
from bec_lib import messages
|
|
11
|
-
from bec_lib.bec_errors import ScanInterruption
|
|
12
12
|
from bec_lib.endpoints import MessageEndpoints
|
|
13
13
|
from bec_lib.redis_connector import MessageObject
|
|
14
14
|
|
|
15
|
-
from .utils import LiveUpdatesBase, check_alarms
|
|
15
|
+
from .utils import LiveUpdatesBase, ScanState, check_alarms, evaluate_scan_state
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
18
|
from bec_lib.client import BECClient
|
|
@@ -200,10 +200,14 @@ class LiveUpdatesReadbackProgressbar(LiveUpdatesBase):
|
|
|
200
200
|
with DeviceProgressBar(
|
|
201
201
|
self.devices, start_values=start_values, target_values=target_values
|
|
202
202
|
) as progress:
|
|
203
|
-
while
|
|
203
|
+
while True:
|
|
204
204
|
check_alarms(self.bec)
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
scan_state = self._check_scan_state()
|
|
206
|
+
if scan_state == ScanState.WAIT:
|
|
207
|
+
time.sleep(0.05)
|
|
208
|
+
continue
|
|
209
|
+
if progress.finished and data_source.done():
|
|
210
|
+
break
|
|
207
211
|
|
|
208
212
|
values = data_source.get_device_values()
|
|
209
213
|
progress.update(values=values)
|
|
@@ -221,3 +225,9 @@ class LiveUpdatesReadbackProgressbar(LiveUpdatesBase):
|
|
|
221
225
|
def run(self):
|
|
222
226
|
"""run the progressbar."""
|
|
223
227
|
self.core()
|
|
228
|
+
|
|
229
|
+
def _check_scan_state(self) -> ScanState:
|
|
230
|
+
"""Check whether the tracked queue item has entered a terminal stop state."""
|
|
231
|
+
if self.scan_queue_request is None or self.scan_queue_request.queue is None:
|
|
232
|
+
return ScanState.CONTINUE
|
|
233
|
+
return evaluate_scan_state(queue=self.scan_queue_request.queue)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import time
|
|
5
|
+
import traceback
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from bec_lib.bec_errors import ScanInterruption, ScanRestart
|
|
11
|
+
from bec_lib.logger import bec_logger
|
|
12
|
+
from bec_lib.request_items import RequestItem
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from bec_lib import messages
|
|
16
|
+
from bec_lib.client import BECClient
|
|
17
|
+
from bec_lib.queue_items import QueueItem
|
|
18
|
+
from bec_lib.scan_items import ScanItem
|
|
19
|
+
|
|
20
|
+
logger = bec_logger.logger
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ScanRequestError(Exception):
|
|
24
|
+
"""Raised when the server rejects a scan request."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ScanState(Enum):
|
|
28
|
+
"""Outcome of evaluating the current scan or queue state."""
|
|
29
|
+
|
|
30
|
+
CONTINUE = "continue"
|
|
31
|
+
DONE = "done"
|
|
32
|
+
WAIT = "wait"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def check_alarms(bec: BECClient) -> None:
|
|
36
|
+
"""Raise any pending alarms for the active client."""
|
|
37
|
+
bec.alarm_handler.raise_alarms()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def evaluate_scan_state(
|
|
41
|
+
*, scan_item: ScanItem | None = None, queue: QueueItem | None = None
|
|
42
|
+
) -> ScanState:
|
|
43
|
+
"""Evaluate restart, completion, and interruption state for live callbacks.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
scan_item: Scan-centric state, used by callbacks that poll a concrete scan item.
|
|
47
|
+
queue: Queue-centric state, used by callbacks that follow a queue entry directly.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
`ScanState.DONE` when the scan should stop cleanly.
|
|
51
|
+
`ScanState.WAIT` when the callback should keep polling, typically while a restart is in
|
|
52
|
+
progress and the replacement request has not arrived yet.
|
|
53
|
+
`ScanState.CONTINUE` when no terminal state has been reached.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ScanRestart: When a restart message is already available.
|
|
57
|
+
ScanInterruption: When the scan ended in an interruption state.
|
|
58
|
+
"""
|
|
59
|
+
current_scan = scan_item or _latest_scan(queue)
|
|
60
|
+
restarted_msg = _restart_signal(scan_item=scan_item, queue=queue)
|
|
61
|
+
if restarted_msg is not None:
|
|
62
|
+
raise ScanRestart(new_scan_msg=restarted_msg)
|
|
63
|
+
|
|
64
|
+
if getattr(current_scan, "status", None) == "user_completed":
|
|
65
|
+
return ScanState.DONE
|
|
66
|
+
|
|
67
|
+
if queue is not None:
|
|
68
|
+
queue_status = getattr(queue, "status", None)
|
|
69
|
+
if queue_status == "STOPPED" and getattr(queue, "reason", None) == "restart":
|
|
70
|
+
return ScanState.WAIT
|
|
71
|
+
if queue_status in ["STOPPED", "CANCELLED"]:
|
|
72
|
+
raise ScanInterruption(_interruption_message(queue_status, current_scan))
|
|
73
|
+
|
|
74
|
+
status_message = getattr(current_scan, "status_message", None)
|
|
75
|
+
if status_message and getattr(status_message, "reason", None) == "user":
|
|
76
|
+
raise ScanInterruption(_aborted_by_user_message(current_scan))
|
|
77
|
+
|
|
78
|
+
if getattr(current_scan, "status", None) in {"aborted", "halted"}:
|
|
79
|
+
raise ScanInterruption(_interrupted_message(current_scan))
|
|
80
|
+
|
|
81
|
+
return ScanState.CONTINUE
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _restart_signal(
|
|
85
|
+
*, scan_item: ScanItem | None = None, queue: QueueItem | None = None
|
|
86
|
+
) -> messages.ScanQueueMessage | None:
|
|
87
|
+
"""Return the restart message currently associated with the scan, if any."""
|
|
88
|
+
current_scan = scan_item or _latest_scan(queue)
|
|
89
|
+
restarted_msg = getattr(current_scan, "restarted_msg", None)
|
|
90
|
+
if restarted_msg is not None:
|
|
91
|
+
return restarted_msg
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _latest_scan(queue: QueueItem | None) -> ScanItem | None:
|
|
96
|
+
"""Return the latest scan item associated with a queue entry."""
|
|
97
|
+
if queue is None:
|
|
98
|
+
return None
|
|
99
|
+
scans = getattr(queue, "scans", None) or []
|
|
100
|
+
return scans[-1] if scans else None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _scan_number(scan_item: ScanItem | None) -> int | None:
|
|
104
|
+
"""Return the scan number when it is present and well-typed."""
|
|
105
|
+
scan_number = getattr(scan_item, "scan_number", None)
|
|
106
|
+
return scan_number if isinstance(scan_number, int) else None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _aborted_by_user_message(scan_item: ScanItem | None) -> str:
|
|
110
|
+
"""Build a user-facing message for a user-initiated abort."""
|
|
111
|
+
scan_number = _scan_number(scan_item)
|
|
112
|
+
if scan_number is None:
|
|
113
|
+
return "Scan was aborted by user."
|
|
114
|
+
return f"Scan {scan_number} was aborted by user."
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _interrupted_message(scan_item: ScanItem | None) -> str:
|
|
118
|
+
"""Build a user-facing message for a non-user interruption."""
|
|
119
|
+
scan_number = _scan_number(scan_item)
|
|
120
|
+
if scan_number is None:
|
|
121
|
+
return "Scan was interrupted."
|
|
122
|
+
return f"Scan {scan_number} was interrupted."
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _interruption_message(queue_status: str, scan_item: ScanItem | None) -> str:
|
|
126
|
+
"""Build a user-facing message for an interrupted queue entry."""
|
|
127
|
+
if queue_status == "CANCELLED":
|
|
128
|
+
return "Scan was cancelled."
|
|
129
|
+
status_message = getattr(scan_item, "status_message", None)
|
|
130
|
+
if status_message is not None and getattr(status_message, "reason", None) == "user":
|
|
131
|
+
return _aborted_by_user_message(scan_item)
|
|
132
|
+
return _interrupted_message(scan_item)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class LiveUpdatesBase(abc.ABC):
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
bec: BECClient,
|
|
139
|
+
report_instruction: dict[str, Any] | None = None,
|
|
140
|
+
request: messages.ScanQueueMessage | None = None,
|
|
141
|
+
callbacks: list[Callable[..., Any]] | Callable[..., Any] | None = None,
|
|
142
|
+
) -> None:
|
|
143
|
+
"""Base class for live update callbacks.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
bec: Active BEC client instance.
|
|
147
|
+
report_instruction: Callback-specific report instruction payload.
|
|
148
|
+
request: Scan queue request currently being processed.
|
|
149
|
+
callbacks: One or more user callbacks to invoke for emitted points.
|
|
150
|
+
"""
|
|
151
|
+
self.bec = bec
|
|
152
|
+
self.request = request
|
|
153
|
+
self.RID = request.metadata["RID"]
|
|
154
|
+
self.scan_queue_request: RequestItem | None = None
|
|
155
|
+
self.report_instruction = report_instruction
|
|
156
|
+
if callbacks is None:
|
|
157
|
+
self.callbacks = []
|
|
158
|
+
self.callbacks = callbacks if isinstance(callbacks, list) else [callbacks]
|
|
159
|
+
|
|
160
|
+
def wait_for_request_acceptance(self):
|
|
161
|
+
scan_request = ScanRequestMixin(self.bec, self.RID)
|
|
162
|
+
scan_request.wait()
|
|
163
|
+
self.scan_queue_request = scan_request.scan_queue_request
|
|
164
|
+
|
|
165
|
+
@abc.abstractmethod
|
|
166
|
+
def run(self) -> None:
|
|
167
|
+
"""Run the live update callback."""
|
|
168
|
+
|
|
169
|
+
def emit_point(self, data: dict[str, Any], metadata: dict[str, Any] | None = None) -> None:
|
|
170
|
+
"""Emit a point update to all registered user callbacks."""
|
|
171
|
+
for cb in self.callbacks:
|
|
172
|
+
if not cb:
|
|
173
|
+
continue
|
|
174
|
+
try:
|
|
175
|
+
cb(data, metadata=metadata)
|
|
176
|
+
except Exception:
|
|
177
|
+
content = traceback.format_exc()
|
|
178
|
+
logger.warning(f"Failed to run callback function: {content}")
|
|
179
|
+
|
|
180
|
+
def _print_client_msgs_asap(self):
|
|
181
|
+
"""Print queued client messages marked for immediate display."""
|
|
182
|
+
# pylint: disable=protected-access
|
|
183
|
+
if self.scan_queue_request is None:
|
|
184
|
+
return
|
|
185
|
+
queue = self.scan_queue_request.queue
|
|
186
|
+
if queue is None:
|
|
187
|
+
return
|
|
188
|
+
msgs = queue.get_client_messages(only_asap=True)
|
|
189
|
+
if not msgs:
|
|
190
|
+
return
|
|
191
|
+
if self.bec.live_updates_config.print_client_messages is False:
|
|
192
|
+
return
|
|
193
|
+
for msg in msgs:
|
|
194
|
+
print(queue.format_client_msg(msg))
|
|
195
|
+
|
|
196
|
+
def _print_client_msgs_all(self):
|
|
197
|
+
"""Print a summary of all queued client messages."""
|
|
198
|
+
# pylint: disable=protected-access
|
|
199
|
+
if self.scan_queue_request is None:
|
|
200
|
+
return
|
|
201
|
+
queue = self.scan_queue_request.queue
|
|
202
|
+
if queue is None:
|
|
203
|
+
return
|
|
204
|
+
msgs = queue.get_client_messages()
|
|
205
|
+
if self.bec.live_updates_config.print_client_messages is False:
|
|
206
|
+
return
|
|
207
|
+
if not msgs:
|
|
208
|
+
return
|
|
209
|
+
print("------------------------")
|
|
210
|
+
print("Summary of client messages")
|
|
211
|
+
print("------------------------")
|
|
212
|
+
# pylint: disable=protected-access
|
|
213
|
+
for msg in msgs:
|
|
214
|
+
print(queue.format_client_msg(msg))
|
|
215
|
+
print("------------------------")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class ScanRequestMixin:
|
|
219
|
+
def __init__(self, bec: BECClient, RID: str) -> None:
|
|
220
|
+
"""Mixin providing request-acceptance waiting helpers.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
bec: Active BEC client instance.
|
|
224
|
+
RID: Request identifier to wait for.
|
|
225
|
+
"""
|
|
226
|
+
self.bec = bec
|
|
227
|
+
self.request_storage = self.bec.queue.request_storage
|
|
228
|
+
self.RID = RID
|
|
229
|
+
self.scan_queue_request: RequestItem | None = None
|
|
230
|
+
|
|
231
|
+
def _wait_for_scan_request(self) -> RequestItem:
|
|
232
|
+
"""Wait until the request item appears in request storage.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
The matching queue request item.
|
|
236
|
+
"""
|
|
237
|
+
logger.trace("Waiting for request ID")
|
|
238
|
+
start = time.time()
|
|
239
|
+
while self.request_storage.find_request_by_ID(self.RID) is None:
|
|
240
|
+
time.sleep(0.1)
|
|
241
|
+
check_alarms(self.bec)
|
|
242
|
+
logger.trace(f"Waiting for request ID finished after {time.time()-start} s.")
|
|
243
|
+
return self.request_storage.find_request_by_ID(self.RID)
|
|
244
|
+
|
|
245
|
+
def _wait_for_scan_request_decision(self) -> None:
|
|
246
|
+
"""Wait until the server has accepted or rejected the request."""
|
|
247
|
+
logger.trace("Waiting for decision")
|
|
248
|
+
start = time.time()
|
|
249
|
+
while self.scan_queue_request.decision_pending:
|
|
250
|
+
time.sleep(0.1)
|
|
251
|
+
check_alarms(self.bec)
|
|
252
|
+
logger.trace(f"Waiting for decision finished after {time.time()-start} s.")
|
|
253
|
+
|
|
254
|
+
def wait(self) -> None:
|
|
255
|
+
"""Wait until the request is accepted and linked to a queue entry."""
|
|
256
|
+
self.scan_queue_request = self._wait_for_scan_request()
|
|
257
|
+
|
|
258
|
+
self._wait_for_scan_request_decision()
|
|
259
|
+
check_alarms(self.bec)
|
|
260
|
+
|
|
261
|
+
while self.scan_queue_request.accepted is None:
|
|
262
|
+
time.sleep(0.1)
|
|
263
|
+
check_alarms(self.bec)
|
|
264
|
+
|
|
265
|
+
if not self.scan_queue_request.accepted[0]:
|
|
266
|
+
raise ScanRequestError(
|
|
267
|
+
"Scan was rejected by the server:"
|
|
268
|
+
f" {self.scan_queue_request.response.content.get('message')}"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
while self.scan_queue_request.queue is None:
|
|
272
|
+
time.sleep(0.1)
|
|
273
|
+
check_alarms(self.bec)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "bec_ipython_client"
|
|
7
|
-
version = "3.142.
|
|
7
|
+
version = "3.142.2"
|
|
8
8
|
description = "BEC IPython client"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
classifiers = [
|
|
@@ -100,6 +100,8 @@ Homepage = "https://github.com/bec-project/bec"
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
|
|
104
|
+
|
|
103
105
|
|
|
104
106
|
|
|
105
107
|
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_device_progress.py
RENAMED
|
@@ -3,6 +3,7 @@ from unittest import mock
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
5
|
from bec_ipython_client.callbacks.device_progress import LiveUpdatesDeviceProgress
|
|
6
|
+
from bec_ipython_client.callbacks.utils import ScanState
|
|
6
7
|
from bec_lib import messages
|
|
7
8
|
from bec_lib.bec_errors import ScanInterruption, ScanRestart
|
|
8
9
|
|
|
@@ -92,6 +93,19 @@ def test_update_progressbar_raises_scan_restart_when_scan_restarted():
|
|
|
92
93
|
mock_print.assert_not_called()
|
|
93
94
|
|
|
94
95
|
|
|
96
|
+
def test_check_scan_state_returns_wait_for_restart_reason():
|
|
97
|
+
bec = mock.MagicMock()
|
|
98
|
+
request = mock.MagicMock()
|
|
99
|
+
live_update = LiveUpdatesDeviceProgress(bec=bec, report_instruction={}, request=request)
|
|
100
|
+
live_update.scan_item = mock.MagicMock(
|
|
101
|
+
status="open",
|
|
102
|
+
restarted_msg=None,
|
|
103
|
+
queue=mock.MagicMock(status="STOPPED", reason="restart", scans=[mock.MagicMock()]),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
assert live_update._check_scan_state() == ScanState.WAIT
|
|
107
|
+
|
|
108
|
+
|
|
95
109
|
def test_update_progressbar_returns_true_when_scan_completed_by_user():
|
|
96
110
|
bec = mock.MagicMock()
|
|
97
111
|
request = mock.MagicMock()
|
|
@@ -123,3 +137,40 @@ def test_update_progressbar_raises_scan_interruption_when_aborted_by_user():
|
|
|
123
137
|
|
|
124
138
|
with pytest.raises(ScanInterruption, match="Scan 5 was aborted by user."):
|
|
125
139
|
live_update._update_progressbar(progressbar, ["async_dev1"])
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_update_progressbar_waits_for_restart_message_before_finishing():
|
|
143
|
+
bec = mock.MagicMock()
|
|
144
|
+
request = mock.MagicMock()
|
|
145
|
+
live_update = LiveUpdatesDeviceProgress(bec=bec, report_instruction={}, request=request)
|
|
146
|
+
progressbar = mock.MagicMock()
|
|
147
|
+
restart_msg = messages.ScanQueueMessage(scan_type="grid_scan", parameter={"args": {}})
|
|
148
|
+
queue = mock.MagicMock(status="STOPPED", reason="restart")
|
|
149
|
+
scan_item = mock.MagicMock(
|
|
150
|
+
scan_id="scan_id",
|
|
151
|
+
scan_number=5,
|
|
152
|
+
restarted_msg=None,
|
|
153
|
+
status="open",
|
|
154
|
+
status_message=None,
|
|
155
|
+
queue=queue,
|
|
156
|
+
)
|
|
157
|
+
queue.scans = [scan_item]
|
|
158
|
+
live_update.scan_item = scan_item
|
|
159
|
+
|
|
160
|
+
bec.connector.get.return_value = messages.ProgressMessage(
|
|
161
|
+
value=10, max_value=10, done=True, metadata={"scan_id": "scan_id"}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
with mock.patch("bec_ipython_client.callbacks.device_progress.time.sleep") as sleep:
|
|
165
|
+
|
|
166
|
+
def trigger_restart(*_args, **_kwargs):
|
|
167
|
+
scan_item.restarted_msg = restart_msg
|
|
168
|
+
|
|
169
|
+
sleep.side_effect = trigger_restart
|
|
170
|
+
|
|
171
|
+
first = live_update._update_progressbar(progressbar, ["async_dev1"])
|
|
172
|
+
assert first is False
|
|
173
|
+
with pytest.raises(ScanRestart) as exc_info:
|
|
174
|
+
live_update._update_progressbar(progressbar, ["async_dev1"])
|
|
175
|
+
|
|
176
|
+
assert exc_info.value.new_scan_msg == restart_msg
|
|
@@ -177,11 +177,97 @@ def test_live_updates_process_queue_cancelled_pending_request_raises_interruptio
|
|
|
177
177
|
with (
|
|
178
178
|
mock.patch.object(queue, "_update_with_buffer"),
|
|
179
179
|
mock.patch("bec_lib.queue_items.QueueItem.queue_position", new_callable=mock.PropertyMock),
|
|
180
|
-
pytest.raises(ScanInterruption, match="
|
|
180
|
+
pytest.raises(ScanInterruption, match="Scan was cancelled."),
|
|
181
181
|
):
|
|
182
182
|
live_updates._process_queue(queue, request_msg, "something")
|
|
183
183
|
|
|
184
184
|
|
|
185
|
+
def test_live_updates_process_queue_stopped_started_request_raises_interruption(bec_client_mock):
|
|
186
|
+
client = bec_client_mock
|
|
187
|
+
live_updates = IPythonLiveUpdates(client)
|
|
188
|
+
request_msg = messages.ScanQueueMessage(
|
|
189
|
+
scan_type="grid_scan",
|
|
190
|
+
parameter={"args": {"samx": (-5, 5, 3)}, "kwargs": {}},
|
|
191
|
+
queue="primary",
|
|
192
|
+
metadata={"RID": "something"},
|
|
193
|
+
)
|
|
194
|
+
request_block = messages.RequestBlock(
|
|
195
|
+
msg=request_msg,
|
|
196
|
+
RID="something",
|
|
197
|
+
report_instructions=[],
|
|
198
|
+
readout_priority={"monitored": ["samx"]},
|
|
199
|
+
is_scan=True,
|
|
200
|
+
scan_number=1,
|
|
201
|
+
scan_id="scan_id",
|
|
202
|
+
)
|
|
203
|
+
queue = QueueItem(
|
|
204
|
+
scan_manager=client.queue,
|
|
205
|
+
queue_id="queue_id",
|
|
206
|
+
request_blocks=[request_block],
|
|
207
|
+
status="STOPPED",
|
|
208
|
+
active_request_block=None,
|
|
209
|
+
scan_id=["scan_id"],
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
with (
|
|
213
|
+
mock.patch.object(queue, "_update_with_buffer"),
|
|
214
|
+
mock.patch.object(
|
|
215
|
+
client.queue.scan_storage,
|
|
216
|
+
"find_scan_by_ID",
|
|
217
|
+
return_value=mock.MagicMock(
|
|
218
|
+
status="aborted",
|
|
219
|
+
scan_number=1,
|
|
220
|
+
restarted_msg=None,
|
|
221
|
+
status_message=mock.MagicMock(reason="user"),
|
|
222
|
+
),
|
|
223
|
+
),
|
|
224
|
+
mock.patch("bec_lib.queue_items.QueueItem.queue_position", new_callable=mock.PropertyMock),
|
|
225
|
+
pytest.raises(ScanInterruption, match="Scan 1 was aborted by user."),
|
|
226
|
+
):
|
|
227
|
+
live_updates._process_queue(queue, request_msg, "something")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_live_updates_process_queue_stopped_restart_without_restart_message_waits_nonblocking(
|
|
231
|
+
bec_client_mock,
|
|
232
|
+
):
|
|
233
|
+
client = bec_client_mock
|
|
234
|
+
live_updates = IPythonLiveUpdates(client)
|
|
235
|
+
request_msg = messages.ScanQueueMessage(
|
|
236
|
+
scan_type="grid_scan",
|
|
237
|
+
parameter={"args": {"samx": (-5, 5, 3)}, "kwargs": {}},
|
|
238
|
+
queue="primary",
|
|
239
|
+
metadata={"RID": "something"},
|
|
240
|
+
)
|
|
241
|
+
request_block = messages.RequestBlock(
|
|
242
|
+
msg=request_msg,
|
|
243
|
+
RID="something",
|
|
244
|
+
report_instructions=[],
|
|
245
|
+
readout_priority={"monitored": ["samx"]},
|
|
246
|
+
is_scan=True,
|
|
247
|
+
scan_number=1,
|
|
248
|
+
scan_id="scan_id",
|
|
249
|
+
)
|
|
250
|
+
queue = QueueItem(
|
|
251
|
+
scan_manager=client.queue,
|
|
252
|
+
queue_id="queue_id",
|
|
253
|
+
request_blocks=[request_block],
|
|
254
|
+
status="STOPPED",
|
|
255
|
+
active_request_block=None,
|
|
256
|
+
scan_id=["scan_id"],
|
|
257
|
+
reason="restart",
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
with (
|
|
261
|
+
mock.patch.object(queue, "_update_with_buffer"),
|
|
262
|
+
mock.patch.object(
|
|
263
|
+
client.queue.scan_storage,
|
|
264
|
+
"find_scan_by_ID",
|
|
265
|
+
return_value=mock.MagicMock(status="aborted", restarted_msg=None),
|
|
266
|
+
),
|
|
267
|
+
):
|
|
268
|
+
assert live_updates._process_queue(queue, request_msg, "something") is False
|
|
269
|
+
|
|
270
|
+
|
|
185
271
|
def test_process_request_repeats_on_ScanRestart_error(
|
|
186
272
|
ipython_live_updates_with_mocked_live, queue_elements
|
|
187
273
|
):
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_move_callback.py
RENAMED
|
@@ -8,7 +8,9 @@ from bec_ipython_client.callbacks.move_device import (
|
|
|
8
8
|
LiveUpdatesReadbackProgressbar,
|
|
9
9
|
ReadbackDataHandler,
|
|
10
10
|
)
|
|
11
|
+
from bec_ipython_client.callbacks.utils import ScanState
|
|
11
12
|
from bec_lib import messages
|
|
13
|
+
from bec_lib.bec_errors import ScanInterruption, ScanRestart
|
|
12
14
|
from bec_lib.endpoints import MessageEndpoints
|
|
13
15
|
|
|
14
16
|
|
|
@@ -103,6 +105,168 @@ def test_move_callback_with_report_instruction(bec_client_mock):
|
|
|
103
105
|
).run()
|
|
104
106
|
|
|
105
107
|
|
|
108
|
+
def test_move_callback_check_scan_state_raises_user_interruption(bec_client_mock):
|
|
109
|
+
request = messages.ScanQueueMessage(
|
|
110
|
+
scan_type="umv",
|
|
111
|
+
parameter={"args": {"samx": [10]}, "kwargs": {"relative": True}},
|
|
112
|
+
metadata={"RID": "something"},
|
|
113
|
+
)
|
|
114
|
+
live_update = LiveUpdatesReadbackProgressbar(bec=bec_client_mock, request=request)
|
|
115
|
+
live_update.scan_queue_request = mock.MagicMock(
|
|
116
|
+
queue=mock.MagicMock(
|
|
117
|
+
status="STOPPED",
|
|
118
|
+
scans=[
|
|
119
|
+
mock.MagicMock(
|
|
120
|
+
scan_number=5, restarted_msg=None, status_message=mock.MagicMock(reason="user")
|
|
121
|
+
)
|
|
122
|
+
],
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
with pytest.raises(ScanInterruption, match="Scan 5 was aborted by user."):
|
|
127
|
+
live_update._check_scan_state()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_move_callback_check_scan_state_raises_scan_restart(bec_client_mock):
|
|
131
|
+
request = messages.ScanQueueMessage(
|
|
132
|
+
scan_type="umv",
|
|
133
|
+
parameter={"args": {"samx": [10]}, "kwargs": {"relative": True}},
|
|
134
|
+
metadata={"RID": "something"},
|
|
135
|
+
)
|
|
136
|
+
restart_msg = messages.ScanQueueMessage(
|
|
137
|
+
scan_type="umv",
|
|
138
|
+
parameter={"args": {"samx": [10]}, "kwargs": {"relative": True}},
|
|
139
|
+
metadata={"RID": "restart"},
|
|
140
|
+
)
|
|
141
|
+
live_update = LiveUpdatesReadbackProgressbar(bec=bec_client_mock, request=request)
|
|
142
|
+
live_update.scan_queue_request = mock.MagicMock(
|
|
143
|
+
queue=mock.MagicMock(
|
|
144
|
+
status="STOPPED",
|
|
145
|
+
scans=[
|
|
146
|
+
mock.MagicMock(
|
|
147
|
+
scan_number=5,
|
|
148
|
+
restarted_msg=restart_msg,
|
|
149
|
+
status_message=mock.MagicMock(reason="alarm"),
|
|
150
|
+
)
|
|
151
|
+
],
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
with pytest.raises(ScanRestart) as exc_info:
|
|
156
|
+
live_update._check_scan_state()
|
|
157
|
+
|
|
158
|
+
assert exc_info.value.new_scan_msg == restart_msg
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_move_callback_check_scan_state_raises_alarm_interruption(bec_client_mock):
|
|
162
|
+
request = messages.ScanQueueMessage(
|
|
163
|
+
scan_type="umv",
|
|
164
|
+
parameter={"args": {"samx": [10]}, "kwargs": {"relative": True}},
|
|
165
|
+
metadata={"RID": "something"},
|
|
166
|
+
allow_restart=False,
|
|
167
|
+
)
|
|
168
|
+
live_update = LiveUpdatesReadbackProgressbar(bec=bec_client_mock, request=request)
|
|
169
|
+
live_update.scan_queue_request = mock.MagicMock(
|
|
170
|
+
queue=mock.MagicMock(
|
|
171
|
+
status="STOPPED",
|
|
172
|
+
scans=[
|
|
173
|
+
mock.MagicMock(
|
|
174
|
+
scan_number=7, restarted_msg=None, status_message=mock.MagicMock(reason="alarm")
|
|
175
|
+
)
|
|
176
|
+
],
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
with pytest.raises(ScanInterruption, match="Scan 7 was interrupted."):
|
|
181
|
+
live_update._check_scan_state()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_move_callback_check_scan_state_raises_cancelled_interruption(bec_client_mock):
|
|
185
|
+
request = messages.ScanQueueMessage(
|
|
186
|
+
scan_type="umv",
|
|
187
|
+
parameter={"args": {"samx": [10]}, "kwargs": {"relative": True}},
|
|
188
|
+
metadata={"RID": "something"},
|
|
189
|
+
)
|
|
190
|
+
live_update = LiveUpdatesReadbackProgressbar(bec=bec_client_mock, request=request)
|
|
191
|
+
live_update.scan_queue_request = mock.MagicMock(
|
|
192
|
+
queue=mock.MagicMock(status="CANCELLED", scans=[None])
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
with pytest.raises(ScanInterruption, match="Scan was cancelled."):
|
|
196
|
+
live_update._check_scan_state()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_move_callback_check_scan_state_restart_without_restart_message_is_nonblocking(
|
|
200
|
+
bec_client_mock,
|
|
201
|
+
):
|
|
202
|
+
request = messages.ScanQueueMessage(
|
|
203
|
+
scan_type="umv",
|
|
204
|
+
parameter={"args": {"samx": [10]}, "kwargs": {"relative": True}},
|
|
205
|
+
metadata={"RID": "something"},
|
|
206
|
+
)
|
|
207
|
+
live_update = LiveUpdatesReadbackProgressbar(bec=bec_client_mock, request=request)
|
|
208
|
+
live_update.scan_queue_request = mock.MagicMock(
|
|
209
|
+
queue=mock.MagicMock(
|
|
210
|
+
status="STOPPED",
|
|
211
|
+
reason="restart",
|
|
212
|
+
scans=[
|
|
213
|
+
mock.MagicMock(restarted_msg=None, status_message=mock.MagicMock(reason="alarm"))
|
|
214
|
+
],
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
assert live_update._check_scan_state() == ScanState.WAIT
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_move_callback_run_waits_for_restart_message_before_exiting(bec_client_mock):
|
|
222
|
+
client = bec_client_mock
|
|
223
|
+
request = messages.ScanQueueMessage(
|
|
224
|
+
scan_type="umv",
|
|
225
|
+
parameter={"args": {"samx": [10]}, "kwargs": {"relative": True}},
|
|
226
|
+
metadata={"RID": "something"},
|
|
227
|
+
)
|
|
228
|
+
report_instruction = {
|
|
229
|
+
"readback": {"RID": "something", "devices": ["samx"], "start": [0], "end": [10]}
|
|
230
|
+
}
|
|
231
|
+
restart_msg = messages.ScanQueueMessage(
|
|
232
|
+
scan_type="umv",
|
|
233
|
+
parameter={"args": {"samx": [10]}, "kwargs": {"relative": True}},
|
|
234
|
+
metadata={"RID": "restart"},
|
|
235
|
+
)
|
|
236
|
+
live_update = LiveUpdatesReadbackProgressbar(
|
|
237
|
+
bec=client, report_instruction=report_instruction, request=request
|
|
238
|
+
)
|
|
239
|
+
queue = mock.MagicMock(
|
|
240
|
+
status="STOPPED",
|
|
241
|
+
reason="restart",
|
|
242
|
+
scans=[mock.MagicMock(restarted_msg=None, status_message=mock.MagicMock(reason="alarm"))],
|
|
243
|
+
)
|
|
244
|
+
live_update.scan_queue_request = mock.MagicMock(queue=queue)
|
|
245
|
+
|
|
246
|
+
with (
|
|
247
|
+
mock.patch("bec_ipython_client.callbacks.move_device.check_alarms"),
|
|
248
|
+
mock.patch.object(LiveUpdatesReadbackProgressbar, "wait_for_request_acceptance"),
|
|
249
|
+
mock.patch.object(LiveUpdatesReadbackProgressbar, "_print_client_msgs_asap"),
|
|
250
|
+
mock.patch.object(LiveUpdatesReadbackProgressbar, "_print_client_msgs_all"),
|
|
251
|
+
mock.patch.object(ReadbackDataHandler, "get_device_values", side_effect=[[0], [10]]),
|
|
252
|
+
mock.patch.object(
|
|
253
|
+
ReadbackDataHandler, "device_states", return_value={"samx": (True, True)}
|
|
254
|
+
),
|
|
255
|
+
mock.patch.object(ReadbackDataHandler, "done", return_value=True),
|
|
256
|
+
mock.patch("bec_ipython_client.callbacks.move_device.time.sleep") as sleep,
|
|
257
|
+
):
|
|
258
|
+
|
|
259
|
+
def trigger_restart(*_args, **_kwargs):
|
|
260
|
+
queue.scans[-1].restarted_msg = restart_msg
|
|
261
|
+
|
|
262
|
+
sleep.side_effect = trigger_restart
|
|
263
|
+
|
|
264
|
+
with pytest.raises(ScanRestart) as exc_info:
|
|
265
|
+
live_update.core()
|
|
266
|
+
|
|
267
|
+
assert exc_info.value.new_scan_msg == restart_msg
|
|
268
|
+
|
|
269
|
+
|
|
106
270
|
def test_readback_data_handler(readback_data_handler):
|
|
107
271
|
readback_data_handler.data = {
|
|
108
272
|
"samx": messages.DeviceMessage(
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import abc
|
|
4
|
-
import time
|
|
5
|
-
import traceback
|
|
6
|
-
from collections.abc import Callable
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
8
|
-
|
|
9
|
-
from bec_lib.logger import bec_logger
|
|
10
|
-
from bec_lib.request_items import RequestItem
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from bec_lib import messages
|
|
14
|
-
from bec_lib.client import BECClient
|
|
15
|
-
|
|
16
|
-
logger = bec_logger.logger
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ScanRequestError(Exception):
|
|
20
|
-
"""Error raised when a scan request is rejected"""
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def check_alarms(bec):
|
|
24
|
-
"""check for alarms and raise them if needed"""
|
|
25
|
-
bec.alarm_handler.raise_alarms()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class LiveUpdatesBase(abc.ABC):
|
|
29
|
-
def __init__(
|
|
30
|
-
self,
|
|
31
|
-
bec: BECClient,
|
|
32
|
-
report_instruction: dict = None,
|
|
33
|
-
request: messages.ScanQueueMessage = None,
|
|
34
|
-
callbacks: list[Callable] = None,
|
|
35
|
-
) -> None:
|
|
36
|
-
"""Base class for live updates
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
bec (BECClient): BECClient instance
|
|
40
|
-
report_instruction (dict, optional): report instruction. Defaults to None.
|
|
41
|
-
request (messages.ScanQueueMessage, optional): scan queue request. Defaults to None.
|
|
42
|
-
callbacks (list[Callable], optional): list of callback functions. Defaults to None.
|
|
43
|
-
"""
|
|
44
|
-
self.bec = bec
|
|
45
|
-
self.request = request
|
|
46
|
-
self.RID = request.metadata["RID"]
|
|
47
|
-
self.scan_queue_request: RequestItem | None = None
|
|
48
|
-
self.report_instruction = report_instruction
|
|
49
|
-
if callbacks is None:
|
|
50
|
-
self.callbacks = []
|
|
51
|
-
self.callbacks = callbacks if isinstance(callbacks, list) else [callbacks]
|
|
52
|
-
|
|
53
|
-
def wait_for_request_acceptance(self):
|
|
54
|
-
scan_request = ScanRequestMixin(self.bec, self.RID)
|
|
55
|
-
scan_request.wait()
|
|
56
|
-
self.scan_queue_request = scan_request.scan_queue_request
|
|
57
|
-
|
|
58
|
-
@abc.abstractmethod
|
|
59
|
-
def run(self):
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
def emit_point(self, data: dict, metadata: dict = None):
|
|
63
|
-
for cb in self.callbacks:
|
|
64
|
-
if not cb:
|
|
65
|
-
continue
|
|
66
|
-
try:
|
|
67
|
-
cb(data, metadata=metadata)
|
|
68
|
-
except Exception:
|
|
69
|
-
content = traceback.format_exc()
|
|
70
|
-
logger.warning(f"Failed to run callback function: {content}")
|
|
71
|
-
|
|
72
|
-
def _print_client_msgs_asap(self):
|
|
73
|
-
"""Print client messages flagged as show_asap"""
|
|
74
|
-
# pylint: disable=protected-access
|
|
75
|
-
if self.scan_queue_request is None:
|
|
76
|
-
return
|
|
77
|
-
queue = self.scan_queue_request.queue
|
|
78
|
-
if queue is None:
|
|
79
|
-
return
|
|
80
|
-
msgs = queue.get_client_messages(only_asap=True)
|
|
81
|
-
if not msgs:
|
|
82
|
-
return
|
|
83
|
-
if self.bec.live_updates_config.print_client_messages is False:
|
|
84
|
-
return
|
|
85
|
-
for msg in msgs:
|
|
86
|
-
print(queue.format_client_msg(msg))
|
|
87
|
-
|
|
88
|
-
def _print_client_msgs_all(self):
|
|
89
|
-
"""Print summary of client messages"""
|
|
90
|
-
# pylint: disable=protected-access
|
|
91
|
-
if self.scan_queue_request is None:
|
|
92
|
-
return
|
|
93
|
-
queue = self.scan_queue_request.queue
|
|
94
|
-
if queue is None:
|
|
95
|
-
return
|
|
96
|
-
msgs = queue.get_client_messages()
|
|
97
|
-
if self.bec.live_updates_config.print_client_messages is False:
|
|
98
|
-
return
|
|
99
|
-
if not msgs:
|
|
100
|
-
return
|
|
101
|
-
print("------------------------")
|
|
102
|
-
print("Summary of client messages")
|
|
103
|
-
print("------------------------")
|
|
104
|
-
# pylint: disable=protected-access
|
|
105
|
-
for msg in msgs:
|
|
106
|
-
print(queue.format_client_msg(msg))
|
|
107
|
-
print("------------------------")
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
class ScanRequestMixin:
|
|
111
|
-
def __init__(self, bec: BECClient, RID: str) -> None:
|
|
112
|
-
"""Mixin to handle scan request acceptance
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
bec (BECClient): BECClient instance
|
|
116
|
-
RID (str): request ID
|
|
117
|
-
"""
|
|
118
|
-
self.bec = bec
|
|
119
|
-
self.request_storage = self.bec.queue.request_storage
|
|
120
|
-
self.RID = RID
|
|
121
|
-
self.scan_queue_request = None
|
|
122
|
-
|
|
123
|
-
def _wait_for_scan_request(self) -> RequestItem:
|
|
124
|
-
"""wait for scan queuest
|
|
125
|
-
|
|
126
|
-
Returns:
|
|
127
|
-
RequestItem: scan queue request
|
|
128
|
-
"""
|
|
129
|
-
logger.trace("Waiting for request ID")
|
|
130
|
-
start = time.time()
|
|
131
|
-
while self.request_storage.find_request_by_ID(self.RID) is None:
|
|
132
|
-
time.sleep(0.1)
|
|
133
|
-
check_alarms(self.bec)
|
|
134
|
-
logger.trace(f"Waiting for request ID finished after {time.time()-start} s.")
|
|
135
|
-
return self.request_storage.find_request_by_ID(self.RID)
|
|
136
|
-
|
|
137
|
-
def _wait_for_scan_request_decision(self):
|
|
138
|
-
"""wait for a scan queuest decision"""
|
|
139
|
-
logger.trace("Waiting for decision")
|
|
140
|
-
start = time.time()
|
|
141
|
-
while self.scan_queue_request.decision_pending:
|
|
142
|
-
time.sleep(0.1)
|
|
143
|
-
check_alarms(self.bec)
|
|
144
|
-
logger.trace(f"Waiting for decision finished after {time.time()-start} s.")
|
|
145
|
-
|
|
146
|
-
def wait(self):
|
|
147
|
-
"""wait for the request acceptance"""
|
|
148
|
-
self.scan_queue_request = self._wait_for_scan_request()
|
|
149
|
-
|
|
150
|
-
self._wait_for_scan_request_decision()
|
|
151
|
-
check_alarms(self.bec)
|
|
152
|
-
|
|
153
|
-
while self.scan_queue_request.accepted is None:
|
|
154
|
-
time.sleep(0.1)
|
|
155
|
-
check_alarms(self.bec)
|
|
156
|
-
|
|
157
|
-
if not self.scan_queue_request.accepted[0]:
|
|
158
|
-
raise ScanRequestError(
|
|
159
|
-
"Scan was rejected by the server:"
|
|
160
|
-
f" {self.scan_queue_request.response.content.get('message')}"
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
while self.scan_queue_request.queue is None:
|
|
164
|
-
time.sleep(0.1)
|
|
165
|
-
check_alarms(self.bec)
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/beamline_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/callbacks/__init__.py
RENAMED
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/callbacks/live_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/plugins/SLS/__init__.py
RENAMED
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/plugins/SLS/sls_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/bec_ipython_client/plugins/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_beamline_mixins.py
RENAMED
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_bec_client.py
RENAMED
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_live_table.py
RENAMED
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_pretty_table.py
RENAMED
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/client_tests/test_signals.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/end-2-end/test_actors_e2e.py
RENAMED
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/end-2-end/test_procedures_e2e.py
RENAMED
|
File without changes
|
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/end-2-end/test_scans_lib_e2e.py
RENAMED
|
File without changes
|
{bec_ipython_client-3.142.0 → bec_ipython_client-3.142.2}/tests/end-2-end/test_scans_v4_lib_e2e.py
RENAMED
|
File without changes
|