bec-ipython-client 3.1.3__tar.gz → 3.86.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.1.3 → bec_ipython_client-3.86.2}/.gitignore +4 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/PKG-INFO +6 -6
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/bec_magics.py +27 -2
- bec_ipython_client-3.86.2/bec_ipython_client/bec_startup.py +60 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/callbacks/device_progress.py +12 -9
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/callbacks/ipython_live_updates.py +53 -22
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/callbacks/live_table.py +88 -39
- bec_ipython_client-3.86.2/bec_ipython_client/callbacks/move_device.py +218 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/callbacks/utils.py +30 -52
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/main.py +141 -18
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/signals.py +9 -3
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/demo.py +2 -1
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/pyproject.toml +5 -5
- bec_ipython_client-3.86.2/tests/client_tests/conftest.py +19 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/tests/client_tests/test_bec_client.py +85 -19
- bec_ipython_client-3.86.2/tests/client_tests/test_ipython_live_updates.py +350 -0
- bec_ipython_client-3.86.2/tests/client_tests/test_live_table.py +423 -0
- bec_ipython_client-3.86.2/tests/client_tests/test_move_callback.py +223 -0
- bec_ipython_client-3.86.2/tests/end-2-end/_ensure_requirements_container.py +21 -0
- bec_ipython_client-3.86.2/tests/end-2-end/test_procedures_e2e.py +134 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/tests/end-2-end/test_scans_e2e.py +73 -16
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/tests/end-2-end/test_scans_lib_e2e.py +150 -32
- bec_ipython_client-3.1.3/bec_ipython_client/bec_startup.py +0 -98
- bec_ipython_client-3.1.3/bec_ipython_client/callbacks/move_device.py +0 -157
- bec_ipython_client-3.1.3/tests/client_tests/test_ipython_live_updates.py +0 -159
- bec_ipython_client-3.1.3/tests/client_tests/test_live_table.py +0 -280
- bec_ipython_client-3.1.3/tests/client_tests/test_move_callback.py +0 -170
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/__init__.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/beamline_mixin.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/callbacks/__init__.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/high_level_interfaces/__init__.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/high_level_interfaces/bec_hli.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/high_level_interfaces/spec_hli.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/plugins/SLS/__init__.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/plugins/SLS/sls_info.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/plugins/XTreme/__init__.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/plugins/XTreme/x-treme.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/plugins/__init__.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/plugins/flomni/flomni_config.yaml +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/prettytable.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/progressbar.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/tests/client_tests/test_beamline_mixins.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/tests/client_tests/test_device_progress.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/tests/client_tests/test_pretty_table.py +0 -0
- {bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/tests/conftest.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: bec_ipython_client
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.86.2
|
|
4
4
|
Summary: BEC IPython client
|
|
5
|
-
Project-URL: Bug Tracker, https://
|
|
6
|
-
Project-URL: Homepage, https://
|
|
5
|
+
Project-URL: Bug Tracker, https://github.com/bec-project/bec/issues
|
|
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
|
|
@@ -15,7 +15,7 @@ Requires-Dist: pyepics~=3.5
|
|
|
15
15
|
Requires-Dist: requests~=2.31
|
|
16
16
|
Requires-Dist: rich~=13.7
|
|
17
17
|
Provides-Extra: dev
|
|
18
|
-
Requires-Dist: black~=
|
|
18
|
+
Requires-Dist: black~=25.0; extra == 'dev'
|
|
19
19
|
Requires-Dist: coverage~=7.0; extra == 'dev'
|
|
20
20
|
Requires-Dist: isort>=5.13.2,~=5.13; extra == 'dev'
|
|
21
21
|
Requires-Dist: pylint~=3.0; extra == 'dev'
|
|
@@ -2,8 +2,11 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
+
import rich
|
|
5
6
|
from IPython.core.magic import Magics, line_magic, magics_class
|
|
6
7
|
|
|
8
|
+
from bec_lib.metadata_schema import get_metadata_schema_for_scan
|
|
9
|
+
|
|
7
10
|
if TYPE_CHECKING:
|
|
8
11
|
from bec_ipython_client import BECIPythonClient
|
|
9
12
|
|
|
@@ -28,7 +31,7 @@ class BECMagics(Magics):
|
|
|
28
31
|
def resume(self, line):
|
|
29
32
|
"Resume the scan"
|
|
30
33
|
self.client.queue.request_scan_continuation()
|
|
31
|
-
return self.client.
|
|
34
|
+
return self.client._live_updates.continue_request()
|
|
32
35
|
|
|
33
36
|
@line_magic
|
|
34
37
|
def pause(self, line):
|
|
@@ -52,9 +55,31 @@ class BECMagics(Magics):
|
|
|
52
55
|
scan_report = self.client.scans._available_scans["fermat_scan"]._get_scan_report_type(
|
|
53
56
|
hide_report
|
|
54
57
|
)
|
|
55
|
-
return self.client.
|
|
58
|
+
return self.client._live_updates.process_request(request, scan_report, [])
|
|
56
59
|
|
|
57
60
|
@line_magic
|
|
58
61
|
def halt(self, line):
|
|
59
62
|
"Request a scan halt, i.e. abort without cleanup."
|
|
60
63
|
return self.client.queue.request_scan_halt()
|
|
64
|
+
|
|
65
|
+
@line_magic
|
|
66
|
+
def server_restart(self, line):
|
|
67
|
+
"Request a server restart"
|
|
68
|
+
return self.client._request_server_restart()
|
|
69
|
+
|
|
70
|
+
@line_magic
|
|
71
|
+
def schema(self, line):
|
|
72
|
+
"print the metadata schema for a given scan"
|
|
73
|
+
scans = self.shell.user_ns["scans"]
|
|
74
|
+
if not hasattr(scans, line):
|
|
75
|
+
print(f"Scan {line} does not exist.")
|
|
76
|
+
else:
|
|
77
|
+
rich.print_json(data=get_metadata_schema_for_scan(line).model_json_schema())
|
|
78
|
+
|
|
79
|
+
@line_magic
|
|
80
|
+
def su(self, line):
|
|
81
|
+
"Switch user"
|
|
82
|
+
# pylint: disable=protected-access
|
|
83
|
+
self.client._client.acl.login(line)
|
|
84
|
+
self.client._client._update_username()
|
|
85
|
+
self.client._refresh_ipython_username()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
import numpy as np # not needed but always nice to have
|
|
6
|
+
|
|
7
|
+
from bec_ipython_client.main import BECIPythonClient as _BECIPythonClient
|
|
8
|
+
from bec_ipython_client.main import main_dict as _main_dict
|
|
9
|
+
from bec_lib import plugin_helper
|
|
10
|
+
from bec_lib.acl_login import BECAuthenticationError
|
|
11
|
+
from bec_lib.logger import bec_logger as _bec_logger
|
|
12
|
+
from bec_lib.redis_connector import RedisConnector as _RedisConnector
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from bec_widgets.cli.client_utils import BECGuiClient
|
|
16
|
+
except ImportError:
|
|
17
|
+
BECGuiClient = None
|
|
18
|
+
|
|
19
|
+
logger = _bec_logger.logger
|
|
20
|
+
|
|
21
|
+
bec = _BECIPythonClient(
|
|
22
|
+
_main_dict["config"], _RedisConnector, wait_for_server=_main_dict["wait_for_server"]
|
|
23
|
+
)
|
|
24
|
+
_main_dict["bec"] = bec
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
bec.start()
|
|
29
|
+
except (BECAuthenticationError, KeyboardInterrupt) as exc:
|
|
30
|
+
logger.error(f"{exc} Exiting.")
|
|
31
|
+
os._exit(0)
|
|
32
|
+
except Exception:
|
|
33
|
+
sys.excepthook(*sys.exc_info())
|
|
34
|
+
else:
|
|
35
|
+
if bec.started and BECGuiClient is not None:
|
|
36
|
+
gui = bec.gui = BECGuiClient()
|
|
37
|
+
if _main_dict["args"].gui_id:
|
|
38
|
+
gui.connect_to_gui_server(_main_dict["args"].gui_id)
|
|
39
|
+
if not _main_dict["args"].nogui:
|
|
40
|
+
gui.show()
|
|
41
|
+
|
|
42
|
+
_available_plugins = plugin_helper.get_ipython_client_startup_plugins(state="post")
|
|
43
|
+
if _available_plugins:
|
|
44
|
+
for name, plugin in _available_plugins.items():
|
|
45
|
+
logger.success(f"Loading plugin: {plugin['source']}")
|
|
46
|
+
base = os.path.dirname(plugin["module"].__file__)
|
|
47
|
+
with open(os.path.join(base, "post_startup.py"), "r", encoding="utf-8") as file:
|
|
48
|
+
# pylint: disable=exec-used
|
|
49
|
+
exec(file.read())
|
|
50
|
+
|
|
51
|
+
else:
|
|
52
|
+
bec._ip.prompts.status = 1
|
|
53
|
+
|
|
54
|
+
if not bec._hli_funcs:
|
|
55
|
+
bec.load_high_level_interface("bec_hli")
|
|
56
|
+
|
|
57
|
+
if _main_dict["startup_file"]:
|
|
58
|
+
with open(_main_dict["startup_file"], "r", encoding="utf-8") as file:
|
|
59
|
+
# pylint: disable=exec-used
|
|
60
|
+
exec(file.read())
|
|
@@ -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
|
|
@@ -27,11 +32,9 @@ class LiveUpdatesDeviceProgress(LiveUpdatesTable):
|
|
|
27
32
|
if self._update_progressbar(progressbar, device_names):
|
|
28
33
|
break
|
|
29
34
|
self._print_client_msgs_asap()
|
|
35
|
+
self._print_client_msgs_all()
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
# self._print_client_msgs_all()
|
|
33
|
-
|
|
34
|
-
def _update_progressbar(self, progressbar: ScanProgressBar, device_names: str) -> bool:
|
|
37
|
+
def _update_progressbar(self, progressbar: ScanProgressBar, device_names: list[str]) -> bool:
|
|
35
38
|
"""Update the progressbar based on the device status message
|
|
36
39
|
|
|
37
40
|
Args:
|
|
@@ -43,17 +46,17 @@ class LiveUpdatesDeviceProgress(LiveUpdatesTable):
|
|
|
43
46
|
self.check_alarms()
|
|
44
47
|
status = self.bec.connector.get(MessageEndpoints.device_progress(device_names[0]))
|
|
45
48
|
if not status:
|
|
46
|
-
logger.
|
|
49
|
+
logger.trace("waiting for new data point")
|
|
47
50
|
time.sleep(0.1)
|
|
48
51
|
return False
|
|
49
52
|
if status.metadata.get("scan_id") != self.scan_item.scan_id:
|
|
50
|
-
logger.
|
|
53
|
+
logger.trace("waiting for new data point")
|
|
51
54
|
time.sleep(0.1)
|
|
52
55
|
return False
|
|
53
56
|
|
|
54
57
|
point_id = status.content.get("value")
|
|
55
58
|
if point_id is None:
|
|
56
|
-
logger.
|
|
59
|
+
logger.trace("waiting for new data point")
|
|
57
60
|
time.sleep(0.1)
|
|
58
61
|
return False
|
|
59
62
|
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import collections
|
|
4
4
|
import time
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
7
|
from bec_ipython_client.callbacks.device_progress import LiveUpdatesDeviceProgress
|
|
8
8
|
from bec_lib.bec_errors import ScanInterruption
|
|
@@ -31,22 +31,27 @@ class IPythonLiveUpdates:
|
|
|
31
31
|
self._interrupted_request = None
|
|
32
32
|
self._active_callback = None
|
|
33
33
|
self._processed_instructions = 0
|
|
34
|
-
self._active_request = None
|
|
34
|
+
self._active_request: messages.ScanQueueMessage | None = None
|
|
35
35
|
self._user_callback = None
|
|
36
36
|
self._request_block_index = collections.defaultdict(lambda: 0)
|
|
37
37
|
self._request_block_id = None
|
|
38
|
-
self.print_table_data = True
|
|
39
38
|
self._current_queue = None
|
|
40
39
|
|
|
40
|
+
@property
|
|
41
|
+
def print_table_data(self):
|
|
42
|
+
return self.client.live_updates_config.print_live_table
|
|
43
|
+
|
|
41
44
|
def _process_report_instructions(self, report_instructions: list) -> None:
|
|
42
45
|
"""Process instructions for the live updates.
|
|
43
46
|
|
|
44
47
|
Args:
|
|
45
48
|
report_instructions (list): The list of report instructions.
|
|
46
49
|
"""
|
|
47
|
-
|
|
50
|
+
if not self._active_request:
|
|
51
|
+
return
|
|
52
|
+
scan_type = self._active_request.scan_type
|
|
48
53
|
if scan_type in ["open_scan_def", "close_scan_def"]:
|
|
49
|
-
self._process_instruction({"scan_progress": 0})
|
|
54
|
+
self._process_instruction({"scan_progress": {"points": 0, "show_table": True}})
|
|
50
55
|
return
|
|
51
56
|
if scan_type == "close_scan_group":
|
|
52
57
|
return
|
|
@@ -71,6 +76,9 @@ class IPythonLiveUpdates:
|
|
|
71
76
|
scan_report_type = list(instr.keys())[0]
|
|
72
77
|
scan_def_id = self.client.scans._scan_def_id
|
|
73
78
|
interactive_scan = self.client.scans._interactive_scan
|
|
79
|
+
if self._active_request is None:
|
|
80
|
+
# Already checked in caller method. It is just for type checking purposes.
|
|
81
|
+
return
|
|
74
82
|
if scan_def_id is None or interactive_scan:
|
|
75
83
|
if scan_report_type == "readback":
|
|
76
84
|
LiveUpdatesReadbackProgressbar(
|
|
@@ -123,21 +131,26 @@ class IPythonLiveUpdates:
|
|
|
123
131
|
)
|
|
124
132
|
self._active_callback.run()
|
|
125
133
|
|
|
126
|
-
def _available_req_blocks(
|
|
134
|
+
def _available_req_blocks(
|
|
135
|
+
self, queue: QueueItem, request: messages.ScanQueueMessage
|
|
136
|
+
) -> list[messages.RequestBlock]:
|
|
127
137
|
"""Get the available request blocks.
|
|
128
138
|
|
|
129
139
|
Args:
|
|
130
140
|
queue (QueueItem): The queue item.
|
|
131
141
|
request (messages.ScanQueueMessage): The request message.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
list[messages.RequestBlock]: The list of available request blocks.
|
|
132
145
|
"""
|
|
133
146
|
available_blocks = [
|
|
134
147
|
req_block
|
|
135
148
|
for req_block in queue.request_blocks
|
|
136
|
-
if req_block
|
|
149
|
+
if req_block.RID == request.metadata["RID"]
|
|
137
150
|
]
|
|
138
151
|
return available_blocks
|
|
139
152
|
|
|
140
|
-
def process_request(self, request: messages.ScanQueueMessage, callbacks:
|
|
153
|
+
def process_request(self, request: messages.ScanQueueMessage, callbacks: Any) -> None:
|
|
141
154
|
"""Process the request and report instructions."""
|
|
142
155
|
# pylint: disable=protected-access
|
|
143
156
|
try:
|
|
@@ -148,11 +161,14 @@ class IPythonLiveUpdates:
|
|
|
148
161
|
scan_request = ScanRequestMixin(self.client, request.metadata["RID"])
|
|
149
162
|
scan_request.wait()
|
|
150
163
|
|
|
164
|
+
# After .wait, we can be sure that the queue item is available, so we can
|
|
165
|
+
assert scan_request.scan_queue_request is not None
|
|
166
|
+
|
|
151
167
|
# get the corresponding queue item
|
|
152
|
-
while not scan_request.
|
|
168
|
+
while not scan_request.scan_queue_request.queue:
|
|
153
169
|
time.sleep(0.01)
|
|
154
170
|
|
|
155
|
-
self._current_queue = queue = scan_request.
|
|
171
|
+
self._current_queue = queue = scan_request.scan_queue_request.queue
|
|
156
172
|
self._request_block_id = req_id = self._active_request.metadata.get("RID")
|
|
157
173
|
|
|
158
174
|
while queue.status not in ["COMPLETED", "ABORTED", "HALTED"]:
|
|
@@ -161,7 +177,7 @@ class IPythonLiveUpdates:
|
|
|
161
177
|
|
|
162
178
|
available_blocks = self._available_req_blocks(queue, request)
|
|
163
179
|
req_block = available_blocks[self._request_block_index[req_id]]
|
|
164
|
-
report_instructions = req_block.
|
|
180
|
+
report_instructions = req_block.report_instructions or []
|
|
165
181
|
self._process_report_instructions(report_instructions)
|
|
166
182
|
|
|
167
183
|
self._reset()
|
|
@@ -185,12 +201,19 @@ class IPythonLiveUpdates:
|
|
|
185
201
|
self.client.queue.request_scan_halt()
|
|
186
202
|
|
|
187
203
|
def _element_in_queue(self) -> bool:
|
|
188
|
-
|
|
189
|
-
"info", []
|
|
190
|
-
)
|
|
191
|
-
if not queue:
|
|
204
|
+
if self.client.queue is None:
|
|
192
205
|
return False
|
|
193
|
-
|
|
206
|
+
if (csq := self.client.queue.queue_storage.current_scan_queue) is None:
|
|
207
|
+
return False
|
|
208
|
+
scan_queue_status = csq.get("primary")
|
|
209
|
+
if scan_queue_status is None:
|
|
210
|
+
return False
|
|
211
|
+
queue_info = scan_queue_status.info
|
|
212
|
+
if not queue_info:
|
|
213
|
+
return False
|
|
214
|
+
if self._current_queue is None:
|
|
215
|
+
return False
|
|
216
|
+
return self._current_queue.queue_id == queue_info[0].queue_id
|
|
194
217
|
|
|
195
218
|
def _process_queue(
|
|
196
219
|
self, queue: QueueItem, request: messages.ScanQueueMessage, req_id: str
|
|
@@ -210,9 +233,11 @@ class IPythonLiveUpdates:
|
|
|
210
233
|
if not queue.request_blocks or not queue.status or queue.queue_position is None:
|
|
211
234
|
return False
|
|
212
235
|
if queue.status == "PENDING" and queue.queue_position > 0:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
236
|
+
primary_queue = self.client.queue.queue_storage.current_scan_queue.get("primary")
|
|
237
|
+
|
|
238
|
+
if primary_queue is None:
|
|
239
|
+
return False
|
|
240
|
+
status = primary_queue.status
|
|
216
241
|
print(
|
|
217
242
|
"Scan is enqueued and is waiting for execution. Current position in queue:"
|
|
218
243
|
f" {queue.queue_position + 1}. Queue status: {status}.",
|
|
@@ -220,14 +245,16 @@ class IPythonLiveUpdates:
|
|
|
220
245
|
flush=True,
|
|
221
246
|
)
|
|
222
247
|
available_blocks = self._available_req_blocks(queue, request)
|
|
248
|
+
if not available_blocks:
|
|
249
|
+
return False
|
|
223
250
|
req_block = available_blocks[self._request_block_index[req_id]]
|
|
224
|
-
if req_block
|
|
251
|
+
if req_block.msg.scan_type in [
|
|
225
252
|
"open_scan_def",
|
|
226
253
|
"mv",
|
|
227
254
|
]: # TODO: make this more general for all scan types that don't have report instructions
|
|
228
255
|
return True
|
|
229
256
|
|
|
230
|
-
report_instructions = req_block[
|
|
257
|
+
report_instructions = req_block.report_instructions or []
|
|
231
258
|
if not report_instructions:
|
|
232
259
|
return False
|
|
233
260
|
self._process_report_instructions(report_instructions)
|
|
@@ -255,7 +282,11 @@ class IPythonLiveUpdates:
|
|
|
255
282
|
self._current_queue = None
|
|
256
283
|
self._user_callback = None
|
|
257
284
|
self._processed_instructions = 0
|
|
258
|
-
scan_closed =
|
|
285
|
+
scan_closed = (
|
|
286
|
+
forced
|
|
287
|
+
or self._active_request is None
|
|
288
|
+
or (self._active_request.scan_type == "close_scan_def")
|
|
289
|
+
)
|
|
259
290
|
self._active_request = None
|
|
260
291
|
|
|
261
292
|
if self.client.scans._scan_def_id and not scan_closed:
|
{bec_ipython_client-3.1.3 → bec_ipython_client-3.86.2}/bec_ipython_client/callbacks/live_table.py
RENAMED
|
@@ -2,7 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
4
|
from collections.abc import Callable
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING, Any, SupportsFloat
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
6
8
|
|
|
7
9
|
from bec_ipython_client.prettytable import PrettyTable
|
|
8
10
|
from bec_ipython_client.progressbar import ScanProgressBar
|
|
@@ -54,7 +56,6 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
54
56
|
super().__init__(
|
|
55
57
|
bec, report_instruction=report_instruction, request=request, callbacks=callbacks
|
|
56
58
|
)
|
|
57
|
-
self.scan_queue_request = None
|
|
58
59
|
self.scan_item = None
|
|
59
60
|
self.dev_values = None
|
|
60
61
|
self.point_data = None
|
|
@@ -66,16 +67,19 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
66
67
|
if print_table_data is not None
|
|
67
68
|
else self.REPORT_TYPE == "scan_progress"
|
|
68
69
|
)
|
|
70
|
+
self._devices_with_bad_precision = set()
|
|
69
71
|
|
|
70
72
|
def wait_for_scan_to_start(self):
|
|
71
73
|
"""wait until the scan starts"""
|
|
72
74
|
while True:
|
|
75
|
+
if not self.scan_item or not self.scan_item.queue:
|
|
76
|
+
raise RuntimeError("No scan item or scan queue available.")
|
|
73
77
|
queue_pos = self.scan_item.queue.queue_position
|
|
74
78
|
self.check_alarms()
|
|
75
79
|
if self.scan_item.status == "closed":
|
|
76
80
|
break
|
|
77
81
|
if queue_pos is None:
|
|
78
|
-
logger.
|
|
82
|
+
logger.trace(f"Could not find queue entry for scan_id {self.scan_item.scan_id}")
|
|
79
83
|
continue
|
|
80
84
|
if queue_pos == 0:
|
|
81
85
|
break
|
|
@@ -157,7 +161,20 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
157
161
|
return header
|
|
158
162
|
|
|
159
163
|
def update_scan_item(self, timeout: float = 15):
|
|
160
|
-
"""
|
|
164
|
+
"""
|
|
165
|
+
Get the current scan item and update self.scan_item
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
timeout (float): timeout in seconds
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
RuntimeError: if no scan queue request is available
|
|
172
|
+
TimeoutError: if no scan item is found before reaching the timeout
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
if not self.scan_queue_request:
|
|
176
|
+
raise RuntimeError("No scan queue request available.")
|
|
177
|
+
|
|
161
178
|
start = time.time()
|
|
162
179
|
while self.scan_queue_request.scan is None:
|
|
163
180
|
self.check_alarms()
|
|
@@ -168,25 +185,38 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
168
185
|
|
|
169
186
|
def core(self):
|
|
170
187
|
"""core function to run the live updates for the table"""
|
|
188
|
+
self._wait_for_report_instructions()
|
|
189
|
+
show_table = self.report_instruction[self.REPORT_TYPE].get("show_table", True)
|
|
190
|
+
self._print_table_data = show_table
|
|
191
|
+
self._run_update(self.report_instruction[self.REPORT_TYPE]["points"])
|
|
192
|
+
|
|
193
|
+
def _wait_for_report_instructions(self):
|
|
194
|
+
"""wait until the report instructions are available"""
|
|
195
|
+
if not self.scan_queue_request or not self.scan_item or not self.scan_item.queue:
|
|
196
|
+
logger.warning(
|
|
197
|
+
f"Cannot wait for report instructions. scan_queue_request: {self.scan_queue_request}, scan_item: {self.scan_item}, scan_item.queue: {getattr(self.scan_item, 'queue', None)}"
|
|
198
|
+
)
|
|
199
|
+
return
|
|
171
200
|
req_ID = self.scan_queue_request.requestID
|
|
172
201
|
while True:
|
|
173
202
|
request_block = [
|
|
174
|
-
req for req in self.scan_item.queue.request_blocks if req
|
|
203
|
+
req for req in self.scan_item.queue.request_blocks if req.RID == req_ID
|
|
175
204
|
][0]
|
|
176
|
-
if not request_block
|
|
205
|
+
if not request_block.is_scan:
|
|
177
206
|
break
|
|
178
|
-
if request_block
|
|
207
|
+
if request_block.report_instructions:
|
|
179
208
|
break
|
|
180
209
|
self.check_alarms()
|
|
181
210
|
|
|
182
|
-
self._run_update(self.report_instruction[self.REPORT_TYPE])
|
|
183
|
-
|
|
184
211
|
def _run_update(self, target_num_points: int):
|
|
185
212
|
"""run the update loop with the progress bar
|
|
186
213
|
|
|
187
214
|
Args:
|
|
188
215
|
target_num_points (int): number of points to be collected
|
|
189
216
|
"""
|
|
217
|
+
if not self.scan_item:
|
|
218
|
+
logger.warning("No scan item available for live updates.")
|
|
219
|
+
return
|
|
190
220
|
with ScanProgressBar(
|
|
191
221
|
scan_number=self.scan_item.scan_number, clear_on_exit=self._print_table_data
|
|
192
222
|
) as progressbar:
|
|
@@ -208,7 +238,7 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
208
238
|
self.bec.callbacks.poll()
|
|
209
239
|
self.scan_item.poll_callbacks()
|
|
210
240
|
else:
|
|
211
|
-
logger.
|
|
241
|
+
logger.trace("waiting for new data point")
|
|
212
242
|
time.sleep(0.1)
|
|
213
243
|
|
|
214
244
|
if not self.scan_item.num_points:
|
|
@@ -219,6 +249,24 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
219
249
|
if self.point_id > self.scan_item.num_points:
|
|
220
250
|
raise RuntimeError("Received more points than expected.")
|
|
221
251
|
|
|
252
|
+
if len(self.scan_item.live_data) == 0 and self.scan_item.status == "closed":
|
|
253
|
+
msg = self.scan_item.status_message
|
|
254
|
+
if not msg:
|
|
255
|
+
continue
|
|
256
|
+
if msg.readout_priority.get("monitored", []):
|
|
257
|
+
continue
|
|
258
|
+
|
|
259
|
+
logger.warning(
|
|
260
|
+
f"\n Scan {self.scan_item.scan_number} finished. No monitored devices enabled, please check your config."
|
|
261
|
+
)
|
|
262
|
+
break
|
|
263
|
+
|
|
264
|
+
def _warn_bad_precisions(self):
|
|
265
|
+
if self._devices_with_bad_precision != set():
|
|
266
|
+
for dev, prec in self._devices_with_bad_precision:
|
|
267
|
+
logger.warning(f"Device {dev} reported malformed precision of {prec}!")
|
|
268
|
+
self._devices_with_bad_precision = set()
|
|
269
|
+
|
|
222
270
|
@property
|
|
223
271
|
def _print_table_data(self) -> bool:
|
|
224
272
|
"""Checks if the table should be printed or not.
|
|
@@ -249,6 +297,8 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
249
297
|
|
|
250
298
|
def print_table_data(self):
|
|
251
299
|
"""print the table data for the current point_id"""
|
|
300
|
+
# pylint: disable=protected-access
|
|
301
|
+
self._print_client_msgs_asap()
|
|
252
302
|
if not self._print_table_data:
|
|
253
303
|
return
|
|
254
304
|
|
|
@@ -259,41 +309,40 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
259
309
|
|
|
260
310
|
if self.point_id % 100 == 0:
|
|
261
311
|
print(self.table.get_header_lines())
|
|
262
|
-
|
|
312
|
+
|
|
313
|
+
signals_precisions = []
|
|
263
314
|
for dev in self.devices:
|
|
264
315
|
if dev in self.bec.device_manager.devices:
|
|
265
316
|
obj = self.bec.device_manager.devices[dev]
|
|
266
317
|
for hint in obj._hints:
|
|
267
318
|
signal = self.point_data.content["data"].get(obj.root.name, {}).get(hint)
|
|
268
|
-
if signal is None
|
|
269
|
-
|
|
319
|
+
if signal is None:
|
|
320
|
+
signals_precisions.append((None, None))
|
|
270
321
|
else:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if isinstance(value, (int, float)):
|
|
277
|
-
print_value = f"{value:.{precision}f}"
|
|
278
|
-
else:
|
|
279
|
-
print_value = str(value)
|
|
280
|
-
self.dev_values[ind] = print_value
|
|
281
|
-
ind += 1
|
|
322
|
+
prec = getattr(obj, "precision", 2)
|
|
323
|
+
if not isinstance(prec, int):
|
|
324
|
+
self._devices_with_bad_precision.add((dev, prec))
|
|
325
|
+
prec = 2
|
|
326
|
+
signals_precisions.append((signal, prec))
|
|
282
327
|
else:
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
else:
|
|
289
|
-
print_value = str(value)
|
|
290
|
-
else:
|
|
291
|
-
print_value = "N/A"
|
|
292
|
-
self.dev_values[ind] = print_value
|
|
293
|
-
ind += 1
|
|
328
|
+
signals_precisions.append((self.point_data.content["data"].get(dev, {}), 2))
|
|
329
|
+
|
|
330
|
+
for i, (signal, precision) in enumerate(signals_precisions):
|
|
331
|
+
self.dev_values[i] = self._format_value(signal, precision)
|
|
332
|
+
|
|
294
333
|
print(self.table.get_row(str(self.point_id), *self.dev_values))
|
|
295
|
-
|
|
296
|
-
|
|
334
|
+
|
|
335
|
+
def _format_value(self, signal: dict | None, precision: int = 2):
|
|
336
|
+
if signal is None:
|
|
337
|
+
return "N/A"
|
|
338
|
+
val = signal.get("value")
|
|
339
|
+
if isinstance(val, SupportsFloat) and not isinstance(val, np.ndarray):
|
|
340
|
+
if precision < 0:
|
|
341
|
+
# This is to cover the special case when EPICS returns a negative precision.
|
|
342
|
+
# More info: https://epics.anl.gov/tech-talk/2004/msg00434.php
|
|
343
|
+
return f"{float(val):.{-precision}g}"
|
|
344
|
+
return f"{float(val):.{precision}f}"
|
|
345
|
+
return str(val)
|
|
297
346
|
|
|
298
347
|
def close_table(self):
|
|
299
348
|
"""close the table and print the footer"""
|
|
@@ -305,8 +354,7 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
305
354
|
f"Scan {self.scan_item.scan_number} finished. Scan ID {self.scan_item.scan_id}. Elapsed time: {elapsed_time:.2f} s"
|
|
306
355
|
)
|
|
307
356
|
)
|
|
308
|
-
|
|
309
|
-
# self._print_client_msgs_all()
|
|
357
|
+
self._warn_bad_precisions()
|
|
310
358
|
|
|
311
359
|
def process_request(self):
|
|
312
360
|
"""process the request and start the core loop for live updates"""
|
|
@@ -336,3 +384,4 @@ class LiveUpdatesTable(LiveUpdatesBase):
|
|
|
336
384
|
self.wait_for_scan_item_to_finish()
|
|
337
385
|
if self._print_table_data:
|
|
338
386
|
self.close_table()
|
|
387
|
+
self._print_client_msgs_all()
|