1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-12-30 02:31:20 +01:00

feat: add SafeConnect

This commit is contained in:
2025-08-29 15:39:58 +02:00
committed by wyzula-jan
parent 34ed0daa98
commit a823dd243e
2 changed files with 52 additions and 5 deletions

View File

@@ -11,7 +11,7 @@ from qtpy.QtWidgets import QApplication, QFileDialog, QWidget
from bec_widgets.cli.rpc.rpc_register import RPCRegister
from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.error_popups import SafeConnect, SafeSlot
from bec_widgets.utils.rpc_decorator import rpc_timeout
from bec_widgets.utils.widget_io import WidgetHierarchy
@@ -61,7 +61,6 @@ class BECWidget(BECConnector):
)
if not isinstance(self, QObject):
raise RuntimeError(f"{repr(self)} is not a subclass of QWidget")
if theme_update:
logger.debug(f"Subscribing to theme updates for {self.__class__.__name__}")
self._connect_to_theme_change()
@@ -70,10 +69,10 @@ class BECWidget(BECConnector):
"""Connect to the theme change signal."""
qapp = QApplication.instance()
if hasattr(qapp, "theme"):
qapp.theme.theme_changed.connect(self._update_theme)
SafeConnect(self, qapp.theme.theme_changed, self._update_theme)
@SafeSlot(str, verify_sender=True)
@SafeSlot(verify_sender=True)
@SafeSlot(str)
@SafeSlot()
def _update_theme(self, theme: str | None = None):
"""Update the theme."""
if theme is None:

View File

@@ -2,7 +2,9 @@ import functools
import sys
import traceback
import shiboken6
from bec_lib.logger import bec_logger
from louie.saferef import safe_ref
from qtpy.QtCore import Property, QObject, Qt, Signal, Slot
from qtpy.QtWidgets import QApplication, QMessageBox, QPushButton, QVBoxLayout, QWidget
@@ -90,6 +92,52 @@ def SafeProperty(prop_type, *prop_args, popup_error: bool = False, default=None,
return decorator
def _safe_connect_slot(weak_instance, weak_slot, *connect_args):
"""Internal function used by SafeConnect to handle weak references to slots."""
instance = weak_instance()
slot_func = weak_slot()
# Check if the python object has already been garbage collected
if instance is None or slot_func is None:
return
# Check if the python object has already been marked for deletion
if getattr(instance, "_destroyed", False):
return
# Check if the C++ object is still valid
if not shiboken6.isValid(instance):
return
if connect_args:
slot_func(*connect_args)
slot_func()
def SafeConnect(instance, signal, slot): # pylint: disable=invalid-name
"""
Method to safely handle Qt signal-slot connections. The python object is only forwarded
as a weak reference to avoid stale objects.
Args:
instance: The instance to connect.
signal: The signal to connect to.
slot: The slot to connect.
Example:
>>> SafeConnect(self, qapp.theme.theme_changed, self._update_theme)
"""
weak_instance = safe_ref(instance)
weak_slot = safe_ref(slot)
# Create a partial function that will check weak references before calling the actual slot
safe_slot = functools.partial(_safe_connect_slot, weak_instance, weak_slot)
# Connect the signal to the safe connect slot wrapper
return signal.connect(safe_slot)
def SafeSlot(*slot_args, **slot_kwargs): # pylint: disable=invalid-name
"""Function with args, acting like a decorator, applying "error_managed" decorator + Qt Slot
to the passed function, to display errors instead of potentially raising an exception