Skip to content

Commit 4b72210

Browse files
authored
Merge pull request #11 from TSignalDev/0.4.3
Update documentation
2 parents 5cd6c27 + 7d92a67 commit 4b72210

File tree

4 files changed

+32
-6
lines changed

4 files changed

+32
-6
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ TSignal is a lightweight, pure-Python signal/slot library that provides thread-s
1313
- **Weak Reference**:
1414
- By setting `weak=True` when connecting a slot, the library holds a weak reference to the receiver object. This allows the receiver to be garbage-collected if there are no other strong references to it. Once garbage-collected, the connection is automatically removed, preventing stale references.
1515

16+
### **Requires an Existing Event Loop**
17+
18+
Since TSignal relies on Python’s `asyncio` infrastructure for scheduling async slots and cross-thread calls, you **must** have a running event loop before using TSignal’s decorators like `@t_with_signals` or `@t_slot`. Typically, this means:
19+
20+
1. **Inside `asyncio.run(...)`:**
21+
For example:
22+
```python
23+
async def main():
24+
# create objects, do your logic
25+
...
26+
asyncio.run(main())
27+
```
28+
29+
2. **@t_with_worker Decorator:**
30+
If you decorate a class with `@t_with_worker`, it automatically creates a worker thread with its own event loop. That pattern is isolated to the worker context, so any other async usage in the main thread also needs its own loop.
31+
32+
If no event loop is running when a slot is called, TSignal will raise a RuntimeError instead of creating a new loop behind the scenes. This ensures consistent concurrency behavior and avoids hidden loops that might never process tasks.
33+
1634
## Why TSignal?
1735

1836
Modern Python applications often rely on asynchronous operations and multi-threading. Traditional event frameworks either require large external dependencies or lack seamless async/thread support. TSignal provides:

docs/api.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# API Reference
22

33
## Requirements
4-
TSignal requires Python 3.10 or higher.
4+
TSignal requires Python 3.10 or higher, and a running `asyncio` event loop for any async usage.
55

66
## Decorators
77
### `@t_with_signals`
88
Enables signal-slot functionality on a class. Classes decorated with `@t_with_signals` can define signals and have their slots automatically assigned event loops and thread affinity.
99

10+
**Important**: `@t_with_signals` expects that you already have an `asyncio` event loop running (e.g., via `asyncio.run(...)`) unless you only rely on synchronous slots in a single-thread scenario. When in doubt, wrap your main logic in an async function and call `asyncio.run(main())`.
11+
1012
**Usage:**
1113
```python
1214
@t_with_signals
@@ -31,7 +33,7 @@ self.my_signal.emit(value)
3133
```
3234

3335
### `@t_slot`
34-
Marks a method as a slot. Slots can be synchronous or asynchronous methods. Slots automatically handle thread affinity and can be connected to signals.
36+
Marks a method as a slot. Slots can be synchronous or asynchronous methods. TSignal automatically handles cross-thread invocation—**but only if there is a running event loop**.
3537

3638
**Usage:**
3739

@@ -46,8 +48,11 @@ async def on_async_signal(self, value):
4648
print("Async Received:", value)
4749
```
4850

51+
**Event Loop Requirement**:
52+
If the decorated slot is async, or if the slot might be called from another thread, TSignal uses asyncio scheduling. That means a running event loop is mandatory. If no loop is found, a RuntimeError is raised.
53+
4954
### `@t_with_worker`
50-
Decorates a class to run inside a dedicated worker thread with its own event loop. Ideal for offloading tasks without blocking the main thread. The worker provides:
55+
Decorates a class to run inside a dedicated worker thread with its own event loop. Ideal for offloading tasks without blocking the main thread. When using @t_with_worker, the worker thread automatically sets up its own event loop, so calls within that worker are safe. For the main thread, you still need an existing loop if you plan on using async slots or cross-thread signals. The worker provides:
5156

5257
A dedicated event loop in another thread.
5358
The `run(*args, **kwargs)` coroutine as the main entry point.

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.2"
7+
version = "0.4.3"
88
description = "A Python Signal-Slot library inspired by Qt"
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/tsignal/core.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -801,8 +801,11 @@ async def wrap(self, *args, **kwargs):
801801
try:
802802
self._tsignal_loop = asyncio.get_running_loop()
803803
except RuntimeError:
804-
self._tsignal_loop = asyncio.new_event_loop()
805-
asyncio.set_event_loop(self._tsignal_loop)
804+
t_signal_log_and_raise_error(
805+
logger,
806+
RuntimeError,
807+
"[TSignal][t_slot][wrap] No running event loop found.",
808+
)
806809

807810
if not _tsignal_from_emit.get():
808811
current_thread = threading.current_thread()

0 commit comments

Comments
 (0)