Skip to content

Commit 5b7811b

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

File tree

4 files changed

+214
-6
lines changed

4 files changed

+214
-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/network/service.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import gi
2+
from loguru import logger
3+
from collections.abc import Callable
4+
from typing import ParamSpec, Literal, Concatenate, Any
5+
from fabric.core.service import Service, Signal, Property
6+
from fabric.utils.helpers import (
7+
bulk_connect,
8+
get_enum_member_name,
9+
snake_case_to_kebab_case,
10+
bridge_signal,
11+
)
12+
13+
from gi.repository import Gio
14+
15+
try:
16+
gi.require_version("GnomeBluetooth", "3.0")
17+
from gi.repository import NM as NetworkManager
18+
except Exception:
19+
raise ImportError("gnome-bluetooth-3 is not installed, please install it first")
20+
21+
22+
class Connection(Service): # base block for both wlan & eth
23+
@Property(NetworkManager.ActiveConnection, flags="readable")
24+
def connection(self) -> NetworkManager.ActiveConnection:
25+
return self._connection
26+
27+
@Property(str, flags="readable")
28+
def connection_type(self) -> Literal["wifi", "wired", "unknown"]:
29+
return self._connection_type # type: ignore
30+
31+
@Property(str, flags="readable")
32+
def status(
33+
self,
34+
) -> Literal["connected", "connecting", "disconnected", "disconnecting", "unknown"]:
35+
return get_enum_member_name(
36+
self._connection.get_state(),
37+
{
38+
"ACTIVATING": "connecting",
39+
"ACTIVATED": "connected",
40+
"DEACTIVATING": "disconnecting",
41+
"DEACTIVATED": "disconnected",
42+
},
43+
default="unknown",
44+
).lower() # type: ignore
45+
46+
def __init__(
47+
self,
48+
connection: NetworkManager.ActiveConnection,
49+
connection_type: str,
50+
**kwargs,
51+
):
52+
super().__init__(**kwargs)
53+
54+
self._connection = connection
55+
self._connection_type = connection_type
56+
57+
self._status = "unknown"
58+
59+
60+
class Network(Service):
61+
def __init__(self, **kwargs):
62+
super().__init__(**kwargs)

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)