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 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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 0.67.0
3
+ Version: 0.69.0
4
4
  Summary: BEC Widgets
5
5
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
6
6
  Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
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:
@@ -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
- if process.stdout in readylist:
73
- output = process.stdout.read(1024)
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
- print(output, end="")
76
- if process.stderr in readylist:
77
- error_output = process.stderr.read(1024)
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
- "bec-gui-server",
91
- "--id",
92
- gui_id,
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, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env_dict
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
- process_output_processing_thread = threading.Thread(target=_get_output, args=(process,))
104
- process_output_processing_thread.start()
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.redis
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.join()
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."""
@@ -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 to connect to redis.")
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
- server = BECWidgetsCLIServer(gui_id=args.id, config=args.config, gui_class=gui_class)
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
- gui = server.gui
163
- win.setCentralWidget(gui)
164
- win.resize(800, 600)
165
- win.show()
209
+ signal.signal(signal.SIGINT, sigint_handler)
166
210
 
167
- app.aboutToQuit.connect(server.shutdown)
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
- host, port = config.split(":")
95
- redis_config = {"host": host, "port": port}
96
- self.client = BECClient(
97
- config=ServiceConfig(redis=redis_config), connector_cls=QtRedisConnector
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 = BECClient(connector_cls=QtRedisConnector) # , forced=True)
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 0.67.0
3
+ Version: 0.69.0
4
4
  Summary: BEC Widgets
5
5
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
6
6
  Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
@@ -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=WkuQFvZMK4X4aOXLwutfGkFzmzy-SLKr8ez6vvTXiBY,6851
5
+ CHANGELOG.md,sha256=48HpbXwe-Y_t9YJAntu3fJ8MU4DN6bSWSJWj-1bJAEY,7057
6
6
  LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
7
- PKG-INFO,sha256=9Cs9V5N0bA887XBwZGWJ08opZvj8rpWinMW3R2r56rs,1302
7
+ PKG-INFO,sha256=GEZve_LqjICZHq1bEuZ7S1woFWxfQSVUsGmIVgudNYE,1302
8
8
  README.md,sha256=y4jB6wvArS7N8_iTbKWnSM_oRAqLA2GqgzUR-FMh5sU,2645
9
- pyproject.toml,sha256=gK6dcc2qr6EKwZzWQhdkPeABrJhhE93t6qRmseFko80,2162
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=Zd4oMSE5-HY3IBUIVcparGGV2Ew86gaWAFTd4OjFVmg,58005
21
- bec_widgets/cli/client_utils.py,sha256=D076XKwcukKBKknd11B1UyOcQN_9sN7ZKMVttyCxS9Q,11586
22
- bec_widgets/cli/generate_cli.py,sha256=Bi8HxHhge1I87vbdYHZUZiZwvbB-OSkLYS5Xfmwiz9M,4922
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=4sigviIyJgZOgikWHc1X998vWAWayKF6S61oAY_mVDQ,5727
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=vvHpD_7ZddRmD6mHR5JWU_XEJYxognpmG7CnEWMjaZk,5989
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=C679bpGUoSqfLtu4_BcEQEmesNj_L9HaTu0buJzb4c0,1526
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=j1O1SxXRJ8jcrunn6dcfbZLK2Jc-VUxyh9ZuCSc6Qj4,1816
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.67.0.dist-info/METADATA,sha256=9Cs9V5N0bA887XBwZGWJ08opZvj8rpWinMW3R2r56rs,1302
190
- bec_widgets-0.67.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
191
- bec_widgets-0.67.0.dist-info/entry_points.txt,sha256=OvoqiNzNF9bizFQNhbAmmdc_njHrnVewLE-Kl-u9sh0,115
192
- bec_widgets-0.67.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
193
- bec_widgets-0.67.0.dist-info/RECORD,,
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_widgets"
7
- version = "0.67.0"
7
+ version = "0.69.0"
8
8
  description = "BEC Widgets"
9
9
  requires-python = ">=3.10"
10
10
  classifiers = [
@@ -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, output_thread = _start_plot_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()