mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 03:31:50 +02:00
feat(slot): add 'verify_sender' argument to SafeSlot for sender verification
This commit is contained in:
@ -96,15 +96,33 @@ def SafeSlot(*slot_args, **slot_kwargs): # pylint: disable=invalid-name
|
||||
|
||||
'popup_error' keyword argument can be passed with boolean value if a dialog should pop up,
|
||||
otherwise error display is left to the original exception hook
|
||||
'verify_sender' keyword argument can be passed with boolean value if the sender should be verified
|
||||
before executing the slot. If True, the slot will only execute if the sender is a QObject. This is
|
||||
useful to prevent function calls from already deleted objects.
|
||||
"""
|
||||
popup_error = bool(slot_kwargs.pop("popup_error", False))
|
||||
verify_sender = bool(slot_kwargs.pop("verify_sender", False))
|
||||
|
||||
def error_managed(method):
|
||||
@Slot(*slot_args, **slot_kwargs)
|
||||
@functools.wraps(method)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
if not verify_sender or len(args) == 0:
|
||||
return method(*args, **kwargs)
|
||||
|
||||
_instance = args[0]
|
||||
if not isinstance(_instance, QObject):
|
||||
return method(*args, **kwargs)
|
||||
sender = _instance.sender()
|
||||
if sender is None:
|
||||
logger.info(
|
||||
f"Sender is None for {method.__module__}.{method.__qualname__}, "
|
||||
"skipping method call."
|
||||
)
|
||||
return
|
||||
return method(*args, **kwargs)
|
||||
|
||||
except Exception:
|
||||
slot_name = f"{method.__module__}.{method.__qualname__}"
|
||||
error_msg = traceback.format_exc()
|
||||
|
@ -1,13 +1,11 @@
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import pytestqt
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import QObject
|
||||
from qtpy.QtCore import QObject, Signal
|
||||
from qtpy.QtWidgets import QMessageBox
|
||||
|
||||
from bec_widgets.utils.error_popups import ErrorPopupUtility, ExampleWidget, SafeProperty
|
||||
from bec_widgets.utils.error_popups import ErrorPopupUtility, ExampleWidget, SafeProperty, SafeSlot
|
||||
|
||||
|
||||
class TestSafePropertyClass(QObject):
|
||||
@ -30,6 +28,32 @@ class TestSafePropertyClass(QObject):
|
||||
self._my_value = val
|
||||
|
||||
|
||||
class TestSafeSlotEmitter(QObject):
|
||||
test_signal = Signal()
|
||||
|
||||
|
||||
class TestSafeSlotClass(QObject):
|
||||
"""
|
||||
Test class to demonstrate the use of SafeSlot decorator.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None, signal_obj: TestSafeSlotEmitter | None = None):
|
||||
super().__init__(parent)
|
||||
assert signal_obj is not None, "Signal object must be provided"
|
||||
signal_obj.test_signal.connect(self.method_without_sender_verification)
|
||||
signal_obj.test_signal.connect(self.method_with_sender_verification)
|
||||
self._method_without_verification_called = False
|
||||
self._method_with_verification_called = False
|
||||
|
||||
@SafeSlot()
|
||||
def method_without_sender_verification(self):
|
||||
self._method_without_verification_called = True
|
||||
|
||||
@SafeSlot(verify_sender=True)
|
||||
def method_with_sender_verification(self):
|
||||
self._method_with_verification_called = True
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def widget(qtbot):
|
||||
test_widget = ExampleWidget()
|
||||
@ -147,3 +171,28 @@ def test_safe_property_setter_error(mock_exec, mock_log_error, qtbot, global_pop
|
||||
logged_msg = mock_log_error.call_args[0][0]
|
||||
assert "SafeProperty error in SETTER" in logged_msg
|
||||
assert "ValueError" in logged_msg
|
||||
|
||||
|
||||
@pytest.mark.timeout(100)
|
||||
def test_safe_slot_emit(qtbot):
|
||||
"""
|
||||
Test that the signal is emitted correctly.
|
||||
"""
|
||||
signal_obj = TestSafeSlotEmitter()
|
||||
test_obj = TestSafeSlotClass(signal_obj=signal_obj)
|
||||
signal_obj.test_signal.emit()
|
||||
|
||||
qtbot.waitUntil(lambda: test_obj._method_without_verification_called, timeout=1000)
|
||||
qtbot.waitUntil(lambda: test_obj._method_with_verification_called, timeout=1000)
|
||||
|
||||
test_obj.deleteLater()
|
||||
|
||||
test_obj = TestSafeSlotClass(signal_obj=signal_obj)
|
||||
test_obj.method_without_sender_verification()
|
||||
test_obj.method_with_sender_verification()
|
||||
|
||||
assert test_obj._method_without_verification_called is True
|
||||
assert test_obj._method_with_verification_called is False
|
||||
|
||||
test_obj.deleteLater()
|
||||
signal_obj.deleteLater()
|
||||
|
Reference in New Issue
Block a user