bec-ipython-client 4.1.6__tar.gz → 4.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/PKG-INFO +1 -1
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/bec_startup.py +9 -42
- bec_ipython_client-4.2.0/bec_ipython_client/gui_client.py +66 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/main.py +24 -5
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/pyproject.toml +1 -1
- bec_ipython_client-4.2.0/tests/client_tests/test_gui_client.py +180 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/.gitignore +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/__init__.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/beamline_mixin.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/bec_magics.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/callbacks/__init__.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/callbacks/device_progress.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/callbacks/ipython_live_updates.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/callbacks/live_table.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/callbacks/move_device.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/callbacks/utils.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/high_level_interfaces/__init__.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/high_level_interfaces/bec_hli.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/high_level_interfaces/spec_hli.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/plugins/SLS/__init__.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/plugins/SLS/sls_info.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/plugins/XTreme/__init__.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/plugins/XTreme/x-treme.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/plugins/__init__.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/plugins/flomni/flomni_config.yaml +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/prettytable.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/progressbar.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/signals.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/demo.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/conftest.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_beamline_mixins.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_bec_client.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_device_progress.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_ipython_live_updates.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_live_table.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_move_callback.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_pretty_table.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_signals.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/conftest.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/end-2-end/_ensure_requirements_container.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/end-2-end/test_acl_e2e.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/end-2-end/test_actors_e2e.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/end-2-end/test_procedures_e2e.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/end-2-end/test_scan_workflows_lib_e2e.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/end-2-end/test_scans_e2e.py +0 -0
- {bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/end-2-end/test_scans_lib_e2e.py +0 -0
|
@@ -13,46 +13,11 @@ from bec_lib.redis_connector import RedisConnector as _RedisConnector
|
|
|
13
13
|
logger = _bec_logger.logger
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class _LazyBECGuiClient:
|
|
17
|
-
"""Defer BEC Widgets import while preserving the interactive ``gui`` object."""
|
|
18
|
-
|
|
19
|
-
def __init__(self, gui_id: str | None = None):
|
|
20
|
-
self._gui_id = gui_id
|
|
21
|
-
self._client = None
|
|
22
|
-
|
|
23
|
-
def _materialize(self):
|
|
24
|
-
if self._client is None:
|
|
25
|
-
try:
|
|
26
|
-
from bec_widgets.cli.client_utils import BECGuiClient
|
|
27
|
-
except ImportError:
|
|
28
|
-
logger.warning("BEC Widgets is not available; skipping GUI startup.")
|
|
29
|
-
bec.gui = None
|
|
30
|
-
globals().pop("gui", None)
|
|
31
|
-
raise
|
|
32
|
-
|
|
33
|
-
self._client = BECGuiClient()
|
|
34
|
-
if self._gui_id:
|
|
35
|
-
self._client.connect_to_gui_server(self._gui_id)
|
|
36
|
-
bec.gui = self._client
|
|
37
|
-
globals()["gui"] = self._client
|
|
38
|
-
return self._client
|
|
39
|
-
|
|
40
|
-
def __getattr__(self, name):
|
|
41
|
-
return getattr(self._materialize(), name)
|
|
42
|
-
|
|
43
|
-
def __setattr__(self, name, value):
|
|
44
|
-
if name.startswith("_"):
|
|
45
|
-
return super().__setattr__(name, value)
|
|
46
|
-
return setattr(self._materialize(), name, value)
|
|
47
|
-
|
|
48
|
-
def __repr__(self) -> str:
|
|
49
|
-
if self._client is None:
|
|
50
|
-
return "LazyBECGuiClient(uninitialized)"
|
|
51
|
-
return repr(self._client)
|
|
52
|
-
|
|
53
|
-
|
|
54
16
|
bec = _BECIPythonClient(
|
|
55
|
-
_main_dict["config"],
|
|
17
|
+
_main_dict["config"],
|
|
18
|
+
_RedisConnector,
|
|
19
|
+
wait_for_server=_main_dict["wait_for_server"],
|
|
20
|
+
gui_id=_main_dict["args"].gui_id,
|
|
56
21
|
)
|
|
57
22
|
_main_dict["bec"] = bec
|
|
58
23
|
|
|
@@ -66,13 +31,15 @@ except Exception:
|
|
|
66
31
|
sys.excepthook(*sys.exc_info())
|
|
67
32
|
else:
|
|
68
33
|
if bec.started:
|
|
69
|
-
gui = bec.
|
|
34
|
+
gui = bec._gui # pylint: disable=protected-access
|
|
70
35
|
if not _main_dict["args"].nogui:
|
|
71
36
|
try:
|
|
72
37
|
gui.show()
|
|
73
38
|
except ImportError:
|
|
74
|
-
logger.warning(
|
|
75
|
-
|
|
39
|
+
logger.warning(
|
|
40
|
+
"BEC Widgets is unavailable; skipping GUI startup. "
|
|
41
|
+
"Install bec-widgets in this Python environment to use the GUI."
|
|
42
|
+
)
|
|
76
43
|
|
|
77
44
|
_available_plugins = plugin_helper.get_ipython_client_startup_plugins(state="post")
|
|
78
45
|
if _available_plugins:
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Deferred GUI access for the interactive BEC client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from bec_widgets.cli.client_utils import BECGuiClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LazyBECGuiClient:
|
|
12
|
+
"""Import and create the GUI client on first use, independently of the shell namespace."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, gui_id: str | None = None) -> None:
|
|
15
|
+
self._gui_id = gui_id
|
|
16
|
+
self._client: BECGuiClient | None = None
|
|
17
|
+
|
|
18
|
+
def get_client(self) -> BECGuiClient:
|
|
19
|
+
"""Return the GUI client, importing BEC Widgets when first requested.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
BECGuiClient: The initialized GUI client.
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
ImportError: If BEC Widgets or one of its dependencies cannot be imported.
|
|
26
|
+
"""
|
|
27
|
+
if self._client is None:
|
|
28
|
+
try:
|
|
29
|
+
# Import only when GUI access is requested.
|
|
30
|
+
# pylint: disable=import-outside-toplevel
|
|
31
|
+
from bec_widgets.cli.client_utils import BECGuiClient
|
|
32
|
+
except ImportError as exc:
|
|
33
|
+
raise ImportError(
|
|
34
|
+
"BEC Widgets is required to use the GUI. Install bec-widgets in the "
|
|
35
|
+
f"Python environment running this client. Import failed: {exc}"
|
|
36
|
+
) from exc
|
|
37
|
+
|
|
38
|
+
client = BECGuiClient()
|
|
39
|
+
if self._gui_id:
|
|
40
|
+
client.connect_to_gui_server(self._gui_id)
|
|
41
|
+
self._client = client
|
|
42
|
+
return self._client
|
|
43
|
+
|
|
44
|
+
def close(self) -> None:
|
|
45
|
+
"""Close an initialized GUI client without creating one during shutdown."""
|
|
46
|
+
if self._client is not None:
|
|
47
|
+
self._client.close()
|
|
48
|
+
|
|
49
|
+
def __getattr__(self, name: str) -> Any:
|
|
50
|
+
return getattr(self.get_client(), name)
|
|
51
|
+
|
|
52
|
+
def __dir__(self) -> list[str]:
|
|
53
|
+
if self._client is None:
|
|
54
|
+
return super().__dir__()
|
|
55
|
+
return dir(self._client)
|
|
56
|
+
|
|
57
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
58
|
+
if name.startswith("_"):
|
|
59
|
+
super().__setattr__(name, value)
|
|
60
|
+
else:
|
|
61
|
+
setattr(self.get_client(), name, value)
|
|
62
|
+
|
|
63
|
+
def __repr__(self) -> str:
|
|
64
|
+
if self._client is None:
|
|
65
|
+
return "LazyBECGuiClient(uninitialized)"
|
|
66
|
+
return repr(self._client)
|
|
@@ -6,7 +6,7 @@ import functools
|
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
import traceback
|
|
9
|
-
from typing import Iterable, Literal, Tuple
|
|
9
|
+
from typing import TYPE_CHECKING, Iterable, Literal, Tuple
|
|
10
10
|
|
|
11
11
|
import IPython
|
|
12
12
|
import redis
|
|
@@ -21,6 +21,7 @@ from rich.text import Text
|
|
|
21
21
|
from bec_ipython_client.beamline_mixin import BeamlineMixin
|
|
22
22
|
from bec_ipython_client.bec_magics import BECMagics
|
|
23
23
|
from bec_ipython_client.callbacks.ipython_live_updates import IPythonLiveUpdates
|
|
24
|
+
from bec_ipython_client.gui_client import LazyBECGuiClient
|
|
24
25
|
from bec_ipython_client.signals import OperationMode, ScanInterruption, SigintHandler
|
|
25
26
|
from bec_lib import messages, plugin_helper
|
|
26
27
|
from bec_lib.alarm_handler import AlarmBase
|
|
@@ -34,6 +35,9 @@ from bec_lib.redis_connector import MessageObject, RedisConnector
|
|
|
34
35
|
from bec_lib.service_config import ServiceConfig
|
|
35
36
|
from bec_lib.utils.pydantic_pretty_print import pretty_print_pydantic_validation_error
|
|
36
37
|
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from bec_widgets.cli.client_utils import BECGuiClient
|
|
40
|
+
|
|
37
41
|
logger = bec_logger.logger
|
|
38
42
|
|
|
39
43
|
|
|
@@ -66,6 +70,7 @@ class BECIPythonClient:
|
|
|
66
70
|
wait_for_server=True,
|
|
67
71
|
forced=False,
|
|
68
72
|
mode: OperationMode = OperationMode.Normal,
|
|
73
|
+
gui_id: str | None = None,
|
|
69
74
|
) -> None:
|
|
70
75
|
self._client = CLIBECClient(
|
|
71
76
|
config,
|
|
@@ -85,12 +90,24 @@ class BECIPythonClient:
|
|
|
85
90
|
self._exit_event = None
|
|
86
91
|
self._exit_handler_thread = None
|
|
87
92
|
self._live_updates = None
|
|
88
|
-
self.
|
|
93
|
+
self._gui = LazyBECGuiClient(gui_id=gui_id)
|
|
89
94
|
self._client.callbacks.register(
|
|
90
95
|
event_type=EventType.NAMESPACE_UPDATE, callback=self._update_namespace_callback
|
|
91
96
|
)
|
|
92
97
|
self._alarm_history = collections.deque(maxlen=100)
|
|
93
98
|
|
|
99
|
+
@property
|
|
100
|
+
def gui(self) -> BECGuiClient:
|
|
101
|
+
"""Access the GUI client, loading BEC Widgets on first access.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
BECGuiClient: The initialized GUI client.
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
ImportError: If BEC Widgets or one of its dependencies cannot be imported.
|
|
108
|
+
"""
|
|
109
|
+
return self._gui.get_client()
|
|
110
|
+
|
|
94
111
|
def __getattr__(self, name):
|
|
95
112
|
return getattr(self._client, name)
|
|
96
113
|
|
|
@@ -218,9 +235,11 @@ class BECIPythonClient:
|
|
|
218
235
|
def shutdown(self, per_thread_timeout_s: float | None = None):
|
|
219
236
|
"""shutdown the client and all its components"""
|
|
220
237
|
try:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
# call close on the lazy GUI proxy, which will close the
|
|
239
|
+
# GUI client if it was initialized, or do nothing if it wasn't
|
|
240
|
+
self._gui.close()
|
|
241
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
242
|
+
logger.error(f"Error closing GUI client: {exc}")
|
|
224
243
|
self._client.shutdown(per_thread_timeout_s)
|
|
225
244
|
logger.success("done")
|
|
226
245
|
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Regression coverage for deferred GUI imports and shell shutdown."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins
|
|
6
|
+
import runpy
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from types import ModuleType, SimpleNamespace
|
|
10
|
+
from unittest import mock
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from bec_ipython_client import BECIPythonClient, main
|
|
15
|
+
from bec_ipython_client.gui_client import LazyBECGuiClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def widgets_unavailable(monkeypatch):
|
|
20
|
+
original_import = builtins.__import__
|
|
21
|
+
attempts = []
|
|
22
|
+
|
|
23
|
+
def import_without_widgets(name, *args, **kwargs):
|
|
24
|
+
if name.startswith("bec_widgets"):
|
|
25
|
+
attempts.append(name)
|
|
26
|
+
raise ModuleNotFoundError("No module named 'bec_widgets'")
|
|
27
|
+
return original_import(name, *args, **kwargs)
|
|
28
|
+
|
|
29
|
+
monkeypatch.setattr(builtins, "__import__", import_without_widgets)
|
|
30
|
+
return attempts
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def gui_factory(monkeypatch):
|
|
35
|
+
module = ModuleType("bec_widgets.cli.client_utils")
|
|
36
|
+
factory = mock.Mock()
|
|
37
|
+
module.BECGuiClient = factory
|
|
38
|
+
monkeypatch.setitem(sys.modules, module.__name__, module)
|
|
39
|
+
return factory
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def client():
|
|
44
|
+
# Exercise the real GUI property and shutdown without connecting to Redis.
|
|
45
|
+
instance = BECIPythonClient.__new__(BECIPythonClient)
|
|
46
|
+
instance.__dict__.update(
|
|
47
|
+
_client=mock.Mock(),
|
|
48
|
+
_gui=LazyBECGuiClient(),
|
|
49
|
+
started=True,
|
|
50
|
+
start=mock.Mock(),
|
|
51
|
+
_ip=mock.Mock(),
|
|
52
|
+
)
|
|
53
|
+
return instance
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.mark.parametrize("nogui", [True, False])
|
|
57
|
+
def test_shutdown_after_startup_namespace_cleanup(nogui, widgets_unavailable, monkeypatch, client):
|
|
58
|
+
monkeypatch.setattr(main, "BECIPythonClient", mock.Mock(return_value=client))
|
|
59
|
+
monkeypatch.setattr(
|
|
60
|
+
"bec_lib.plugin_helper.get_ipython_client_startup_plugins", mock.Mock(return_value={})
|
|
61
|
+
)
|
|
62
|
+
with mock.patch.dict(
|
|
63
|
+
main.main_dict,
|
|
64
|
+
{
|
|
65
|
+
"config": None,
|
|
66
|
+
"wait_for_server": False,
|
|
67
|
+
"args": SimpleNamespace(nogui=nogui, gui_id=None),
|
|
68
|
+
"startup_file": None,
|
|
69
|
+
},
|
|
70
|
+
clear=True,
|
|
71
|
+
):
|
|
72
|
+
namespace = runpy.run_path(str(Path(main.__file__).with_name("bec_startup.py")))
|
|
73
|
+
assert len(widgets_unavailable) == (0 if nogui else 1)
|
|
74
|
+
assert namespace["gui"] is client._gui
|
|
75
|
+
namespace.clear()
|
|
76
|
+
client.shutdown(per_thread_timeout_s=1)
|
|
77
|
+
client._client.shutdown.assert_called_once_with(1)
|
|
78
|
+
assert len(widgets_unavailable) == (0 if nogui else 1)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.mark.parametrize("gui_id", [None, "existing-gui"])
|
|
82
|
+
def test_gui_can_be_started_later_and_is_created_once(gui_factory, gui_id):
|
|
83
|
+
gui = LazyBECGuiClient(gui_id=gui_id)
|
|
84
|
+
gui_factory.assert_not_called()
|
|
85
|
+
assert repr(gui) == "LazyBECGuiClient(uninitialized)"
|
|
86
|
+
gui.close()
|
|
87
|
+
gui_factory.assert_not_called()
|
|
88
|
+
|
|
89
|
+
gui.show()
|
|
90
|
+
gui.show()
|
|
91
|
+
gui.set_rpc_timeout(5)
|
|
92
|
+
gui.some_setting = "value"
|
|
93
|
+
gui.close()
|
|
94
|
+
|
|
95
|
+
gui_factory.assert_called_once_with()
|
|
96
|
+
real_gui = gui_factory.return_value
|
|
97
|
+
assert real_gui.show.call_count == 2
|
|
98
|
+
real_gui.set_rpc_timeout.assert_called_once_with(5)
|
|
99
|
+
assert real_gui.some_setting == "value"
|
|
100
|
+
real_gui.close.assert_called_once_with()
|
|
101
|
+
if gui_id:
|
|
102
|
+
real_gui.connect_to_gui_server.assert_called_once_with(gui_id)
|
|
103
|
+
else:
|
|
104
|
+
real_gui.connect_to_gui_server.assert_not_called()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_missing_widgets_on_explicit_use_does_not_break_shutdown(widgets_unavailable):
|
|
108
|
+
gui = LazyBECGuiClient()
|
|
109
|
+
with pytest.raises(ImportError, match="Install bec-widgets"):
|
|
110
|
+
gui.show()
|
|
111
|
+
gui.close()
|
|
112
|
+
assert len(widgets_unavailable) == 1
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_gui_completion_does_not_import_widgets_before_initialization(widgets_unavailable):
|
|
116
|
+
gui = LazyBECGuiClient()
|
|
117
|
+
|
|
118
|
+
assert "close" in dir(gui)
|
|
119
|
+
assert widgets_unavailable == []
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_gui_completion_exposes_initialized_client_methods(gui_factory):
|
|
123
|
+
real_client = gui_factory.return_value
|
|
124
|
+
real_client.show = mock.Mock()
|
|
125
|
+
real_client.set_rpc_timeout = mock.Mock()
|
|
126
|
+
gui = LazyBECGuiClient()
|
|
127
|
+
|
|
128
|
+
gui.show()
|
|
129
|
+
|
|
130
|
+
assert dir(gui) == dir(real_client)
|
|
131
|
+
assert "show" in dir(gui)
|
|
132
|
+
assert "set_rpc_timeout" in dir(gui)
|
|
133
|
+
gui_factory.assert_called_once_with()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_failed_gui_connection_can_be_retried(gui_factory):
|
|
137
|
+
failed_client = mock.Mock()
|
|
138
|
+
failed_client.connect_to_gui_server.side_effect = RuntimeError("Connection failed")
|
|
139
|
+
connected_client = mock.Mock()
|
|
140
|
+
gui_factory.side_effect = [failed_client, connected_client]
|
|
141
|
+
gui = LazyBECGuiClient(gui_id="existing-gui")
|
|
142
|
+
|
|
143
|
+
with pytest.raises(RuntimeError, match="Connection failed"):
|
|
144
|
+
gui.get_client()
|
|
145
|
+
|
|
146
|
+
assert gui.get_client() is connected_client
|
|
147
|
+
assert gui.get_client() is connected_client
|
|
148
|
+
assert gui_factory.call_count == 2
|
|
149
|
+
failed_client.connect_to_gui_server.assert_called_once_with("existing-gui")
|
|
150
|
+
connected_client.connect_to_gui_server.assert_called_once_with("existing-gui")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_gui_property_reports_missing_widgets(client, widgets_unavailable):
|
|
154
|
+
with pytest.raises(ImportError, match="Install bec-widgets") as exc:
|
|
155
|
+
client.gui
|
|
156
|
+
assert isinstance(exc.value.__cause__, ModuleNotFoundError)
|
|
157
|
+
client.shutdown()
|
|
158
|
+
client._client.shutdown.assert_called_once_with(None)
|
|
159
|
+
assert len(widgets_unavailable) == 1
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_gui_property_returns_real_client_and_caches_it(client, gui_factory):
|
|
163
|
+
gui_factory.assert_not_called()
|
|
164
|
+
assert client.gui is gui_factory.return_value
|
|
165
|
+
assert client.gui is gui_factory.return_value
|
|
166
|
+
client.shutdown()
|
|
167
|
+
gui_factory.assert_called_once_with()
|
|
168
|
+
gui_factory.return_value.close.assert_called_once_with()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_gui_cleanup_failure_does_not_prevent_core_shutdown(client, gui_factory):
|
|
172
|
+
real_gui = client.gui
|
|
173
|
+
real_gui.close.side_effect = RuntimeError("GUI cleanup failed")
|
|
174
|
+
|
|
175
|
+
with mock.patch.object(main, "logger") as logger:
|
|
176
|
+
client.shutdown(per_thread_timeout_s=1)
|
|
177
|
+
|
|
178
|
+
real_gui.close.assert_called_once_with()
|
|
179
|
+
client._client.shutdown.assert_called_once_with(1)
|
|
180
|
+
logger.error.assert_called_once_with("Error closing GUI client: GUI cleanup failed")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/callbacks/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/callbacks/live_table.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/callbacks/move_device.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/plugins/SLS/__init__.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/plugins/SLS/sls_info.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/plugins/XTreme/__init__.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/plugins/XTreme/x-treme.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/bec_ipython_client/plugins/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_beamline_mixins.py
RENAMED
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_device_progress.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_move_callback.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/client_tests/test_pretty_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/end-2-end/test_procedures_e2e.py
RENAMED
|
File without changes
|
{bec_ipython_client-4.1.6 → bec_ipython_client-4.2.0}/tests/end-2-end/test_scan_workflows_lib_e2e.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|