TSignal is a lightweight, pure-Python signal/slot library that provides thread-safe, asyncio-compatible event handling inspired by the Qt signal/slot pattern—but without the heavyweight Qt dependencies. It enables clean decoupling of components, seamless thread-to-thread communication, and flexible asynchronous/synchronous slot handling.
@t_with_signals
, @t_signal
, and @t_slot
let you define signals and slots declaratively.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.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:
Async-Ready
Thread-Safe by Design
Flexible Slots
Robust Testing & Examples
TSignal requires Python 3.10 or higher.
git clone https://github.com/TSignalDev/tsignal-python.git
cd tsignal-python
pip install -e .
For development (includes tests and linting tools):
pip install -e ".[dev]
from tsignal import t_with_signals, t_signal, t_slot
@t_with_signals
class Counter:
def __init__(self):
self.count = 0
@t_signal
def count_changed(self):
pass
def increment(self):
self.count += 1
self.count_changed.emit(self.count)
@t_with_signals
class Display:
@t_slot
async def on_count_changed(self, value):
print(f"Count is now: {value}")
# Connect and use
counter = Counter()
display = Display()
counter.count_changed.connect(display, display.on_count_changed)
counter.increment() # Will print: "Count is now: 1"
@t_with_signals
class AsyncDisplay:
@t_slot
async def on_count_changed(self, value):
await asyncio.sleep(1) # Simulate async operation
print(f"Count updated to: {value}")
# Usage in async context
async def main():
counter = Counter()
display = AsyncDisplay()
counter.count_changed.connect(display, display.on_count_changed)
counter.increment()
# Wait for async processing
await asyncio.sleep(1.1)
asyncio.run(main())
@t_signal
. Signals are attributes of a class that can be emitted to notify interested parties.@t_slot
. Slots are methods that respond to signals. Slots can be synchronous or async functions.signal.connect(receiver, slot)
to link signals to slots. Connections can also be made directly to functions or lambdas.TSignal automatically detects whether the signal emission and slot execution occur in the same thread or different threads:
This mechanism frees you from manually dispatching calls across threads.
For background work, TSignal provides a @t_with_worker
decorator that:
Worker Example
from tsignal import t_with_worker, t_signal
@t_with_worker
class DataProcessor:
@t_signal
def processing_done(self):
"""Emitted when processing completes"""
async def run(self, *args, **kwargs):
# The main entry point for the worker thread’s event loop
# Wait for tasks or stopping signal
await self._tsignal_stopping.wait()
async def process_data(self, data):
# Perform heavy computation in the worker thread
result = await heavy_computation(data)
self.processing_done.emit(result)
processor = DataProcessor()
processor.start()
# Queue a task to run in the worker thread:
processor.queue_task(processor.process_data(some_data))
# Stop the worker gracefully
processor.stop()
We’ve expanded TSignal’s examples to guide you from simple demos to full-fledged applications. Each example has its own GitHub link with fully commented code.
For detailed explanations, code walkthroughs, and architecture diagrams of these examples, check out our Examples Documentation.
Stock Monitor Console: Real-time price updates, alert configuration, and notification history in action
Stock Monitor UI: Real-time price updates, alert configuration, and notification history in action
Together, these examples highlight TSignal’s versatility—covering everything from quick demos to production-like patterns with threads, queues, and reactive UI updates.
TSignal is licensed under the MIT License. See LICENSE for details.