bec-ipython-client 3.84.0__py3-none-any.whl → 3.89.3__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.

PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_ipython_client
3
- Version: 3.84.0
3
+ Version: 3.89.3
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
@@ -31,7 +31,7 @@ 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
@@ -47,7 +47,9 @@ class IPythonLiveUpdates:
47
47
  Args:
48
48
  report_instructions (list): The list of report instructions.
49
49
  """
50
- scan_type = self._active_request.content["scan_type"]
50
+ if not self._active_request:
51
+ return
52
+ scan_type = self._active_request.scan_type
51
53
  if scan_type in ["open_scan_def", "close_scan_def"]:
52
54
  self._process_instruction({"scan_progress": {"points": 0, "show_table": True}})
53
55
  return
@@ -74,6 +76,9 @@ class IPythonLiveUpdates:
74
76
  scan_report_type = list(instr.keys())[0]
75
77
  scan_def_id = self.client.scans._scan_def_id
76
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
77
82
  if scan_def_id is None or interactive_scan:
78
83
  if scan_report_type == "readback":
79
84
  LiveUpdatesReadbackProgressbar(
@@ -126,17 +131,22 @@ class IPythonLiveUpdates:
126
131
  )
127
132
  self._active_callback.run()
128
133
 
129
- def _available_req_blocks(self, queue: QueueItem, request: messages.ScanQueueMessage):
134
+ def _available_req_blocks(
135
+ self, queue: QueueItem, request: messages.ScanQueueMessage
136
+ ) -> list[messages.RequestBlock]:
130
137
  """Get the available request blocks.
131
138
 
132
139
  Args:
133
140
  queue (QueueItem): The queue item.
134
141
  request (messages.ScanQueueMessage): The request message.
142
+
143
+ Returns:
144
+ list[messages.RequestBlock]: The list of available request blocks.
135
145
  """
136
146
  available_blocks = [
137
147
  req_block
138
148
  for req_block in queue.request_blocks
139
- if req_block["RID"] == request.metadata["RID"]
149
+ if req_block.RID == request.metadata["RID"]
140
150
  ]
141
151
  return available_blocks
142
152
 
@@ -167,7 +177,7 @@ class IPythonLiveUpdates:
167
177
 
168
178
  available_blocks = self._available_req_blocks(queue, request)
169
179
  req_block = available_blocks[self._request_block_index[req_id]]
170
- report_instructions = req_block.get("report_instructions", [])
180
+ report_instructions = req_block.report_instructions or []
171
181
  self._process_report_instructions(report_instructions)
172
182
 
173
183
  self._reset()
@@ -191,12 +201,19 @@ class IPythonLiveUpdates:
191
201
  self.client.queue.request_scan_halt()
192
202
 
193
203
  def _element_in_queue(self) -> bool:
194
- queue = self.client.queue.queue_storage.current_scan_queue.get("primary", {}).get(
195
- "info", []
196
- )
197
- if not queue:
204
+ if self.client.queue is None:
198
205
  return False
199
- return self._current_queue.queue_id in queue[0].get("queue_id")
206
+ if (csq := self.client.queue.queue_storage.current_scan_queue) is None:
207
+ return False
208
+ scan_queue_status = csq.get(self.client.queue.get_default_scan_queue())
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
200
217
 
201
218
  def _process_queue(
202
219
  self, queue: QueueItem, request: messages.ScanQueueMessage, req_id: str
@@ -216,11 +233,15 @@ class IPythonLiveUpdates:
216
233
  if not queue.request_blocks or not queue.status or queue.queue_position is None:
217
234
  return False
218
235
  if queue.status == "PENDING" and queue.queue_position > 0:
219
- status = self.client.queue.queue_storage.current_scan_queue.get("primary", {}).get(
220
- "status"
236
+ target_queue = self.client.queue.queue_storage.current_scan_queue.get(
237
+ self.client.queue.get_default_scan_queue()
221
238
  )
239
+
240
+ if target_queue is None:
241
+ return False
242
+ status = target_queue.status
222
243
  print(
223
- "Scan is enqueued and is waiting for execution. Current position in queue:"
244
+ f"Scan is enqueued and is waiting for execution. Current position in queue {self.client.queue.get_default_scan_queue()}:"
224
245
  f" {queue.queue_position + 1}. Queue status: {status}.",
225
246
  end="\r",
226
247
  flush=True,
@@ -229,13 +250,13 @@ class IPythonLiveUpdates:
229
250
  if not available_blocks:
230
251
  return False
231
252
  req_block = available_blocks[self._request_block_index[req_id]]
232
- if req_block["content"]["scan_type"] in [
253
+ if req_block.msg.scan_type in [
233
254
  "open_scan_def",
234
255
  "mv",
235
256
  ]: # TODO: make this more general for all scan types that don't have report instructions
236
257
  return True
237
258
 
238
- report_instructions = req_block["report_instructions"]
259
+ report_instructions = req_block.report_instructions or []
239
260
  if not report_instructions:
240
261
  return False
241
262
  self._process_report_instructions(report_instructions)
@@ -263,7 +284,11 @@ class IPythonLiveUpdates:
263
284
  self._current_queue = None
264
285
  self._user_callback = None
265
286
  self._processed_instructions = 0
266
- scan_closed = forced or (self._active_request.content["scan_type"] == "close_scan_def")
287
+ scan_closed = (
288
+ forced
289
+ or self._active_request is None
290
+ or (self._active_request.scan_type == "close_scan_def")
291
+ )
267
292
  self._active_request = None
268
293
 
269
294
  if self.client.scans._scan_def_id and not scan_closed:
@@ -56,7 +56,6 @@ class LiveUpdatesTable(LiveUpdatesBase):
56
56
  super().__init__(
57
57
  bec, report_instruction=report_instruction, request=request, callbacks=callbacks
58
58
  )
59
- self.scan_queue_request = None
60
59
  self.scan_item = None
61
60
  self.dev_values = None
62
61
  self.point_data = None
@@ -73,6 +72,8 @@ class LiveUpdatesTable(LiveUpdatesBase):
73
72
  def wait_for_scan_to_start(self):
74
73
  """wait until the scan starts"""
75
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.")
76
77
  queue_pos = self.scan_item.queue.queue_position
77
78
  self.check_alarms()
78
79
  if self.scan_item.status == "closed":
@@ -160,7 +161,20 @@ class LiveUpdatesTable(LiveUpdatesBase):
160
161
  return header
161
162
 
162
163
  def update_scan_item(self, timeout: float = 15):
163
- """get the current scan item"""
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
+
164
178
  start = time.time()
165
179
  while self.scan_queue_request.scan is None:
166
180
  self.check_alarms()
@@ -178,14 +192,19 @@ class LiveUpdatesTable(LiveUpdatesBase):
178
192
 
179
193
  def _wait_for_report_instructions(self):
180
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
181
200
  req_ID = self.scan_queue_request.requestID
182
201
  while True:
183
202
  request_block = [
184
- req for req in self.scan_item.queue.request_blocks if req["RID"] == req_ID
203
+ req for req in self.scan_item.queue.request_blocks if req.RID == req_ID
185
204
  ][0]
186
- if not request_block["is_scan"]:
205
+ if not request_block.is_scan:
187
206
  break
188
- if request_block["report_instructions"]:
207
+ if request_block.report_instructions:
189
208
  break
190
209
  self.check_alarms()
191
210
 
@@ -195,6 +214,9 @@ class LiveUpdatesTable(LiveUpdatesBase):
195
214
  Args:
196
215
  target_num_points (int): number of points to be collected
197
216
  """
217
+ if not self.scan_item:
218
+ logger.warning("No scan item available for live updates.")
219
+ return
198
220
  with ScanProgressBar(
199
221
  scan_number=self.scan_item.scan_number, clear_on_exit=self._print_table_data
200
222
  ) as progressbar:
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import abc
4
- import threading
5
4
  import time
6
5
  import traceback
7
6
  from collections.abc import Callable
@@ -21,23 +20,6 @@ class ScanRequestError(Exception):
21
20
  """Error raised when a scan request is rejected"""
22
21
 
23
22
 
24
- def set_event_delayed(event: threading.Event, delay: int) -> None:
25
- """Set event with a delay
26
-
27
- Args:
28
- event (threading.Event): event that should be set
29
- delay (int): delay time in seconds
30
-
31
- """
32
-
33
- def call_set():
34
- time.sleep(delay)
35
- event.set()
36
-
37
- thread = threading.Thread(target=call_set, daemon=True)
38
- thread.start()
39
-
40
-
41
23
  def check_alarms(bec):
42
24
  """check for alarms and raise them if needed"""
43
25
  bec.alarm_handler.raise_alarms()
@@ -62,7 +44,7 @@ class LiveUpdatesBase(abc.ABC):
62
44
  self.bec = bec
63
45
  self.request = request
64
46
  self.RID = request.metadata["RID"]
65
- self.scan_queue_request = None
47
+ self.scan_queue_request: RequestItem | None = None
66
48
  self.report_instruction = report_instruction
67
49
  if callbacks is None:
68
50
  self.callbacks = []
@@ -29,6 +29,7 @@ from bec_lib.bec_service import parse_cmdline_args
29
29
  from bec_lib.callback_handler import EventType
30
30
  from bec_lib.client import BECClient
31
31
  from bec_lib.logger import bec_logger
32
+ from bec_lib.procedures.hli import ProcedureHli
32
33
  from bec_lib.redis_connector import RedisConnector
33
34
  from bec_lib.service_config import ServiceConfig
34
35
  from bec_lib.utils.pydantic_pretty_print import pretty_print_pydantic_validation_error
@@ -58,6 +59,7 @@ class BECIPythonClient:
58
59
  # the CLIBECClient but directly through the BECIPythonClient. While this is not
59
60
  # needed for normal usage, it is required, e.g. for mocks.
60
61
  _local_only_types: Tuple = ()
62
+ _client: CLIBECClient | BECClient
61
63
 
62
64
  def __init__(
63
65
  self,
@@ -75,6 +77,7 @@ class BECIPythonClient:
75
77
  name="BECIPythonClient",
76
78
  prompt_for_acl=True,
77
79
  )
80
+
78
81
  self._ip = IPython.get_ipython()
79
82
  self.started = False
80
83
  self._sighandler = None
@@ -150,6 +153,7 @@ class BECIPythonClient:
150
153
  object,
151
154
  lambda o, p, cycle: o.__str__ is object.__str__ and p.text(repr(o)) or p.text(str(o)),
152
155
  )
156
+ self._set_idle()
153
157
 
154
158
  def _update_namespace_callback(self, action: Literal["add", "remove"], ns_objects: dict):
155
159
  """Callback to update the global namespace of ipython.
@@ -187,13 +191,14 @@ class BECIPythonClient:
187
191
  magics = BECMagics(self._ip, self)
188
192
  self._ip.register_magics(magics)
189
193
 
190
- def shutdown(self):
194
+ def shutdown(self, per_thread_timeout_s: float | None = None):
191
195
  """shutdown the client and all its components"""
192
196
  try:
193
197
  self.gui.close()
194
198
  except AttributeError:
195
199
  pass
196
- self._client.shutdown()
200
+ self._client.shutdown(per_thread_timeout_s)
201
+ logger.success("done")
197
202
 
198
203
  def _create_exception_handler(self):
199
204
  return functools.partial(_ip_exception_handler, parent=self)
@@ -338,7 +343,6 @@ def main():
338
343
  parser = argparse.ArgumentParser(
339
344
  prog="BEC IPython client", description="BEC command line interface"
340
345
  )
341
- parser.add_argument("--version", action="store_true", default=False)
342
346
  parser.add_argument("--nogui", action="store_true", default=False)
343
347
  parser.add_argument(
344
348
  "--gui-id",
@@ -358,10 +362,6 @@ def main():
358
362
  # remove already parsed args from command line args
359
363
  sys.argv = sys.argv[:1] + left_args
360
364
 
361
- if args.version:
362
- print(f"BEC IPython client: {version('bec_ipython_client')}")
363
- sys.exit(0)
364
-
365
365
  if available_plugins and config.is_default():
366
366
  # check if config is defined in a plugin;
367
367
  # in this case the plugin config takes precedence over
@@ -1,9 +1,15 @@
1
+ from __future__ import annotations
2
+
1
3
  import signal
2
4
  import threading
3
5
  import time
6
+ from typing import TYPE_CHECKING
4
7
 
5
8
  from bec_lib.bec_errors import ScanInterruption
6
9
 
10
+ if TYPE_CHECKING: # pragma: no cover
11
+ from bec_lib.client import BECClient
12
+
7
13
  PAUSE_MSG = """
8
14
  The Scan Queue is entering a paused state. These are your options for changing
9
15
  the state of the queue:
@@ -73,7 +79,7 @@ class SignalHandler:
73
79
 
74
80
 
75
81
  class SigintHandler(SignalHandler):
76
- def __init__(self, bec):
82
+ def __init__(self, bec: BECClient):
77
83
  super().__init__(signal.SIGINT)
78
84
  self.bec = bec
79
85
  self.last_sigint_time = None # time most recent SIGINT was processed
@@ -84,11 +90,11 @@ class SigintHandler(SignalHandler):
84
90
  if not current_scan:
85
91
  raise KeyboardInterrupt
86
92
 
87
- status = current_scan.get("status").lower()
93
+ status = current_scan.status.lower()
88
94
  if status not in ["running", "deferred_pause"]:
89
95
  raise KeyboardInterrupt
90
96
 
91
- if any(current_scan.get("is_scan")) and (
97
+ if any(current_scan.is_scan) and (
92
98
  self.last_sigint_time is None or time.time() - self.last_sigint_time > 10
93
99
  ):
94
100
  # reset the counter to 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_ipython_client
3
- Version: 3.84.0
3
+ Version: 3.89.3
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
@@ -1,21 +1,21 @@
1
1
  .gitignore,sha256=XxC6jyyftTo2CLtm4K8axuNPYwA9Wgaz2R93WhX8bTQ,3364
2
- PKG-INFO,sha256=ijJpv2TMYSqXFg607-vZwabQv7Ib-B-cWeiu71D9-Xc,1052
2
+ PKG-INFO,sha256=trZwsE2OpNig8TZ6MjQ6HN5lX5tRxLrygSFHiz4FATs,1052
3
3
  demo.py,sha256=AquJB-0Tu2bIEFpcJ0Q3RUBt1xldqS6GgU5CjOAg_ww,7083
4
- pyproject.toml,sha256=HuXmixsLCNg82K4Z_GDHOdRWAVHza_oeQ7Pe5fKipHU,1229
4
+ pyproject.toml,sha256=Gv8V_TLxvNXUR9W-ROwlP7dGq9cbC8BfRC9anNsoddQ,1229
5
5
  bec_ipython_client/__init__.py,sha256=ihd_V8I7Qo0MWKMo7bcvPf-ZyUQqkcNf8IAWLJKiFJE,79
6
6
  bec_ipython_client/beamline_mixin.py,sha256=scMWIFbHJajyECzbwEVKyQUGjpqA9C_KiU2M6FuRH_Q,1067
7
7
  bec_ipython_client/bec_magics.py,sha256=Rz2aXkUCeAV_VxdXqLUNHh8T44kSH9ha83OiEtdptzI,2792
8
8
  bec_ipython_client/bec_startup.py,sha256=GGlHyxnSCQfYF5n-pYq2ic0pSyW5zvnT2PAlI5kY77w,1930
9
- bec_ipython_client/main.py,sha256=XxSaiRuvDPKRzM3yuPz3FW5I0wnfyL2SgEfEG3VrDJg,13543
9
+ bec_ipython_client/main.py,sha256=h7417qQj0rwj5gjbKg6pn8Y_yYJf4b8Fs5EP29sXXOg,13564
10
10
  bec_ipython_client/prettytable.py,sha256=TnhGPGuU0XEvnIYJ1UfTEwadcowFW4rqJW8z_Sm0EDw,2534
11
11
  bec_ipython_client/progressbar.py,sha256=aDKYjzXmGSwa82ewm59V8WSuqVQz9GiZPx5G65fEwpk,11090
12
- bec_ipython_client/signals.py,sha256=mbThPo6h3mQ6RFRm9viETDMC_unFa7QxiymCdM_ZK7U,4194
12
+ bec_ipython_client/signals.py,sha256=P7FrIB66dGdPESUngBnIVPGrAhcd2Tek8ufq9ALBoYU,4340
13
13
  bec_ipython_client/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  bec_ipython_client/callbacks/device_progress.py,sha256=NzWIO1oowxpN14hPXBne6RfZxOW82EhwDM3jHLnn3xI,2603
15
- bec_ipython_client/callbacks/ipython_live_updates.py,sha256=Cx62yJSzIcRvAA_lC86PSs8825hJtk22yBPhuJjgRPc,10520
16
- bec_ipython_client/callbacks/live_table.py,sha256=3qmwCEjqe-xFqo3Ez2dhKchawlr0cOmNgzpg857-gp0,13503
15
+ bec_ipython_client/callbacks/ipython_live_updates.py,sha256=zaXR6UX7fA01-N0B0C2t67lOqjBgq8nal8eZpgf_nDc,11416
16
+ bec_ipython_client/callbacks/live_table.py,sha256=ewdter1z1arV1EvEsReOihluyTmAHMrHtOu_S29vixo,14432
17
17
  bec_ipython_client/callbacks/move_device.py,sha256=TkbtKFBr1nuJ8THTSm70Y8D7_va2YQ57oBGGt0BavW4,8153
18
- bec_ipython_client/callbacks/utils.py,sha256=jS92ndZNU2t5n9FsLrSbulmUIeepHhwdQdfO60YjiJk,5702
18
+ bec_ipython_client/callbacks/utils.py,sha256=e8USLnufNTpI2Bkp-sjRsSFvIXxB2CirF6WRPVsOets,5338
19
19
  bec_ipython_client/high_level_interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  bec_ipython_client/high_level_interfaces/bec_hli.py,sha256=G1eF-lbLEDuN2mr_AYSxhhGb4LT6P72g0a4sU9M9zgk,1221
21
21
  bec_ipython_client/high_level_interfaces/spec_hli.py,sha256=z7WtjiC4LtMfKJn12SbguHPCLqbAsZNfUDyiUW0LOiU,5818
@@ -30,15 +30,15 @@ tests/client_tests/conftest.py,sha256=dUvYqvl6wogxN-kRCUuo_Uzna3P2uSYD7qGHyMGBlP
30
30
  tests/client_tests/test_beamline_mixins.py,sha256=8Ws0bmzl2gSW0VuOVu80_JbYNb5Y-htchmrrptwjwDo,4611
31
31
  tests/client_tests/test_bec_client.py,sha256=gks7IYoDDXpbkC_EGWQfKtH08Yig71PRZtleXFhkU8A,8903
32
32
  tests/client_tests/test_device_progress.py,sha256=GEw2g8MQZnv5mxABEZlxBmaMpxVS33wogaYohFolDEs,2353
33
- tests/client_tests/test_ipython_live_updates.py,sha256=HpA16Mx0WdseqlfP0FojlAd6VfvjnwAUReDkItZVYWs,6081
33
+ tests/client_tests/test_ipython_live_updates.py,sha256=s4HLqF2hVQaaJhRKv0dxj0DiZK5M9Z_WwMAbGk19EFg,11918
34
34
  tests/client_tests/test_live_table.py,sha256=0EKgWOgDqpjN8fJEeAzoKNCwFneDEsUY2NfWQkxB6xc,18155
35
35
  tests/client_tests/test_move_callback.py,sha256=bUCcWoz_tc-yRAtwmEUMrKE_qKwatop_wReSugGGEIQ,8670
36
36
  tests/client_tests/test_pretty_table.py,sha256=uQ-KPb3RXoCFE_t1IrpkT6kZAoqW7pFXxbFc445sX0Y,469
37
- tests/end-2-end/_ensure_requirements_container.py,sha256=RT2a5cUZnXtMjJRlPImGzgyX-KJPLiav5WSp_l3wzv8,725
38
- tests/end-2-end/test_procedures_e2e.py,sha256=xjczbB33Cf5pDLe1KWcalRRBrSxOQ3cbV3s3w3GsC_A,4922
39
- tests/end-2-end/test_scans_e2e.py,sha256=GMStotxqu24794TxIFc55PCtOjuG2qbkmibcUj7r-DI,31069
37
+ tests/end-2-end/_ensure_requirements_container.py,sha256=dv_ACfo9nHyiiEL3AMccaEg38VxGqB0W7u_iPyCVeiE,701
38
+ tests/end-2-end/test_procedures_e2e.py,sha256=ai8pUrHaL-5sYuaKYucuYEwyvhelS1dsW9KGoBb1Riw,4872
39
+ tests/end-2-end/test_scans_e2e.py,sha256=JhyWjDyUlBZAM_gL6QtfKXfI_fXdUnykYCIWCSorRiA,32527
40
40
  tests/end-2-end/test_scans_lib_e2e.py,sha256=dqs0ojkyQWStIQbqABq9mQrjqQyE43gr37VPhxvQ8b8,19427
41
- bec_ipython_client-3.84.0.dist-info/METADATA,sha256=ijJpv2TMYSqXFg607-vZwabQv7Ib-B-cWeiu71D9-Xc,1052
42
- bec_ipython_client-3.84.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
43
- bec_ipython_client-3.84.0.dist-info/entry_points.txt,sha256=oQUXYY0jjD9ZvKPHwaGn2wkUIWpDZM8L4ixDA3RlBWE,53
44
- bec_ipython_client-3.84.0.dist-info/RECORD,,
41
+ bec_ipython_client-3.89.3.dist-info/METADATA,sha256=trZwsE2OpNig8TZ6MjQ6HN5lX5tRxLrygSFHiz4FATs,1052
42
+ bec_ipython_client-3.89.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
43
+ bec_ipython_client-3.89.3.dist-info/entry_points.txt,sha256=oQUXYY0jjD9ZvKPHwaGn2wkUIWpDZM8L4ixDA3RlBWE,53
44
+ bec_ipython_client-3.89.3.dist-info/RECORD,,
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_ipython_client"
7
- version = "3.84.0"
7
+ version = "3.89.3"
8
8
  description = "BEC IPython client"
9
9
  requires-python = ">=3.11"
10
10
  classifiers = [
@@ -7,34 +7,94 @@ from bec_lib import messages
7
7
  from bec_lib.queue_items import QueueItem
8
8
 
9
9
 
10
- @pytest.mark.timeout(20)
11
- def test_live_updates_process_queue_pending(bec_client_mock):
10
+ @pytest.fixture
11
+ def queue_elements(bec_client_mock):
12
12
  client = bec_client_mock
13
- live_updates = IPythonLiveUpdates(client)
14
13
  request_msg = messages.ScanQueueMessage(
15
14
  scan_type="grid_scan",
16
15
  parameter={"args": {"samx": (-5, 5, 3)}, "kwargs": {}},
17
16
  queue="primary",
18
17
  metadata={"RID": "something"},
19
18
  )
19
+ request_block = messages.RequestBlock(
20
+ msg=request_msg,
21
+ RID="req_id",
22
+ scan_motors=["samx"],
23
+ report_instructions=[],
24
+ readout_priority={"monitored": ["samx"]},
25
+ is_scan=True,
26
+ scan_number=1,
27
+ scan_id="scan_id",
28
+ )
20
29
  queue = QueueItem(
21
30
  scan_manager=client.queue,
22
31
  queue_id="queue_id",
23
- request_blocks=[request_msg],
32
+ request_blocks=[request_block],
24
33
  status="PENDING",
25
34
  active_request_block={},
26
35
  scan_id=["scan_id"],
27
36
  )
28
- client.queue.queue_storage.current_scan_queue = {"primary": {"status": "RUNNING"}}
37
+ return queue, request_block, request_msg
38
+
39
+
40
+ @pytest.fixture
41
+ def sample_request_msg():
42
+ return messages.ScanQueueMessage(
43
+ scan_type="grid_scan",
44
+ parameter={"args": {"samx": (-5, 5, 3)}, "kwargs": {}},
45
+ queue="primary",
46
+ metadata={"RID": "something"},
47
+ )
48
+
49
+
50
+ @pytest.fixture
51
+ def sample_request_block(sample_request_msg):
52
+ return messages.RequestBlock(
53
+ msg=sample_request_msg,
54
+ RID="req_id",
55
+ scan_motors=["samx"],
56
+ report_instructions=[],
57
+ readout_priority={"monitored": ["samx"]},
58
+ is_scan=True,
59
+ scan_number=1,
60
+ scan_id="scan_id",
61
+ )
62
+
63
+
64
+ @pytest.fixture
65
+ def sample_queue_info_entry(sample_request_block):
66
+ return messages.QueueInfoEntry(
67
+ queue_id="test_queue_id",
68
+ scan_id=["scan_id"],
69
+ is_scan=[True],
70
+ request_blocks=[sample_request_block],
71
+ scan_number=[1],
72
+ status="RUNNING",
73
+ active_request_block=None,
74
+ )
75
+
76
+
77
+ @pytest.fixture
78
+ def sample_scan_queue_status(sample_queue_info_entry):
79
+ return messages.ScanQueueStatus(info=[sample_queue_info_entry], status="RUNNING")
80
+
81
+
82
+ @pytest.mark.timeout(20)
83
+ def test_live_updates_process_queue_pending(bec_client_mock, queue_elements):
84
+ client = bec_client_mock
85
+ live_updates = IPythonLiveUpdates(client)
86
+ queue, request_block, request_msg = queue_elements
87
+
88
+ client.queue.queue_storage.current_scan_queue = {
89
+ "primary": messages.ScanQueueStatus(info=[], status="RUNNING")
90
+ }
29
91
  with mock.patch.object(queue, "_update_with_buffer"):
30
92
  with mock.patch(
31
93
  "bec_lib.queue_items.QueueItem.queue_position", new_callable=mock.PropertyMock
32
94
  ) as queue_pos:
33
95
  queue_pos.return_value = 2
34
96
  with mock.patch.object(
35
- live_updates,
36
- "_available_req_blocks",
37
- return_value=[{"report_instructions": [], "content": {"scan_type": "grid_scan"}}],
97
+ live_updates, "_available_req_blocks", return_value=[request_block]
38
98
  ):
39
99
  with mock.patch.object(live_updates, "_process_report_instructions") as process:
40
100
  with mock.patch("builtins.print") as prt:
@@ -45,39 +105,30 @@ def test_live_updates_process_queue_pending(bec_client_mock):
45
105
 
46
106
 
47
107
  @pytest.mark.timeout(20)
48
- def test_live_updates_process_queue_running(bec_client_mock):
108
+ def test_live_updates_process_queue_running(bec_client_mock, queue_elements):
49
109
  client = bec_client_mock
50
110
  live_updates = IPythonLiveUpdates(client)
51
- request_msg = messages.ScanQueueMessage(
52
- scan_type="grid_scan",
53
- parameter={"args": {"samx": (-5, 5, 3)}, "kwargs": {}},
54
- queue="primary",
55
- metadata={"RID": "something"},
56
- )
111
+ _, request_block, request_msg = queue_elements
57
112
  queue = QueueItem(
58
113
  scan_manager=client.queue,
59
114
  queue_id="queue_id",
60
- request_blocks=[request_msg],
115
+ request_blocks=[request_block],
61
116
  status="RUNNING",
62
117
  active_request_block={},
63
118
  scan_id=["scan_id"],
64
119
  )
65
120
  live_updates._active_request = request_msg
66
- client.queue.queue_storage.current_scan_queue = {"primary": {"status": "RUNNING"}}
121
+ request_block.report_instructions = [{"wait_table": 10}]
122
+ client.queue.queue_storage.current_scan_queue = {
123
+ "primary": messages.ScanQueueStatus(info=[], status="RUNNING")
124
+ }
67
125
  with mock.patch.object(queue, "_update_with_buffer"):
68
126
  with mock.patch(
69
127
  "bec_lib.queue_items.QueueItem.queue_position", new_callable=mock.PropertyMock
70
128
  ) as queue_pos:
71
129
  queue_pos.return_value = 2
72
130
  with mock.patch.object(
73
- live_updates,
74
- "_available_req_blocks",
75
- return_value=[
76
- {
77
- "report_instructions": [{"wait_table": 10}],
78
- "content": {"scan_type": "grid_scan"},
79
- }
80
- ],
131
+ live_updates, "_available_req_blocks", return_value=[request_block]
81
132
  ):
82
133
  with mock.patch.object(live_updates, "_process_instruction") as process:
83
134
  with mock.patch("builtins.print") as prt:
@@ -88,37 +139,19 @@ def test_live_updates_process_queue_running(bec_client_mock):
88
139
 
89
140
 
90
141
  @pytest.mark.timeout(20)
91
- def test_live_updates_process_queue_without_status(bec_client_mock):
142
+ def test_live_updates_process_queue_without_status(bec_client_mock, queue_elements):
92
143
  client = bec_client_mock
93
144
  live_updates = IPythonLiveUpdates(client)
94
- request_msg = messages.ScanQueueMessage(
95
- scan_type="grid_scan",
96
- parameter={"args": {"samx": (-5, 5, 3)}, "kwargs": {}},
97
- queue="primary",
98
- metadata={"RID": "something"},
99
- )
100
- queue = QueueItem(
101
- scan_manager=client.queue,
102
- queue_id="queue_id",
103
- request_blocks=[request_msg],
104
- status=None,
105
- active_request_block={},
106
- scan_id=["scan_id"],
107
- )
145
+ queue, _, request_msg = queue_elements
108
146
  with mock.patch.object(queue, "_update_with_buffer"):
109
147
  assert live_updates._process_queue(queue, request_msg, "req_id") is False
110
148
 
111
149
 
112
150
  @pytest.mark.timeout(20)
113
- def test_live_updates_process_queue_without_queue_number(bec_client_mock):
151
+ def test_live_updates_process_queue_without_queue_number(bec_client_mock, queue_elements):
114
152
  client = bec_client_mock
115
153
  live_updates = IPythonLiveUpdates(client)
116
- request_msg = messages.ScanQueueMessage(
117
- scan_type="grid_scan",
118
- parameter={"args": {"samx": (-5, 5, 3)}, "kwargs": {}},
119
- queue="primary",
120
- metadata={"RID": "something"},
121
- )
154
+ queue, _, request_msg = queue_elements
122
155
 
123
156
  with mock.patch(
124
157
  "bec_lib.queue_items.QueueItem.queue_position", new_callable=mock.PropertyMock
@@ -136,24 +169,182 @@ def test_live_updates_process_queue_without_queue_number(bec_client_mock):
136
169
  assert live_updates._process_queue(queue, request_msg, "req_id") is False
137
170
 
138
171
 
139
- # @pytest.mark.timeout(20)
140
- # @pytest.mark.asyncio
141
- # def test_live_updates_process_instruction_readback(bec_client_mock):
142
- # client = bec_client_mock
143
- # live_updates = IPythonLiveUpdates(client)
144
- # request_msg = messages.ScanQueueMessage(
145
- # scan_type="grid_scan",
146
- # parameter={"args": {"samx": (-5, 5, 3)}, "kwargs": {}},
147
- # queue="primary",
148
- # metadata={"RID": "something"},
149
- # )
150
- # live_updates._active_request = request_msg
151
- # live_updates._user_callback = []
152
- # client.queue.queue_storage.current_scan_queue = {"primary": {"status": "RUNNING"}}
153
- # with mock.patch(
154
- # "bec_client_mock.callbacks.ipython_live_updates.LiveUpdatesTable", new_callable=mock.Co
155
- # ) as table:
156
- # live_updates._process_instruction({"scan_progress": 10})
157
- # table.assert_called_once_with(
158
- # client, report_instructions={"scan_progress": 10}, request=request_msg, callbacks=[]
159
- # )
172
+ @pytest.mark.timeout(20)
173
+ def test_available_req_blocks(bec_client_mock, queue_elements):
174
+ client = bec_client_mock
175
+ live_updates = IPythonLiveUpdates(client)
176
+ queue, request_block, request_msg = queue_elements
177
+
178
+ # Test with matching RID
179
+ available_blocks = live_updates._available_req_blocks(queue, request_msg)
180
+ assert (
181
+ len(available_blocks) == 0
182
+ ) # request_block.RID is "req_id", request_msg.metadata["RID"] is "something"
183
+
184
+ # Test with correct RID
185
+ request_block.RID = "something"
186
+ available_blocks = live_updates._available_req_blocks(queue, request_msg)
187
+ assert len(available_blocks) == 1
188
+ assert available_blocks[0] == request_block
189
+
190
+
191
+ @pytest.mark.timeout(20)
192
+ def test_available_req_blocks_multiple_blocks(bec_client_mock):
193
+ client = bec_client_mock
194
+ live_updates = IPythonLiveUpdates(client)
195
+
196
+ request_msg = messages.ScanQueueMessage(
197
+ scan_type="grid_scan",
198
+ parameter={"args": {"samx": (-5, 5, 3)}, "kwargs": {}},
199
+ queue="primary",
200
+ metadata={"RID": "test_rid"},
201
+ )
202
+
203
+ request_block1 = messages.RequestBlock(
204
+ msg=request_msg,
205
+ RID="test_rid",
206
+ scan_motors=["samx"],
207
+ report_instructions=[],
208
+ readout_priority={"monitored": ["samx"]},
209
+ is_scan=True,
210
+ scan_number=1,
211
+ scan_id="scan_id_1",
212
+ )
213
+
214
+ request_block2 = messages.RequestBlock(
215
+ msg=request_msg,
216
+ RID="test_rid",
217
+ scan_motors=["samy"],
218
+ report_instructions=[],
219
+ readout_priority={"monitored": ["samy"]},
220
+ is_scan=True,
221
+ scan_number=2,
222
+ scan_id="scan_id_2",
223
+ )
224
+
225
+ request_block3 = messages.RequestBlock(
226
+ msg=request_msg,
227
+ RID="different_rid",
228
+ scan_motors=["samz"],
229
+ report_instructions=[],
230
+ readout_priority={"monitored": ["samz"]},
231
+ is_scan=True,
232
+ scan_number=3,
233
+ scan_id="scan_id_3",
234
+ )
235
+
236
+ queue = QueueItem(
237
+ scan_manager=client.queue,
238
+ queue_id="queue_id",
239
+ request_blocks=[request_block1, request_block2, request_block3],
240
+ status="RUNNING",
241
+ active_request_block={},
242
+ scan_id=["scan_id_1", "scan_id_2", "scan_id_3"],
243
+ )
244
+
245
+ available_blocks = live_updates._available_req_blocks(queue, request_msg)
246
+ assert len(available_blocks) == 2
247
+ assert request_block1 in available_blocks
248
+ assert request_block2 in available_blocks
249
+ assert request_block3 not in available_blocks
250
+
251
+
252
+ @pytest.mark.timeout(20)
253
+ def test_element_in_queue_no_queue(bec_client_mock):
254
+ client = bec_client_mock
255
+ live_updates = IPythonLiveUpdates(client)
256
+
257
+ # Test when client.queue is None
258
+ client.queue = None
259
+ assert live_updates._element_in_queue() is False
260
+
261
+
262
+ @pytest.mark.timeout(20)
263
+ def test_element_in_queue_no_current_scan_queue(bec_client_mock):
264
+ client = bec_client_mock
265
+ live_updates = IPythonLiveUpdates(client)
266
+
267
+ # Test when current_scan_queue is None
268
+ client.queue.queue_storage.current_scan_queue = None
269
+ assert live_updates._element_in_queue() is False
270
+
271
+
272
+ @pytest.mark.timeout(20)
273
+ def test_element_in_queue_no_primary_queue(bec_client_mock):
274
+ client = bec_client_mock
275
+ live_updates = IPythonLiveUpdates(client)
276
+
277
+ # Test when primary queue doesn't exist
278
+ scan_queue_status = messages.ScanQueueStatus(info=[], status="RUNNING")
279
+ client.queue.queue_storage.current_scan_queue = {"secondary": scan_queue_status}
280
+ assert live_updates._element_in_queue() is False
281
+
282
+
283
+ @pytest.mark.timeout(20)
284
+ def test_element_in_queue_no_queue_info(bec_client_mock):
285
+ client = bec_client_mock
286
+ live_updates = IPythonLiveUpdates(client)
287
+
288
+ # Test when queue_info is empty
289
+ scan_queue_status = messages.ScanQueueStatus(info=[], status="RUNNING")
290
+ client.queue.queue_storage.current_scan_queue = {"primary": scan_queue_status}
291
+ assert live_updates._element_in_queue() is False
292
+
293
+
294
+ @pytest.mark.timeout(20)
295
+ def test_element_in_queue_no_current_queue(bec_client_mock, sample_scan_queue_status):
296
+ client = bec_client_mock
297
+ live_updates = IPythonLiveUpdates(client)
298
+
299
+ # Test when _current_queue is None
300
+ live_updates._current_queue = None
301
+ client.queue.queue_storage.current_scan_queue = {"primary": sample_scan_queue_status}
302
+ assert live_updates._element_in_queue() is False
303
+
304
+
305
+ @pytest.mark.timeout(20)
306
+ def test_element_in_queue_queue_id_not_in_info(bec_client_mock, sample_request_block):
307
+ client = bec_client_mock
308
+ live_updates = IPythonLiveUpdates(client)
309
+
310
+ # Test when queue_id is not in info
311
+ current_queue = mock.MagicMock()
312
+ current_queue.queue_id = "my_queue_id"
313
+ live_updates._current_queue = current_queue
314
+
315
+ queue_info_entry = messages.QueueInfoEntry(
316
+ queue_id="different_queue_id",
317
+ scan_id=["scan_id"],
318
+ is_scan=[True],
319
+ request_blocks=[sample_request_block],
320
+ scan_number=[1],
321
+ status="RUNNING",
322
+ active_request_block=None,
323
+ )
324
+ scan_queue_status = messages.ScanQueueStatus(info=[queue_info_entry], status="RUNNING")
325
+ client.queue.queue_storage.current_scan_queue = {"primary": scan_queue_status}
326
+ assert live_updates._element_in_queue() is False
327
+
328
+
329
+ @pytest.mark.timeout(20)
330
+ def test_element_in_queue_queue_id_in_info(bec_client_mock, sample_request_block):
331
+ client = bec_client_mock
332
+ live_updates = IPythonLiveUpdates(client)
333
+
334
+ # Test when queue_id is in info (should return True)
335
+ current_queue = mock.MagicMock()
336
+ current_queue.queue_id = "my_queue_id"
337
+ live_updates._current_queue = current_queue
338
+
339
+ queue_info_entry = messages.QueueInfoEntry(
340
+ queue_id="my_queue_id",
341
+ scan_id=["scan_id"],
342
+ is_scan=[True],
343
+ request_blocks=[sample_request_block],
344
+ scan_number=[1],
345
+ status="RUNNING",
346
+ active_request_block=None,
347
+ )
348
+ scan_queue_status = messages.ScanQueueStatus(info=[queue_info_entry], status="RUNNING")
349
+ client.queue.queue_storage.current_scan_queue = {"primary": scan_queue_status}
350
+ assert live_updates._element_in_queue() is True
@@ -1,7 +1,7 @@
1
1
  from time import sleep
2
2
 
3
- from bec_server.scan_server.procedures.constants import PROCEDURE, ProcedureWorkerError
4
- from bec_server.scan_server.procedures.container_utils import PodmanCliUtils
3
+ from bec_server.procedures.constants import PROCEDURE, ProcedureWorkerError
4
+ from bec_server.procedures.container_utils import PodmanCliUtils
5
5
 
6
6
  image_name = (
7
7
  f"ghcr.io/bec-project/{PROCEDURE.CONTAINER.REQUIREMENTS_IMAGE_NAME}:v{PROCEDURE.BEC_VERSION}"
@@ -12,10 +12,10 @@ from bec_ipython_client.main import BECIPythonClient
12
12
  from bec_lib import messages
13
13
  from bec_lib.endpoints import MessageEndpoints
14
14
  from bec_lib.logger import bec_logger
15
- from bec_server.scan_server.procedures.constants import _CONTAINER, _WORKER
16
- from bec_server.scan_server.procedures.container_utils import get_backend
17
- from bec_server.scan_server.procedures.container_worker import ContainerProcedureWorker
18
- from bec_server.scan_server.procedures.manager import ProcedureManager
15
+ from bec_server.procedures.constants import _CONTAINER, _WORKER
16
+ from bec_server.procedures.container_utils import get_backend
17
+ from bec_server.procedures.container_worker import ContainerProcedureWorker
18
+ from bec_server.procedures.manager import ProcedureManager
19
19
 
20
20
  if TYPE_CHECKING:
21
21
  from pytest_bec_e2e.plugin import LogTestTool
@@ -43,9 +43,9 @@ def client_logtool_and_manager(
43
43
  bec_ipython_client_fixture_with_logtool: tuple[BECIPythonClient, "LogTestTool"],
44
44
  ) -> Generator[tuple[BECIPythonClient, "LogTestTool", ProcedureManager], None, None]:
45
45
  client, logtool = bec_ipython_client_fixture_with_logtool
46
- server = MagicMock()
47
- server.bootstrap_server = f"{client.connector.host}:{client.connector.port}"
48
- manager = ProcedureManager(server, ContainerProcedureWorker)
46
+ manager = ProcedureManager(
47
+ f"{client.connector.host}:{client.connector.port}", ContainerProcedureWorker
48
+ )
49
49
  yield client, logtool, manager
50
50
  manager.shutdown()
51
51
 
@@ -67,7 +67,8 @@ def test_building_worker_image():
67
67
 
68
68
 
69
69
  @pytest.mark.timeout(100)
70
- @patch("bec_server.scan_server.procedures.manager.procedure_registry.is_registered", lambda _: True)
70
+ @patch("bec_server.procedures.manager.procedure_registry.is_registered", lambda _: True)
71
+ @patch("bec_server.procedures.container_worker.PROCEDURE", PATCHED_CONSTANTS())
71
72
  def test_procedure_runner_spawns_worker(
72
73
  client_logtool_and_manager: tuple[BECIPythonClient, "LogTestTool", ProcedureManager],
73
74
  ):
@@ -75,7 +76,7 @@ def test_procedure_runner_spawns_worker(
75
76
  assert manager._active_workers == {}
76
77
  endpoint = MessageEndpoints.procedure_request()
77
78
  msg = messages.ProcedureRequestMessage(
78
- identifier="sleep", args_kwargs=((), {"time_s": 2}), queue="test"
79
+ identifier="sleep", args_kwargs=((), {"time_s": 0.1}), queue="test"
79
80
  )
80
81
 
81
82
  logs = []
@@ -88,14 +89,14 @@ def test_procedure_runner_spawns_worker(
88
89
  client.connector.xadd(topic=endpoint, msg_dict=msg.model_dump())
89
90
 
90
91
  _wait_while(lambda: manager._active_workers == {}, 5)
91
- _wait_while(lambda: manager._active_workers != {}, 20)
92
+ _wait_while(lambda: manager._active_workers != {}, 90)
92
93
 
93
94
  assert logs != []
94
95
 
95
96
 
96
97
  @pytest.mark.timeout(100)
97
- @patch("bec_server.scan_server.procedures.manager.procedure_registry.is_registered", lambda _: True)
98
- @patch("bec_server.scan_server.procedures.container_worker.PROCEDURE", PATCHED_CONSTANTS())
98
+ @patch("bec_server.procedures.manager.procedure_registry.is_registered", lambda _: True)
99
+ @patch("bec_server.procedures.container_worker.PROCEDURE", PATCHED_CONSTANTS())
99
100
  def test_happy_path_container_procedure_runner(
100
101
  client_logtool_and_manager: tuple[BECIPythonClient, "LogTestTool", ProcedureManager],
101
102
  ):
@@ -111,7 +112,7 @@ def test_happy_path_container_procedure_runner(
111
112
  conn.xadd(topic=endpoint, msg_dict=msg.model_dump())
112
113
 
113
114
  _wait_while(lambda: manager._active_workers == {}, 5)
114
- _wait_while(lambda: manager._active_workers != {}, 20)
115
+ _wait_while(lambda: manager._active_workers != {}, 90)
115
116
 
116
117
  logtool.fetch()
117
118
  assert logtool.is_present_in_any_message("procedure accepted: True, message:")
@@ -1,9 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  import _thread
2
4
  import io
3
5
  import os
4
6
  import threading
5
7
  import time
6
8
  from contextlib import redirect_stdout
9
+ from typing import TYPE_CHECKING
7
10
  from unittest.mock import PropertyMock
8
11
 
9
12
  import h5py
@@ -19,6 +22,9 @@ from bec_lib.logger import bec_logger
19
22
 
20
23
  logger = bec_logger.logger
21
24
 
25
+ if TYPE_CHECKING: # pragma: no cover
26
+ from bec_ipython_client.main import BECIPythonClient
27
+
22
28
  # pylint: disable=protected-access
23
29
 
24
30
 
@@ -201,13 +207,13 @@ def test_mv_scan_mv(bec_ipython_client_fixture):
201
207
 
202
208
 
203
209
  @pytest.mark.timeout(100)
204
- def test_scan_abort(bec_ipython_client_fixture):
210
+ def test_scan_abort(bec_ipython_client_fixture: BECIPythonClient):
205
211
  def send_abort(bec):
206
212
  while True:
207
213
  current_scan_info = bec.queue.scan_storage.current_scan_info
208
214
  if not current_scan_info:
209
215
  continue
210
- status = current_scan_info.get("status").lower()
216
+ status = current_scan_info.status.lower()
211
217
  if status not in ["running", "deferred_pause"]:
212
218
  continue
213
219
  if bec.queue.scan_storage.current_scan is None:
@@ -217,7 +223,7 @@ def test_scan_abort(bec_ipython_client_fixture):
217
223
  break
218
224
  while True:
219
225
  queue = bec.queue.queue_storage.current_scan_queue
220
- if queue["primary"]["info"][0]["status"] == "DEFERRED_PAUSE":
226
+ if queue["primary"].info[0].status == "DEFERRED_PAUSE":
221
227
  break
222
228
  time.sleep(0.5)
223
229
  _thread.interrupt_main()
@@ -241,7 +247,7 @@ def test_scan_abort(bec_ipython_client_fixture):
241
247
  time.sleep(0.5)
242
248
 
243
249
  current_queue = bec.queue.queue_storage.current_scan_queue["primary"]
244
- while current_queue["info"] or current_queue["status"] != "RUNNING":
250
+ while current_queue.info or current_queue.status != "RUNNING":
245
251
  time.sleep(0.5)
246
252
  current_queue = bec.queue.queue_storage.current_scan_queue["primary"]
247
253
 
@@ -307,7 +313,7 @@ def test_queued_scan(bec_ipython_client_fixture):
307
313
  while len(scan2.scan.live_data) != 50:
308
314
  time.sleep(0.5)
309
315
  current_queue = bec.queue.queue_storage.current_scan_queue["primary"]
310
- while current_queue["info"] or current_queue["status"] != "RUNNING":
316
+ while current_queue.info or current_queue.status != "RUNNING":
311
317
  time.sleep(0.5)
312
318
  current_queue = bec.queue.queue_storage.current_scan_queue["primary"]
313
319
  scan_number_end = bec.queue.next_scan_number
@@ -356,7 +362,7 @@ def test_scan_restart(bec_ipython_client_fixture):
356
362
  scan2.wait()
357
363
 
358
364
  current_queue = bec.queue.queue_storage.current_scan_queue["primary"]
359
- while current_queue["info"] or current_queue["status"] != "RUNNING":
365
+ while current_queue.info or current_queue.status != "RUNNING":
360
366
  time.sleep(0.5)
361
367
  current_queue = bec.queue.queue_storage.current_scan_queue["primary"]
362
368
  scan_number_end = bec.queue.next_scan_number
@@ -364,7 +370,7 @@ def test_scan_restart(bec_ipython_client_fixture):
364
370
 
365
371
 
366
372
  @pytest.mark.timeout(100)
367
- def test_scan_observer_repeat_queued(bec_ipython_client_fixture):
373
+ def test_scan_observer_repeat_queued(bec_ipython_client_fixture: BECIPythonClient):
368
374
  bec = bec_ipython_client_fixture
369
375
  bec.metadata.update({"unit_test": "test_scan_observer_repeat_queued"})
370
376
  scans = bec.scans
@@ -396,7 +402,7 @@ def test_scan_observer_repeat_queued(bec_ipython_client_fixture):
396
402
  scan2.wait()
397
403
 
398
404
  current_queue = bec.queue.queue_storage.current_scan_queue["primary"]
399
- while current_queue["info"] or current_queue["status"] != "RUNNING":
405
+ while current_queue.info or current_queue.status != "RUNNING":
400
406
  time.sleep(0.5)
401
407
  current_queue = bec.queue.queue_storage.current_scan_queue["primary"]
402
408
  scan_number_end = bec.queue.next_scan_number
@@ -433,7 +439,7 @@ def test_scan_observer_repeat(bec_ipython_client_fixture):
433
439
  scan1.wait()
434
440
 
435
441
  current_queue = bec.queue.queue_storage.current_scan_queue["primary"]
436
- while current_queue["info"] or current_queue["status"] != "RUNNING":
442
+ while current_queue.info or current_queue.status != "RUNNING":
437
443
  time.sleep(0.5)
438
444
  current_queue = bec.queue.queue_storage.current_scan_queue["primary"]
439
445
  while True:
@@ -819,3 +825,46 @@ def test_client_info_message(bec_ipython_client_fixture):
819
825
  s1 = scans.line_scan(dev.samx, 0, 1, steps=10, exp_time=0.5, relative=False)
820
826
  output = buffer.getvalue()
821
827
  assert "test_client_info_message" in output
828
+
829
+
830
+ @pytest.mark.timeout(100)
831
+ def test_device_progress_grid_scan(bec_ipython_client_fixture, capsys):
832
+ bec = bec_ipython_client_fixture
833
+ scans = bec.scans
834
+ bec.metadata.update({"unit_test": "test_device_progress_grid_scan"})
835
+ dev = bec.device_manager.devices
836
+ scans.device_progress_grid_scan(
837
+ dev.samx, -5, 5, 10, dev.samy, -5, 5, 10, relative=True, exp_time=0.01
838
+ )
839
+ captured = capsys.readouterr()
840
+ assert "bpm4i" not in captured.out
841
+ assert "samx" not in captured.out
842
+ assert "Scan" in captured.out
843
+ assert "100 %" in captured.out
844
+
845
+
846
+ @pytest.mark.timeout(100)
847
+ def test_grid_scan_secondary_queue(capsys, bec_ipython_client_fixture):
848
+ bec = bec_ipython_client_fixture
849
+ scans = bec.scans
850
+ bec.metadata.update({"unit_test": "test_grid_scan_secondary_queue"})
851
+ dev = bec.device_manager.devices
852
+ status = scans.grid_scan(
853
+ dev.samx,
854
+ -5,
855
+ 5,
856
+ 10,
857
+ dev.samy,
858
+ -5,
859
+ 5,
860
+ 10,
861
+ exp_time=0.01,
862
+ relative=False,
863
+ scan_queue="secondary",
864
+ )
865
+ assert len(status.scan.live_data) == 100
866
+ assert status.scan.num_points == 100
867
+ captured = capsys.readouterr()
868
+ assert "finished. Scan ID" in captured.out
869
+
870
+ assert "secondary" in bec.queue.queue_storage.current_scan_queue