bec-ipython-client 3.64.5__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.

@@ -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,24 +11,28 @@ 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
23
29
  from bec_lib.callback_handler import EventType
24
30
  from bec_lib.client import BECClient
25
31
  from bec_lib.logger import bec_logger
32
+ from bec_lib.procedures.hli import ProcedureHli
26
33
  from bec_lib.redis_connector import RedisConnector
27
34
  from bec_lib.service_config import ServiceConfig
35
+ from bec_lib.utils.pydantic_pretty_print import pretty_print_pydantic_validation_error
28
36
 
29
37
  logger = bec_logger.logger
30
38
 
@@ -51,6 +59,7 @@ class BECIPythonClient:
51
59
  # the CLIBECClient but directly through the BECIPythonClient. While this is not
52
60
  # needed for normal usage, it is required, e.g. for mocks.
53
61
  _local_only_types: Tuple = ()
62
+ _client: CLIBECClient | BECClient
54
63
 
55
64
  def __init__(
56
65
  self,
@@ -68,6 +77,7 @@ class BECIPythonClient:
68
77
  name="BECIPythonClient",
69
78
  prompt_for_acl=True,
70
79
  )
80
+
71
81
  self._ip = IPython.get_ipython()
72
82
  self.started = False
73
83
  self._sighandler = None
@@ -79,6 +89,7 @@ class BECIPythonClient:
79
89
  self._client.callbacks.register(
80
90
  event_type=EventType.NAMESPACE_UPDATE, callback=self._update_namespace_callback
81
91
  )
92
+ self._alarm_history = collections.deque(maxlen=100)
82
93
 
83
94
  def __getattr__(self, name):
84
95
  return getattr(self._client, name)
@@ -136,12 +147,13 @@ class BECIPythonClient:
136
147
  self._refresh_ipython_username()
137
148
  self._load_magics()
138
149
  self._ip.events.register("post_run_cell", log_console)
139
- self._ip.set_custom_exc((Exception,), _ip_exception_handler) # register your handler
150
+ self._ip.set_custom_exc((Exception,), self._create_exception_handler())
140
151
  # represent objects using __str__, if overwritten, otherwise use __repr__
141
152
  self._ip.display_formatter.formatters["text/plain"].for_type(
142
153
  object,
143
154
  lambda o, p, cycle: o.__str__ is object.__str__ and p.text(repr(o)) or p.text(str(o)),
144
155
  )
156
+ self._set_idle()
145
157
 
146
158
  def _update_namespace_callback(self, action: Literal["add", "remove"], ns_objects: dict):
147
159
  """Callback to update the global namespace of ipython.
@@ -179,18 +191,75 @@ class BECIPythonClient:
179
191
  magics = BECMagics(self._ip, self)
180
192
  self._ip.register_magics(magics)
181
193
 
182
- def shutdown(self):
194
+ def shutdown(self, per_thread_timeout_s: float | None = None):
183
195
  """shutdown the client and all its components"""
184
196
  try:
185
197
  self.gui.close()
186
198
  except AttributeError:
187
199
  pass
188
- self._client.shutdown()
200
+ self._client.shutdown(per_thread_timeout_s)
201
+ logger.success("done")
202
+
203
+ def _create_exception_handler(self):
204
+ return functools.partial(_ip_exception_handler, parent=self)
189
205
 
206
+ def show_last_alarm(self, offset: int = 0):
207
+ """
208
+ Show the last alarm raised in this session with rich formatting.
209
+ """
210
+ try:
211
+ alarm: AlarmBase = self._alarm_history[-1 - offset][1]
212
+ except IndexError:
213
+ print("No alarm has been raised in this session.")
214
+ return
190
215
 
191
- def _ip_exception_handler(self, etype, evalue, tb, tb_offset=None):
192
- if issubclass(etype, (AlarmBase, ScanInterruption, DeviceConfigError)):
193
- print(f"\x1b[31m BEC alarm:\x1b[0m {evalue}")
216
+ console = Console()
217
+
218
+ # --- HEADER ---
219
+ header = Text()
220
+ header.append("Alarm Raised\n", style="bold red")
221
+ header.append(f"Severity: {alarm.severity.name}\n", style="bold")
222
+ header.append(f"Type: {alarm.alarm_type}\n", style="bold")
223
+ if alarm.alarm.info.device:
224
+ header.append(f"Device: {alarm.alarm.info.device}\n", style="bold")
225
+
226
+ console.print(Panel(header, title="Alarm Info", border_style="red", expand=False))
227
+
228
+ # --- SHOW SUMMARY
229
+ if alarm.alarm.info.compact_error_message:
230
+ console.print(
231
+ Panel(
232
+ Text(alarm.alarm.info.compact_error_message, style="yellow"),
233
+ title="Summary",
234
+ border_style="yellow",
235
+ expand=False,
236
+ )
237
+ )
238
+
239
+ # --- SHOW FULL TRACEBACK
240
+ tb_str = alarm.alarm.info.error_message
241
+ if tb_str:
242
+ try:
243
+ console.print(tb_str)
244
+ except Exception:
245
+ # fallback in case msg is not a traceback
246
+ console.print(Panel(tb_str, title="Message", border_style="cyan"))
247
+
248
+
249
+ def _ip_exception_handler(
250
+ self, etype, evalue, tb, tb_offset=None, parent: BECIPythonClient = None, **kwargs
251
+ ):
252
+ if issubclass(etype, AlarmBase):
253
+ parent._alarm_history.append((etype, evalue, tb, tb_offset))
254
+ print("\x1b[31m BEC alarm:\x1b[0m")
255
+ evalue.pretty_print()
256
+ print("For more details, use 'bec.show_last_alarm()'")
257
+ return
258
+ if issubclass(etype, ValidationError):
259
+ pretty_print_pydantic_validation_error(evalue)
260
+ return
261
+ if issubclass(etype, (ScanInterruption, DeviceConfigError)):
262
+ print(f"\x1b[31m {evalue.__class__.__name__}:\x1b[0m {evalue}")
194
263
  return
195
264
  if issubclass(etype, redis.exceptions.NoPermissionError):
196
265
  # pylint: disable=protected-access
@@ -220,9 +289,14 @@ class BECClientPrompt(Prompts):
220
289
  next_scan_number = str(self.client.queue.next_scan_number)
221
290
  except Exception:
222
291
  next_scan_number = "?"
292
+
293
+ if self.client.active_account:
294
+ username = f"{self.client.active_account} | {self.username}"
295
+ else:
296
+ username = self.username
223
297
  return [
224
298
  (status_led, "\u2022"),
225
- (Token.Prompt, " " + self.username),
299
+ (Token.Prompt, " " + username), # BEC ACL username and pgroup
226
300
  (Token.Prompt, "@" + self.session_name),
227
301
  (Token.Prompt, " ["),
228
302
  (Token.PromptNum, str(self.shell.execution_count)),
@@ -269,7 +343,6 @@ def main():
269
343
  parser = argparse.ArgumentParser(
270
344
  prog="BEC IPython client", description="BEC command line interface"
271
345
  )
272
- parser.add_argument("--version", action="store_true", default=False)
273
346
  parser.add_argument("--nogui", action="store_true", default=False)
274
347
  parser.add_argument(
275
348
  "--gui-id",
@@ -289,10 +362,6 @@ def main():
289
362
  # remove already parsed args from command line args
290
363
  sys.argv = sys.argv[:1] + left_args
291
364
 
292
- if args.version:
293
- print(f"BEC IPython client: {version('bec_ipython_client')}")
294
- sys.exit(0)
295
-
296
365
  if available_plugins and config.is_default():
297
366
  # check if config is defined in a plugin;
298
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,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_ipython_client
3
- Version: 3.64.5
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
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
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
@@ -0,0 +1,44 @@
1
+ .gitignore,sha256=XxC6jyyftTo2CLtm4K8axuNPYwA9Wgaz2R93WhX8bTQ,3364
2
+ PKG-INFO,sha256=trZwsE2OpNig8TZ6MjQ6HN5lX5tRxLrygSFHiz4FATs,1052
3
+ demo.py,sha256=AquJB-0Tu2bIEFpcJ0Q3RUBt1xldqS6GgU5CjOAg_ww,7083
4
+ pyproject.toml,sha256=Gv8V_TLxvNXUR9W-ROwlP7dGq9cbC8BfRC9anNsoddQ,1229
5
+ bec_ipython_client/__init__.py,sha256=ihd_V8I7Qo0MWKMo7bcvPf-ZyUQqkcNf8IAWLJKiFJE,79
6
+ bec_ipython_client/beamline_mixin.py,sha256=scMWIFbHJajyECzbwEVKyQUGjpqA9C_KiU2M6FuRH_Q,1067
7
+ bec_ipython_client/bec_magics.py,sha256=Rz2aXkUCeAV_VxdXqLUNHh8T44kSH9ha83OiEtdptzI,2792
8
+ bec_ipython_client/bec_startup.py,sha256=GGlHyxnSCQfYF5n-pYq2ic0pSyW5zvnT2PAlI5kY77w,1930
9
+ bec_ipython_client/main.py,sha256=h7417qQj0rwj5gjbKg6pn8Y_yYJf4b8Fs5EP29sXXOg,13564
10
+ bec_ipython_client/prettytable.py,sha256=TnhGPGuU0XEvnIYJ1UfTEwadcowFW4rqJW8z_Sm0EDw,2534
11
+ bec_ipython_client/progressbar.py,sha256=aDKYjzXmGSwa82ewm59V8WSuqVQz9GiZPx5G65fEwpk,11090
12
+ bec_ipython_client/signals.py,sha256=P7FrIB66dGdPESUngBnIVPGrAhcd2Tek8ufq9ALBoYU,4340
13
+ bec_ipython_client/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ bec_ipython_client/callbacks/device_progress.py,sha256=NzWIO1oowxpN14hPXBne6RfZxOW82EhwDM3jHLnn3xI,2603
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
+ bec_ipython_client/callbacks/move_device.py,sha256=TkbtKFBr1nuJ8THTSm70Y8D7_va2YQ57oBGGt0BavW4,8153
18
+ bec_ipython_client/callbacks/utils.py,sha256=e8USLnufNTpI2Bkp-sjRsSFvIXxB2CirF6WRPVsOets,5338
19
+ bec_ipython_client/high_level_interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ bec_ipython_client/high_level_interfaces/bec_hli.py,sha256=G1eF-lbLEDuN2mr_AYSxhhGb4LT6P72g0a4sU9M9zgk,1221
21
+ bec_ipython_client/high_level_interfaces/spec_hli.py,sha256=z7WtjiC4LtMfKJn12SbguHPCLqbAsZNfUDyiUW0LOiU,5818
22
+ bec_ipython_client/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ bec_ipython_client/plugins/SLS/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ bec_ipython_client/plugins/SLS/sls_info.py,sha256=93GU_XNRDsCO0ZvUrCf_SWnUMJ1FlRTJSuXXh_bYJ30,4934
25
+ bec_ipython_client/plugins/XTreme/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ bec_ipython_client/plugins/XTreme/x-treme.py,sha256=ZKI_EEPYyWW-sdrK9DIllb81aeObc1O3fU1eWqipjTQ,3631
27
+ bec_ipython_client/plugins/flomni/flomni_config.yaml,sha256=bCN1VKCzF6tw24HYeUE2b81kXMXzhEd69Ea8_q8Ywfk,6184
28
+ tests/conftest.py,sha256=4yzGYqs8EVOxxY9Bu-yGbzmOpzG9dxs3cwSCzjmRziQ,233
29
+ tests/client_tests/conftest.py,sha256=dUvYqvl6wogxN-kRCUuo_Uzna3P2uSYD7qGHyMGBlPY,413
30
+ tests/client_tests/test_beamline_mixins.py,sha256=8Ws0bmzl2gSW0VuOVu80_JbYNb5Y-htchmrrptwjwDo,4611
31
+ tests/client_tests/test_bec_client.py,sha256=gks7IYoDDXpbkC_EGWQfKtH08Yig71PRZtleXFhkU8A,8903
32
+ tests/client_tests/test_device_progress.py,sha256=GEw2g8MQZnv5mxABEZlxBmaMpxVS33wogaYohFolDEs,2353
33
+ tests/client_tests/test_ipython_live_updates.py,sha256=s4HLqF2hVQaaJhRKv0dxj0DiZK5M9Z_WwMAbGk19EFg,11918
34
+ tests/client_tests/test_live_table.py,sha256=0EKgWOgDqpjN8fJEeAzoKNCwFneDEsUY2NfWQkxB6xc,18155
35
+ tests/client_tests/test_move_callback.py,sha256=bUCcWoz_tc-yRAtwmEUMrKE_qKwatop_wReSugGGEIQ,8670
36
+ tests/client_tests/test_pretty_table.py,sha256=uQ-KPb3RXoCFE_t1IrpkT6kZAoqW7pFXxbFc445sX0Y,469
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
+ tests/end-2-end/test_scans_lib_e2e.py,sha256=dqs0ojkyQWStIQbqABq9mQrjqQyE43gr37VPhxvQ8b8,19427
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
demo.py CHANGED
@@ -23,7 +23,7 @@ scans = bec.scans
23
23
 
24
24
 
25
25
  logger.success("Started BECClient")
26
- scans.umv(dev.samx, 5, relative=True)
26
+ scans.umv(dev.samx, 5, dev.samy, 20, relative=False)
27
27
 
28
28
  # with scans.interactive_scan() as scan:
29
29
  # for ii in range(10):
@@ -226,4 +226,5 @@ scans.umv(dev.samx, 5, relative=True)
226
226
  # event = threading.Event()
227
227
  # event.wait()
228
228
  print("eos")
229
+ bec.shutdown()
229
230
  # p.join()
pyproject.toml CHANGED
@@ -4,9 +4,9 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_ipython_client"
7
- version = "3.64.5"
7
+ version = "3.89.3"
8
8
  description = "BEC IPython client"
9
- requires-python = ">=3.10"
9
+ requires-python = ">=3.11"
10
10
  classifiers = [
11
11
  "Development Status :: 3 - Alpha",
12
12
  "Programming Language :: Python :: 3",
@@ -0,0 +1,19 @@
1
+ import fakeredis
2
+ import pytest
3
+
4
+ from bec_lib.redis_connector import RedisConnector
5
+
6
+
7
+ def fake_redis_server(host, port, **kwargs):
8
+ redis = fakeredis.FakeRedis()
9
+ return redis
10
+
11
+
12
+ @pytest.fixture
13
+ def connected_connector():
14
+ connector = RedisConnector("localhost:1", redis_cls=fake_redis_server)
15
+ connector._redis_conn.flushall()
16
+ try:
17
+ yield connector
18
+ finally:
19
+ connector.shutdown()
@@ -7,6 +7,7 @@ import pytest
7
7
 
8
8
  from bec_ipython_client import BECIPythonClient, main
9
9
  from bec_lib import messages
10
+ from bec_lib.alarm_handler import AlarmBase, AlarmHandler, Alarms
10
11
  from bec_lib.redis_connector import RedisConnector
11
12
  from bec_lib.service_config import ServiceConfig
12
13
 
@@ -204,10 +205,40 @@ def test_bec_ipython_client_property_access(ipython_client):
204
205
  client.start()
205
206
 
206
207
  with mock.patch.object(client._client, "connector") as mock_connector:
207
- mock_connector.get.return_value = messages.VariableMessage(value="account")
208
+ mock_connector.get_last.return_value = messages.VariableMessage(value="account")
208
209
  assert client._client.active_account == "account"
209
210
 
210
211
  with pytest.raises(AttributeError):
211
212
  client.active_account = "account"
212
213
  with pytest.raises(AttributeError):
213
214
  client._client.active_account = "account"
215
+
216
+
217
+ def test_bec_ipython_client_show_last_alarm(ipython_client, capsys):
218
+ client = ipython_client
219
+ error_info = messages.ErrorInfo(
220
+ error_message="This is a test alarm",
221
+ compact_error_message="Test alarm",
222
+ exception_type="TestAlarm",
223
+ device=None,
224
+ )
225
+ alarm_msg = messages.AlarmMessage(severity=Alarms.MAJOR, info=error_info)
226
+ client.alarm_handler = AlarmHandler(connector=mock.MagicMock())
227
+ client.alarm_handler.add_alarm(alarm_msg)
228
+ client._alarm_history.append(
229
+ (AlarmBase, client.alarm_handler.get_unhandled_alarms()[0], None, None)
230
+ )
231
+ client.show_last_alarm()
232
+ captured = capsys.readouterr()
233
+ assert "Alarm Raised" in captured.out
234
+ assert "Severity: MAJOR" in captured.out
235
+ assert "Type: TestAlarm" in captured.out
236
+ assert "This is a test alarm" in captured.out
237
+
238
+
239
+ def test_bec_ipython_client_show_last_no_alarm(ipython_client, capsys):
240
+ client = ipython_client
241
+ client._alarm_history = []
242
+ client.show_last_alarm()
243
+ captured = capsys.readouterr()
244
+ assert "No alarm has been raised in this session." in captured.out