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,
|
'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
|
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))
|
popup_error = bool(slot_kwargs.pop("popup_error", False))
|
||||||
|
verify_sender = bool(slot_kwargs.pop("verify_sender", False))
|
||||||
|
|
||||||
def error_managed(method):
|
def error_managed(method):
|
||||||
@Slot(*slot_args, **slot_kwargs)
|
@Slot(*slot_args, **slot_kwargs)
|
||||||
@functools.wraps(method)
|
@functools.wraps(method)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
try:
|
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)
|
return method(*args, **kwargs)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
slot_name = f"{method.__module__}.{method.__qualname__}"
|
slot_name = f"{method.__module__}.{method.__qualname__}"
|
||||||
error_msg = traceback.format_exc()
|
error_msg = traceback.format_exc()
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import sys
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytestqt
|
|
||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
from qtpy.QtCore import QObject
|
from qtpy.QtCore import QObject, Signal
|
||||||
from qtpy.QtWidgets import QMessageBox
|
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):
|
class TestSafePropertyClass(QObject):
|
||||||
@ -30,6 +28,32 @@ class TestSafePropertyClass(QObject):
|
|||||||
self._my_value = val
|
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
|
@pytest.fixture
|
||||||
def widget(qtbot):
|
def widget(qtbot):
|
||||||
test_widget = ExampleWidget()
|
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]
|
logged_msg = mock_log_error.call_args[0][0]
|
||||||
assert "SafeProperty error in SETTER" in logged_msg
|
assert "SafeProperty error in SETTER" in logged_msg
|
||||||
assert "ValueError" 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