bec-ipython-client 3.64.5__py3-none-any.whl → 3.84.0__py3-none-any.whl
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.
Potentially problematic release.
This version of bec-ipython-client might be problematic. Click here for more details.
- .gitignore +3 -0
- PKG-INFO +2 -2
- bec_ipython_client/callbacks/device_progress.py +11 -6
- bec_ipython_client/callbacks/ipython_live_updates.py +8 -3
- bec_ipython_client/callbacks/live_table.py +9 -4
- bec_ipython_client/callbacks/move_device.py +121 -59
- bec_ipython_client/callbacks/utils.py +4 -4
- bec_ipython_client/main.py +76 -7
- {bec_ipython_client-3.64.5.dist-info → bec_ipython_client-3.84.0.dist-info}/METADATA +2 -2
- {bec_ipython_client-3.64.5.dist-info → bec_ipython_client-3.84.0.dist-info}/RECORD +22 -21
- {bec_ipython_client-3.64.5.dist-info → bec_ipython_client-3.84.0.dist-info}/WHEEL +1 -1
- demo.py +2 -1
- pyproject.toml +2 -2
- tests/client_tests/conftest.py +19 -0
- tests/client_tests/test_bec_client.py +32 -1
- tests/client_tests/test_live_table.py +33 -12
- tests/client_tests/test_move_callback.py +112 -70
- tests/end-2-end/_ensure_requirements_container.py +3 -3
- tests/end-2-end/test_procedures_e2e.py +26 -17
- tests/end-2-end/test_scans_e2e.py +4 -4
- tests/end-2-end/test_scans_lib_e2e.py +23 -19
- {bec_ipython_client-3.64.5.dist-info → bec_ipython_client-3.84.0.dist-info}/entry_points.txt +0 -0
.gitignore
CHANGED
PKG-INFO
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bec_ipython_client
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.84.0
|
|
4
4
|
Summary: BEC IPython client
|
|
5
5
|
Project-URL: Bug Tracker, https://github.com/bec-project/bec/issues
|
|
6
6
|
Project-URL: Homepage, https://github.com/bec-project/bec
|
|
7
7
|
Classifier: Development Status :: 3 - Alpha
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Topic :: Scientific/Engineering
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
11
|
Requires-Dist: bec-lib~=3.0
|
|
12
12
|
Requires-Dist: ipython~=8.22
|
|
13
13
|
Requires-Dist: numpy<3.0,>=1.24
|
|
@@ -14,11 +14,16 @@ class LiveUpdatesDeviceProgress(LiveUpdatesTable):
|
|
|
14
14
|
|
|
15
15
|
REPORT_TYPE = "device_progress"
|
|
16
16
|
|
|
17
|
-
def
|
|
17
|
+
def core(self):
|
|
18
|
+
"""core function to run the live updates for the table"""
|
|
19
|
+
self._wait_for_report_instructions()
|
|
20
|
+
self._run_update(self.report_instruction[self.REPORT_TYPE])
|
|
21
|
+
|
|
22
|
+
def _run_update(self, device_names: list[str]):
|
|
18
23
|
"""Run the update loop for the progress bar.
|
|
19
24
|
|
|
20
25
|
Args:
|
|
21
|
-
device_names (str): The name of the device to monitor.
|
|
26
|
+
device_names (list[str]): The name of the device to monitor.
|
|
22
27
|
"""
|
|
23
28
|
with ScanProgressBar(
|
|
24
29
|
scan_number=self.scan_item.scan_number, clear_on_exit=False
|
|
@@ -29,7 +34,7 @@ class LiveUpdatesDeviceProgress(LiveUpdatesTable):
|
|
|
29
34
|
self._print_client_msgs_asap()
|
|
30
35
|
self._print_client_msgs_all()
|
|
31
36
|
|
|
32
|
-
def _update_progressbar(self, progressbar: ScanProgressBar, device_names: str) -> bool:
|
|
37
|
+
def _update_progressbar(self, progressbar: ScanProgressBar, device_names: list[str]) -> bool:
|
|
33
38
|
"""Update the progressbar based on the device status message
|
|
34
39
|
|
|
35
40
|
Args:
|
|
@@ -41,17 +46,17 @@ class LiveUpdatesDeviceProgress(LiveUpdatesTable):
|
|
|
41
46
|
self.check_alarms()
|
|
42
47
|
status = self.bec.connector.get(MessageEndpoints.device_progress(device_names[0]))
|
|
43
48
|
if not status:
|
|
44
|
-
logger.
|
|
49
|
+
logger.trace("waiting for new data point")
|
|
45
50
|
time.sleep(0.1)
|
|
46
51
|
return False
|
|
47
52
|
if status.metadata.get("scan_id") != self.scan_item.scan_id:
|
|
48
|
-
logger.
|
|
53
|
+
logger.trace("waiting for new data point")
|
|
49
54
|
time.sleep(0.1)
|
|
50
55
|
return False
|
|
51
56
|
|
|
52
57
|
point_id = status.content.get("value")
|
|
53
58
|
if point_id is None:
|
|
54
|
-
logger.
|
|
59
|
+
logger.trace("waiting for new data point")
|
|
55
60
|
time.sleep(0.1)
|
|
56
61
|
return False
|
|
57
62
|
|
|
@@ -49,7 +49,7 @@ class IPythonLiveUpdates:
|
|
|
49
49
|
"""
|
|
50
50
|
scan_type = self._active_request.content["scan_type"]
|
|
51
51
|
if scan_type in ["open_scan_def", "close_scan_def"]:
|
|
52
|
-
self._process_instruction({"scan_progress": 0})
|
|
52
|
+
self._process_instruction({"scan_progress": {"points": 0, "show_table": True}})
|
|
53
53
|
return
|
|
54
54
|
if scan_type == "close_scan_group":
|
|
55
55
|
return
|
|
@@ -151,11 +151,14 @@ class IPythonLiveUpdates:
|
|
|
151
151
|
scan_request = ScanRequestMixin(self.client, request.metadata["RID"])
|
|
152
152
|
scan_request.wait()
|
|
153
153
|
|
|
154
|
+
# After .wait, we can be sure that the queue item is available, so we can
|
|
155
|
+
assert scan_request.scan_queue_request is not None
|
|
156
|
+
|
|
154
157
|
# get the corresponding queue item
|
|
155
|
-
while not scan_request.
|
|
158
|
+
while not scan_request.scan_queue_request.queue:
|
|
156
159
|
time.sleep(0.01)
|
|
157
160
|
|
|
158
|
-
self._current_queue = queue = scan_request.
|
|
161
|
+
self._current_queue = queue = scan_request.scan_queue_request.queue
|
|
159
162
|
self._request_block_id = req_id = self._active_request.metadata.get("RID")
|
|
160
163
|
|
|
161
164
|
while queue.status not in ["COMPLETED", "ABORTED", "HALTED"]:
|
|
@@ -223,6 +226,8 @@ class IPythonLiveUpdates:
|
|
|
223
226
|
flush=True,
|
|
224
227
|
)
|
|
225
228
|
available_blocks = self._available_req_blocks(queue, request)
|
|
229
|
+
if not available_blocks:
|
|
230
|
+
return False
|
|
226
231
|
req_block = available_blocks[self._request_block_index[req_id]]
|
|
227
232
|
if req_block["content"]["scan_type"] in [
|
|
228
233
|
"open_scan_def",
|
|
@@ -78,7 +78,7 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
78
78
|
if self.scan_item.status == "closed":
|
|
79
79
|
break
|
|
80
80
|
if queue_pos is None:
|
|
81
|
-
logger.
|
|
81
|
+
logger.trace(f"Could not find queue entry for scan_id {self.scan_item.scan_id}")
|
|
82
82
|
continue
|
|
83
83
|
if queue_pos == 0:
|
|
84
84
|
break
|
|
@@ -171,6 +171,13 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
171
171
|
|
|
172
172
|
def core(self):
|
|
173
173
|
"""core function to run the live updates for the table"""
|
|
174
|
+
self._wait_for_report_instructions()
|
|
175
|
+
show_table = self.report_instruction[self.REPORT_TYPE].get("show_table", True)
|
|
176
|
+
self._print_table_data = show_table
|
|
177
|
+
self._run_update(self.report_instruction[self.REPORT_TYPE]["points"])
|
|
178
|
+
|
|
179
|
+
def _wait_for_report_instructions(self):
|
|
180
|
+
"""wait until the report instructions are available"""
|
|
174
181
|
req_ID = self.scan_queue_request.requestID
|
|
175
182
|
while True:
|
|
176
183
|
request_block = [
|
|
@@ -182,8 +189,6 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
182
189
|
break
|
|
183
190
|
self.check_alarms()
|
|
184
191
|
|
|
185
|
-
self._run_update(self.report_instruction[self.REPORT_TYPE])
|
|
186
|
-
|
|
187
192
|
def _run_update(self, target_num_points: int):
|
|
188
193
|
"""run the update loop with the progress bar
|
|
189
194
|
|
|
@@ -211,7 +216,7 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
211
216
|
self.bec.callbacks.poll()
|
|
212
217
|
self.scan_item.poll_callbacks()
|
|
213
218
|
else:
|
|
214
|
-
logger.
|
|
219
|
+
logger.trace("waiting for new data point")
|
|
215
220
|
time.sleep(0.1)
|
|
216
221
|
|
|
217
222
|
if not self.scan_item.num_points:
|
|
@@ -1,31 +1,110 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import threading
|
|
3
4
|
from collections.abc import Callable
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING, cast
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
|
|
8
9
|
from bec_ipython_client.progressbar import DeviceProgressBar
|
|
10
|
+
from bec_lib import messages
|
|
9
11
|
from bec_lib.endpoints import MessageEndpoints
|
|
12
|
+
from bec_lib.redis_connector import MessageObject
|
|
10
13
|
|
|
11
14
|
from .utils import LiveUpdatesBase, check_alarms
|
|
12
15
|
|
|
13
16
|
if TYPE_CHECKING:
|
|
14
|
-
from bec_lib import messages
|
|
15
17
|
from bec_lib.client import BECClient
|
|
16
18
|
from bec_lib.devicemanager import DeviceManagerBase
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
class
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
class ReadbackDataHandler:
|
|
22
|
+
"""Helper class to get the current device values and request-done messages."""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self, device_manager: DeviceManagerBase, devices: list[str], request_id: str
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Helper class to get the current device values and request-done messages.
|
|
22
28
|
|
|
23
29
|
Args:
|
|
24
30
|
device_manager (DeviceManagerBase): device manager
|
|
25
31
|
devices (list): list of devices to monitor
|
|
32
|
+
request_id (str): request ID
|
|
26
33
|
"""
|
|
27
34
|
self.device_manager = device_manager
|
|
28
35
|
self.devices = devices
|
|
36
|
+
self.connector = device_manager.connector
|
|
37
|
+
self.request_id = request_id
|
|
38
|
+
self._devices_received = {dev: False for dev in devices}
|
|
39
|
+
self.data: dict[str, messages.DeviceMessage] = {}
|
|
40
|
+
self._devices_done_state: dict[str, tuple[bool, bool]] = {
|
|
41
|
+
dev: (False, False) for dev in devices
|
|
42
|
+
}
|
|
43
|
+
self.requests_done = threading.Event()
|
|
44
|
+
self._register_callbacks()
|
|
45
|
+
|
|
46
|
+
def _register_callbacks(self):
|
|
47
|
+
"""register callbacks for device readback messages."""
|
|
48
|
+
for dev in self.devices:
|
|
49
|
+
self.connector.register(
|
|
50
|
+
MessageEndpoints.device_readback(dev), cb=self.on_readback, parent=self, device=dev
|
|
51
|
+
)
|
|
52
|
+
self.connector.register(
|
|
53
|
+
MessageEndpoints.device_req_status(self.request_id),
|
|
54
|
+
cb=self.on_req_status,
|
|
55
|
+
from_start=True,
|
|
56
|
+
parent=self,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def _unregister_callbacks(self):
|
|
60
|
+
"""unregister callbacks for device readback messages."""
|
|
61
|
+
for dev in self.devices:
|
|
62
|
+
self.connector.unregister(MessageEndpoints.device_readback(dev), cb=self.on_readback)
|
|
63
|
+
self.connector.unregister(
|
|
64
|
+
MessageEndpoints.device_req_status(self.request_id), cb=self.on_req_status
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def on_req_status(
|
|
69
|
+
msg_obj: dict[str, messages.DeviceReqStatusMessage], parent: ReadbackDataHandler
|
|
70
|
+
):
|
|
71
|
+
"""Callback for device request status messages to track which devices are done.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
msg_obj (dict[str, messages.DeviceReqStatusMessage]): message object or device request status message
|
|
75
|
+
parent (ReadbackDataHandler): parent instance
|
|
76
|
+
"""
|
|
77
|
+
# pylint: disable=protected-access
|
|
78
|
+
msg = msg_obj["data"]
|
|
79
|
+
if msg.request_id != parent.request_id:
|
|
80
|
+
return
|
|
81
|
+
device = msg.device
|
|
82
|
+
parent._devices_done_state[device] = (True, msg.success)
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
all(done for done, _ in parent._devices_done_state.values())
|
|
86
|
+
and not parent.requests_done.is_set()
|
|
87
|
+
):
|
|
88
|
+
parent._on_request_done()
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def on_readback(msg_obj: MessageObject, parent: ReadbackDataHandler, device: str):
|
|
92
|
+
"""Callback for updating device readback data.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
msg_obj (MessageObject): message object
|
|
96
|
+
parent (ReadbackDataHandler): parent instance
|
|
97
|
+
device (str): device name
|
|
98
|
+
"""
|
|
99
|
+
# pylint: disable=protected-access
|
|
100
|
+
msg: messages.DeviceMessage = cast(messages.DeviceMessage, msg_obj.value)
|
|
101
|
+
parent._devices_received[device] = True
|
|
102
|
+
parent.data[device] = msg
|
|
103
|
+
|
|
104
|
+
def _on_request_done(self):
|
|
105
|
+
"""Callback for when all requests are done."""
|
|
106
|
+
self.requests_done.set()
|
|
107
|
+
self._unregister_callbacks()
|
|
29
108
|
|
|
30
109
|
def get_device_values(self) -> list:
|
|
31
110
|
"""get the current device values
|
|
@@ -35,57 +114,55 @@ class ReadbackDataMixin:
|
|
|
35
114
|
"""
|
|
36
115
|
values = []
|
|
37
116
|
for dev in self.devices:
|
|
38
|
-
val = self.
|
|
39
|
-
if
|
|
40
|
-
|
|
41
|
-
|
|
117
|
+
val = self.data.get(dev)
|
|
118
|
+
if val is None:
|
|
119
|
+
signal_data = self.device_manager.devices[dev].read(cached=True)
|
|
120
|
+
else:
|
|
121
|
+
signal_data = val.signals
|
|
42
122
|
# pylint: disable=protected-access
|
|
43
123
|
hints = self.device_manager.devices[dev]._hints
|
|
44
124
|
# if we have hints, use them to get the value, otherwise just use the first value
|
|
45
125
|
if hints:
|
|
46
|
-
values.append(
|
|
126
|
+
values.append(signal_data.get(hints[0]).get("value"))
|
|
47
127
|
else:
|
|
48
|
-
values.append(
|
|
128
|
+
values.append(signal_data.get(list(signal_data.keys())[0]).get("value"))
|
|
49
129
|
return values
|
|
50
130
|
|
|
51
|
-
def
|
|
52
|
-
"""
|
|
53
|
-
pipe = self.device_manager.connector.pipeline()
|
|
54
|
-
for dev in self.devices:
|
|
55
|
-
self.device_manager.connector.get(MessageEndpoints.device_req_status(dev), pipe)
|
|
56
|
-
return self.device_manager.connector.execute_pipeline(pipe)
|
|
131
|
+
def done(self) -> bool:
|
|
132
|
+
"""check if all devices are done
|
|
57
133
|
|
|
58
|
-
|
|
59
|
-
|
|
134
|
+
Returns:
|
|
135
|
+
bool: True if all devices are done, False otherwise
|
|
136
|
+
"""
|
|
137
|
+
return self.requests_done.is_set()
|
|
60
138
|
|
|
61
|
-
|
|
62
|
-
request (messages.ScanQueueMessage): request message
|
|
139
|
+
def device_states(self) -> dict[str, tuple[bool, bool]]:
|
|
63
140
|
"""
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
break
|
|
71
|
-
check_alarms(self.device_manager.parent)
|
|
141
|
+
Return the current device done states.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
dict: dictionary with device names as keys and tuples of (done, success) as values
|
|
145
|
+
"""
|
|
146
|
+
return self._devices_done_state
|
|
72
147
|
|
|
73
148
|
|
|
74
149
|
class LiveUpdatesReadbackProgressbar(LiveUpdatesBase):
|
|
75
150
|
"""Live feedback on motor movements using a progressbar.
|
|
76
151
|
|
|
77
152
|
Args:
|
|
78
|
-
|
|
79
|
-
|
|
153
|
+
bec (BECClient): BECClient instance
|
|
154
|
+
report_instruction (list, optional): report instruction for the scan. Defaults to None.
|
|
155
|
+
request (messages.ScanQueueMessage, optional): scan queue request message. Defaults to None.
|
|
156
|
+
callbacks (list[Callable], optional): list of callbacks to register. Defaults to None.
|
|
80
157
|
|
|
81
158
|
"""
|
|
82
159
|
|
|
83
160
|
def __init__(
|
|
84
161
|
self,
|
|
85
162
|
bec: BECClient,
|
|
86
|
-
report_instruction: list = None,
|
|
87
|
-
request: messages.ScanQueueMessage = None,
|
|
88
|
-
callbacks: list[Callable] = None,
|
|
163
|
+
report_instruction: list | None = None,
|
|
164
|
+
request: messages.ScanQueueMessage | None = None,
|
|
165
|
+
callbacks: list[Callable] | None = None,
|
|
89
166
|
) -> None:
|
|
90
167
|
super().__init__(
|
|
91
168
|
bec, report_instruction=report_instruction, request=request, callbacks=callbacks
|
|
@@ -97,18 +174,20 @@ class LiveUpdatesReadbackProgressbar(LiveUpdatesBase):
|
|
|
97
174
|
|
|
98
175
|
def core(self):
|
|
99
176
|
"""core function to monitor the device values and update the progressbar accordingly."""
|
|
100
|
-
|
|
177
|
+
request_id = self.request.metadata["RID"]
|
|
178
|
+
if self.report_instruction:
|
|
179
|
+
self.devices = self.report_instruction["readback"]["devices"]
|
|
180
|
+
request_id = self.report_instruction["readback"]["RID"]
|
|
181
|
+
data_source = ReadbackDataHandler(self.bec.device_manager, self.devices, request_id)
|
|
101
182
|
start_values = data_source.get_device_values()
|
|
102
183
|
self.wait_for_request_acceptance()
|
|
103
|
-
|
|
184
|
+
|
|
104
185
|
if self.report_instruction:
|
|
105
|
-
self.devices = self.report_instruction["readback"]["devices"]
|
|
106
186
|
target_values = self.report_instruction["readback"]["end"]
|
|
107
187
|
|
|
108
188
|
start_instr = self.report_instruction["readback"].get("start")
|
|
109
189
|
if start_instr:
|
|
110
190
|
start_values = self.report_instruction["readback"]["start"]
|
|
111
|
-
data_source = ReadbackDataMixin(self.bec.device_manager, self.devices)
|
|
112
191
|
else:
|
|
113
192
|
target_values = [
|
|
114
193
|
x for xs in self.request.content["parameter"]["args"].values() for x in xs
|
|
@@ -119,33 +198,16 @@ class LiveUpdatesReadbackProgressbar(LiveUpdatesBase):
|
|
|
119
198
|
with DeviceProgressBar(
|
|
120
199
|
self.devices, start_values=start_values, target_values=target_values
|
|
121
200
|
) as progress:
|
|
122
|
-
|
|
123
|
-
while not progress.finished or not
|
|
201
|
+
|
|
202
|
+
while not progress.finished or not data_source.done():
|
|
124
203
|
check_alarms(self.bec)
|
|
125
204
|
|
|
126
205
|
values = data_source.get_device_values()
|
|
127
206
|
progress.update(values=values)
|
|
128
207
|
self._print_client_msgs_asap()
|
|
129
208
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
msg.metadata["RID"] if (msg and msg.metadata.get("RID")) else None
|
|
133
|
-
for msg in msgs
|
|
134
|
-
]
|
|
135
|
-
|
|
136
|
-
if self.report_instruction:
|
|
137
|
-
compare_rids = set([self.report_instruction["readback"]["RID"]])
|
|
138
|
-
else:
|
|
139
|
-
compare_rids = set([self.request.metadata["RID"]])
|
|
140
|
-
if set(request_ids) != set(compare_rids):
|
|
141
|
-
progress.sleep()
|
|
142
|
-
continue
|
|
143
|
-
|
|
144
|
-
req_done = True
|
|
145
|
-
for dev, msg in zip(self.devices, msgs):
|
|
146
|
-
if not msg:
|
|
147
|
-
continue
|
|
148
|
-
if msg.content.get("success", False):
|
|
209
|
+
for dev, (done, success) in data_source.device_states().items():
|
|
210
|
+
if done and success:
|
|
149
211
|
progress.set_finished(dev)
|
|
150
212
|
# pylint: disable=protected-access
|
|
151
213
|
progress._progress.refresh()
|
|
@@ -138,22 +138,22 @@ class ScanRequestMixin:
|
|
|
138
138
|
Returns:
|
|
139
139
|
RequestItem: scan queue request
|
|
140
140
|
"""
|
|
141
|
-
logger.
|
|
141
|
+
logger.trace("Waiting for request ID")
|
|
142
142
|
start = time.time()
|
|
143
143
|
while self.request_storage.find_request_by_ID(self.RID) is None:
|
|
144
144
|
time.sleep(0.1)
|
|
145
145
|
check_alarms(self.bec)
|
|
146
|
-
logger.
|
|
146
|
+
logger.trace(f"Waiting for request ID finished after {time.time()-start} s.")
|
|
147
147
|
return self.request_storage.find_request_by_ID(self.RID)
|
|
148
148
|
|
|
149
149
|
def _wait_for_scan_request_decision(self):
|
|
150
150
|
"""wait for a scan queuest decision"""
|
|
151
|
-
logger.
|
|
151
|
+
logger.trace("Waiting for decision")
|
|
152
152
|
start = time.time()
|
|
153
153
|
while self.scan_queue_request.decision_pending:
|
|
154
154
|
time.sleep(0.1)
|
|
155
155
|
check_alarms(self.bec)
|
|
156
|
-
logger.
|
|
156
|
+
logger.trace(f"Waiting for decision finished after {time.time()-start} s.")
|
|
157
157
|
|
|
158
158
|
def wait(self):
|
|
159
159
|
"""wait for the request acceptance"""
|
bec_ipython_client/main.py
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import argparse
|
|
4
|
+
import collections
|
|
5
|
+
import functools
|
|
2
6
|
import os
|
|
3
7
|
import sys
|
|
4
8
|
from importlib.metadata import version
|
|
@@ -7,16 +11,18 @@ from typing import Iterable, Literal, Tuple
|
|
|
7
11
|
import IPython
|
|
8
12
|
import redis
|
|
9
13
|
import redis.exceptions
|
|
10
|
-
import requests
|
|
11
14
|
from IPython.terminal.ipapp import TerminalIPythonApp
|
|
12
15
|
from IPython.terminal.prompts import Prompts, Token
|
|
16
|
+
from pydantic import ValidationError
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
from rich.panel import Panel
|
|
19
|
+
from rich.text import Text
|
|
13
20
|
|
|
14
21
|
from bec_ipython_client.beamline_mixin import BeamlineMixin
|
|
15
22
|
from bec_ipython_client.bec_magics import BECMagics
|
|
16
23
|
from bec_ipython_client.callbacks.ipython_live_updates import IPythonLiveUpdates
|
|
17
24
|
from bec_ipython_client.signals import ScanInterruption, SigintHandler
|
|
18
25
|
from bec_lib import plugin_helper
|
|
19
|
-
from bec_lib.acl_login import BECAuthenticationError
|
|
20
26
|
from bec_lib.alarm_handler import AlarmBase
|
|
21
27
|
from bec_lib.bec_errors import DeviceConfigError
|
|
22
28
|
from bec_lib.bec_service import parse_cmdline_args
|
|
@@ -25,6 +31,7 @@ from bec_lib.client import BECClient
|
|
|
25
31
|
from bec_lib.logger import bec_logger
|
|
26
32
|
from bec_lib.redis_connector import RedisConnector
|
|
27
33
|
from bec_lib.service_config import ServiceConfig
|
|
34
|
+
from bec_lib.utils.pydantic_pretty_print import pretty_print_pydantic_validation_error
|
|
28
35
|
|
|
29
36
|
logger = bec_logger.logger
|
|
30
37
|
|
|
@@ -79,6 +86,7 @@ class BECIPythonClient:
|
|
|
79
86
|
self._client.callbacks.register(
|
|
80
87
|
event_type=EventType.NAMESPACE_UPDATE, callback=self._update_namespace_callback
|
|
81
88
|
)
|
|
89
|
+
self._alarm_history = collections.deque(maxlen=100)
|
|
82
90
|
|
|
83
91
|
def __getattr__(self, name):
|
|
84
92
|
return getattr(self._client, name)
|
|
@@ -136,7 +144,7 @@ class BECIPythonClient:
|
|
|
136
144
|
self._refresh_ipython_username()
|
|
137
145
|
self._load_magics()
|
|
138
146
|
self._ip.events.register("post_run_cell", log_console)
|
|
139
|
-
self._ip.set_custom_exc((Exception,),
|
|
147
|
+
self._ip.set_custom_exc((Exception,), self._create_exception_handler())
|
|
140
148
|
# represent objects using __str__, if overwritten, otherwise use __repr__
|
|
141
149
|
self._ip.display_formatter.formatters["text/plain"].for_type(
|
|
142
150
|
object,
|
|
@@ -187,10 +195,66 @@ class BECIPythonClient:
|
|
|
187
195
|
pass
|
|
188
196
|
self._client.shutdown()
|
|
189
197
|
|
|
198
|
+
def _create_exception_handler(self):
|
|
199
|
+
return functools.partial(_ip_exception_handler, parent=self)
|
|
200
|
+
|
|
201
|
+
def show_last_alarm(self, offset: int = 0):
|
|
202
|
+
"""
|
|
203
|
+
Show the last alarm raised in this session with rich formatting.
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
alarm: AlarmBase = self._alarm_history[-1 - offset][1]
|
|
207
|
+
except IndexError:
|
|
208
|
+
print("No alarm has been raised in this session.")
|
|
209
|
+
return
|
|
190
210
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
211
|
+
console = Console()
|
|
212
|
+
|
|
213
|
+
# --- HEADER ---
|
|
214
|
+
header = Text()
|
|
215
|
+
header.append("Alarm Raised\n", style="bold red")
|
|
216
|
+
header.append(f"Severity: {alarm.severity.name}\n", style="bold")
|
|
217
|
+
header.append(f"Type: {alarm.alarm_type}\n", style="bold")
|
|
218
|
+
if alarm.alarm.info.device:
|
|
219
|
+
header.append(f"Device: {alarm.alarm.info.device}\n", style="bold")
|
|
220
|
+
|
|
221
|
+
console.print(Panel(header, title="Alarm Info", border_style="red", expand=False))
|
|
222
|
+
|
|
223
|
+
# --- SHOW SUMMARY
|
|
224
|
+
if alarm.alarm.info.compact_error_message:
|
|
225
|
+
console.print(
|
|
226
|
+
Panel(
|
|
227
|
+
Text(alarm.alarm.info.compact_error_message, style="yellow"),
|
|
228
|
+
title="Summary",
|
|
229
|
+
border_style="yellow",
|
|
230
|
+
expand=False,
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# --- SHOW FULL TRACEBACK
|
|
235
|
+
tb_str = alarm.alarm.info.error_message
|
|
236
|
+
if tb_str:
|
|
237
|
+
try:
|
|
238
|
+
console.print(tb_str)
|
|
239
|
+
except Exception:
|
|
240
|
+
# fallback in case msg is not a traceback
|
|
241
|
+
console.print(Panel(tb_str, title="Message", border_style="cyan"))
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _ip_exception_handler(
|
|
245
|
+
self, etype, evalue, tb, tb_offset=None, parent: BECIPythonClient = None, **kwargs
|
|
246
|
+
):
|
|
247
|
+
if issubclass(etype, AlarmBase):
|
|
248
|
+
parent._alarm_history.append((etype, evalue, tb, tb_offset))
|
|
249
|
+
print("\x1b[31m BEC alarm:\x1b[0m")
|
|
250
|
+
evalue.pretty_print()
|
|
251
|
+
print("For more details, use 'bec.show_last_alarm()'")
|
|
252
|
+
return
|
|
253
|
+
if issubclass(etype, ValidationError):
|
|
254
|
+
pretty_print_pydantic_validation_error(evalue)
|
|
255
|
+
return
|
|
256
|
+
if issubclass(etype, (ScanInterruption, DeviceConfigError)):
|
|
257
|
+
print(f"\x1b[31m {evalue.__class__.__name__}:\x1b[0m {evalue}")
|
|
194
258
|
return
|
|
195
259
|
if issubclass(etype, redis.exceptions.NoPermissionError):
|
|
196
260
|
# pylint: disable=protected-access
|
|
@@ -220,9 +284,14 @@ class BECClientPrompt(Prompts):
|
|
|
220
284
|
next_scan_number = str(self.client.queue.next_scan_number)
|
|
221
285
|
except Exception:
|
|
222
286
|
next_scan_number = "?"
|
|
287
|
+
|
|
288
|
+
if self.client.active_account:
|
|
289
|
+
username = f"{self.client.active_account} | {self.username}"
|
|
290
|
+
else:
|
|
291
|
+
username = self.username
|
|
223
292
|
return [
|
|
224
293
|
(status_led, "\u2022"),
|
|
225
|
-
(Token.Prompt, " " +
|
|
294
|
+
(Token.Prompt, " " + username), # BEC ACL username and pgroup
|
|
226
295
|
(Token.Prompt, "@" + self.session_name),
|
|
227
296
|
(Token.Prompt, " ["),
|
|
228
297
|
(Token.PromptNum, str(self.shell.execution_count)),
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bec_ipython_client
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.84.0
|
|
4
4
|
Summary: BEC IPython client
|
|
5
5
|
Project-URL: Bug Tracker, https://github.com/bec-project/bec/issues
|
|
6
6
|
Project-URL: Homepage, https://github.com/bec-project/bec
|
|
7
7
|
Classifier: Development Status :: 3 - Alpha
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Topic :: Scientific/Engineering
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
11
|
Requires-Dist: bec-lib~=3.0
|
|
12
12
|
Requires-Dist: ipython~=8.22
|
|
13
13
|
Requires-Dist: numpy<3.0,>=1.24
|