Skip to content

Commit b4afb97

Browse files
committed
Add automatic receiver setup for object methods in TSignal.connect
- Enhanced the connect method to automatically set the receiver for object methods that have the @t_with_signals decorator. - Updated API documentation to reflect the new behavior of the connect method. - Added unit tests to verify the functionality of automatic receiver setup.
1 parent 525a209 commit b4afb97

File tree

3 files changed

+84
-1
lines changed

3 files changed

+84
-1
lines changed

docs/api.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,33 @@ Connects the signal to a slot.
9292
- `receiver_or_slot`: The callable (function, lambda, or method)
9393
- `slot`: None
9494

95+
**Connection Behavior:**
96+
1. Object Method with Signal Support:
97+
```python
98+
@t_with_signals
99+
class Receiver:
100+
def on_signal(self, value):
101+
print(value)
102+
103+
receiver = Receiver()
104+
signal.connect(receiver.on_signal) # Automatically sets up receiver
105+
```
106+
107+
2. Regular Object Method:
108+
```python
109+
class RegularClass:
110+
def on_signal(self, value):
111+
print(value)
112+
113+
obj = RegularClass()
114+
signal.connect(obj.on_signal) # Treated as direct connection
115+
```
116+
117+
The connection type is automatically determined:
118+
- Methods from objects with `@t_with_signals` are set up with their object as receiver
119+
- Regular object methods are treated as direct connections
120+
- Async methods always use queued connections
121+
95122
##### `disconnect(receiver: Optional[object] = None, slot: Optional[Callable] = None) -> int`
96123
Disconnects one or more slots from the signal.
97124

src/tsignal/core.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,20 @@ def connect(
5959
f"Invalid connection attempt - receiver_or_slot is not callable: {receiver_or_slot}"
6060
)
6161
raise TypeError("When slot is not provided, receiver must be callable")
62-
slot = _wrap_direct_function(receiver_or_slot)
62+
6363
receiver = None
64+
65+
if hasattr(receiver_or_slot, "__self__"):
66+
obj = receiver_or_slot.__self__
67+
if hasattr(obj, _SignalConstants.THREAD) and hasattr(
68+
obj, _SignalConstants.LOOP
69+
):
70+
receiver = obj
71+
slot = receiver_or_slot
72+
else:
73+
slot = _wrap_direct_function(receiver_or_slot)
74+
else:
75+
slot = _wrap_direct_function(receiver_or_slot)
6476
else:
6577
if receiver_or_slot is None:
6678
logger.error("Invalid connection attempt - receiver cannot be None")

tests/unit/test_signal.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,47 @@ def test_direct_function_disconnect(sender):
216216
# Second emit - should not add value since connection is disconnected
217217
sender.emit_value(43)
218218
assert received_values == [42]
219+
220+
221+
def test_method_connection_with_signal_attributes(sender):
222+
"""Test connecting a method with _thread and _loop attributes automatically sets up receiver"""
223+
received_values = []
224+
225+
@t_with_signals
226+
class SignalReceiver:
227+
def collect_value(self, value):
228+
received_values.append(value)
229+
230+
class RegularClass:
231+
def collect_value(self, value):
232+
received_values.append(value * 2)
233+
234+
# t_with_signals가 적용된 객체의 메소드
235+
signal_receiver = SignalReceiver()
236+
sender.value_changed.connect(signal_receiver.collect_value)
237+
238+
# 일반 객체의 메소드
239+
regular_receiver = RegularClass()
240+
sender.value_changed.connect(regular_receiver.collect_value)
241+
242+
# Emit signal
243+
sender.emit_value(42)
244+
245+
# signal_receiver는 QueuedConnection으로 처리되어야 함
246+
connection = next(
247+
conn
248+
for conn in sender.value_changed.connections
249+
if conn[1] == signal_receiver.collect_value
250+
)
251+
assert connection[0] == signal_receiver # receiver가 자동으로 설정됨
252+
253+
# regular_receiver는 DirectConnection으로 처리되어야 함
254+
connection = next(
255+
conn
256+
for conn in sender.value_changed.connections
257+
if hasattr(conn[1], "__wrapped__")
258+
)
259+
assert connection[0] is None # receiver가 None임
260+
261+
assert 42 in received_values # SignalReceiver의 결과
262+
assert 84 in received_values # RegularClass의 결과 (42 * 2)

0 commit comments

Comments
 (0)