bec-widgets 2.9.1__py3-none-any.whl → 2.10.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.
@@ -47,4 +47,12 @@ jobs:
47
47
  source ./bin/install_bec_dev.sh -t
48
48
  cd ../
49
49
  pip install -e ./ophyd_devices -e .[dev,pyside6] -e ./bec_testing_plugin
50
- pytest -v --files-path ./ --start-servers --random-order ./tests/end-2-end
50
+ pytest -v --files-path ./ --start-servers --random-order ./tests/end-2-end
51
+
52
+ - name: Upload logs if job fails
53
+ if: failure()
54
+ uses: actions/upload-artifact@v4
55
+ with:
56
+ name: pytest-logs
57
+ path: ./logs/*.log
58
+ retention-days: 7
CHANGELOG.md CHANGED
@@ -1,6 +1,41 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v2.10.0 (2025-06-02)
5
+
6
+ ### Bug Fixes
7
+
8
+ - **waveform**: Waveform only update async data when scan is currently running
9
+ ([`f90150d`](https://github.com/bec-project/bec_widgets/commit/f90150d1c708331d4ee78f82ebf5ef23cd81fd17))
10
+
11
+ ### Continuous Integration
12
+
13
+ - Add job logs to e2e test
14
+ ([`d12bd9f`](https://github.com/bec-project/bec_widgets/commit/d12bd9fe1a010babc94dc86405d1b75a2b07534c))
15
+
16
+ - Fix artifact version
17
+ ([`2b4454a`](https://github.com/bec-project/bec_widgets/commit/2b4454a291bc69399ddd08780c44e1339825fb36))
18
+
19
+ ### Features
20
+
21
+ - **waveform**: Large async dataset warning popup
22
+ ([`d0c1ac0`](https://github.com/bec-project/bec_widgets/commit/d0c1ac0cf5d421d14c9e050ccf5832cd30ca0764))
23
+
24
+
25
+ ## v2.9.2 (2025-05-30)
26
+
27
+ ### Bug Fixes
28
+
29
+ - Logpanel error cycle
30
+ ([`d9dc60e`](https://github.com/bec-project/bec_widgets/commit/d9dc60ee9974e2e6e6005378cc17ef088a4ded2c))
31
+
32
+ - Move log panel to bec connector and add rate limiter
33
+ ([`7322cd1`](https://github.com/bec-project/bec_widgets/commit/7322cd194fcf7f56d41c86ecbcd97a5d8bd60c3e))
34
+
35
+ - **log_panel**: Removed lambda callback method
36
+ ([`9112616`](https://github.com/bec-project/bec_widgets/commit/91126168b62f3e1623521ceb205dd854287cfef7))
37
+
38
+
4
39
  ## v2.9.1 (2025-05-30)
5
40
 
6
41
  ### Bug Fixes
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 2.9.1
3
+ Version: 2.10.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
@@ -3970,6 +3970,48 @@ class Waveform(RPCBase):
3970
3970
  The color palette of the figure widget.
3971
3971
  """
3972
3972
 
3973
+ @property
3974
+ @rpc_call
3975
+ def skip_large_dataset_warning(self) -> "bool":
3976
+ """
3977
+ Whether to skip the large dataset warning when fetching async data.
3978
+ """
3979
+
3980
+ @skip_large_dataset_warning.setter
3981
+ @rpc_call
3982
+ def skip_large_dataset_warning(self) -> "bool":
3983
+ """
3984
+ Whether to skip the large dataset warning when fetching async data.
3985
+ """
3986
+
3987
+ @property
3988
+ @rpc_call
3989
+ def skip_large_dataset_check(self) -> "bool":
3990
+ """
3991
+ Whether to skip the large dataset warning when fetching async data.
3992
+ """
3993
+
3994
+ @skip_large_dataset_check.setter
3995
+ @rpc_call
3996
+ def skip_large_dataset_check(self) -> "bool":
3997
+ """
3998
+ Whether to skip the large dataset warning when fetching async data.
3999
+ """
4000
+
4001
+ @property
4002
+ @rpc_call
4003
+ def max_dataset_size_mb(self) -> "float":
4004
+ """
4005
+ The maximum dataset size (in MB) permitted when fetching async data from history before prompting the user.
4006
+ """
4007
+
4008
+ @max_dataset_size_mb.setter
4009
+ @rpc_call
4010
+ def max_dataset_size_mb(self) -> "float":
4011
+ """
4012
+ The maximum dataset size (in MB) permitted when fetching async data from history before prompting the user.
4013
+ """
4014
+
3973
4015
  @rpc_call
3974
4016
  def plot(
3975
4017
  self,
@@ -9,8 +9,19 @@ import pyqtgraph as pg
9
9
  from bec_lib import bec_logger, messages
10
10
  from bec_lib.endpoints import MessageEndpoints
11
11
  from pydantic import Field, ValidationError, field_validator
12
- from qtpy.QtCore import QTimer, Signal
13
- from qtpy.QtWidgets import QApplication, QDialog, QHBoxLayout, QMainWindow, QVBoxLayout, QWidget
12
+ from qtpy.QtCore import Qt, QTimer, Signal
13
+ from qtpy.QtWidgets import (
14
+ QApplication,
15
+ QCheckBox,
16
+ QDialog,
17
+ QDialogButtonBox,
18
+ QDoubleSpinBox,
19
+ QHBoxLayout,
20
+ QLabel,
21
+ QMainWindow,
22
+ QVBoxLayout,
23
+ QWidget,
24
+ )
14
25
 
15
26
  from bec_widgets.utils import ConnectionConfig
16
27
  from bec_widgets.utils.bec_signal_proxy import BECSignalProxy
@@ -33,6 +44,11 @@ class WaveformConfig(ConnectionConfig):
33
44
  color_palette: str | None = Field(
34
45
  "plasma", description="The color palette of the figure widget.", validate_default=True
35
46
  )
47
+ max_dataset_size_mb: float = Field(
48
+ 10,
49
+ description="Maximum dataset size (in MB) permitted when fetching async data from history before prompting the user.",
50
+ validate_default=True,
51
+ )
36
52
 
37
53
  model_config: dict = {"validate_assignment": True}
38
54
  _validate_color_palette = field_validator("color_palette")(Colors.validate_color_map)
@@ -96,6 +112,12 @@ class Waveform(PlotBase):
96
112
  "x_entry.setter",
97
113
  "color_palette",
98
114
  "color_palette.setter",
115
+ "skip_large_dataset_warning",
116
+ "skip_large_dataset_warning.setter",
117
+ "skip_large_dataset_check",
118
+ "skip_large_dataset_check.setter",
119
+ "max_dataset_size_mb",
120
+ "max_dataset_size_mb.setter",
99
121
  "plot",
100
122
  "add_dap_curve",
101
123
  "remove_curve",
@@ -144,6 +166,7 @@ class Waveform(PlotBase):
144
166
  self._mode: Literal["none", "sync", "async", "mixed"] = "none"
145
167
 
146
168
  # Scan data
169
+ self._scan_done = True # means scan is not running
147
170
  self.old_scan_id = None
148
171
  self.scan_id = None
149
172
  self.scan_item = None
@@ -163,6 +186,10 @@ class Waveform(PlotBase):
163
186
  self._init_curve_dialog()
164
187
  self.curve_settings_dialog = None
165
188
 
189
+ # Large‑dataset guard
190
+ self._skip_large_dataset_warning = False # session flag
191
+ self._skip_large_dataset_check = False # per-plot flag, to skip the warning for this plot
192
+
166
193
  # Scan status update loop
167
194
  self.bec_dispatcher.connect_slot(self.on_scan_status, MessageEndpoints.scan_status())
168
195
  self.bec_dispatcher.connect_slot(self.on_scan_progress, MessageEndpoints.scan_progress())
@@ -561,6 +588,59 @@ class Waveform(PlotBase):
561
588
  """
562
589
  return [item for item in self.plot_item.curves if isinstance(item, Curve)]
563
590
 
591
+ @SafeProperty(bool)
592
+ def skip_large_dataset_check(self) -> bool:
593
+ """
594
+ Whether to skip the large dataset warning when fetching async data.
595
+ """
596
+ return self._skip_large_dataset_check
597
+
598
+ @skip_large_dataset_check.setter
599
+ def skip_large_dataset_check(self, value: bool):
600
+ """
601
+ Set whether to skip the large dataset warning when fetching async data.
602
+
603
+ Args:
604
+ value(bool): Whether to skip the large dataset warning.
605
+ """
606
+ self._skip_large_dataset_check = value
607
+
608
+ @SafeProperty(bool)
609
+ def skip_large_dataset_warning(self) -> bool:
610
+ """
611
+ Whether to skip the large dataset warning when fetching async data.
612
+ """
613
+ return self._skip_large_dataset_warning
614
+
615
+ @skip_large_dataset_warning.setter
616
+ def skip_large_dataset_warning(self, value: bool):
617
+ """
618
+ Set whether to skip the large dataset warning when fetching async data.
619
+
620
+ Args:
621
+ value(bool): Whether to skip the large dataset warning.
622
+ """
623
+ self._skip_large_dataset_warning = value
624
+
625
+ @SafeProperty(float)
626
+ def max_dataset_size_mb(self) -> float:
627
+ """
628
+ The maximum dataset size (in MB) permitted when fetching async data from history before prompting the user.
629
+ """
630
+ return self.config.max_dataset_size_mb
631
+
632
+ @max_dataset_size_mb.setter
633
+ def max_dataset_size_mb(self, value: float):
634
+ """
635
+ Set the maximum dataset size (in MB) permitted when fetching async data from history before prompting the user.
636
+
637
+ Args:
638
+ value(float): The maximum dataset size in MB.
639
+ """
640
+ if value <= 0:
641
+ raise ValueError("Maximum dataset size must be greater than 0.")
642
+ self.config.max_dataset_size_mb = value
643
+
564
644
  ################################################################################
565
645
  # High Level methods for API
566
646
  ################################################################################
@@ -807,8 +887,6 @@ class Waveform(PlotBase):
807
887
  if config.source == "device":
808
888
  if self.scan_item is None:
809
889
  self.update_with_scan_history(-1)
810
- if curve in self._async_curves:
811
- self._setup_async_curve(curve)
812
890
  self.async_signal_update.emit()
813
891
  self.sync_signal_update.emit()
814
892
  if config.source == "dap":
@@ -1056,8 +1134,8 @@ class Waveform(PlotBase):
1056
1134
  meta(dict): The message metadata.
1057
1135
  """
1058
1136
  self.sync_signal_update.emit()
1059
- status = msg.get("done")
1060
- if status:
1137
+ self._scan_done = msg.get("done")
1138
+ if self._scan_done:
1061
1139
  QTimer.singleShot(100, self.update_sync_curves)
1062
1140
  QTimer.singleShot(300, self.update_sync_curves)
1063
1141
 
@@ -1135,9 +1213,11 @@ class Waveform(PlotBase):
1135
1213
  if access_key == "val": # live access
1136
1214
  device_data = data.get(device_name, {}).get(device_entry, {}).get(access_key, None)
1137
1215
  else: # history access
1138
- device_data = (
1139
- data.get(device_name, {}).get(device_entry, {}).read().get("value", None)
1140
- )
1216
+ dataset_obj = data.get(device_name, {})
1217
+ if self._skip_large_dataset_check is False:
1218
+ if not self._check_dataset_size_and_confirm(dataset_obj, device_entry):
1219
+ continue # user declined to load; skip this curve
1220
+ device_data = dataset_obj.get(device_entry, {}).read().get("value", None)
1141
1221
 
1142
1222
  # if shape is 2D cast it into 1D and take the last waveform
1143
1223
  if len(np.shape(device_data)) > 1:
@@ -1581,6 +1661,8 @@ class Waveform(PlotBase):
1581
1661
  dev_name = curve.config.signal.name
1582
1662
  if dev_name in readout_priority_async:
1583
1663
  self._async_curves.append(curve)
1664
+ if hasattr(self.scan_item, "live_data"):
1665
+ self._setup_async_curve(curve)
1584
1666
  found_async = True
1585
1667
  elif dev_name in readout_priority_sync:
1586
1668
  self._sync_curves.append(curve)
@@ -1657,6 +1739,106 @@ class Waveform(PlotBase):
1657
1739
  ################################################################################
1658
1740
  # Utility Methods
1659
1741
  ################################################################################
1742
+
1743
+ # Large dataset handling helpers
1744
+ def _check_dataset_size_and_confirm(self, dataset_obj, device_entry: str) -> bool:
1745
+ """
1746
+ Check the size of the dataset and confirm with the user if it exceeds the limit.
1747
+
1748
+ Args:
1749
+ dataset_obj: The dataset object containing the information.
1750
+ device_entry( str): The specific device entry to check.
1751
+
1752
+ Returns:
1753
+ bool: True if the dataset is within the size limit or user confirmed to load it,
1754
+ False if the dataset exceeds the size limit and user declined to load it.
1755
+ """
1756
+ try:
1757
+ info = dataset_obj._info
1758
+ mem_bytes = info.get(device_entry, {}).get("value", {}).get("mem_size", 0)
1759
+ # Fallback – grab first entry if lookup failed
1760
+ if mem_bytes == 0 and info:
1761
+ first_key = next(iter(info))
1762
+ mem_bytes = info[first_key]["value"]["mem_size"]
1763
+ size_mb = mem_bytes / (1024 * 1024)
1764
+ print(f"Dataset size: {size_mb:.1f} MB")
1765
+ except Exception as exc: # noqa: BLE001
1766
+ logger.error(f"Unable to evaluate dataset size: {exc}")
1767
+ return True
1768
+
1769
+ if size_mb <= self.config.max_dataset_size_mb:
1770
+ return True
1771
+ logger.warning(
1772
+ f"Attempt to load large dataset: {size_mb:.1f} MB "
1773
+ f"(limit {self.config.max_dataset_size_mb} MB)"
1774
+ )
1775
+ if self._skip_large_dataset_warning:
1776
+ logger.info("Skipping large dataset warning dialog.")
1777
+ return False
1778
+ return self._confirm_large_dataset(size_mb)
1779
+
1780
+ def _confirm_large_dataset(self, size_mb: float) -> bool:
1781
+ """
1782
+ Confirm with the user whether to load a large dataset with dialog popup.
1783
+ Also allows the user to adjust the maximum dataset size limit and if user
1784
+ wants to see this popup again during session.
1785
+
1786
+ Args:
1787
+ size_mb(float): Size of the dataset in MB.
1788
+
1789
+ Returns:
1790
+ bool: True if the user confirmed to load the dataset, False otherwise.
1791
+ """
1792
+ if self._skip_large_dataset_warning:
1793
+ return True
1794
+
1795
+ dialog = QDialog(self)
1796
+ dialog.setWindowTitle("Large dataset detected")
1797
+ main_dialog_layout = QVBoxLayout(dialog)
1798
+
1799
+ # Limit adjustment widgets
1800
+ limit_adjustment_layout = QHBoxLayout()
1801
+ limit_adjustment_layout.addWidget(QLabel("New limit (MB):"))
1802
+ spin = QDoubleSpinBox()
1803
+ spin.setRange(0.001, 4096)
1804
+ spin.setDecimals(3)
1805
+ spin.setSingleStep(0.01)
1806
+ spin.setValue(self.config.max_dataset_size_mb)
1807
+ spin.valueChanged.connect(lambda value: setattr(self.config, "max_dataset_size_mb", value))
1808
+ limit_adjustment_layout.addWidget(spin)
1809
+
1810
+ # Don't show again checkbox
1811
+ checkbox = QCheckBox("Don't show this again for this session")
1812
+
1813
+ buttons = QDialogButtonBox(
1814
+ QDialogButtonBox.Yes | QDialogButtonBox.No, Qt.Horizontal, dialog
1815
+ )
1816
+ buttons.accepted.connect(dialog.accept) # Yes
1817
+ buttons.rejected.connect(dialog.reject) # No
1818
+
1819
+ # widget layout
1820
+ main_dialog_layout.addWidget(
1821
+ QLabel(
1822
+ f"The selected dataset is {size_mb:.1f} MB which exceeds the "
1823
+ f"current limit of {self.config.max_dataset_size_mb} MB.\n"
1824
+ )
1825
+ )
1826
+ main_dialog_layout.addLayout(limit_adjustment_layout)
1827
+ main_dialog_layout.addWidget(checkbox)
1828
+ main_dialog_layout.addWidget(QLabel("Would you like to display dataset anyway?"))
1829
+ main_dialog_layout.addWidget(buttons)
1830
+
1831
+ result = dialog.exec() # modal; waits for user choice
1832
+
1833
+ # Respect the “don't show again” checkbox for *either* choice
1834
+ if checkbox.isChecked():
1835
+ self._skip_large_dataset_warning = True
1836
+
1837
+ if result == QDialog.Accepted:
1838
+ self.config.max_dataset_size_mb = spin.value()
1839
+ return True
1840
+ return False
1841
+
1660
1842
  def _ensure_str_list(self, entries: list | tuple | np.ndarray):
1661
1843
  """
1662
1844
  Convert a variety of possible inputs (string, bytes, list/tuple/ndarray of either)
@@ -1787,7 +1969,7 @@ class DemoApp(QMainWindow): # pragma: no cover
1787
1969
  self.setCentralWidget(self.main_widget)
1788
1970
 
1789
1971
  self.waveform_popup = Waveform(popups=True)
1790
- self.waveform_popup.plot(y_name="monitor_async")
1972
+ self.waveform_popup.plot(y_name="waveform")
1791
1973
 
1792
1974
  self.waveform_side = Waveform(popups=False)
1793
1975
  self.waveform_side.plot(y_name="bpm4i", y_entry="bpm4i", dap="GaussianModel")
@@ -11,12 +11,11 @@ from re import Pattern
11
11
  from typing import TYPE_CHECKING, Literal
12
12
 
13
13
  from bec_lib.client import BECClient
14
- from bec_lib.connector import ConnectorBase
15
14
  from bec_lib.endpoints import MessageEndpoints
16
15
  from bec_lib.logger import LogLevel, bec_logger
17
16
  from bec_lib.messages import LogMessage, StatusMessage
18
- from PySide6.QtCore import QObject
19
- from qtpy.QtCore import QDateTime, Qt, Signal
17
+ from pyqtgraph import SignalProxy
18
+ from qtpy.QtCore import QDateTime, QObject, Qt, Signal
20
19
  from qtpy.QtGui import QFont
21
20
  from qtpy.QtWidgets import (
22
21
  QApplication,
@@ -35,6 +34,7 @@ from qtpy.QtWidgets import (
35
34
  QWidget,
36
35
  )
37
36
 
37
+ from bec_widgets.utils.bec_connector import BECConnector
38
38
  from bec_widgets.utils.colors import get_theme_palette, set_theme
39
39
  from bec_widgets.utils.error_popups import SafeSlot
40
40
  from bec_widgets.widgets.editors.text_box.text_box import TextBox
@@ -69,22 +69,22 @@ DEFAULT_LOG_COLORS = {
69
69
  }
70
70
 
71
71
 
72
- class BecLogsQueue(QObject):
72
+ class BecLogsQueue(BECConnector, QObject):
73
73
  """Manages getting logs from BEC Redis and formatting them for display"""
74
74
 
75
+ RPC = False
75
76
  new_message = Signal()
76
77
 
77
78
  def __init__(
78
79
  self,
79
80
  parent: QObject | None,
80
- conn: ConnectorBase,
81
81
  maxlen: int = 1000,
82
82
  line_formatter: LineFormatter = noop_format,
83
+ **kwargs,
83
84
  ) -> None:
84
- super().__init__(parent=parent)
85
+ super().__init__(parent=parent, **kwargs)
85
86
  self._timestamp_start: QDateTime | None = None
86
87
  self._timestamp_end: QDateTime | None = None
87
- self._conn = conn
88
88
  self._max_length = maxlen
89
89
  self._data: deque[LogMessage] = deque([], self._max_length)
90
90
  self._display_queue: deque[str] = deque([], self._max_length)
@@ -92,20 +92,26 @@ class BecLogsQueue(QObject):
92
92
  self._search_query: Pattern | str | None = None
93
93
  self._selected_services: set[str] | None = None
94
94
  self._set_formatter_and_update_filter(line_formatter)
95
- self._conn.register([MessageEndpoints.log()], None, self._process_incoming_log_msg)
95
+ # instance attribute still accessible after c++ object is deleted, so the callback can be unregistered
96
+ self.bec_dispatcher.connect_slot(self._process_incoming_log_msg, MessageEndpoints.log())
96
97
 
97
- def unsub_from_redis(self):
98
+ def cleanup(self, *_):
98
99
  """Stop listening to the Redis log stream"""
99
- self._conn.unregister([MessageEndpoints.log()], None, self._process_incoming_log_msg)
100
+ self.bec_dispatcher.disconnect_slot(
101
+ self._process_incoming_log_msg, [MessageEndpoints.log()]
102
+ )
100
103
 
101
- def _process_incoming_log_msg(self, msg: dict):
104
+ @SafeSlot(verify_sender=True)
105
+ def _process_incoming_log_msg(self, msg: dict, _metadata: dict):
102
106
  try:
103
- _msg: LogMessage = msg["data"]
107
+ _msg = LogMessage(**msg)
104
108
  self._data.append(_msg)
105
109
  if self.filter is None or self.filter(_msg):
106
110
  self._display_queue.append(self._line_formatter(_msg))
107
111
  self.new_message.emit()
108
112
  except Exception as e:
113
+ if "Internal C++ object (BecLogsQueue) already deleted." in e.args:
114
+ return
109
115
  logger.warning(f"Error in LogPanel incoming message callback: {e}")
110
116
 
111
117
  def _set_formatter_and_update_filter(self, line_formatter: LineFormatter = noop_format):
@@ -202,7 +208,7 @@ class BecLogsQueue(QObject):
202
208
  """Fetch all available messages from Redis"""
203
209
  self._data = deque(
204
210
  item["data"]
205
- for item in self._conn.xread(
211
+ for item in self.bec_dispatcher.client.connector.xread(
206
212
  MessageEndpoints.log().endpoint, from_start=True, count=self._max_length
207
213
  )
208
214
  )
@@ -396,7 +402,6 @@ class LogPanel(TextBox):
396
402
  """Displays a log panel"""
397
403
 
398
404
  ICON_NAME = "terminal"
399
- _new_messages = Signal()
400
405
  service_list_update = Signal(dict, set)
401
406
 
402
407
  def __init__(
@@ -407,17 +412,17 @@ class LogPanel(TextBox):
407
412
  **kwargs,
408
413
  ):
409
414
  """Initialize the LogPanel widget."""
410
- super().__init__(parent=parent, client=client, **kwargs)
415
+ super().__init__(parent=parent, client=client, config={"text": ""}, **kwargs)
411
416
  self._update_colors()
412
417
  self._service_status = service_status or BECServiceStatusMixin(self, client=self.client) # type: ignore
413
418
  self._log_manager = BecLogsQueue(
414
- parent,
415
- self.client.connector, # type: ignore
416
- line_formatter=partial(simple_color_format, colors=self._colors),
419
+ parent=self, line_formatter=partial(simple_color_format, colors=self._colors)
420
+ )
421
+ self._proxy_update = SignalProxy(
422
+ self._log_manager.new_message, rateLimit=1, slot=self._on_append
417
423
  )
418
- self._log_manager.new_message.connect(self._new_messages)
419
424
 
420
- self.toolbar = LogPanelToolbar(parent=parent)
425
+ self.toolbar = LogPanelToolbar(parent=self)
421
426
  self.toolbar_area = QScrollArea()
422
427
  self.toolbar_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
423
428
  self.toolbar_area.setSizeAdjustPolicy(QScrollArea.SizeAdjustPolicy.AdjustToContents)
@@ -431,7 +436,6 @@ class LogPanel(TextBox):
431
436
  self.toolbar.search_textbox.returnPressed.connect(self._on_re_update)
432
437
  self.toolbar.regex_enabled.checkStateChanged.connect(self._on_re_update)
433
438
  self.toolbar.filter_level_dropdown.currentTextChanged.connect(self._set_level_filter)
434
- self._new_messages.connect(self._on_append)
435
439
 
436
440
  self.toolbar.timerange_button.clicked.connect(self._choose_datetime)
437
441
  self._service_status.services_update.connect(self._update_service_list)
@@ -483,10 +487,10 @@ class LogPanel(TextBox):
483
487
  self.set_html_text(self._log_manager.display_all())
484
488
  self._cursor_to_end()
485
489
 
486
- @SafeSlot()
487
- def _on_append(self):
488
- self._cursor_to_end()
490
+ @SafeSlot(verify_sender=True)
491
+ def _on_append(self, *_):
489
492
  self.text_box_text_edit.insertHtml(self._log_manager.format_new())
493
+ self._cursor_to_end()
490
494
 
491
495
  @SafeSlot()
492
496
  def _on_clear(self):
@@ -529,9 +533,8 @@ class LogPanel(TextBox):
529
533
 
530
534
  def cleanup(self):
531
535
  self._service_status.cleanup()
532
- self._log_manager.unsub_from_redis()
533
- self._log_manager.new_message.disconnect(self._new_messages)
534
- self._new_messages.disconnect(self._on_append)
536
+ self._log_manager.cleanup()
537
+ self._log_manager.deleteLater()
535
538
  super().cleanup()
536
539
 
537
540
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 2.9.1
3
+ Version: 2.10.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=1nMYldzVk0tFkBWYTcUjumOrdSADASheWOAc0kOFDYs,9509
3
3
  .pylintrc,sha256=eeY8YwSI74oFfq6IYIbCqnx3Vk8ZncKaatv96n_Y8Rs,18544
4
4
  .readthedocs.yaml,sha256=ivqg3HTaOxNbEW3bzWh9MXAkrekuGoNdj0Mj3SdRYuw,639
5
- CHANGELOG.md,sha256=M__eaV2qFZ_rzZPL5yHSuCg6ymh4veC-MLWH7I20Qyc,290970
5
+ CHANGELOG.md,sha256=ZCoCGrGicUMSpl9R4w6AlvmPh0TeualolLm0VyLRFX0,292161
6
6
  LICENSE,sha256=Daeiu871NcAp8uYi4eB_qHgvypG-HX0ioRQyQxFwjeg,1531
7
- PKG-INFO,sha256=JmSUIffA_-E4bhT7es7Y81PRNsG76VEYuRCqWw_DbcY,1273
7
+ PKG-INFO,sha256=Smer6N4138nsW9L0c5DAvG64Wioj9ku14Joj42MfAMs,1274
8
8
  README.md,sha256=oY5Jc1uXehRASuwUJ0umin2vfkFh7tHF-LLruHTaQx0,3560
9
- pyproject.toml,sha256=xInUTzdYljzVQdPaETaRqGblNXIaAZuFvCK3yhJsLog,2902
9
+ pyproject.toml,sha256=9G1m-vohVa87nvTCSVSYhLCV-Vm8XlkWFmZXseyMXlo,2903
10
10
  .git_hooks/pre-commit,sha256=n3RofIZHJl8zfJJIUomcMyYGFi_rwq4CC19z0snz3FI,286
11
11
  .github/pull_request_template.md,sha256=F_cJXzooWMFgMGtLK-7KeGcQt0B4AYFse5oN0zQ9p6g,801
12
12
  .github/ISSUE_TEMPLATE/bug_report.yml,sha256=WdRnt7HGxvsIBLzhkaOWNfg8IJQYa_oV9_F08Ym6znQ,1081
@@ -17,7 +17,7 @@ pyproject.toml,sha256=xInUTzdYljzVQdPaETaRqGblNXIaAZuFvCK3yhJsLog,2902
17
17
  .github/scripts/pr_issue_sync/requirements.txt,sha256=bFVVn4_gJjJ-BCykMhfQZLPaG2cthrKwM_skcG9wJkU,17
18
18
  .github/workflows/check_pr.yml,sha256=jKMtYBJTNRAdw_MAS4JzdKVSoNrv8nxUQMqobsYFAXw,903
19
19
  .github/workflows/ci.yml,sha256=OVZt0UxN4wQInuMucuQcKsvHDiz27sLeuQRskxjtkY0,1863
20
- .github/workflows/end2end-conda.yml,sha256=S9EyUoc084J0sUBg2_jgDAbvPZ9jrzEHOLZS91JF9Xc,2076
20
+ .github/workflows/end2end-conda.yml,sha256=yTH-CS8xxQ7kMo4BDpWwOeef1xmsV6gyrgqnFPHTo30,2278
21
21
  .github/workflows/formatter.yml,sha256=CdUVrRxWyVes9y-Ez3jiCJXqANPJtl6kfpGQxeWMhcQ,1694
22
22
  .github/workflows/generate-cli-check.yml,sha256=b6TcK8F5Hy0sFjgXpk0w3BO9eMDZw9WysTl3P7zEPuQ,1742
23
23
  .github/workflows/pytest-matrix.yml,sha256=0gL5wNPJKJF1JapqstlYNYiJ44ko05uaTD7epa7smVw,1834
@@ -35,7 +35,7 @@ bec_widgets/assets/app_icons/bec_widgets_icon.png,sha256=K8dgGwIjalDh9PRHUsSQBqg
35
35
  bec_widgets/assets/app_icons/ui_loader_tile.png,sha256=qSK3XHqvnAVGV9Q0ulORcGFbXJ9LDq2uz8l9uTtMsNk,1812476
36
36
  bec_widgets/assets/app_icons/widget_launch_tile.png,sha256=bWsICHFfSe9-ESUj3AwlE95dDOea-f6M-s9fBapsxB4,2252911
37
37
  bec_widgets/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- bec_widgets/cli/client.py,sha256=Pl93U3IyN5OisaEDCrS2ytY5CppRfuyIiCppXNMf8yg,96703
38
+ bec_widgets/cli/client.py,sha256=6fDvgkNBt2Veli4pLARYP-vUT6_FiKKjeqgQLQyK9ik,97919
39
39
  bec_widgets/cli/client_utils.py,sha256=F2hyt--jL53bN8NoWifNUMqwwx5FbpS6I1apERdTRzM,18114
40
40
  bec_widgets/cli/generate_cli.py,sha256=K_wMxo2XBUn92SnY3dSrlyUn8ax6Y20QBGCuP284DsQ,10986
41
41
  bec_widgets/cli/server.py,sha256=h7QyBOOGjyrP_fxJIIOSEMc4E06cLG0JyaofjNV6oCA,5671
@@ -315,7 +315,7 @@ bec_widgets/widgets/plots/toolbar_bundles/save_state.py,sha256=H3fu-bRzNIycCUFb2
315
315
  bec_widgets/widgets/plots/waveform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
316
316
  bec_widgets/widgets/plots/waveform/curve.py,sha256=KlcGbd60lPO9BYcca08fMhWkfx5qu4O9IbSNTwvajGM,10401
317
317
  bec_widgets/widgets/plots/waveform/register_waveform.py,sha256=pdcLCYKkLkSb-5DqbJdC6M3JyoXQBRVAKf7BZVCto80,467
318
- bec_widgets/widgets/plots/waveform/waveform.py,sha256=g4aAaeRgvOrwMW6Ju1cx-OKc0lXqN21Xo0rxbAcGBgk,68637
318
+ bec_widgets/widgets/plots/waveform/waveform.py,sha256=_EmQTWiPJmUzDo8PmvLd2gshzE-IECtswfaY7nCTZb4,75499
319
319
  bec_widgets/widgets/plots/waveform/waveform.pyproject,sha256=X2T6d4JGt9YSI28e-myjXh1YkUM4Yr3kNb0-F84KvUA,26
320
320
  bec_widgets/widgets/plots/waveform/waveform_plugin.py,sha256=2AZPtBHs75l9cdhwQnY3jpIMRPUUqK3RNvQbTvrFyvg,1237
321
321
  bec_widgets/widgets/plots/waveform/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -361,7 +361,7 @@ bec_widgets/widgets/utility/logpanel/__init__.py,sha256=HldSvPLYgrqBjCgIQj0f7Wa4
361
361
  bec_widgets/widgets/utility/logpanel/_util.py,sha256=GqzHbdOTmWBru9OR4weeYdziWj_cWxqSJhS4_6W3Qjg,1836
362
362
  bec_widgets/widgets/utility/logpanel/log_panel.pyproject,sha256=2ncs1bsu-wICstR1gOYwFFdr0UuZmrBQEpwhvNKVFMY,26
363
363
  bec_widgets/widgets/utility/logpanel/log_panel_plugin.py,sha256=KY7eS1uGZzLYtDAdBv6S2mw8UjcDGVt3UklN_D5M06A,1250
364
- bec_widgets/widgets/utility/logpanel/logpanel.py,sha256=tnjczAwtfe1biL-u9h9tntoQerWo3iLVD9RTSLOvd5o,20651
364
+ bec_widgets/widgets/utility/logpanel/logpanel.py,sha256=5c59r1Z368mqIZhS_0075P4gg2G1sK5NvPFMK5B1DuQ,20861
365
365
  bec_widgets/widgets/utility/logpanel/register_log_panel.py,sha256=LFUE5JzCYvIwJQtTqZASLVAHYy3gO1nrHzPVH_kpCEY,470
366
366
  bec_widgets/widgets/utility/signal_label/register_signal_label.py,sha256=wDB4Q3dSbZ51hsxnuB74oXdMRoLgDRd-XfhaomYY2OA,483
367
367
  bec_widgets/widgets/utility/signal_label/signal_label.py,sha256=bht1zpHKxrslfFCknnLe3Q9FeF8Do0j6onWAiLXZan0,15875
@@ -408,8 +408,8 @@ bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py,sha256=O
408
408
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.pyproject,sha256=Lbi9zb6HNlIq14k6hlzR-oz6PIFShBuF7QxE6d87d64,34
409
409
  bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button_plugin.py,sha256=CzChz2SSETYsR8-36meqWnsXCT-FIy_J_xeU5coWDY8,1350
410
410
  bec_widgets/widgets/utility/visual/dark_mode_button/register_dark_mode_button.py,sha256=rMpZ1CaoucwobgPj1FuKTnt07W82bV1GaSYdoqcdMb8,521
411
- bec_widgets-2.9.1.dist-info/METADATA,sha256=JmSUIffA_-E4bhT7es7Y81PRNsG76VEYuRCqWw_DbcY,1273
412
- bec_widgets-2.9.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
413
- bec_widgets-2.9.1.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
414
- bec_widgets-2.9.1.dist-info/licenses/LICENSE,sha256=Daeiu871NcAp8uYi4eB_qHgvypG-HX0ioRQyQxFwjeg,1531
415
- bec_widgets-2.9.1.dist-info/RECORD,,
411
+ bec_widgets-2.10.0.dist-info/METADATA,sha256=Smer6N4138nsW9L0c5DAvG64Wioj9ku14Joj42MfAMs,1274
412
+ bec_widgets-2.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
413
+ bec_widgets-2.10.0.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
414
+ bec_widgets-2.10.0.dist-info/licenses/LICENSE,sha256=Daeiu871NcAp8uYi4eB_qHgvypG-HX0ioRQyQxFwjeg,1531
415
+ bec_widgets-2.10.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 = "2.9.1"
7
+ version = "2.10.0"
8
8
  description = "BEC Widgets"
9
9
  requires-python = ">=3.10"
10
10
  classifiers = [