feat: add the ability for signal callbacks to ignore arguments

This commit is contained in:
The Darsh committed 2025-07-04 23:56:27 +03:00
1 parent 49c00af999
commit 4c39360d8e
3 files changed
+152 -6

No files matched your search

+20 -5
View File
@@ -27,6 +27,7 @@ from fabric.utils.helpers import (
snake_case_to_kebab_case,
kebab_case_to_snake_case,
get_function_annotations,
make_arguments_ignorable,
)
OldSignal = gi._signalhelper.Signal
@@ -252,9 +253,18 @@ class SignalWrapper(Generic[G, P, R]):
# TODO: make it hint self's instance for the callback
def connect(
self, callback: Callable[Concatenate[GObject.Object, P], Any], *args, **kwargs
self,
callback: Callable[Concatenate[G, P], Any] | Callable,
*args,
ignore_missing: bool = True,
) -> int:
return self.instance.connect(self.name, callback) # type: ignore
return Service.connect(
self.instance, # type: ignore
self.name,
callback,
*args,
ignore_missing=ignore_missing,
)
class Signal(Generic[G, P, R]):
@@ -372,6 +382,7 @@ class Signal(Generic[G, P, R]):
# all aboard...
setattr(klass, "__gsignals__", klass_signals)
return
@dataclass
@@ -503,11 +514,15 @@ class Service(GObject.Object, Generic[P, T]):
def connect(
self,
signal_name: str,
callback: Callable[Concatenate[Self, P], Any],
callback: Callable[Concatenate[Self, P], Any] | Callable,
*args,
**kwargs,
ignore_missing: bool = True,
) -> int:
return super().connect(signal_name, callback, *args, **kwargs) # type: ignore
return super().connect(
signal_name,
make_arguments_ignorable(callback) if ignore_missing else callback,
*args,
) # type: ignore
@staticmethod
def filter_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]:
+22 -1
View File
@@ -589,7 +589,9 @@ def monitor_file(
def cooldown(
cooldown_time: int | float , error: Callable | None = None, return_error: bool = False
cooldown_time: int | float,
error: Callable | None = None,
return_error: bool = False,
):
"""
Decorator function that adds a cooldown period to a given function
@@ -856,6 +858,25 @@ def get_function_annotations(
return FunctionAnnotations(args, return_type)
def make_arguments_ignorable(func: Callable[..., T]) -> Callable[..., T]:
params = inspect.signature(func).parameters.values()
if any(p.kind == inspect.Parameter.VAR_POSITIONAL for p in params):
return func # no
args_len = sum(
1
for p in params
if p.kind
in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD)
)
@wraps(func)
def wrapper(*passed_args):
return func(*passed_args[:args_len])
return wrapper
def truncate(string: str, max_length: int, suffix: str = "...") -> str:
return (
string
+110
View File
@@ -196,6 +196,116 @@ class TestServiceSignals(unittest.TestCase):
self.assertEqual(emittedSignals, expectedOutput)
emittedSignals = []
def testServiceSignalsConnectAndEmit5(self):
# ignorable arguments
emittedSignals: list[str] = []
expectedOutput = [
"int-signal",
"str-signal",
"bool-signal",
"boxed-signal",
"complex-signal",
]
def onSignal(signalName):
emittedSignals.append(signalName)
service = self.ServiceWithSignals(
on_int_signal=lambda: onSignal("int-signal"),
on_str_signal=lambda: onSignal("str-signal"),
on_bool_signal=lambda: onSignal("bool-signal"),
on_boxed_signal=lambda: onSignal("boxed-signal"),
on_complex_signal=lambda: onSignal("complex-signal"),
)
service.emit("int-signal", 42)
service.emit("str-signal", "42")
service.emit("bool-signal", True)
service.emit("boxed-signal", object)
service.emit("complex-signal", 42, "42", True, object)
self.assertEqual(emittedSignals, expectedOutput)
emittedSignals = []
def testServiceSignalsConnectAndEmit6(self):
# ignorable arguments
emittedSignals: list[str] = []
expectedOutput = [
"int-signal",
"str-signal",
"bool-signal",
"boxed-signal",
"complex-signal",
]
def onSignal(signalName):
emittedSignals.append(signalName)
service = self.ServiceWithSignals(
on_int_signal=lambda s: self.assertEqual(s, service)
or onSignal("int-signal"),
on_str_signal=lambda s: self.assertEqual(s, service)
or onSignal("str-signal"),
on_bool_signal=lambda s: self.assertEqual(s, service)
or onSignal("bool-signal"),
on_boxed_signal=lambda s: self.assertEqual(s, service)
or onSignal("boxed-signal"),
on_complex_signal=lambda s: self.assertEqual(s, service)
or onSignal("complex-signal"),
)
service.emit("int-signal", 42)
service.emit("str-signal", "42")
service.emit("bool-signal", True)
service.emit("boxed-signal", object)
service.emit("complex-signal", 42, "42", True, object)
self.assertEqual(emittedSignals, expectedOutput)
emittedSignals = []
def testServiceSignalsConnectAndEmit7(self):
# ignorable arguments
emittedSignals: list[str] = []
expectedOutput = [
"int-signal",
"str-signal",
"bool-signal",
"boxed-signal",
"complex-signal",
]
def onSignal(signalName):
emittedSignals.append(signalName)
service = self.ServiceWithSignals(
on_int_signal=lambda s, i: self.assertEqual(i, 42)
or self.assertEqual(s, service)
or onSignal("int-signal"),
on_str_signal=lambda s, i: self.assertEqual(i, "42")
or self.assertEqual(s, service)
or onSignal("str-signal"),
on_bool_signal=lambda s, i: self.assertEqual(i, True)
or self.assertEqual(s, service)
or onSignal("bool-signal"),
on_boxed_signal=lambda s, i: self.assertEqual(i, object)
or self.assertEqual(s, service)
or onSignal("boxed-signal"),
on_complex_signal=lambda s, i1, i2, i3, i4: self.assertEqual(
(i1, i2, i3, i4), (42, "42", True, object)
)
or self.assertEqual(s, service)
or onSignal("complex-signal"),
)
service.emit("int-signal", 42)
service.emit("str-signal", "42")
service.emit("bool-signal", True)
service.emit("boxed-signal", object)
service.emit("complex-signal", 42, "42", True, object)
self.assertEqual(emittedSignals, expectedOutput)
emittedSignals = []
if __name__ == "__main__":
unittest.main()