bec-ipython-client 3.70.0__py3-none-any.whl → 3.86.1__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,16 +11,18 @@ from typing import Iterable, Literal, Tuple
7
11
  import IPython
8
12
  import redis
9
13
  import redis.exceptions
10
- import requests
11
14
  from IPython.terminal.ipapp import TerminalIPythonApp
12
15
  from IPython.terminal.prompts import Prompts, Token
16
+ from pydantic import ValidationError
17
+ from rich.console import Console
18
+ from rich.panel import Panel
19
+ from rich.text import Text
13
20
 
14
21
  from bec_ipython_client.beamline_mixin import BeamlineMixin
15
22
  from bec_ipython_client.bec_magics import BECMagics
16
23
  from bec_ipython_client.callbacks.ipython_live_updates import IPythonLiveUpdates
17
24
  from bec_ipython_client.signals import ScanInterruption, SigintHandler
18
25
  from bec_lib import plugin_helper
19
- from bec_lib.acl_login import BECAuthenticationError
20
26
  from bec_lib.alarm_handler import AlarmBase
21
27
  from bec_lib.bec_errors import DeviceConfigError
22
28
  from bec_lib.bec_service import parse_cmdline_args
@@ -25,6 +31,7 @@ from bec_lib.client import BECClient
25
31
  from bec_lib.logger import bec_logger
26
32
  from bec_lib.redis_connector import RedisConnector
27
33
  from bec_lib.service_config import ServiceConfig
34
+ from bec_lib.utils.pydantic_pretty_print import pretty_print_pydantic_validation_error
28
35
 
29
36
  logger = bec_logger.logger
30
37
 
@@ -51,6 +58,7 @@ class BECIPythonClient:
51
58
  # the CLIBECClient but directly through the BECIPythonClient. While this is not
52
59
  # needed for normal usage, it is required, e.g. for mocks.
53
60
  _local_only_types: Tuple = ()
61
+ _client: CLIBECClient | BECClient
54
62
 
55
63
  def __init__(
56
64
  self,
@@ -79,6 +87,7 @@ class BECIPythonClient:
79
87
  self._client.callbacks.register(
80
88
  event_type=EventType.NAMESPACE_UPDATE, callback=self._update_namespace_callback
81
89
  )
90
+ self._alarm_history = collections.deque(maxlen=100)
82
91
 
83
92
  def __getattr__(self, name):
84
93
  return getattr(self._client, name)
@@ -136,7 +145,7 @@ class BECIPythonClient:
136
145
  self._refresh_ipython_username()
137
146
  self._load_magics()
138
147
  self._ip.events.register("post_run_cell", log_console)
139
- self._ip.set_custom_exc((Exception,), _ip_exception_handler) # register your handler
148
+ self._ip.set_custom_exc((Exception,), self._create_exception_handler())
140
149
  # represent objects using __str__, if overwritten, otherwise use __repr__
141
150
  self._ip.display_formatter.formatters["text/plain"].for_type(
142
151
  object,
@@ -187,10 +196,66 @@ class BECIPythonClient:
187
196
  pass
188
197
  self._client.shutdown()
189
198
 
199
+ def _create_exception_handler(self):
200
+ return functools.partial(_ip_exception_handler, parent=self)
201
+
202
+ def show_last_alarm(self, offset: int = 0):
203
+ """
204
+ Show the last alarm raised in this session with rich formatting.
205
+ """
206
+ try:
207
+ alarm: AlarmBase = self._alarm_history[-1 - offset][1]
208
+ except IndexError:
209
+ print("No alarm has been raised in this session.")
210
+ return
190
211
 
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}")
212
+ console = Console()
213
+
214
+ # --- HEADER ---
215
+ header = Text()
216
+ header.append("Alarm Raised\n", style="bold red")
217
+ header.append(f"Severity: {alarm.severity.name}\n", style="bold")
218
+ header.append(f"Type: {alarm.alarm_type}\n", style="bold")
219
+ if alarm.alarm.info.device:
220
+ header.append(f"Device: {alarm.alarm.info.device}\n", style="bold")
221
+
222
+ console.print(Panel(header, title="Alarm Info", border_style="red", expand=False))
223
+
224
+ # --- SHOW SUMMARY
225
+ if alarm.alarm.info.compact_error_message:
226
+ console.print(
227
+ Panel(
228
+ Text(alarm.alarm.info.compact_error_message, style="yellow"),
229
+ title="Summary",
230
+ border_style="yellow",
231
+ expand=False,
232
+ )
233
+ )
234
+
235
+ # --- SHOW FULL TRACEBACK
236
+ tb_str = alarm.alarm.info.error_message
237
+ if tb_str:
238
+ try:
239
+ console.print(tb_str)
240
+ except Exception:
241
+ # fallback in case msg is not a traceback
242
+ console.print(Panel(tb_str, title="Message", border_style="cyan"))
243
+
244
+
245
+ def _ip_exception_handler(
246
+ self, etype, evalue, tb, tb_offset=None, parent: BECIPythonClient = None, **kwargs
247
+ ):
248
+ if issubclass(etype, AlarmBase):
249
+ parent._alarm_history.append((etype, evalue, tb, tb_offset))
250
+ print("\x1b[31m BEC alarm:\x1b[0m")
251
+ evalue.pretty_print()
252
+ print("For more details, use 'bec.show_last_alarm()'")
253
+ return
254
+ if issubclass(etype, ValidationError):
255
+ pretty_print_pydantic_validation_error(evalue)
256
+ return
257
+ if issubclass(etype, (ScanInterruption, DeviceConfigError)):
258
+ print(f"\x1b[31m {evalue.__class__.__name__}:\x1b[0m {evalue}")
194
259
  return
195
260
  if issubclass(etype, redis.exceptions.NoPermissionError):
196
261
  # pylint: disable=protected-access
@@ -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.70.0
3
+ Version: 3.86.1
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
@@ -0,0 +1,44 @@
1
+ .gitignore,sha256=XxC6jyyftTo2CLtm4K8axuNPYwA9Wgaz2R93WhX8bTQ,3364
2
+ PKG-INFO,sha256=WyJiPK_VXcT5PwALxqVLaTGweyx5TlaJ6iZbWlZOmAE,1052
3
+ demo.py,sha256=AquJB-0Tu2bIEFpcJ0Q3RUBt1xldqS6GgU5CjOAg_ww,7083
4
+ pyproject.toml,sha256=Xh1I15jBg5rdV9FGNqQP6etbSMC22JEKlj09CHyy7fU,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=_Yz9MVCiWYfIui40Ja1zhAmVTZPGJ8OyG-vAYFGYmPU,13581
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=qO1x70wAqu03qS73IrB9D2Fgwp7zJDQsBPiBxg1ouLI,11277
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=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=1gDAnpfToEgE7ssaLTh5Z6YiRmHzk6UnVPo8exXk-as,31227
40
+ tests/end-2-end/test_scans_lib_e2e.py,sha256=dqs0ojkyQWStIQbqABq9mQrjqQyE43gr37VPhxvQ8b8,19427
41
+ bec_ipython_client-3.86.1.dist-info/METADATA,sha256=WyJiPK_VXcT5PwALxqVLaTGweyx5TlaJ6iZbWlZOmAE,1052
42
+ bec_ipython_client-3.86.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
43
+ bec_ipython_client-3.86.1.dist-info/entry_points.txt,sha256=oQUXYY0jjD9ZvKPHwaGn2wkUIWpDZM8L4ixDA3RlBWE,53
44
+ bec_ipython_client-3.86.1.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,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_ipython_client"
7
- version = "3.70.0"
7
+ version = "3.86.1"
8
8
  description = "BEC IPython client"
9
9
  requires-python = ">=3.11"
10
10
  classifiers = [
@@ -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
 
@@ -211,3 +212,33 @@ def test_bec_ipython_client_property_access(ipython_client):
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