bec-widgets 0.67.0__py3-none-any.whl → 0.69.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.
- CHANGELOG.md +30 -32
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +4 -0
- bec_widgets/cli/client_utils.py +46 -34
- bec_widgets/cli/generate_cli.py +3 -0
- bec_widgets/cli/server.py +61 -18
- bec_widgets/utils/bec_dispatcher.py +14 -6
- bec_widgets/widgets/vscode/__init__.py +0 -0
- bec_widgets/widgets/vscode/vscode.py +86 -0
- bec_widgets/widgets/website/website.py +10 -1
- {bec_widgets-0.67.0.dist-info → bec_widgets-0.69.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.67.0.dist-info → bec_widgets-0.69.0.dist-info}/RECORD +18 -15
- pyproject.toml +1 -1
- tests/end-2-end/conftest.py +1 -4
- tests/unit_tests/test_vscode_widget.py +61 -0
- {bec_widgets-0.67.0.dist-info → bec_widgets-0.69.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.67.0.dist-info → bec_widgets-0.69.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.67.0.dist-info → bec_widgets-0.69.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -1,5 +1,35 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## v0.69.0 (2024-06-21)
|
4
|
+
|
5
|
+
### Feature
|
6
|
+
|
7
|
+
* feat(widgets): added vscode widget ([`48ae950`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/48ae950d57b454307ce409e2511f7b7adf3cfc6b))
|
8
|
+
|
9
|
+
### Fix
|
10
|
+
|
11
|
+
* fix(generate_cli): fixed rpc generate for classes without user access; closes #226 ([`925c893`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/925c893f3ff4337fc8b4d237c8ffc19a597b0996))
|
12
|
+
|
13
|
+
## v0.68.0 (2024-06-21)
|
14
|
+
|
15
|
+
### Feature
|
16
|
+
|
17
|
+
* feat: properly handle SIGINT (ctrl-c) in BEC GUI server -> calls qapplication.quit() ([`3644f34`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3644f344da2df674bc0d5740c376a86b9d0dfe95))
|
18
|
+
|
19
|
+
* feat: bec-gui-server: redirect stdout and stderr (if any) as proper debug and error log entries ([`d1266a1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d1266a1ce148ff89557a039e3a182a87a3948f49))
|
20
|
+
|
21
|
+
* feat: add logger for BEC GUI server ([`630616e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/630616ec729f60aa0b4d17a9e0379f9c6198eb96))
|
22
|
+
|
23
|
+
### Fix
|
24
|
+
|
25
|
+
* fix: ignore GUI server output (any output will go to log file)
|
26
|
+
|
27
|
+
If a logger is given to log `_start_log_process`, the server stdout and
|
28
|
+
stderr streams will be redirected as log entries with levels DEBUG or ERROR
|
29
|
+
in their parent process ([`ce37416`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ce374163cab87a92847409051739777bc505a77b))
|
30
|
+
|
31
|
+
* fix: do not create 'BECClient' logger when instantiating BECDispatcher ([`f7d0b07`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f7d0b0768ace42a33e2556bb33611d4f02e5a6d9))
|
32
|
+
|
3
33
|
## v0.67.0 (2024-06-21)
|
4
34
|
|
5
35
|
### Documentation
|
@@ -146,35 +176,3 @@ on SIGTERM ([`9263f8e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9263f8ef5
|
|
146
176
|
### Test
|
147
177
|
|
148
178
|
* test: add test for text box ([`b49462a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b49462abeb186e56bac79d2ef0b0add1ef28a1a5))
|
149
|
-
|
150
|
-
### Unknown
|
151
|
-
|
152
|
-
* Revert "feat: implement non-polling, interruptible waiting of gui instruction response with timeout"
|
153
|
-
|
154
|
-
This reverts commit abc6caa2d0b6141dfbe1f3d025f78ae14deddcb3 ([`fe04dd8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fe04dd80e59a0e74f7fdea603e0642707ecc7c2a))
|
155
|
-
|
156
|
-
## v0.62.0 (2024-06-12)
|
157
|
-
|
158
|
-
### Feature
|
159
|
-
|
160
|
-
* feat: implement non-polling, interruptible waiting of gui instruction response with timeout ([`abc6caa`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/abc6caa2d0b6141dfbe1f3d025f78ae14deddcb3))
|
161
|
-
|
162
|
-
### Unknown
|
163
|
-
|
164
|
-
* doc: add documentation about creating custom GUI applications embedding BEC Widgets ([`17a0068`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/17a00687579f5efab1990cd83862ec0e78198633))
|
165
|
-
|
166
|
-
## v0.61.0 (2024-06-12)
|
167
|
-
|
168
|
-
### Feature
|
169
|
-
|
170
|
-
* feat(widgets/stop_button): General stop button added ([`61ba08d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/61ba08d0b8df9f48f5c54c7c2b4e6d395206e7e6))
|
171
|
-
|
172
|
-
### Refactor
|
173
|
-
|
174
|
-
* refactor: improve labe of auto_update script ([`40b5688`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/40b568815893cd41af3531bb2e647ca1e2e315f4))
|
175
|
-
|
176
|
-
## v0.60.0 (2024-06-08)
|
177
|
-
|
178
|
-
### Fix
|
179
|
-
|
180
|
-
* fix: removed BECConnector from rpc client interface ([`6428e38`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6428e38ab94c15a2c904e75cc6404bb6d0394e04))
|
PKG-INFO
CHANGED
bec_widgets/cli/client.py
CHANGED
@@ -19,6 +19,7 @@ class Widgets(str, enum.Enum):
|
|
19
19
|
BECFigure = "BECFigure"
|
20
20
|
SpiralProgressBar = "SpiralProgressBar"
|
21
21
|
TextBox = "TextBox"
|
22
|
+
VSCodeEditor = "VSCodeEditor"
|
22
23
|
WebsiteWidget = "WebsiteWidget"
|
23
24
|
|
24
25
|
|
@@ -2049,6 +2050,9 @@ class TextBox(RPCBase):
|
|
2049
2050
|
"""
|
2050
2051
|
|
2051
2052
|
|
2053
|
+
class VSCodeEditor(RPCBase): ...
|
2054
|
+
|
2055
|
+
|
2052
2056
|
class WebsiteWidget(RPCBase):
|
2053
2057
|
@rpc_call
|
2054
2058
|
def set_url(self, url: str) -> None:
|
bec_widgets/cli/client_utils.py
CHANGED
@@ -13,6 +13,7 @@ from functools import wraps
|
|
13
13
|
from typing import TYPE_CHECKING
|
14
14
|
|
15
15
|
from bec_lib.endpoints import MessageEndpoints
|
16
|
+
from bec_lib.logger import bec_logger
|
16
17
|
from bec_lib.utils.import_utils import isinstance_based_on_class_name, lazy_import, lazy_import_from
|
17
18
|
from qtpy.QtCore import QEventLoop, QSocketNotifier, QTimer
|
18
19
|
|
@@ -31,6 +32,8 @@ messages = lazy_import("bec_lib.messages")
|
|
31
32
|
MessageObject = lazy_import_from("bec_lib.connector", ("MessageObject",))
|
32
33
|
BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",))
|
33
34
|
|
35
|
+
logger = bec_logger.logger
|
36
|
+
|
34
37
|
|
35
38
|
def rpc_call(func):
|
36
39
|
"""
|
@@ -63,45 +66,64 @@ def rpc_call(func):
|
|
63
66
|
return wrapper
|
64
67
|
|
65
68
|
|
66
|
-
def _get_output(process) -> None:
|
69
|
+
def _get_output(process, logger) -> None:
|
70
|
+
log_func = {process.stdout: logger.debug, process.stderr: logger.error}
|
71
|
+
stream_buffer = {process.stdout: [], process.stderr: []}
|
67
72
|
try:
|
68
73
|
os.set_blocking(process.stdout.fileno(), False)
|
69
74
|
os.set_blocking(process.stderr.fileno(), False)
|
70
75
|
while process.poll() is None:
|
71
76
|
readylist, _, _ = select.select([process.stdout, process.stderr], [], [], 1)
|
72
|
-
|
73
|
-
|
77
|
+
for stream in (process.stdout, process.stderr):
|
78
|
+
buf = stream_buffer[stream]
|
79
|
+
if stream in readylist:
|
80
|
+
buf.append(stream.read(4096))
|
81
|
+
output, _, remaining = "".join(buf).rpartition("\n")
|
74
82
|
if output:
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
if error_output:
|
79
|
-
print(error_output, end="", file=sys.stderr)
|
83
|
+
log_func[stream](output)
|
84
|
+
buf.clear()
|
85
|
+
buf.append(remaining)
|
80
86
|
except Exception as e:
|
81
87
|
print(f"Error reading process output: {str(e)}")
|
82
88
|
|
83
89
|
|
84
|
-
def _start_plot_process(gui_id, gui_class, config) -> None:
|
90
|
+
def _start_plot_process(gui_id, gui_class, config, logger=None) -> None:
|
85
91
|
"""
|
86
92
|
Start the plot in a new process.
|
93
|
+
|
94
|
+
Logger must be a logger object with "debug" and "error" functions,
|
95
|
+
or it can be left to "None" as default. None means output from the
|
96
|
+
process will not be captured.
|
87
97
|
"""
|
88
98
|
# pylint: disable=subprocess-run-check
|
89
|
-
command = [
|
90
|
-
|
91
|
-
"--
|
92
|
-
|
93
|
-
"--config",
|
94
|
-
config,
|
95
|
-
"--gui_class",
|
96
|
-
gui_class.__name__,
|
97
|
-
]
|
99
|
+
command = ["bec-gui-server", "--id", gui_id, "--gui_class", gui_class.__name__]
|
100
|
+
if config:
|
101
|
+
command.extend(["--config", config])
|
102
|
+
|
98
103
|
env_dict = os.environ.copy()
|
99
104
|
env_dict["PYTHONUNBUFFERED"] = "1"
|
105
|
+
if logger is None:
|
106
|
+
stdout_redirect = subprocess.DEVNULL
|
107
|
+
stderr_redirect = subprocess.DEVNULL
|
108
|
+
else:
|
109
|
+
stdout_redirect = subprocess.PIPE
|
110
|
+
stderr_redirect = subprocess.PIPE
|
111
|
+
|
100
112
|
process = subprocess.Popen(
|
101
|
-
command,
|
113
|
+
command,
|
114
|
+
text=True,
|
115
|
+
start_new_session=True,
|
116
|
+
stdout=stdout_redirect,
|
117
|
+
stderr=stderr_redirect,
|
118
|
+
env=env_dict,
|
102
119
|
)
|
103
|
-
|
104
|
-
|
120
|
+
if logger is None:
|
121
|
+
process_output_processing_thread = None
|
122
|
+
else:
|
123
|
+
process_output_processing_thread = threading.Thread(
|
124
|
+
target=_get_output, args=(process, logger)
|
125
|
+
)
|
126
|
+
process_output_processing_thread.start()
|
105
127
|
return process, process_output_processing_thread
|
106
128
|
|
107
129
|
|
@@ -113,7 +135,6 @@ class BECGuiClientMixin:
|
|
113
135
|
self.auto_updates = self._get_update_script()
|
114
136
|
self._target_endpoint = MessageEndpoints.scan_status()
|
115
137
|
self._selected_device = None
|
116
|
-
self.stderr_output = []
|
117
138
|
|
118
139
|
def _get_update_script(self) -> AutoUpdates | None:
|
119
140
|
eps = imd.entry_points(group="bec.widgets.auto_updates")
|
@@ -165,7 +186,7 @@ class BECGuiClientMixin:
|
|
165
186
|
if self._process is None or self._process.poll() is not None:
|
166
187
|
self._start_update_script()
|
167
188
|
self._process, self._process_output_processing_thread = _start_plot_process(
|
168
|
-
self._gui_id, self.__class__, self._client._service_config.
|
189
|
+
self._gui_id, self.__class__, self._client._service_config.config_path
|
169
190
|
)
|
170
191
|
while not self.gui_is_alive():
|
171
192
|
print("Waiting for GUI to start...")
|
@@ -185,19 +206,10 @@ class BECGuiClientMixin:
|
|
185
206
|
self._client.shutdown()
|
186
207
|
if self._process:
|
187
208
|
self._process.terminate()
|
188
|
-
self._process_output_processing_thread
|
209
|
+
if self._process_output_processing_thread:
|
210
|
+
self._process_output_processing_thread.join()
|
189
211
|
self._process = None
|
190
212
|
|
191
|
-
def print_log(self) -> None:
|
192
|
-
"""
|
193
|
-
Print the log of the plot process.
|
194
|
-
"""
|
195
|
-
if self._process is None:
|
196
|
-
return
|
197
|
-
print("".join(self.stderr_output))
|
198
|
-
# Flush list
|
199
|
-
self.stderr_output.clear()
|
200
|
-
|
201
213
|
|
202
214
|
class RPCResponseTimeoutError(Exception):
|
203
215
|
"""Exception raised when an RPC response is not received within the expected time."""
|
bec_widgets/cli/generate_cli.py
CHANGED
@@ -83,6 +83,9 @@ class {class_name}(RPCBase, BECGuiClientMixin):"""
|
|
83
83
|
else:
|
84
84
|
self.content += f"""
|
85
85
|
class {class_name}(RPCBase):"""
|
86
|
+
if not cls.USER_ACCESS:
|
87
|
+
self.content += """...
|
88
|
+
"""
|
86
89
|
for method in cls.USER_ACCESS:
|
87
90
|
obj = getattr(cls, method)
|
88
91
|
if isinstance(obj, property):
|
bec_widgets/cli/server.py
CHANGED
@@ -1,17 +1,24 @@
|
|
1
1
|
import inspect
|
2
|
+
import signal
|
3
|
+
import sys
|
4
|
+
from contextlib import redirect_stderr, redirect_stdout
|
2
5
|
from typing import Union
|
3
6
|
|
4
7
|
from bec_lib.endpoints import MessageEndpoints
|
8
|
+
from bec_lib.logger import bec_logger
|
9
|
+
from bec_lib.service_config import ServiceConfig
|
5
10
|
from bec_lib.utils.import_utils import lazy_import
|
6
11
|
from qtpy.QtCore import QTimer
|
7
12
|
|
8
13
|
from bec_widgets.cli.rpc_register import RPCRegister
|
9
14
|
from bec_widgets.utils import BECDispatcher
|
10
15
|
from bec_widgets.utils.bec_connector import BECConnector
|
16
|
+
from bec_widgets.utils.bec_dispatcher import QtRedisConnector
|
11
17
|
from bec_widgets.widgets.dock.dock_area import BECDockArea
|
12
18
|
from bec_widgets.widgets.figure import BECFigure
|
13
19
|
|
14
20
|
messages = lazy_import("bec_lib.messages")
|
21
|
+
logger = bec_logger.logger
|
15
22
|
|
16
23
|
|
17
24
|
class BECWidgetsCLIServer:
|
@@ -114,6 +121,23 @@ class BECWidgetsCLIServer:
|
|
114
121
|
self.client.shutdown()
|
115
122
|
|
116
123
|
|
124
|
+
class SimpleFileLikeFromLogOutputFunc:
|
125
|
+
def __init__(self, log_func):
|
126
|
+
self._log_func = log_func
|
127
|
+
|
128
|
+
def write(self, buffer):
|
129
|
+
for line in buffer.rstrip().splitlines():
|
130
|
+
line = line.rstrip()
|
131
|
+
if line:
|
132
|
+
self._log_func(line)
|
133
|
+
|
134
|
+
def flush(self):
|
135
|
+
return
|
136
|
+
|
137
|
+
def close(self):
|
138
|
+
return
|
139
|
+
|
140
|
+
|
117
141
|
def main():
|
118
142
|
import argparse
|
119
143
|
import os
|
@@ -125,16 +149,6 @@ def main():
|
|
125
149
|
|
126
150
|
import bec_widgets
|
127
151
|
|
128
|
-
app = QApplication(sys.argv)
|
129
|
-
app.setApplicationName("BEC Figure")
|
130
|
-
module_path = os.path.dirname(bec_widgets.__file__)
|
131
|
-
icon = QIcon()
|
132
|
-
icon.addFile(os.path.join(module_path, "assets", "bec_widgets_icon.png"), size=QSize(48, 48))
|
133
|
-
app.setWindowIcon(icon)
|
134
|
-
|
135
|
-
win = QMainWindow()
|
136
|
-
win.setWindowTitle("BEC Widgets")
|
137
|
-
|
138
152
|
parser = argparse.ArgumentParser(description="BEC Widgets CLI Server")
|
139
153
|
parser.add_argument("--id", type=str, help="The id of the server")
|
140
154
|
parser.add_argument(
|
@@ -142,7 +156,7 @@ def main():
|
|
142
156
|
type=str,
|
143
157
|
help="Name of the gui class to be rendered. Possible values: \n- BECFigure\n- BECDockArea",
|
144
158
|
)
|
145
|
-
parser.add_argument("--config", type=str, help="Config
|
159
|
+
parser.add_argument("--config", type=str, help="Config file")
|
146
160
|
|
147
161
|
args = parser.parse_args()
|
148
162
|
|
@@ -157,15 +171,44 @@ def main():
|
|
157
171
|
)
|
158
172
|
gui_class = BECFigure
|
159
173
|
|
160
|
-
|
174
|
+
with redirect_stdout(SimpleFileLikeFromLogOutputFunc(logger.debug)):
|
175
|
+
with redirect_stderr(SimpleFileLikeFromLogOutputFunc(logger.error)):
|
176
|
+
app = QApplication(sys.argv)
|
177
|
+
app.setApplicationName("BEC Figure")
|
178
|
+
module_path = os.path.dirname(bec_widgets.__file__)
|
179
|
+
icon = QIcon()
|
180
|
+
icon.addFile(
|
181
|
+
os.path.join(module_path, "assets", "bec_widgets_icon.png"), size=QSize(48, 48)
|
182
|
+
)
|
183
|
+
app.setWindowIcon(icon)
|
184
|
+
|
185
|
+
win = QMainWindow()
|
186
|
+
win.setWindowTitle("BEC Widgets")
|
187
|
+
|
188
|
+
service_config = ServiceConfig(args.config)
|
189
|
+
bec_logger.configure(
|
190
|
+
service_config.redis,
|
191
|
+
QtRedisConnector,
|
192
|
+
service_name="BECWidgetsCLIServer",
|
193
|
+
service_config=service_config.service_config,
|
194
|
+
)
|
195
|
+
server = BECWidgetsCLIServer(gui_id=args.id, config=service_config, gui_class=gui_class)
|
196
|
+
|
197
|
+
gui = server.gui
|
198
|
+
win.setCentralWidget(gui)
|
199
|
+
win.resize(800, 600)
|
200
|
+
win.show()
|
201
|
+
|
202
|
+
app.aboutToQuit.connect(server.shutdown)
|
203
|
+
|
204
|
+
def sigint_handler(*args):
|
205
|
+
# display message, for people to let it terminate gracefully
|
206
|
+
print("Caught SIGINT, exiting")
|
207
|
+
app.quit()
|
161
208
|
|
162
|
-
|
163
|
-
win.setCentralWidget(gui)
|
164
|
-
win.resize(800, 600)
|
165
|
-
win.show()
|
209
|
+
signal.signal(signal.SIGINT, sigint_handler)
|
166
210
|
|
167
|
-
|
168
|
-
sys.exit(app.exec())
|
211
|
+
sys.exit(app.exec())
|
169
212
|
|
170
213
|
|
171
214
|
if __name__ == "__main__": # pragma: no cover
|
@@ -66,6 +66,11 @@ class QtRedisConnector(RedisConnector):
|
|
66
66
|
cb(msg.content, msg.metadata)
|
67
67
|
|
68
68
|
|
69
|
+
class BECClientWithoutLoggerInit(BECClient):
|
70
|
+
def _initialize_logger(self):
|
71
|
+
return
|
72
|
+
|
73
|
+
|
69
74
|
class BECDispatcher:
|
70
75
|
"""Utility class to keep track of slots connected to a particular redis connector"""
|
71
76
|
|
@@ -79,7 +84,7 @@ class BECDispatcher:
|
|
79
84
|
cls._initialized = False
|
80
85
|
return cls._instance
|
81
86
|
|
82
|
-
def __init__(self, client=None, config: str = None):
|
87
|
+
def __init__(self, client=None, config: str | ServiceConfig = None):
|
83
88
|
if self._initialized:
|
84
89
|
return
|
85
90
|
|
@@ -91,13 +96,16 @@ class BECDispatcher:
|
|
91
96
|
|
92
97
|
if self.client is None:
|
93
98
|
if config is not None:
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
99
|
+
if not isinstance(config, ServiceConfig):
|
100
|
+
# config is supposed to be a path
|
101
|
+
config = ServiceConfig(config)
|
102
|
+
self.client = BECClientWithoutLoggerInit(
|
103
|
+
config=config, connector_cls=QtRedisConnector
|
98
104
|
) # , forced=True)
|
99
105
|
else:
|
100
|
-
self.client =
|
106
|
+
self.client = BECClientWithoutLoggerInit(
|
107
|
+
connector_cls=QtRedisConnector
|
108
|
+
) # , forced=True)
|
101
109
|
else:
|
102
110
|
if self.client.started:
|
103
111
|
# have to reinitialize client to use proper connector
|
File without changes
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import os
|
2
|
+
import select
|
3
|
+
import shlex
|
4
|
+
import signal
|
5
|
+
import subprocess
|
6
|
+
import sys
|
7
|
+
|
8
|
+
from bec_widgets.widgets.website.website import WebsiteWidget
|
9
|
+
|
10
|
+
|
11
|
+
class VSCodeEditor(WebsiteWidget):
|
12
|
+
"""
|
13
|
+
A widget to display the VSCode editor.
|
14
|
+
"""
|
15
|
+
|
16
|
+
token = "bec"
|
17
|
+
host = "127.0.0.1"
|
18
|
+
port = 7000
|
19
|
+
|
20
|
+
USER_ACCESS = []
|
21
|
+
|
22
|
+
def __init__(self, parent=None, config=None, client=None, gui_id=None):
|
23
|
+
|
24
|
+
self.process = None
|
25
|
+
self._url = f"http://{self.host}:{self.port}?tkn={self.token}"
|
26
|
+
super().__init__(parent=parent, config=config, client=client, gui_id=gui_id)
|
27
|
+
self.start_server()
|
28
|
+
|
29
|
+
def start_server(self):
|
30
|
+
"""
|
31
|
+
Start the server.
|
32
|
+
|
33
|
+
This method starts the server for the VSCode editor in a subprocess.
|
34
|
+
"""
|
35
|
+
|
36
|
+
cmd = shlex.split(
|
37
|
+
f"code serve-web --port {self.port} --connection-token={self.token} --accept-server-license-terms"
|
38
|
+
)
|
39
|
+
self.process = subprocess.Popen(
|
40
|
+
cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, preexec_fn=os.setsid
|
41
|
+
)
|
42
|
+
|
43
|
+
os.set_blocking(self.process.stdout.fileno(), False)
|
44
|
+
while self.process.poll() is None:
|
45
|
+
readylist, _, _ = select.select([self.process.stdout], [], [], 1)
|
46
|
+
if self.process.stdout in readylist:
|
47
|
+
output = self.process.stdout.read(1024)
|
48
|
+
if output and f"available at {self._url}" in output:
|
49
|
+
break
|
50
|
+
self.set_url(self._url)
|
51
|
+
|
52
|
+
def closeEvent(self, event):
|
53
|
+
"""
|
54
|
+
Hook for the close event to terminate the server.
|
55
|
+
"""
|
56
|
+
self.cleanup_vscode()
|
57
|
+
super().closeEvent(event)
|
58
|
+
|
59
|
+
def cleanup_vscode(self):
|
60
|
+
"""
|
61
|
+
Cleanup the VSCode editor.
|
62
|
+
"""
|
63
|
+
if not self.process:
|
64
|
+
return
|
65
|
+
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
66
|
+
self.process.wait()
|
67
|
+
|
68
|
+
def cleanup(self):
|
69
|
+
"""
|
70
|
+
Cleanup the widget. This method is called from the dock area when the widget is removed.
|
71
|
+
"""
|
72
|
+
self.cleanup_vscode()
|
73
|
+
return super().cleanup()
|
74
|
+
|
75
|
+
|
76
|
+
if __name__ == "__main__": # pragma: no cover
|
77
|
+
import sys
|
78
|
+
|
79
|
+
from qtpy.QtWidgets import QApplication
|
80
|
+
|
81
|
+
app = QApplication(sys.argv)
|
82
|
+
widget = VSCodeEditor()
|
83
|
+
widget.show()
|
84
|
+
app.exec_()
|
85
|
+
widget.bec_dispatcher.disconnect_all()
|
86
|
+
widget.client.shutdown()
|
@@ -1,10 +1,19 @@
|
|
1
|
-
from qtpy.QtCore import QUrl
|
1
|
+
from qtpy.QtCore import QUrl, qInstallMessageHandler
|
2
2
|
from qtpy.QtWebEngineWidgets import QWebEngineView
|
3
3
|
from qtpy.QtWidgets import QApplication
|
4
4
|
|
5
5
|
from bec_widgets.utils import BECConnector
|
6
6
|
|
7
7
|
|
8
|
+
def suppress_qt_messages(type_, context, msg):
|
9
|
+
if context.category in ["js", "default"]:
|
10
|
+
return
|
11
|
+
print(msg)
|
12
|
+
|
13
|
+
|
14
|
+
qInstallMessageHandler(suppress_qt_messages)
|
15
|
+
|
16
|
+
|
8
17
|
class WebsiteWidget(BECConnector, QWebEngineView):
|
9
18
|
"""
|
10
19
|
A simple widget to display a website
|
@@ -2,11 +2,11 @@
|
|
2
2
|
.gitlab-ci.yml,sha256=RnYDz4zKXjlqltTryprlB1s5vLXxI2-seW-Vb70NNF0,8162
|
3
3
|
.pylintrc,sha256=OstrgmEyP0smNFBKoIN5_26-UmNZgMHnbjvAWX0UrLs,18535
|
4
4
|
.readthedocs.yaml,sha256=aSOc277LqXcsTI6lgvm_JY80lMlr69GbPKgivua2cS0,603
|
5
|
-
CHANGELOG.md,sha256=
|
5
|
+
CHANGELOG.md,sha256=48HpbXwe-Y_t9YJAntu3fJ8MU4DN6bSWSJWj-1bJAEY,7057
|
6
6
|
LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
7
|
-
PKG-INFO,sha256=
|
7
|
+
PKG-INFO,sha256=GEZve_LqjICZHq1bEuZ7S1woFWxfQSVUsGmIVgudNYE,1302
|
8
8
|
README.md,sha256=y4jB6wvArS7N8_iTbKWnSM_oRAqLA2GqgzUR-FMh5sU,2645
|
9
|
-
pyproject.toml,sha256=
|
9
|
+
pyproject.toml,sha256=Td8223p7vyZ4AVOTaqH50fwtAgfFtSIRq-QYp625MOA,2162
|
10
10
|
.git_hooks/pre-commit,sha256=n3RofIZHJl8zfJJIUomcMyYGFi_rwq4CC19z0snz3FI,286
|
11
11
|
.gitlab/issue_templates/bug_report_template.md,sha256=gAuyEwl7XlnebBrkiJ9AqffSNOywmr8vygUFWKTuQeI,386
|
12
12
|
.gitlab/issue_templates/documentation_update_template.md,sha256=FHLdb3TS_D9aL4CYZCjyXSulbaW5mrN2CmwTaeLPbNw,860
|
@@ -17,12 +17,12 @@ bec_widgets/assets/bec_widgets_icon.png,sha256=K8dgGwIjalDh9PRHUsSQBqgdX7a00nM3i
|
|
17
17
|
bec_widgets/assets/terminal_icon.png,sha256=bJl7Tft4Fi2uxvuXI8o14uMHnI9eAWKSU2uftXCH9ws,3889
|
18
18
|
bec_widgets/cli/__init__.py,sha256=d0Q6Fn44e7wFfLabDOBxpcJ1DPKWlFunGYDUBmO-4hA,22
|
19
19
|
bec_widgets/cli/auto_updates.py,sha256=DyBV3HnjMSH-cvVkYNcDiYKVf0Xut4Qy2qGQqkW47Bw,4833
|
20
|
-
bec_widgets/cli/client.py,sha256=
|
21
|
-
bec_widgets/cli/client_utils.py,sha256=
|
22
|
-
bec_widgets/cli/generate_cli.py,sha256=
|
20
|
+
bec_widgets/cli/client.py,sha256=DNsCueEdVwW0MWjBIIg-vhTu_p64qr0QurT7mHM79is,58074
|
21
|
+
bec_widgets/cli/client_utils.py,sha256=_Hb2nl1rKEf7k4By9VZDYl5YyGFczxMuYIFMVrOAZD0,12182
|
22
|
+
bec_widgets/cli/generate_cli.py,sha256=InKBVYM7DRfAVLNJhRJbWWSSPBQBHI8Ek6v7NCsK0ME,4997
|
23
23
|
bec_widgets/cli/rpc_register.py,sha256=QxXUZu5XNg00Yf5O3UHWOXg3-f_pzKjjoZYMOa-MOJc,2216
|
24
24
|
bec_widgets/cli/rpc_wigdet_handler.py,sha256=1qQOGrM8rozaWLkoxAW8DTVLv_L_DZdZgUMDPy5MOek,1486
|
25
|
-
bec_widgets/cli/server.py,sha256=
|
25
|
+
bec_widgets/cli/server.py,sha256=3bFBPmtXKXFMjeja18d0hF3CO66Jo0-LEDtcF7lYb7k,7166
|
26
26
|
bec_widgets/examples/__init__.py,sha256=WWQ0cu7m8sA4Ehy-DWdTIqSISjaHsbxhsNmNrMnhDZU,202
|
27
27
|
bec_widgets/examples/jupyter_console/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
28
|
bec_widgets/examples/jupyter_console/jupyter_console_window.py,sha256=FXf0q7oz9GyJSct8PAgeOalzNnIJjApiaRvNfXsZPs0,5345
|
@@ -32,7 +32,7 @@ bec_widgets/examples/motor_movement/motor_control_compilations.py,sha256=8rpA7a2
|
|
32
32
|
bec_widgets/examples/motor_movement/motor_controller.ui,sha256=83XX6NGILwntoUIghvzWnMuGf80O8khK3SduVKTAEFM,29105
|
33
33
|
bec_widgets/utils/__init__.py,sha256=1930ji1Jj6dVuY81Wd2kYBhHYNV-2R0bN_L4o9zBj1U,533
|
34
34
|
bec_widgets/utils/bec_connector.py,sha256=RxHJNF7JjtY5pRbTMu2eQTiRXvoyJ53QuTYxHjZba38,5357
|
35
|
-
bec_widgets/utils/bec_dispatcher.py,sha256=
|
35
|
+
bec_widgets/utils/bec_dispatcher.py,sha256=yM9PG04O7ABhiA9Nzk38Rv9Qbjc5O93wi2xfSbOlOxc,6202
|
36
36
|
bec_widgets/utils/bec_table.py,sha256=nA2b8ukSeUfquFMAxGrUVOqdrzMoDYD6O_4EYbOG2zk,717
|
37
37
|
bec_widgets/utils/colors.py,sha256=GYSDe0ZxsJSwxvuy-yG2BH17qlf_Sjq8dhDcyp9IhBI,8532
|
38
38
|
bec_widgets/utils/container_utils.py,sha256=m3VUyAYmSWkEwApP9tBvKxPYVtc2kHw4toxIpMryJy4,1495
|
@@ -100,8 +100,10 @@ bec_widgets/widgets/text_box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
100
100
|
bec_widgets/widgets/text_box/text_box.py,sha256=kykQ_Zcxh8IGcPEP5-oGGQwoZEpY9vhxRIM8TY8kTYg,4240
|
101
101
|
bec_widgets/widgets/toolbar/__init__.py,sha256=d-TP4_cr_VbpwreMM4ePnfZ5YXsEPQ45ibEf75nuGoE,36
|
102
102
|
bec_widgets/widgets/toolbar/toolbar.py,sha256=e0zCD_0q7K4NVhrzD8001Qvfxt-VhqHTgofchS9NgCM,5125
|
103
|
+
bec_widgets/widgets/vscode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
104
|
+
bec_widgets/widgets/vscode/vscode.py,sha256=k4Y54zp9jGfeUKsFc482TnUJQd3pj-jdIb3i_dLiWUA,2376
|
103
105
|
bec_widgets/widgets/website/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
104
|
-
bec_widgets/widgets/website/website.py,sha256=
|
106
|
+
bec_widgets/widgets/website/website.py,sha256=Scvpl4I52qpL7s69tnNBRQSG6GcRI9jzoR3RsSTXfPE,1722
|
105
107
|
docs/Makefile,sha256=i2WHuFlgfyAPEW4ssEP8NY4cOibDJrVjvzSEU8_Ggwc,634
|
106
108
|
docs/conf.py,sha256=HxLxupNGu0Smhwn57g1kFdjZzFuaWVREgRJKhT1zi2k,2464
|
107
109
|
docs/index.md,sha256=8ZCgaLIbJsYvt-jwi--QxsNwnK4-k3rejIeOOLclG40,1101
|
@@ -149,7 +151,7 @@ docs/user/widgets/website.md,sha256=wfudAupdtHX-Sfritg0xMWXZLLczJ4XwMLNWvu6ww-w,
|
|
149
151
|
docs/user/widgets/widgets.md,sha256=6H8C8M2fFmTxFFlrAuOE-jBpOUVUrOIIzWP0l_EwMGo,397
|
150
152
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
151
153
|
tests/end-2-end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
152
|
-
tests/end-2-end/conftest.py,sha256
|
154
|
+
tests/end-2-end/conftest.py,sha256=-BLnFE-NeCerf6xahGCkbZ4Ktactowi6RkBnboIzRvg,1767
|
153
155
|
tests/end-2-end/test_bec_dock_rpc_e2e.py,sha256=8iJz4lITspY7eHdSgy9YvGUGTu3fsSperoVGBvTGT0U,9067
|
154
156
|
tests/end-2-end/test_bec_figure_rpc_e2e.py,sha256=zTbB_F4Fs-QG8KhMK24xfsrCQBgZUAguMk3KFdEdP2o,5095
|
155
157
|
tests/end-2-end/test_rpc_register_e2e.py,sha256=3dfCnSvdcRO92pzHt9WlCTK0vzTKAvPtliEoEKrtuzQ,1604
|
@@ -177,6 +179,7 @@ tests/unit_tests/test_scan_control.py,sha256=Xf8bGt8lRJobRwBoqUdVXxsHno8ejvC77Fq
|
|
177
179
|
tests/unit_tests/test_spiral_progress_bar.py,sha256=n5aLSZ2B6K5a1vQuKTERnCSmIz9hYGFyk7jP3TU0AwQ,12438
|
178
180
|
tests/unit_tests/test_stop_button.py,sha256=2OH9dhs_-S5QovPPgU-5hJoViE1YKZa0gxisb4vOY28,712
|
179
181
|
tests/unit_tests/test_text_box_widget.py,sha256=cT0uEHt_6d-FwST0A_wE9sFW9E3F_nJbKhuBAeU4yHg,1862
|
182
|
+
tests/unit_tests/test_vscode_widget.py,sha256=sCVNAuWVMiPFinh9mDqz_ulBay_H3qwHyEwkHsbWh4c,2173
|
180
183
|
tests/unit_tests/test_waveform1d.py,sha256=I3_pF0ieltcTWtweOBjICaOxJ8NCQ0-NWxpKg8Pas3E,15893
|
181
184
|
tests/unit_tests/test_website_widget.py,sha256=fBADIJJBAHU4Ro7u95kdemFVNv196UOcuO9oLHuHt8A,761
|
182
185
|
tests/unit_tests/test_widget_io.py,sha256=FeL3ZYSBQnRt6jxj8VGYw1cmcicRQyHKleahw7XIyR0,3475
|
@@ -186,8 +189,8 @@ tests/unit_tests/test_configs/config_device_no_entry.yaml,sha256=hdvue9KLc_kfNzG
|
|
186
189
|
tests/unit_tests/test_configs/config_scan.yaml,sha256=vo484BbWOjA_e-h6bTjSV9k7QaQHrlAvx-z8wtY-P4E,1915
|
187
190
|
tests/unit_tests/test_msgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
188
191
|
tests/unit_tests/test_msgs/available_scans_message.py,sha256=m_z97hIrjHXXMa2Ex-UvsPmTxOYXfjxyJaGkIY6StTY,46532
|
189
|
-
bec_widgets-0.
|
190
|
-
bec_widgets-0.
|
191
|
-
bec_widgets-0.
|
192
|
-
bec_widgets-0.
|
193
|
-
bec_widgets-0.
|
192
|
+
bec_widgets-0.69.0.dist-info/METADATA,sha256=GEZve_LqjICZHq1bEuZ7S1woFWxfQSVUsGmIVgudNYE,1302
|
193
|
+
bec_widgets-0.69.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
194
|
+
bec_widgets-0.69.0.dist-info/entry_points.txt,sha256=OvoqiNzNF9bizFQNhbAmmdc_njHrnVewLE-Kl-u9sh0,115
|
195
|
+
bec_widgets-0.69.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
196
|
+
bec_widgets-0.69.0.dist-info/RECORD,,
|
pyproject.toml
CHANGED
tests/end-2-end/conftest.py
CHANGED
@@ -29,9 +29,7 @@ def gui_id():
|
|
29
29
|
@contextmanager
|
30
30
|
def plot_server(gui_id, klass, client_lib):
|
31
31
|
dispatcher = BECDispatcher(client=client_lib) # Has to init singleton with fixture client
|
32
|
-
process,
|
33
|
-
gui_id, klass, client_lib._client._service_config.redis
|
34
|
-
)
|
32
|
+
process, _ = _start_plot_process(gui_id, klass, client_lib._client._service_config.config_path)
|
35
33
|
try:
|
36
34
|
while client_lib._client.connector.get(MessageEndpoints.gui_heartbeat(gui_id)) is None:
|
37
35
|
time.sleep(0.3)
|
@@ -39,7 +37,6 @@ def plot_server(gui_id, klass, client_lib):
|
|
39
37
|
finally:
|
40
38
|
process.terminate()
|
41
39
|
process.wait()
|
42
|
-
output_thread.join()
|
43
40
|
dispatcher.disconnect_all()
|
44
41
|
dispatcher.reset_singleton()
|
45
42
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import os
|
2
|
+
import shlex
|
3
|
+
import subprocess
|
4
|
+
from unittest import mock
|
5
|
+
|
6
|
+
import pytest
|
7
|
+
|
8
|
+
from bec_widgets.widgets.vscode.vscode import VSCodeEditor
|
9
|
+
|
10
|
+
from .client_mocks import mocked_client
|
11
|
+
|
12
|
+
|
13
|
+
@pytest.fixture
|
14
|
+
def vscode_widget(qtbot, mocked_client):
|
15
|
+
with mock.patch("bec_widgets.widgets.vscode.vscode.subprocess.Popen") as mock_popen:
|
16
|
+
widget = VSCodeEditor(client=mocked_client)
|
17
|
+
yield widget
|
18
|
+
|
19
|
+
|
20
|
+
def test_vscode_widget(qtbot, vscode_widget):
|
21
|
+
assert vscode_widget.process is not None
|
22
|
+
assert vscode_widget._url == "http://127.0.0.1:7000?tkn=bec"
|
23
|
+
|
24
|
+
|
25
|
+
def test_start_server(qtbot, mocked_client):
|
26
|
+
|
27
|
+
with mock.patch("bec_widgets.widgets.vscode.vscode.subprocess.Popen") as mock_popen:
|
28
|
+
mock_process = mock.Mock()
|
29
|
+
mock_process.stdout.fileno.return_value = 1
|
30
|
+
mock_process.poll.return_value = None
|
31
|
+
mock_process.stdout.read.return_value = (
|
32
|
+
f"available at http://{VSCodeEditor.host}:{VSCodeEditor.port}?tkn={VSCodeEditor.token}"
|
33
|
+
)
|
34
|
+
mock_popen.return_value = mock_process
|
35
|
+
|
36
|
+
widget = VSCodeEditor(client=mocked_client)
|
37
|
+
|
38
|
+
mock_popen.assert_called_once_with(
|
39
|
+
shlex.split(
|
40
|
+
f"code serve-web --port {widget.port} --connection-token={widget.token} --accept-server-license-terms"
|
41
|
+
),
|
42
|
+
text=True,
|
43
|
+
stdout=subprocess.PIPE,
|
44
|
+
stderr=subprocess.DEVNULL,
|
45
|
+
preexec_fn=os.setsid,
|
46
|
+
)
|
47
|
+
|
48
|
+
|
49
|
+
def test_close_event(qtbot, vscode_widget):
|
50
|
+
with mock.patch("bec_widgets.widgets.vscode.vscode.os.killpg") as mock_killpg:
|
51
|
+
with mock.patch("bec_widgets.widgets.vscode.vscode.os.getpgid") as mock_getpgid:
|
52
|
+
with mock.patch(
|
53
|
+
"bec_widgets.widgets.website.website.WebsiteWidget.closeEvent"
|
54
|
+
) as mock_close_event:
|
55
|
+
mock_getpgid.return_value = 123
|
56
|
+
vscode_widget.process = mock.Mock()
|
57
|
+
vscode_widget.process.pid = 123
|
58
|
+
vscode_widget.closeEvent(None)
|
59
|
+
mock_killpg.assert_called_once_with(123, 15)
|
60
|
+
vscode_widget.process.wait.assert_called_once()
|
61
|
+
mock_close_event.assert_called_once()
|
File without changes
|
File without changes
|
File without changes
|