bec-widgets 1.19.2__py3-none-any.whl → 1.21.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.
@@ -0,0 +1,529 @@
1
+ """ Module for a LogPanel widget to display BEC log messages """
2
+
3
+ from __future__ import annotations
4
+
5
+ import operator
6
+ import os
7
+ import re
8
+ from collections import deque
9
+ from functools import partial, reduce
10
+ from re import Pattern
11
+ from typing import TYPE_CHECKING, Literal
12
+
13
+ from bec_lib.client import BECClient
14
+ from bec_lib.connector import ConnectorBase
15
+ from bec_lib.endpoints import MessageEndpoints
16
+ from bec_lib.logger import LogLevel, bec_logger
17
+ from bec_lib.messages import LogMessage, StatusMessage
18
+ from qtpy.QtCore import QDateTime, Qt, Signal # type: ignore
19
+ from qtpy.QtGui import QFont
20
+ from qtpy.QtWidgets import (
21
+ QApplication,
22
+ QCheckBox,
23
+ QComboBox,
24
+ QDateTimeEdit,
25
+ QDialog,
26
+ QGridLayout,
27
+ QHBoxLayout,
28
+ QLabel,
29
+ QLineEdit,
30
+ QPushButton,
31
+ QScrollArea,
32
+ QTextEdit,
33
+ QVBoxLayout,
34
+ QWidget,
35
+ )
36
+
37
+ from bec_widgets.qt_utils.error_popups import SafeSlot
38
+ from bec_widgets.utils.colors import get_theme_palette, set_theme
39
+ from bec_widgets.widgets.editors.text_box.text_box import TextBox
40
+ from bec_widgets.widgets.services.bec_status_box.bec_status_box import BECServiceStatusMixin
41
+ from bec_widgets.widgets.utility.logpanel._util import (
42
+ LineFilter,
43
+ LineFormatter,
44
+ LinesHtmlFormatter,
45
+ create_formatter,
46
+ level_filter,
47
+ log_svc,
48
+ log_time,
49
+ log_txt,
50
+ noop_format,
51
+ simple_color_format,
52
+ )
53
+
54
+ if TYPE_CHECKING:
55
+ from PySide6.QtCore import SignalInstance
56
+
57
+ logger = bec_logger.logger
58
+
59
+ MODULE_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
60
+
61
+ # TODO: improve log color handling
62
+ DEFAULT_LOG_COLORS = {
63
+ LogLevel.INFO: "#FFFFFF",
64
+ LogLevel.SUCCESS: "#00FF00",
65
+ LogLevel.WARNING: "#FFCC00",
66
+ LogLevel.ERROR: "#FF0000",
67
+ LogLevel.DEBUG: "#0000CC",
68
+ }
69
+
70
+
71
+ class BecLogsQueue:
72
+ """Manages getting logs from BEC Redis and formatting them for display"""
73
+
74
+ def __init__(
75
+ self,
76
+ conn: ConnectorBase,
77
+ new_message_signal: SignalInstance,
78
+ maxlen: int = 1000,
79
+ line_formatter: LineFormatter = noop_format,
80
+ ) -> None:
81
+ self._timestamp_start: QDateTime | None = None
82
+ self._timestamp_end: QDateTime | None = None
83
+ self._conn = conn
84
+ self._new_message_signal: SignalInstance | None = new_message_signal
85
+ self._max_length = maxlen
86
+ self._data: deque[LogMessage] = deque([], self._max_length)
87
+ self._display_queue: deque[str] = deque([], self._max_length)
88
+ self._log_level: str | None = None
89
+ self._search_query: Pattern | str | None = None
90
+ self._selected_services: set[str] | None = None
91
+ self._set_formatter_and_update_filter(line_formatter)
92
+ self._conn.register([MessageEndpoints.log()], None, self._process_incoming_log_msg)
93
+
94
+ def disconnect(self):
95
+ self._conn.unregister([MessageEndpoints.log()], None, self._process_incoming_log_msg)
96
+ self._new_message_signal.disconnect()
97
+
98
+ def _process_incoming_log_msg(self, msg: dict):
99
+ try:
100
+ _msg: LogMessage = msg["data"]
101
+ self._data.append(_msg)
102
+ if self.filter is None or self.filter(_msg):
103
+ self._display_queue.append(self._line_formatter(_msg))
104
+ if self._new_message_signal:
105
+ self._new_message_signal.emit()
106
+ except Exception:
107
+ logger.warning("Error in LogPanel incoming message callback!")
108
+
109
+ def _set_formatter_and_update_filter(self, line_formatter: LineFormatter = noop_format):
110
+ self._line_formatter: LineFormatter = line_formatter
111
+ self._queue_formatter: LinesHtmlFormatter = create_formatter(
112
+ self._line_formatter, self.filter
113
+ )
114
+
115
+ def _combine_filters(self, *args: LineFilter):
116
+ return lambda msg: reduce(operator.and_, [filt(msg) for filt in args if filt is not None])
117
+
118
+ def _create_re_filter(self) -> LineFilter:
119
+ if self._search_query is None:
120
+ return None
121
+ elif isinstance(self._search_query, str):
122
+ return lambda line: self._search_query in log_txt(line)
123
+ return lambda line: self._search_query.match(log_txt(line)) is not None
124
+
125
+ def _create_service_filter(self):
126
+ return (
127
+ lambda line: self._selected_services is None or log_svc(line) in self._selected_services
128
+ )
129
+
130
+ def _create_timestamp_filter(self) -> LineFilter:
131
+ s, e = self._timestamp_start, self._timestamp_end
132
+ if s is e is None:
133
+ return lambda msg: True
134
+
135
+ def _time_filter(msg):
136
+ msg_time = log_time(msg)
137
+ if s is None:
138
+ return msg_time <= e
139
+ if e is None:
140
+ return s <= msg_time
141
+ return s <= msg_time <= e
142
+
143
+ return _time_filter
144
+
145
+ @property
146
+ def filter(self) -> LineFilter:
147
+ thresh = LogLevel[self._log_level].value if self._log_level is not None else 0
148
+ return self._combine_filters(
149
+ partial(level_filter, thresh=thresh),
150
+ self._create_re_filter(),
151
+ self._create_timestamp_filter(),
152
+ self._create_service_filter(),
153
+ )
154
+
155
+ def update_level_filter(self, level: str):
156
+ if level not in [l.name for l in LogLevel]:
157
+ logger.error(f"Logging level {level} unrecognized for filter!")
158
+ return
159
+ self._log_level = level
160
+ self._set_formatter_and_update_filter(self._line_formatter)
161
+
162
+ def update_search_filter(self, search_query: Pattern | str | None = None):
163
+ self._search_query = search_query
164
+ self._set_formatter_and_update_filter(self._line_formatter)
165
+
166
+ def update_time_filter(self, start: QDateTime | None, end: QDateTime | None):
167
+ self._timestamp_start = start
168
+ self._timestamp_end = end
169
+ self._set_formatter_and_update_filter(self._line_formatter)
170
+
171
+ def update_service_filter(self, services: set[str]):
172
+ self._selected_services = services
173
+ self._set_formatter_and_update_filter(self._line_formatter)
174
+
175
+ def update_line_formatter(self, line_formatter: LineFormatter):
176
+ self._set_formatter_and_update_filter(line_formatter)
177
+
178
+ def display_all(self) -> str:
179
+ return "\n".join(self._queue_formatter(self._data.copy()))
180
+
181
+ def format_new(self):
182
+ res = "\n".join(self._display_queue)
183
+ self._display_queue = deque([], self._max_length)
184
+ return res
185
+
186
+ def clear_logs(self):
187
+ self._data = deque([])
188
+ self._display_queue = deque([])
189
+
190
+ def fetch_history(self):
191
+ self._data = deque(
192
+ item["data"]
193
+ for item in self._conn.xread(
194
+ MessageEndpoints.log().endpoint, from_start=True, count=self._max_length
195
+ )
196
+ )
197
+
198
+ def unique_service_names_from_history(self) -> set[str]:
199
+ return set(msg.log_msg["service_name"] for msg in self._data)
200
+
201
+
202
+ class LogPanelToolbar(QWidget):
203
+
204
+ services_selected: pyqtBoundSignal = Signal(set)
205
+
206
+ def __init__(self, parent: QWidget | None = None) -> None:
207
+ super().__init__(parent)
208
+
209
+ # in unix time
210
+ self._timestamp_start: QDateTime | None = None
211
+ self._timestamp_end: QDateTime | None = None
212
+
213
+ self._unique_service_names: set[str] = set()
214
+ self._services_selected: set[str] | None = None
215
+
216
+ self.layout = QHBoxLayout(self) # type: ignore
217
+
218
+ self.service_choice_button = QPushButton("Select services", self)
219
+ self.layout.addWidget(self.service_choice_button)
220
+ self.service_choice_button.clicked.connect(self._open_service_filter_dialog)
221
+
222
+ self.filter_level_dropdown = self._log_level_box()
223
+ self.layout.addWidget(self.filter_level_dropdown)
224
+
225
+ self.clear_button = QPushButton("Clear all", self)
226
+ self.layout.addWidget(self.clear_button)
227
+ self.fetch_button = QPushButton("Fetch history", self)
228
+ self.layout.addWidget(self.fetch_button)
229
+
230
+ self._string_search_box()
231
+
232
+ self.timerange_button = QPushButton("Set time range", self)
233
+ self.layout.addWidget(self.timerange_button)
234
+
235
+ @property
236
+ def time_start(self):
237
+ return self._timestamp_start
238
+
239
+ @property
240
+ def time_end(self):
241
+ return self._timestamp_end
242
+
243
+ def _string_search_box(self):
244
+ self.layout.addWidget(QLabel("Search: "))
245
+ self.search_textbox = QLineEdit()
246
+ self.layout.addWidget(self.search_textbox)
247
+ self.layout.addWidget(QLabel("Use regex: "))
248
+ self.regex_enabled = QCheckBox()
249
+ self.layout.addWidget(self.regex_enabled)
250
+ self.update_re_button = QPushButton("Update search", self)
251
+ self.layout.addWidget(self.update_re_button)
252
+
253
+ def _log_level_box(self):
254
+ box = QComboBox()
255
+ box.setToolTip("Display logs with equal or greater significance to the selected level.")
256
+ [box.addItem(l.name) for l in LogLevel]
257
+ return box
258
+
259
+ def _current_ts(self, selection_type: Literal["start", "end"]):
260
+ if selection_type == "start":
261
+ return self._timestamp_start
262
+ elif selection_type == "end":
263
+ return self._timestamp_end
264
+ else:
265
+ raise ValueError(f"timestamps can only be for the start or end, not {selection_type}")
266
+
267
+ def _open_datetime_dialog(self):
268
+ """Open dialog window for timestamp filter selection"""
269
+ self._dt_dialog = QDialog(self)
270
+ self._dt_dialog.setWindowTitle("Time range selection")
271
+ layout = QVBoxLayout()
272
+ self._dt_dialog.setLayout(layout)
273
+
274
+ label_start = QLabel(parent=self._dt_dialog)
275
+ label_end = QLabel(parent=self._dt_dialog)
276
+
277
+ def date_button_set(selection_type: Literal["start", "end"], label: QLabel):
278
+ dt = self._current_ts(selection_type)
279
+ _layout = QHBoxLayout()
280
+ layout.addLayout(_layout)
281
+ date_button = QPushButton(f"Time {selection_type}", parent=self._dt_dialog)
282
+ _layout.addWidget(date_button)
283
+ label.setText(dt.toString() if dt else "not selected")
284
+ _layout.addWidget(label)
285
+ date_button.clicked.connect(partial(self._open_cal_dialog, selection_type, label))
286
+ date_clear_button = QPushButton("clear", parent=self._dt_dialog)
287
+ date_clear_button.clicked.connect(
288
+ lambda: (
289
+ partial(self._update_time, selection_type)(None),
290
+ label.setText("not selected"),
291
+ )
292
+ )
293
+ _layout.addWidget(date_clear_button)
294
+
295
+ for v in [("start", label_start), ("end", label_end)]:
296
+ date_button_set(*v)
297
+
298
+ close_button = QPushButton("Close", parent=self._dt_dialog)
299
+ close_button.clicked.connect(self._dt_dialog.accept)
300
+ layout.addWidget(close_button)
301
+
302
+ self._dt_dialog.exec()
303
+ self._dt_dialog.deleteLater()
304
+
305
+ def _open_cal_dialog(self, selection_type: Literal["start", "end"], label: QLabel):
306
+ """Open dialog window for timestamp filter selection"""
307
+ dt = self._current_ts(selection_type) or QDateTime.currentDateTime()
308
+ label.setText(dt.toString() if dt else "not selected")
309
+ if selection_type == "start":
310
+ self._timestamp_start = dt
311
+ else:
312
+ self._timestamp_end = dt
313
+ self._cal_dialog = QDialog(self)
314
+ self._cal_dialog.setWindowTitle(f"Select time range {selection_type}")
315
+ layout = QVBoxLayout()
316
+ self._cal_dialog.setLayout(layout)
317
+ cal = QDateTimeEdit(parent=self._cal_dialog)
318
+ cal.setCalendarPopup(True)
319
+ cal.setDateTime(dt)
320
+ cal.setDisplayFormat("yyyy-MM-dd HH:mm:ss.zzz")
321
+ cal.dateTimeChanged.connect(partial(self._update_time, selection_type))
322
+ layout.addWidget(cal)
323
+ close_button = QPushButton("Close", parent=self._cal_dialog)
324
+ close_button.clicked.connect(self._cal_dialog.accept)
325
+ layout.addWidget(close_button)
326
+
327
+ self._cal_dialog.exec()
328
+ self._cal_dialog.deleteLater()
329
+
330
+ def _update_time(self, selection_type: Literal["start", "end"], dt: QDateTime | None):
331
+ if selection_type == "start":
332
+ self._timestamp_start = dt
333
+ else:
334
+ self._timestamp_end = dt
335
+
336
+ @SafeSlot(dict, set)
337
+ def service_list_update(
338
+ self, services_info: dict[str, StatusMessage], services_from_history: set[str], *_, **__
339
+ ):
340
+ self._unique_service_names = set([s.split("/")[0] for s in services_info.keys()])
341
+ self._unique_service_names |= services_from_history
342
+ if self._services_selected is None:
343
+ self._services_selected = self._unique_service_names
344
+
345
+ @SafeSlot()
346
+ def _open_service_filter_dialog(self):
347
+ if len(self._unique_service_names) == 0 or self._services_selected is None:
348
+ return
349
+ self._svc_dialog = QDialog(self)
350
+ self._svc_dialog.setWindowTitle(f"Select services to show logs from")
351
+ layout = QVBoxLayout()
352
+ self._svc_dialog.setLayout(layout)
353
+
354
+ service_cb_grid = QGridLayout(parent=self._svc_dialog)
355
+ layout.addLayout(service_cb_grid)
356
+
357
+ def check_box(name: str, checked: Qt.CheckState):
358
+ if checked == Qt.CheckState.Checked:
359
+ self._services_selected.add(name)
360
+ else:
361
+ if name in self._services_selected:
362
+ self._services_selected.remove(name)
363
+ self.services_selected.emit(self._services_selected)
364
+
365
+ for i, svc in enumerate(self._unique_service_names):
366
+ service_cb_grid.addWidget(QLabel(svc, parent=self._svc_dialog), i, 0)
367
+ cb = QCheckBox(parent=self._svc_dialog)
368
+ cb.setChecked(svc in self._services_selected)
369
+ cb.checkStateChanged.connect(partial(check_box, svc))
370
+ service_cb_grid.addWidget(cb, i, 1)
371
+
372
+ close_button = QPushButton("Close", parent=self._svc_dialog)
373
+ close_button.clicked.connect(self._svc_dialog.accept)
374
+ layout.addWidget(close_button)
375
+
376
+ self._svc_dialog.exec()
377
+ self._svc_dialog.deleteLater()
378
+
379
+
380
+ class LogPanel(TextBox):
381
+ """Displays a log panel"""
382
+
383
+ ICON_NAME = "terminal"
384
+ _new_messages = Signal()
385
+ service_list_update = Signal(dict, set)
386
+
387
+ def __init__(
388
+ self,
389
+ parent=None,
390
+ client: BECClient | None = None,
391
+ service_status: BECServiceStatusMixin | None = None,
392
+ **kwargs,
393
+ ):
394
+ """Initialize the LogPanel widget."""
395
+ super().__init__(parent=parent, client=client, **kwargs)
396
+ self._update_colors()
397
+ self._service_status = service_status or BECServiceStatusMixin(self, client=self.client) # type: ignore
398
+ self._log_manager = BecLogsQueue(
399
+ self.client.connector, # type: ignore
400
+ new_message_signal=self._new_messages,
401
+ line_formatter=partial(simple_color_format, colors=self._colors),
402
+ )
403
+
404
+ self.toolbar = LogPanelToolbar(parent=parent)
405
+ self.toolbar_area = QScrollArea()
406
+ self.toolbar_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
407
+ self.toolbar_area.setSizeAdjustPolicy(QScrollArea.SizeAdjustPolicy.AdjustToContents)
408
+ self.toolbar_area.setFixedHeight(int(self.toolbar.clear_button.height() * 2))
409
+ self.toolbar_area.setWidget(self.toolbar)
410
+
411
+ self.layout.addWidget(self.toolbar_area)
412
+ self.toolbar.clear_button.clicked.connect(self._on_clear)
413
+ self.toolbar.fetch_button.clicked.connect(self._on_fetch)
414
+ self.toolbar.update_re_button.clicked.connect(self._on_re_update)
415
+ self.toolbar.search_textbox.returnPressed.connect(self._on_re_update)
416
+ self.toolbar.regex_enabled.checkStateChanged.connect(self._on_re_update)
417
+ self.toolbar.filter_level_dropdown.currentTextChanged.connect(self._set_level_filter)
418
+ self._new_messages.connect(self._on_append)
419
+
420
+ self.toolbar.timerange_button.clicked.connect(self._choose_datetime)
421
+ self._service_status.services_update.connect(self._update_service_list)
422
+ self.service_list_update.connect(self.toolbar.service_list_update)
423
+ self.toolbar.services_selected.connect(self._update_service_filter)
424
+
425
+ self.text_box_text_edit.setFont(QFont("monospace", 12))
426
+ self.text_box_text_edit.setHtml("")
427
+ self.text_box_text_edit.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
428
+
429
+ self._connect_to_theme_change()
430
+
431
+ @SafeSlot(set)
432
+ def _update_service_filter(self, services: set[str]):
433
+ self._log_manager.update_service_filter(services)
434
+ self._on_redraw()
435
+
436
+ @SafeSlot(dict, dict)
437
+ def _update_service_list(self, services_info: dict[str, StatusMessage], *_, **__):
438
+ self.service_list_update.emit(
439
+ services_info, self._log_manager.unique_service_names_from_history()
440
+ )
441
+
442
+ @SafeSlot()
443
+ def _choose_datetime(self):
444
+ self.toolbar._open_datetime_dialog()
445
+ self._set_time_filter()
446
+
447
+ def _connect_to_theme_change(self):
448
+ """Connect to the theme change signal."""
449
+ qapp = QApplication.instance()
450
+ if hasattr(qapp, "theme_signal"):
451
+ qapp.theme_signal.theme_updated.connect(self._on_redraw) # type: ignore
452
+
453
+ def _update_colors(self):
454
+ self._colors = DEFAULT_LOG_COLORS.copy()
455
+ self._colors.update({LogLevel.INFO: get_theme_palette().text().color().name()})
456
+
457
+ def _cursor_to_end(self):
458
+ c = self.text_box_text_edit.textCursor()
459
+ c.movePosition(c.MoveOperation.End)
460
+ self.text_box_text_edit.setTextCursor(c)
461
+
462
+ @SafeSlot()
463
+ @SafeSlot(str)
464
+ def _on_redraw(self, *_):
465
+ self._update_colors()
466
+ self._log_manager.update_line_formatter(partial(simple_color_format, colors=self._colors))
467
+ self.set_html_text(self._log_manager.display_all())
468
+ self._cursor_to_end()
469
+
470
+ @SafeSlot()
471
+ def _on_append(self):
472
+ self._cursor_to_end()
473
+ self.text_box_text_edit.insertHtml(self._log_manager.format_new())
474
+
475
+ @SafeSlot()
476
+ def _on_clear(self):
477
+ self._log_manager.clear_logs()
478
+ self.set_html_text(self._log_manager.display_all())
479
+ self._cursor_to_end()
480
+
481
+ @SafeSlot()
482
+ @SafeSlot(Qt.CheckState)
483
+ def _on_re_update(self, *_):
484
+ if self.toolbar.regex_enabled.isChecked():
485
+ try:
486
+ search_query = re.compile(self.toolbar.search_textbox.text())
487
+ except Exception as e:
488
+ logger.warning(f"Failed to compile search regex with error {e}")
489
+ search_query = None
490
+ logger.info(f"Setting LogPanel search regex to {search_query}")
491
+ else:
492
+ search_query = self.toolbar.search_textbox.text()
493
+ logger.info(f'Setting LogPanel search string to "{search_query}"')
494
+ self._log_manager.update_search_filter(search_query)
495
+ self.set_html_text(self._log_manager.display_all())
496
+ self._cursor_to_end()
497
+
498
+ @SafeSlot()
499
+ def _on_fetch(self):
500
+ self._log_manager.fetch_history()
501
+ self.set_html_text(self._log_manager.display_all())
502
+ self._cursor_to_end()
503
+
504
+ @SafeSlot(str)
505
+ def _set_level_filter(self, level: str):
506
+ self._log_manager.update_level_filter(level)
507
+ self._on_redraw()
508
+
509
+ @SafeSlot()
510
+ def _set_time_filter(self):
511
+ self._log_manager.update_time_filter(self.toolbar.time_start, self.toolbar.time_end)
512
+ self._on_redraw()
513
+
514
+ def cleanup(self):
515
+ self._service_status.cleanup()
516
+ self._log_manager.disconnect()
517
+
518
+
519
+ if __name__ == "__main__": # pragma: no cover
520
+ import sys
521
+
522
+ from qtpy.QtWidgets import QApplication # pylint: disable=ungrouped-imports
523
+
524
+ app = QApplication(sys.argv)
525
+ set_theme("dark")
526
+ widget = LogPanel()
527
+
528
+ widget.show()
529
+ sys.exit(app.exec())
@@ -0,0 +1,15 @@
1
+ def main(): # pragma: no cover
2
+ from qtpy import PYSIDE6
3
+
4
+ if not PYSIDE6:
5
+ print("PYSIDE6 is not available in the environment. Cannot patch designer.")
6
+ return
7
+ from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
8
+
9
+ from bec_widgets.widgets.utility.logpanel.log_panel_plugin import LogPanelPlugin
10
+
11
+ QPyDesignerCustomWidgetCollection.addCustomWidget(LogPanelPlugin())
12
+
13
+
14
+ if __name__ == "__main__": # pragma: no cover
15
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 1.19.2
3
+ Version: 1.21.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
@@ -16,6 +16,7 @@ Requires-Dist: black~=24.0
16
16
  Requires-Dist: isort>=5.13.2,~=5.13
17
17
  Requires-Dist: pydantic~=2.0
18
18
  Requires-Dist: pyqtgraph~=0.13
19
+ Requires-Dist: pyside6==6.7.2
19
20
  Requires-Dist: pyte
20
21
  Requires-Dist: qtconsole>=5.5.1,~=5.5
21
22
  Requires-Dist: qtpy~=2.4
@@ -28,5 +29,3 @@ Requires-Dist: pytest-random-order~=1.1; extra == 'dev'
28
29
  Requires-Dist: pytest-timeout~=2.2; extra == 'dev'
29
30
  Requires-Dist: pytest-xvfb~=3.0; extra == 'dev'
30
31
  Requires-Dist: pytest~=8.0; extra == 'dev'
31
- Provides-Extra: pyside6
32
- Requires-Dist: pyside6==6.7.2; extra == 'pyside6'
@@ -2,11 +2,11 @@
2
2
  .gitlab-ci.yml,sha256=PuL-FmkTHm7qs467Mh9D8quWcEj4tgEA-UUGDieMuWk,8774
3
3
  .pylintrc,sha256=eeY8YwSI74oFfq6IYIbCqnx3Vk8ZncKaatv96n_Y8Rs,18544
4
4
  .readthedocs.yaml,sha256=aSOc277LqXcsTI6lgvm_JY80lMlr69GbPKgivua2cS0,603
5
- CHANGELOG.md,sha256=NsBLyDicCXyFNi9gn0sPyHlNRbfY-gO0tZ6vntUTJso,227295
5
+ CHANGELOG.md,sha256=nwcFNPwNl0Hm-pvcpA5AGUtZ6d5S2HTS7XGYS8W0jmA,227882
6
6
  LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
7
- PKG-INFO,sha256=RIhLy9KOwKoFLGH-riiB869HO1UuJUezWNnk5gXtiQ4,1219
7
+ PKG-INFO,sha256=UCBSBJegtFWSYbJEwxDRB3c18aL2rojyKCuVFJV5zeg,1175
8
8
  README.md,sha256=KgdKusjlvEvFtdNZCeDMO91y77MWK2iDcYMDziksOr4,2553
9
- pyproject.toml,sha256=WCC4BXIEXRJOQEjR8th1Qm0yiDxK6-bphMGEEF5wHWI,2549
9
+ pyproject.toml,sha256=lQdjFQkuXXHccxf9Xqtjt0kPEUCJ6tyFrCGPMuwQaMY,2542
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
@@ -24,7 +24,7 @@ bec_widgets/assets/app_icons/alignment_1d.png,sha256=5VouaWieb4lVv3wUBNHaO5ovUW2
24
24
  bec_widgets/assets/app_icons/bec_widgets_icon.png,sha256=K8dgGwIjalDh9PRHUsSQBqgdX7a00nM3igZdc20pkYM,1747017
25
25
  bec_widgets/cli/__init__.py,sha256=d0Q6Fn44e7wFfLabDOBxpcJ1DPKWlFunGYDUBmO-4hA,22
26
26
  bec_widgets/cli/auto_updates.py,sha256=Pj8OHlSlKN3JOAmuuBC5oUMzdfC8TYRY7QKT5BQ2cZo,5171
27
- bec_widgets/cli/client.py,sha256=QI27Eb6ehePBpFVhNzEFRfxxEOKTMFoCN9LNexsi8-g,99850
27
+ bec_widgets/cli/client.py,sha256=k1dSiZy4CBHb9pgfpV4ygTNaDUeqtWUbigKMxk9gtsY,100272
28
28
  bec_widgets/cli/client_utils.py,sha256=r5z1kTEJ7Ceu0MnSpbpZTg4Kq_2e5ys9PXBf7sbvIm0,12529
29
29
  bec_widgets/cli/generate_cli.py,sha256=lT-eEXEPpzk9cPrfFVf0U5gxQg7HF2oulxDH16T-SBk,6849
30
30
  bec_widgets/cli/server.py,sha256=Hzhhzhc9PQhTtjpNeu8Jmbmahad0DbyieutaRp7Vejc,10485
@@ -87,11 +87,11 @@ bec_widgets/utils/widget_state_manager.py,sha256=tzrxVmnGa6IHSEdeh-R68aQ934BsuS9
87
87
  bec_widgets/utils/yaml_dialog.py,sha256=T6UyGNGdmpXW74fa_7Nk6b99T5pp2Wvyw3AOauRc8T8,2407
88
88
  bec_widgets/utils/plugin_templates/plugin.template,sha256=DWtJdHpdsVtbiTTOniH3zBe5a40ztQ20o_-Hclyu38s,1266
89
89
  bec_widgets/utils/plugin_templates/register.template,sha256=XyL3OZPT_FTArLAM8tHd5qMqv2ZuAbJAZLsNNnHcagU,417
90
- bec_widgets/widgets/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
90
+ bec_widgets/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
91
  bec_widgets/widgets/containers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
92
  bec_widgets/widgets/containers/dock/__init__.py,sha256=B7foHt02gnhM7mFksa7GJVwT7n0j_JvYDCt6wc6XR5g,61
93
93
  bec_widgets/widgets/containers/dock/dock.py,sha256=_cw5bbqCLZxaD5LmosHNmm_eXelMDoTyxFdTnSWkoOI,10379
94
- bec_widgets/widgets/containers/dock/dock_area.py,sha256=9325BpsIR_0Del2O__iCIKjxsa_rJsnTzlkG8m21W1o,17943
94
+ bec_widgets/widgets/containers/dock/dock_area.py,sha256=5OZHapVkxdjWU9RwDxwscgvUJbA-khoIRg21vTQdavU,18354
95
95
  bec_widgets/widgets/containers/dock/dock_area.pyproject,sha256=URW0UrDXCnkzk80rbQmUMgF6Uqay2TjHsq8Dq0g1j-c,37
96
96
  bec_widgets/widgets/containers/dock/dock_area_plugin.py,sha256=fDVXKPZuHr85B2fLfAYf_Ic5d9mZQpnZrODTDquzZpM,1331
97
97
  bec_widgets/widgets/containers/dock/register_dock_area.py,sha256=L7BL4qknCjtqsDP-RMQzk2qRPpcYuzXWlb7sJB_0DDM,475
@@ -220,9 +220,14 @@ bec_widgets/widgets/editors/console/console_plugin.py,sha256=EvFTruYDVHiS4pHIwZn
220
220
  bec_widgets/widgets/editors/console/register_console.py,sha256=zoF-i3R9sRGzb85sdoxVunebYOfOD53fkCELTPtrFRc,471
221
221
  bec_widgets/widgets/editors/jupyter_console/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
222
222
  bec_widgets/widgets/editors/jupyter_console/jupyter_console.py,sha256=-e7HQOECeH5eDrJYh4BFIzRL78LDkooU4otabyN0aX4,2343
223
+ bec_widgets/widgets/editors/scan_metadata/__init__.py,sha256=IhDv6xQ7tpSbdaAQDOenrb0IU3wfSoElV9k6ajIM1IY,315
224
+ bec_widgets/widgets/editors/scan_metadata/_metadata_widgets.py,sha256=2WZf0Ej_R3ZQJ9QLvxCrVAoOgNPT5unh9OuhyYiUVyY,9294
225
+ bec_widgets/widgets/editors/scan_metadata/_util.py,sha256=8qn2clcJqi9nvPSvZzO9ornHxn_PGw4Z_O_1XpSmq8E,1861
226
+ bec_widgets/widgets/editors/scan_metadata/additional_metadata_table.py,sha256=XysmHU8B6WqLOLjQQBRG3sbN6_KaralvrIlrylkJ56E,5105
227
+ bec_widgets/widgets/editors/scan_metadata/scan_metadata.py,sha256=fo9CvYFUWbuypYUcdzTXlUJ1rIIIVCIFPX1ejbJGL8Q,7263
223
228
  bec_widgets/widgets/editors/text_box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
224
229
  bec_widgets/widgets/editors/text_box/register_text_box.py,sha256=xRgVugvjLhX3iKb-vaAxflE6pWpal7pVFWDaUSUZLyE,467
225
- bec_widgets/widgets/editors/text_box/text_box.py,sha256=7Pb6GMeAoh8caIEd2FRDsQyJtXxHlxZ473UuIPLYFJk,4265
230
+ bec_widgets/widgets/editors/text_box/text_box.py,sha256=F_BdWKPwEjltgfAsPmGHipOs5sPtI4o0Y0EO68s3Og0,4311
226
231
  bec_widgets/widgets/editors/text_box/text_box.pyproject,sha256=XohO1BIe2hrpU-z_KHKRgjcUkXru7jeFte31j2TPbNk,26
227
232
  bec_widgets/widgets/editors/text_box/text_box_plugin.py,sha256=0sgOZ_2Z0tpLGHYJyRlDGcPHWR79vzj38sQ6V9lnpjc,1310
228
233
  bec_widgets/widgets/editors/vscode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -313,6 +318,12 @@ bec_widgets/widgets/services/device_browser/register_device_browser.py,sha256=Or
313
318
  bec_widgets/widgets/services/device_browser/device_item/__init__.py,sha256=VGY-uNVCnpcY-q-gijteB2N8KxFNgYR-qQ209MVu1QI,36
314
319
  bec_widgets/widgets/services/device_browser/device_item/device_item.py,sha256=u3CTgXJBqwNP3uxrIWYGacEuoOu1a_DxfXYZ3Q4sK_M,1901
315
320
  bec_widgets/widgets/utility/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
321
+ bec_widgets/widgets/utility/logpanel/__init__.py,sha256=HldSvPLYgrqBjCgIQj0f7Wa4slkSMksk4bsRJOQi__Y,91
322
+ bec_widgets/widgets/utility/logpanel/_util.py,sha256=4vkN2KDJI8CHgwo_R8QjN76Lqg8zKoWAa_pWyFgLP94,1837
323
+ bec_widgets/widgets/utility/logpanel/log_panel.pyproject,sha256=2ncs1bsu-wICstR1gOYwFFdr0UuZmrBQEpwhvNKVFMY,26
324
+ bec_widgets/widgets/utility/logpanel/log_panel_plugin.py,sha256=KY7eS1uGZzLYtDAdBv6S2mw8UjcDGVt3UklN_D5M06A,1250
325
+ bec_widgets/widgets/utility/logpanel/logpanel.py,sha256=jMQKn5O7qUFkN-2YnQ3HY7vSf8LIH5ox-T1E_lL3zfQ,19675
326
+ bec_widgets/widgets/utility/logpanel/register_log_panel.py,sha256=LFUE5JzCYvIwJQtTqZASLVAHYy3gO1nrHzPVH_kpCEY,470
316
327
  bec_widgets/widgets/utility/spinner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
317
328
  bec_widgets/widgets/utility/spinner/register_spinner_widget.py,sha256=96A13dEcyTgXfc9G0sTdlXYCDcVav8Z2P2eDC95bESQ,484
318
329
  bec_widgets/widgets/utility/spinner/spinner.py,sha256=6c0fN7mdGzELg4mf_yG08ubses3svb6w0EqMeHDFkIw,2651
@@ -344,8 +355,8 @@ bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py,sha256=Z
344
355
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.pyproject,sha256=Lbi9zb6HNlIq14k6hlzR-oz6PIFShBuF7QxE6d87d64,34
345
356
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button_plugin.py,sha256=CzChz2SSETYsR8-36meqWnsXCT-FIy_J_xeU5coWDY8,1350
346
357
  bec_widgets/widgets/utility/visual/dark_mode_button/register_dark_mode_button.py,sha256=rMpZ1CaoucwobgPj1FuKTnt07W82bV1GaSYdoqcdMb8,521
347
- bec_widgets-1.19.2.dist-info/METADATA,sha256=RIhLy9KOwKoFLGH-riiB869HO1UuJUezWNnk5gXtiQ4,1219
348
- bec_widgets-1.19.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
349
- bec_widgets-1.19.2.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
350
- bec_widgets-1.19.2.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
351
- bec_widgets-1.19.2.dist-info/RECORD,,
358
+ bec_widgets-1.21.0.dist-info/METADATA,sha256=UCBSBJegtFWSYbJEwxDRB3c18aL2rojyKCuVFJV5zeg,1175
359
+ bec_widgets-1.21.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
360
+ bec_widgets-1.21.0.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
361
+ bec_widgets-1.21.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
362
+ bec_widgets-1.21.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 = "1.19.2"
7
+ version = "1.21.0"
8
8
  description = "BEC Widgets"
9
9
  requires-python = ">=3.10"
10
10
  classifiers = [
@@ -15,14 +15,15 @@ classifiers = [
15
15
  dependencies = [
16
16
  "bec_ipython_client>=2.21.4, <=4.0", # needed for jupyter console
17
17
  "bec_lib>=2.21.4, <=4.0",
18
+ "bec_qthemes~=0.7, >=0.7",
18
19
  "black~=24.0", # needed for bw-generate-cli
19
20
  "isort~=5.13, >=5.13.2", # needed for bw-generate-cli
20
21
  "pydantic~=2.0",
21
22
  "pyqtgraph~=0.13",
22
- "bec_qthemes~=0.7, >=0.7",
23
+ "PySide6==6.7.2",
24
+ "pyte", # needed for vt100 console
23
25
  "qtconsole~=5.5, >=5.5.1", # needed for jupyter console
24
26
  "qtpy~=2.4",
25
- "pyte", # needed for vt100 console
26
27
  ]
27
28
 
28
29
 
@@ -37,7 +38,6 @@ dev = [
37
38
  "pytest-xvfb~=3.0",
38
39
  "pytest~=8.0",
39
40
  ]
40
- pyside6 = ["PySide6==6.7.2"]
41
41
 
42
42
  [project.urls]
43
43
  "Bug Tracker" = "https://gitlab.psi.ch/bec/bec_widgets/issues"