Skip to content

Commit 4c39360

Browse files
committed
feat: add the ability for signal callbacks to ignore arguments
1 parent 49c00af commit 4c39360

File tree

3 files changed

+152
-6
lines changed

3 files changed

+152
-6
lines changed

fabric/core/service.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
snake_case_to_kebab_case,
2828
kebab_case_to_snake_case,
2929
get_function_annotations,
30+
make_arguments_ignorable,
3031
)
3132

3233
OldSignal = gi._signalhelper.Signal
@@ -252,9 +253,18 @@ def emit(self, /, *args: P.args, **kwargs: P.kwargs) -> Optional[R]:
252253

253254
# TODO: make it hint self's instance for the callback
254255
def connect(
255-
self, callback: Callable[Concatenate[GObject.Object, P], Any], *args, **kwargs
256+
self,
257+
callback: Callable[Concatenate[G, P], Any] | Callable,
258+
*args,
259+
ignore_missing: bool = True,
256260
) -> int:
257-
return self.instance.connect(self.name, callback) # type: ignore
261+
return Service.connect(
262+
self.instance, # type: ignore
263+
self.name,
264+
callback,
265+
*args,
266+
ignore_missing=ignore_missing,
267+
)
258268

259269

260270
class Signal(Generic[G, P, R]):
@@ -372,6 +382,7 @@ def installer(klass):
372382

373383
# all aboard...
374384
setattr(klass, "__gsignals__", klass_signals)
385+
return
375386

376387

377388
@dataclass
@@ -503,11 +514,15 @@ def emit(self, signal_name: str, *args, **kwargs) -> None:
503514
def connect(
504515
self,
505516
signal_name: str,
506-
callback: Callable[Concatenate[Self, P], Any],
517+
callback: Callable[Concatenate[Self, P], Any] | Callable,
507518
*args,
508-
**kwargs,
519+
ignore_missing: bool = True,
509520
) -> int:
510-
return super().connect(signal_name, callback, *args, **kwargs) # type: ignore
521+
return super().connect(
522+
signal_name,
523+
make_arguments_ignorable(callback) if ignore_missing else callback,
524+
*args,
525+
) # type: ignore
511526

512527
@staticmethod
513528
def filter_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]:

fabric/utils/helpers.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,9 @@ def monitor_file(
589589

590590

591591
def cooldown(
592-
cooldown_time: int | float , error: Callable | None = None, return_error: bool = False
592+
cooldown_time: int | float,
593+
error: Callable | None = None,
594+
return_error: bool = False,
593595
):
594596
"""
595597
Decorator function that adds a cooldown period to a given function
@@ -856,6 +858,25 @@ def get_function_annotations(
856858
return FunctionAnnotations(args, return_type)
857859

858860

861+
def make_arguments_ignorable(func: Callable[..., T]) -> Callable[..., T]:
862+
params = inspect.signature(func).parameters.values()
863+
if any(p.kind == inspect.Parameter.VAR_POSITIONAL for p in params):
864+
return func # no
865+
866+
args_len = sum(
867+
1
868+
for p in params
869+
if p.kind
870+
in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD)
871+
)
872+
873+
@wraps(func)
874+
def wrapper(*passed_args):
875+
return func(*passed_args[:args_len])
876+
877+
return wrapper
878+
879+
859880
def truncate(string: str, max_length: int, suffix: str = "...") -> str:
860881
return (
861882
string

tests/service.3.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,116 @@ def onSignal(signalName):
196196
self.assertEqual(emittedSignals, expectedOutput)
197197
emittedSignals = []
198198

199+
def testServiceSignalsConnectAndEmit5(self):
200+
# ignorable arguments
201+
emittedSignals: list[str] = []
202+
expectedOutput = [
203+
"int-signal",
204+
"str-signal",
205+
"bool-signal",
206+
"boxed-signal",
207+
"complex-signal",
208+
]
209+
210+
def onSignal(signalName):
211+
emittedSignals.append(signalName)
212+
213+
service = self.ServiceWithSignals(
214+
on_int_signal=lambda: onSignal("int-signal"),
215+
on_str_signal=lambda: onSignal("str-signal"),
216+
on_bool_signal=lambda: onSignal("bool-signal"),
217+
on_boxed_signal=lambda: onSignal("boxed-signal"),
218+
on_complex_signal=lambda: onSignal("complex-signal"),
219+
)
220+
221+
service.emit("int-signal", 42)
222+
service.emit("str-signal", "42")
223+
service.emit("bool-signal", True)
224+
service.emit("boxed-signal", object)
225+
service.emit("complex-signal", 42, "42", True, object)
226+
227+
self.assertEqual(emittedSignals, expectedOutput)
228+
emittedSignals = []
229+
230+
def testServiceSignalsConnectAndEmit6(self):
231+
# ignorable arguments
232+
emittedSignals: list[str] = []
233+
expectedOutput = [
234+
"int-signal",
235+
"str-signal",
236+
"bool-signal",
237+
"boxed-signal",
238+
"complex-signal",
239+
]
240+
241+
def onSignal(signalName):
242+
emittedSignals.append(signalName)
243+
244+
service = self.ServiceWithSignals(
245+
on_int_signal=lambda s: self.assertEqual(s, service)
246+
or onSignal("int-signal"),
247+
on_str_signal=lambda s: self.assertEqual(s, service)
248+
or onSignal("str-signal"),
249+
on_bool_signal=lambda s: self.assertEqual(s, service)
250+
or onSignal("bool-signal"),
251+
on_boxed_signal=lambda s: self.assertEqual(s, service)
252+
or onSignal("boxed-signal"),
253+
on_complex_signal=lambda s: self.assertEqual(s, service)
254+
or onSignal("complex-signal"),
255+
)
256+
257+
service.emit("int-signal", 42)
258+
service.emit("str-signal", "42")
259+
service.emit("bool-signal", True)
260+
service.emit("boxed-signal", object)
261+
service.emit("complex-signal", 42, "42", True, object)
262+
263+
self.assertEqual(emittedSignals, expectedOutput)
264+
emittedSignals = []
265+
266+
def testServiceSignalsConnectAndEmit7(self):
267+
# ignorable arguments
268+
emittedSignals: list[str] = []
269+
expectedOutput = [
270+
"int-signal",
271+
"str-signal",
272+
"bool-signal",
273+
"boxed-signal",
274+
"complex-signal",
275+
]
276+
277+
def onSignal(signalName):
278+
emittedSignals.append(signalName)
279+
280+
service = self.ServiceWithSignals(
281+
on_int_signal=lambda s, i: self.assertEqual(i, 42)
282+
or self.assertEqual(s, service)
283+
or onSignal("int-signal"),
284+
on_str_signal=lambda s, i: self.assertEqual(i, "42")
285+
or self.assertEqual(s, service)
286+
or onSignal("str-signal"),
287+
on_bool_signal=lambda s, i: self.assertEqual(i, True)
288+
or self.assertEqual(s, service)
289+
or onSignal("bool-signal"),
290+
on_boxed_signal=lambda s, i: self.assertEqual(i, object)
291+
or self.assertEqual(s, service)
292+
or onSignal("boxed-signal"),
293+
on_complex_signal=lambda s, i1, i2, i3, i4: self.assertEqual(
294+
(i1, i2, i3, i4), (42, "42", True, object)
295+
)
296+
or self.assertEqual(s, service)
297+
or onSignal("complex-signal"),
298+
)
299+
300+
service.emit("int-signal", 42)
301+
service.emit("str-signal", "42")
302+
service.emit("bool-signal", True)
303+
service.emit("boxed-signal", object)
304+
service.emit("complex-signal", 42, "42", True, object)
305+
306+
self.assertEqual(emittedSignals, expectedOutput)
307+
emittedSignals = []
308+
199309

200310
if __name__ == "__main__":
201311
unittest.main()

0 commit comments

Comments
 (0)