Skip to content

Commit 43e4ede

Browse files
authored
Replace _tsignal_stopping.wait() with wait_for_stop()
- Introduced new wait_for_stop() method in WorkerClass for better encapsulation - Updated all direct calls to _tsignal_stopping.wait() to use wait_for_stop() - Updated documentation to reflect the new method - Added code formatting improvements and whitespace cleanup - Bump version to 0.4.4
1 parent 4b72210 commit 43e4ede

File tree

8 files changed

+37
-21
lines changed

8 files changed

+37
-21
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ class DataProcessor:
147147
async def run(self, *args, **kwargs):
148148
# The main entry point for the worker thread’s event loop
149149
# Wait for tasks or stopping signal
150-
await self._tsignal_stopping.wait()
150+
await self.wait_for_stop()
151151

152152
async def process_data(self, data):
153153
# Perform heavy computation in the worker thread

docs/api.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class Worker:
7575
# run is the main entry point in the worker thread
7676
print("Worker started with config:", config)
7777
# Wait until stop is requested
78-
await self._tsignal_stopping.wait()
78+
await self.wait_for_stop()
7979
self.finished.emit()
8080

8181
async def do_work(self, data):
@@ -201,6 +201,7 @@ Slots can be async. When a signal with an async slot is emitted:
201201
- `run(*args, **kwargs)` defines the worker’s main logic.
202202
- `queue_task(coro)` schedules coroutines on the worker's event loop.
203203
- `stop()` requests a graceful shutdown, causing `run()` to end after `_tsignal_stopping` is triggered.
204+
- `wait_for_stop()` is a coroutine that waits for the worker to stop.
204205

205206
**Signature Match for** ``run()``:
206207

@@ -262,7 +263,7 @@ class BackgroundWorker:
262263

263264
async def run(self):
264265
# Just wait until stopped
265-
await self._tsignal_stopping.wait()
266+
await self.wait_for_stop()
266267

267268
async def heavy_task(self, data):
268269
await asyncio.sleep(2) # Simulate heavy computation

docs/usage.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ class BackgroundWorker:
269269
async def run(self, *args, **kwargs):
270270
# The main entry point in the worker thread.
271271
# Wait until stopped
272-
await self._tsignal_stopping.wait()
272+
await self.wait_for_stop()
273273

274274
async def heavy_task(self, data):
275275
await asyncio.sleep(2) # Simulate heavy computation
@@ -292,7 +292,7 @@ If `run()` accepts additional parameters, simply provide them to `start()`:
292292
```python
293293
async def run(self, config=None):
294294
# Use config here
295-
await self._tsignal_stopping.wait()
295+
await self.wait_for_stop()
296296
```
297297

298298
```python

examples/stock_monitor_simple.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ async def run(self, *args, **kwargs):
7070
self._running = True
7171
self._update_task = asyncio.create_task(self.update_loop())
7272
# Wait until run() is finished
73-
await self._tsignal_stopping.wait()
73+
await self.wait_for_stop()
7474
# Clean up
7575
self._running = False
7676

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "tsignal"
7-
version = "0.4.3"
7+
version = "0.4.4"
88
description = "A Python Signal-Slot library inspired by Qt"
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/tsignal/contrib/extensions/property.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def __set_name__(self, owner, name):
9393
def __get__(self, obj, objtype=None):
9494
if obj is None:
9595
return self
96+
9697
if self.fget is None:
9798
raise AttributeError("unreadable attribute")
9899

@@ -104,6 +105,7 @@ def __get__(self, obj, objtype=None):
104105
future = asyncio.run_coroutine_threadsafe(
105106
self._get_value(obj), obj._tsignal_loop
106107
)
108+
107109
return future.result()
108110
else:
109111
return self._get_value_sync(obj)
@@ -123,6 +125,7 @@ def __set__(self, obj, value):
123125
future = asyncio.run_coroutine_threadsafe(
124126
self._set_value(obj, value), obj._tsignal_loop
125127
)
128+
126129
# Wait for completion like slot direct calls
127130
return future.result()
128131
else:

src/tsignal/contrib/patterns/worker/decorators.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def result_ready(self):
7575
async def run(self, config=None):
7676
print("Worker started with config:", config)
7777
# Wait until stop is requested
78-
await self._tsignal_stopping.wait()
78+
await self.wait_for_stop()
7979
print("Worker finishing...")
8080
8181
async def do_work(self, data):
@@ -108,9 +108,7 @@ def __init__(self):
108108
All operations that access or modify worker's lifecycle state must be
109109
performed while holding this lock.
110110
"""
111-
self._tsignal_lifecycle_lock = (
112-
threading.RLock()
113-
) # Renamed lock for loop and thread
111+
self._tsignal_lifecycle_lock = threading.RLock()
114112
self._tsignal_stopping = asyncio.Event()
115113
self._tsignal_affinity = object()
116114
self._tsignal_process_queue_task = None
@@ -120,8 +118,10 @@ def __init__(self):
120118
@property
121119
def event_loop(self) -> asyncio.AbstractEventLoop:
122120
"""Returns the worker's event loop"""
121+
123122
if not self._tsignal_loop:
124123
raise RuntimeError("Worker not started")
124+
125125
return self._tsignal_loop
126126

127127
@t_signal
@@ -134,6 +134,7 @@ def stopped(self):
134134

135135
async def run(self, *args, **kwargs):
136136
"""Run the worker."""
137+
137138
logger.debug("[WorkerClass][run] calling super")
138139

139140
super_run = getattr(super(), _WorkerConstants.RUN, None)
@@ -161,8 +162,10 @@ async def run(self, *args, **kwargs):
161162

162163
async def _process_queue(self):
163164
"""Process the task queue."""
165+
164166
while not self._tsignal_stopping.is_set():
165167
coro = await self._tsignal_task_queue.get()
168+
166169
try:
167170
await coro
168171
except Exception as e:
@@ -176,12 +179,14 @@ async def _process_queue(self):
176179

177180
async def start_queue(self):
178181
"""Start the task queue processing. Returns the queue task."""
182+
179183
self._tsignal_process_queue_task = asyncio.create_task(
180184
self._process_queue()
181185
)
182186

183187
def queue_task(self, coro):
184188
"""Method to add a task to the queue"""
189+
185190
if not asyncio.iscoroutine(coro):
186191
logger.error(
187192
"[WorkerClass][queue_task] Task must be a coroutine object: %s",
@@ -196,6 +201,7 @@ def queue_task(self, coro):
196201

197202
def start(self, *args, **kwargs):
198203
"""Start the worker thread."""
204+
199205
run_coro = kwargs.pop(_WorkerConstants.RUN_CORO, None)
200206

201207
if run_coro is not None and not asyncio.iscoroutine(run_coro):
@@ -207,6 +213,7 @@ def start(self, *args, **kwargs):
207213

208214
def thread_main():
209215
"""Thread main function."""
216+
210217
self._tsignal_task_queue = asyncio.Queue()
211218

212219
with self._tsignal_lifecycle_lock:
@@ -215,6 +222,7 @@ def thread_main():
215222

216223
async def runner():
217224
"""Runner function."""
225+
218226
self.started.emit()
219227

220228
if run_coro is not None:
@@ -324,4 +332,9 @@ def move_to_thread(self, target):
324332
self._tsignal_affinity,
325333
)
326334

335+
async def wait_for_stop(self):
336+
"""Wait for the worker to stop."""
337+
338+
await self._tsignal_stopping.wait()
339+
327340
return WorkerClass

src/tsignal/core.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def is_valid(self):
7373

7474
if self.is_weak and isinstance(self.receiver_ref, weakref.ref):
7575
return self.receiver_ref() is not None
76+
7677
return True
7778

7879
def get_slot_to_call(self):
@@ -126,10 +127,7 @@ def wrap(*args, **kwargs):
126127
),
127128
)
128129

129-
return func(*args, **kwargs)
130-
else:
131-
# Call sync function -> return result
132-
return func(*args, **kwargs)
130+
return func(*args, **kwargs)
133131

134132
return wrap
135133

@@ -303,12 +301,6 @@ def standalone_func(value):
303301
)
304302
is_coro_slot = asyncio.iscoroutinefunction(maybe_slot)
305303

306-
is_coro_slot = asyncio.iscoroutinefunction(
307-
receiver_or_slot.__func__
308-
if hasattr(receiver_or_slot, "__self__")
309-
else receiver_or_slot
310-
)
311-
312304
if is_bound_method:
313305
obj = receiver_or_slot.__self__
314306

@@ -394,6 +386,7 @@ def standalone_func(value):
394386

395387
def _cleanup_on_ref_dead(self, ref):
396388
"""Cleanup connections on weak reference death."""
389+
397390
# ref is a weak reference to the receiver
398391
# Remove connections associated with the dead receiver
399392
with self.connections_lock:
@@ -448,6 +441,7 @@ def disconnect(self, receiver: object = None, slot: Callable = None) -> int:
448441
# No receiver or slot specified, remove all connections.
449442
count = len(self.connections)
450443
self.connections.clear()
444+
451445
return count
452446

453447
original_count = len(self.connections)
@@ -496,6 +490,7 @@ def disconnect(self, receiver: object = None, slot: Callable = None) -> int:
496490
"[TSignal][disconnect][END] disconnected: %s",
497491
disconnected,
498492
)
493+
499494
return disconnected
500495

501496
def emit(self, *args, **kwargs):
@@ -726,6 +721,7 @@ def wrap(self):
726721

727722
if not hasattr(self, f"_{sig_name}"):
728723
setattr(self, f"_{sig_name}", TSignal())
724+
729725
return getattr(self, f"_{sig_name}")
730726

731727
return TSignalProperty(wrap, sig_name)
@@ -814,6 +810,7 @@ async def wrap(self, *args, **kwargs):
814810
future = asyncio.run_coroutine_threadsafe(
815811
func(self, *args, **kwargs), self._tsignal_loop
816812
)
813+
817814
return await asyncio.wrap_future(future)
818815

819816
return await func(self, *args, **kwargs)
@@ -853,6 +850,7 @@ def callback():
853850
future.set_exception(e)
854851

855852
self._tsignal_loop.call_soon_threadsafe(callback)
853+
856854
return future.result()
857855

858856
return func(self, *args, **kwargs)
@@ -938,6 +936,7 @@ def __init__(self, *args, **kwargs):
938936
original_init(self, *args, **kwargs)
939937

940938
cls.__init__ = __init__
939+
941940
return cls
942941

943942
if cls is None:

0 commit comments

Comments
 (0)