bec-ipython-client 3.35.6__py3-none-any.whl → 3.84.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bec-ipython-client might be problematic. Click here for more details.

@@ -1,4 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  import argparse
4
+ import collections
5
+ import functools
2
6
  import os
3
7
  import sys
4
8
  from importlib.metadata import version
@@ -7,16 +11,18 @@ from typing import Iterable, Literal, Tuple
7
11
  import IPython
8
12
  import redis
9
13
  import redis.exceptions
10
- import requests
11
14
  from IPython.terminal.ipapp import TerminalIPythonApp
12
15
  from IPython.terminal.prompts import Prompts, Token
16
+ from pydantic import ValidationError
17
+ from rich.console import Console
18
+ from rich.panel import Panel
19
+ from rich.text import Text
13
20
 
14
21
  from bec_ipython_client.beamline_mixin import BeamlineMixin
15
22
  from bec_ipython_client.bec_magics import BECMagics
16
23
  from bec_ipython_client.callbacks.ipython_live_updates import IPythonLiveUpdates
17
24
  from bec_ipython_client.signals import ScanInterruption, SigintHandler
18
25
  from bec_lib import plugin_helper
19
- from bec_lib.acl_login import BECAuthenticationError
20
26
  from bec_lib.alarm_handler import AlarmBase
21
27
  from bec_lib.bec_errors import DeviceConfigError
22
28
  from bec_lib.bec_service import parse_cmdline_args
@@ -25,6 +31,7 @@ from bec_lib.client import BECClient
25
31
  from bec_lib.logger import bec_logger
26
32
  from bec_lib.redis_connector import RedisConnector
27
33
  from bec_lib.service_config import ServiceConfig
34
+ from bec_lib.utils.pydantic_pretty_print import pretty_print_pydantic_validation_error
28
35
 
29
36
  logger = bec_logger.logger
30
37
 
@@ -79,6 +86,7 @@ class BECIPythonClient:
79
86
  self._client.callbacks.register(
80
87
  event_type=EventType.NAMESPACE_UPDATE, callback=self._update_namespace_callback
81
88
  )
89
+ self._alarm_history = collections.deque(maxlen=100)
82
90
 
83
91
  def __getattr__(self, name):
84
92
  return getattr(self._client, name)
@@ -136,7 +144,7 @@ class BECIPythonClient:
136
144
  self._refresh_ipython_username()
137
145
  self._load_magics()
138
146
  self._ip.events.register("post_run_cell", log_console)
139
- self._ip.set_custom_exc((Exception,), _ip_exception_handler) # register your handler
147
+ self._ip.set_custom_exc((Exception,), self._create_exception_handler())
140
148
  # represent objects using __str__, if overwritten, otherwise use __repr__
141
149
  self._ip.display_formatter.formatters["text/plain"].for_type(
142
150
  object,
@@ -187,10 +195,66 @@ class BECIPythonClient:
187
195
  pass
188
196
  self._client.shutdown()
189
197
 
198
+ def _create_exception_handler(self):
199
+ return functools.partial(_ip_exception_handler, parent=self)
200
+
201
+ def show_last_alarm(self, offset: int = 0):
202
+ """
203
+ Show the last alarm raised in this session with rich formatting.
204
+ """
205
+ try:
206
+ alarm: AlarmBase = self._alarm_history[-1 - offset][1]
207
+ except IndexError:
208
+ print("No alarm has been raised in this session.")
209
+ return
190
210
 
191
- 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}")
211
+ console = Console()
212
+
213
+ # --- HEADER ---
214
+ header = Text()
215
+ header.append("Alarm Raised\n", style="bold red")
216
+ header.append(f"Severity: {alarm.severity.name}\n", style="bold")
217
+ header.append(f"Type: {alarm.alarm_type}\n", style="bold")
218
+ if alarm.alarm.info.device:
219
+ header.append(f"Device: {alarm.alarm.info.device}\n", style="bold")
220
+
221
+ console.print(Panel(header, title="Alarm Info", border_style="red", expand=False))
222
+
223
+ # --- SHOW SUMMARY
224
+ if alarm.alarm.info.compact_error_message:
225
+ console.print(
226
+ Panel(
227
+ Text(alarm.alarm.info.compact_error_message, style="yellow"),
228
+ title="Summary",
229
+ border_style="yellow",
230
+ expand=False,
231
+ )
232
+ )
233
+
234
+ # --- SHOW FULL TRACEBACK
235
+ tb_str = alarm.alarm.info.error_message
236
+ if tb_str:
237
+ try:
238
+ console.print(tb_str)
239
+ except Exception:
240
+ # fallback in case msg is not a traceback
241
+ console.print(Panel(tb_str, title="Message", border_style="cyan"))
242
+
243
+
244
+ def _ip_exception_handler(
245
+ self, etype, evalue, tb, tb_offset=None, parent: BECIPythonClient = None, **kwargs
246
+ ):
247
+ if issubclass(etype, AlarmBase):
248
+ parent._alarm_history.append((etype, evalue, tb, tb_offset))
249
+ print("\x1b[31m BEC alarm:\x1b[0m")
250
+ evalue.pretty_print()
251
+ print("For more details, use 'bec.show_last_alarm()'")
252
+ return
253
+ if issubclass(etype, ValidationError):
254
+ pretty_print_pydantic_validation_error(evalue)
255
+ return
256
+ if issubclass(etype, (ScanInterruption, DeviceConfigError)):
257
+ print(f"\x1b[31m {evalue.__class__.__name__}:\x1b[0m {evalue}")
194
258
  return
195
259
  if issubclass(etype, redis.exceptions.NoPermissionError):
196
260
  # pylint: disable=protected-access
@@ -220,9 +284,14 @@ class BECClientPrompt(Prompts):
220
284
  next_scan_number = str(self.client.queue.next_scan_number)
221
285
  except Exception:
222
286
  next_scan_number = "?"
287
+
288
+ if self.client.active_account:
289
+ username = f"{self.client.active_account} | {self.username}"
290
+ else:
291
+ username = self.username
223
292
  return [
224
293
  (status_led, "\u2022"),
225
- (Token.Prompt, " " + self.username),
294
+ (Token.Prompt, " " + username), # BEC ACL username and pgroup
226
295
  (Token.Prompt, "@" + self.session_name),
227
296
  (Token.Prompt, " ["),
228
297
  (Token.PromptNum, str(self.shell.execution_count)),
@@ -271,6 +340,12 @@ def main():
271
340
  )
272
341
  parser.add_argument("--version", action="store_true", default=False)
273
342
  parser.add_argument("--nogui", action="store_true", default=False)
343
+ parser.add_argument(
344
+ "--gui-id",
345
+ action="store",
346
+ default=None,
347
+ help="ID of the GUI to connect to, if not set, a new GUI will be created",
348
+ )
274
349
  parser.add_argument("--dont-wait-for-server", action="store_true", default=False)
275
350
  parser.add_argument("--post-startup-file", action="store", default=None)
276
351
 
@@ -278,7 +353,7 @@ def main():
278
353
  if hasattr(plugin["module"], "extend_command_line_args"):
279
354
  plugin["module"].extend_command_line_args(parser)
280
355
 
281
- args, left_args, config = parse_cmdline_args(parser)
356
+ args, left_args, config = parse_cmdline_args(parser, config_name="client")
282
357
 
283
358
  # remove already parsed args from command line args
284
359
  sys.argv = sys.argv[:1] + left_args
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_ipython_client
3
- Version: 3.35.6
3
+ Version: 3.84.0
4
4
  Summary: BEC IPython client
5
- Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec/issues
6
- Project-URL: Homepage, https://gitlab.psi.ch/bec/bec
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
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
@@ -1,21 +1,21 @@
1
- .gitignore,sha256=UmJ0gTRAPp4EI1A6tb9Pi-k5uxDIb_NWJU9ye6AW-8Y,3306
2
- PKG-INFO,sha256=s2CbZvQumJ7v15ZfE0_xuHM9zRpm6BJzRQHQzCvKd4Y,1042
3
- demo.py,sha256=TE-niyXPUEAP-GaL-QRBPgnaxfXqBmTsBiKUxl3F_VQ,7053
4
- pyproject.toml,sha256=wtZG5v0G-id1KhouYlViubc_l4QlCH37zbvS1CbCLQo,1219
1
+ .gitignore,sha256=XxC6jyyftTo2CLtm4K8axuNPYwA9Wgaz2R93WhX8bTQ,3364
2
+ PKG-INFO,sha256=ijJpv2TMYSqXFg607-vZwabQv7Ib-B-cWeiu71D9-Xc,1052
3
+ demo.py,sha256=AquJB-0Tu2bIEFpcJ0Q3RUBt1xldqS6GgU5CjOAg_ww,7083
4
+ pyproject.toml,sha256=HuXmixsLCNg82K4Z_GDHOdRWAVHza_oeQ7Pe5fKipHU,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
- bec_ipython_client/bec_startup.py,sha256=6qtqp-naj8HUPq6_UC_mZD9PIBdbrZGtRoHJlSNmqAI,1816
9
- bec_ipython_client/main.py,sha256=6S57jE9zdGyIUi_NclnsF2brpEkU5_zJYVWWVX3IIBs,10845
8
+ bec_ipython_client/bec_startup.py,sha256=GGlHyxnSCQfYF5n-pYq2ic0pSyW5zvnT2PAlI5kY77w,1930
9
+ bec_ipython_client/main.py,sha256=XxSaiRuvDPKRzM3yuPz3FW5I0wnfyL2SgEfEG3VrDJg,13543
10
10
  bec_ipython_client/prettytable.py,sha256=TnhGPGuU0XEvnIYJ1UfTEwadcowFW4rqJW8z_Sm0EDw,2534
11
11
  bec_ipython_client/progressbar.py,sha256=aDKYjzXmGSwa82ewm59V8WSuqVQz9GiZPx5G65fEwpk,11090
12
12
  bec_ipython_client/signals.py,sha256=mbThPo6h3mQ6RFRm9viETDMC_unFa7QxiymCdM_ZK7U,4194
13
13
  bec_ipython_client/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- bec_ipython_client/callbacks/device_progress.py,sha256=y3GFSlCD-etOIrLJkX2ozxEyCNRMaRAaTdZmN8xOcjk,2385
15
- bec_ipython_client/callbacks/ipython_live_updates.py,sha256=2giQImMMaqRKnD7PW1xrDPmrxi-qy8MHdY_daWtJe-g,10290
16
- bec_ipython_client/callbacks/live_table.py,sha256=AMYHXoXR0KPGfGMnsmFNxY_SeFedA80flV-ROSoX6lE,12163
17
- bec_ipython_client/callbacks/move_device.py,sha256=FXCztPGqKNmaSKs9Q13y7syZJs57IZo4mJH6GTOIbOU,5863
18
- bec_ipython_client/callbacks/utils.py,sha256=AEEzsfD9S2ZMuJA4yAxy9gMa1iPMCxQ3zr7ZDO01bHE,5702
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
17
+ bec_ipython_client/callbacks/move_device.py,sha256=TkbtKFBr1nuJ8THTSm70Y8D7_va2YQ57oBGGt0BavW4,8153
18
+ bec_ipython_client/callbacks/utils.py,sha256=jS92ndZNU2t5n9FsLrSbulmUIeepHhwdQdfO60YjiJk,5702
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
@@ -26,16 +26,19 @@ bec_ipython_client/plugins/XTreme/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeR
26
26
  bec_ipython_client/plugins/XTreme/x-treme.py,sha256=ZKI_EEPYyWW-sdrK9DIllb81aeObc1O3fU1eWqipjTQ,3631
27
27
  bec_ipython_client/plugins/flomni/flomni_config.yaml,sha256=bCN1VKCzF6tw24HYeUE2b81kXMXzhEd69Ea8_q8Ywfk,6184
28
28
  tests/conftest.py,sha256=4yzGYqs8EVOxxY9Bu-yGbzmOpzG9dxs3cwSCzjmRziQ,233
29
+ tests/client_tests/conftest.py,sha256=dUvYqvl6wogxN-kRCUuo_Uzna3P2uSYD7qGHyMGBlPY,413
29
30
  tests/client_tests/test_beamline_mixins.py,sha256=8Ws0bmzl2gSW0VuOVu80_JbYNb5Y-htchmrrptwjwDo,4611
30
- tests/client_tests/test_bec_client.py,sha256=H6ZYUlh7VUN1OHxZ-wk6pUBC09XlMmmAn8cInzOjQw4,7584
31
+ tests/client_tests/test_bec_client.py,sha256=gks7IYoDDXpbkC_EGWQfKtH08Yig71PRZtleXFhkU8A,8903
31
32
  tests/client_tests/test_device_progress.py,sha256=GEw2g8MQZnv5mxABEZlxBmaMpxVS33wogaYohFolDEs,2353
32
33
  tests/client_tests/test_ipython_live_updates.py,sha256=HpA16Mx0WdseqlfP0FojlAd6VfvjnwAUReDkItZVYWs,6081
33
- tests/client_tests/test_live_table.py,sha256=8SUFBSiGmomqUc4d7VwxbbjcFiCrbdKZxU2AKRXJ_Cc,11219
34
- tests/client_tests/test_move_callback.py,sha256=e5c14pa9tUsMPgNhI9m7vrml5TKEAmMAQKLuiZPftDw,7026
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
35
36
  tests/client_tests/test_pretty_table.py,sha256=uQ-KPb3RXoCFE_t1IrpkT6kZAoqW7pFXxbFc445sX0Y,469
36
- tests/end-2-end/test_scans_e2e.py,sha256=CxlXjQrzDJebaoZObt_oYx41wbnHLuJLSjKlno4u2B4,30981
37
- tests/end-2-end/test_scans_lib_e2e.py,sha256=gHQ1qzuvX49LciGB3FPrd_Ha2KpVPRmc3Q5oBZb9pLw,19128
38
- bec_ipython_client-3.35.6.dist-info/METADATA,sha256=s2CbZvQumJ7v15ZfE0_xuHM9zRpm6BJzRQHQzCvKd4Y,1042
39
- bec_ipython_client-3.35.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
40
- bec_ipython_client-3.35.6.dist-info/entry_points.txt,sha256=oQUXYY0jjD9ZvKPHwaGn2wkUIWpDZM8L4ixDA3RlBWE,53
41
- bec_ipython_client-3.35.6.dist-info/RECORD,,
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
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,,
@@ -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.35.6"
7
+ version = "3.84.0"
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",
@@ -39,8 +39,8 @@ dev = [
39
39
  bec = "bec_ipython_client.main:main"
40
40
 
41
41
  [project.urls]
42
- "Bug Tracker" = "https://gitlab.psi.ch/bec/bec/issues"
43
- Homepage = "https://gitlab.psi.ch/bec/bec"
42
+ "Bug Tracker" = "https://github.com/bec-project/bec/issues"
43
+ Homepage = "https://github.com/bec-project/bec"
44
44
 
45
45
  [tool.hatch.build.targets.wheel]
46
46
  include = ["*"]
@@ -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
 
@@ -30,6 +31,7 @@ try:
30
31
  import sys
31
32
  print(completer.all_completions('bec.'), flush=True)
32
33
  print(completer.all_completions('BECIP'), flush=True)
34
+ print(_main_dict["args"].gui_id, flush=True)
33
35
  finally:
34
36
  import os
35
37
  import signal
@@ -41,6 +43,8 @@ finally:
41
43
  sys.executable,
42
44
  main.__file__,
43
45
  "--nogui",
46
+ "--gui-id",
47
+ "test_gui_id",
44
48
  "--dont-wait-for-server",
45
49
  "--post-startup-file",
46
50
  file_to_execute,
@@ -58,6 +62,7 @@ finally:
58
62
  assert (
59
63
  "BECIPythonClient" not in output_lines[1]
60
64
  ) # just to ensure something we don't want is really not there
65
+ assert "test_gui_id" in output
61
66
 
62
67
 
63
68
  def test_bec_load_hli_tab_completion(tmpdir):
@@ -200,10 +205,40 @@ def test_bec_ipython_client_property_access(ipython_client):
200
205
  client.start()
201
206
 
202
207
  with mock.patch.object(client._client, "connector") as mock_connector:
203
- mock_connector.get.return_value = messages.VariableMessage(value="account")
208
+ mock_connector.get_last.return_value = messages.VariableMessage(value="account")
204
209
  assert client._client.active_account == "account"
205
210
 
206
211
  with pytest.raises(AttributeError):
207
212
  client.active_account = "account"
208
213
  with pytest.raises(AttributeError):
209
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