Skip to content

Commit 0feb3f4

Browse files
san-tekartsan-tekart
andauthored
feat: Add worker thread pattern and enhance signal connections (#3)
* feat: Add worker thread pattern and enhance signal connections This commit introduces several major enhancements: 1. Worker Thread Pattern - Add @t_with_worker decorator for background thread management - Implement task queue with async support - Add graceful initialization/cleanup lifecycle - Add comprehensive worker thread tests 2. Signal Connection Enhancement - Support direct function/lambda connections - Support method connections without @t_slot - Add automatic receiver setup for object methods in TSignal.connect - Improve thread-safe signal emission 3. Documentation - Add worker pattern documentation and examples - Update API reference for new connection types - Reorganize examples for better clarity 4. Example Reorganization - Rename examples for consistency - Add worker thread pattern example - Update thread communication examples --------- Co-authored-by: san-tekart <[email protected]>
1 parent 366de23 commit 0feb3f4

29 files changed

+1592
-183
lines changed

.github/workflows/tests.yml renamed to .github/workflows/ci.yml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
name: Tests
1+
# .github/workflows/ci.yml
2+
name: CI
23

34
on:
45
push:
@@ -11,7 +12,7 @@ jobs:
1112
runs-on: ubuntu-latest
1213
strategy:
1314
matrix:
14-
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
15+
python-version: ['3.10', '3.11', '3.12']
1516

1617
steps:
1718
- uses: actions/checkout@v2
@@ -24,9 +25,18 @@ jobs:
2425
- name: Install dependencies
2526
run: |
2627
python -m pip install --upgrade pip
27-
python -m pip install pytest pytest-cov pytest-asyncio
2828
pip install -e ".[dev]"
29+
pip install pylint mypy
2930
3031
- name: Run tests
3132
run: |
3233
pytest --cov=tsignal
34+
35+
- name: Code quality checks
36+
run: |
37+
pylint src/tsignal
38+
mypy src/tsignal
39+
40+
- name: Performance tests
41+
run: |
42+
pytest -v -m performance

CHANGELOG.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,54 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [0.2.0] - 2024-03-19
8+
9+
### Changed
10+
- Updated minimum Python version requirement to 3.10
11+
- This change was necessary to ensure reliable worker thread functionality
12+
- Python 3.10+ provides improved async features and type handling
13+
- Better support for async context management and error handling
14+
- Updated documentation to reflect new Python version requirement
15+
- Enhanced worker thread implementation with Python 3.10+ features
16+
17+
### Added
18+
- Performance tests for stress testing and memory usage analysis
19+
- Includes `test_stress.py` for heavy signal load testing
20+
- Includes `test_memory.py` for memory profiling
21+
22+
### Removed
23+
- Support for Python versions below 3.10
24+
25+
### Note
26+
Core features are now implemented and stable:
27+
- Robust signal-slot mechanism
28+
- Thread-safe operations
29+
- Async/await support
30+
- Worker thread pattern
31+
- Comprehensive documentation
32+
- Full test coverage
33+
34+
Next steps before 1.0.0:
35+
- Additional stress testing
36+
- Memory leak verification
37+
- Production environment validation
38+
- Enhanced CI/CD pipeline
39+
- Extended documentation
40+
41+
## [0.1.1] - 2024-12-01
42+
43+
### Changed
44+
- Refactored signal connection logic to support direct function connections
45+
- Improved error handling for invalid connections
46+
- Enhanced logging for signal emissions and connections
47+
48+
### Fixed
49+
- Resolved issues with disconnecting slots during signal emissions
50+
- Fixed bugs related to async slot processing and connection management
51+
52+
### Removed
53+
- Deprecated unused constants and methods from the core module
54+
755
## [0.1.0] - 2024-01-26
856

957
### Added

README.md

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ asyncio.run(main())
7070
```
7171

7272
## Features
73+
- Requires Python 3.10+
7374
- Easy-to-use signal-slot mechanism with decorators
7475
- Support for both synchronous and asynchronous slots
7576
- Thread-safe signal emissions
@@ -78,7 +79,7 @@ asyncio.run(main())
7879

7980
## Installation
8081

81-
Currently, this package is under development. You can install it directly from the repository:
82+
TSignal requires Python 3.10 or higher. You can install it directly from the repository:
8283

8384
```bash
8485
git clone https://github.com/tsignal/tsignal-python.git
@@ -132,3 +133,88 @@ Please see [Contributing Guidelines](CONTRIBUTING.md) for details on how to cont
132133

133134
## License
134135
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
136+
137+
## Connecting Signals and Slots
138+
139+
### Classic Object-Member Connection
140+
```python
141+
@t_with_signals
142+
class Counter:
143+
@t_signal
144+
def count_changed(self):
145+
pass
146+
147+
@t_with_signals
148+
class Display:
149+
@t_slot
150+
def on_count_changed(self, value):
151+
print(f"Count is now: {value}")
152+
153+
counter = Counter()
154+
display = Display()
155+
counter.count_changed.connect(display, display.on_count_changed)
156+
```
157+
158+
### Function Connection
159+
```python
160+
# Connect to a simple function
161+
def print_value(value):
162+
print(f"Value: {value}")
163+
164+
counter.count_changed.connect(print_value)
165+
166+
# Connect to a lambda
167+
counter.count_changed.connect(lambda x: print(f"Lambda received: {x}"))
168+
169+
# Connect to an object method without @t_slot
170+
class Handler:
171+
def process_value(self, value):
172+
print(f"Processing: {value}")
173+
174+
handler = Handler()
175+
counter.count_changed.connect(handler.process_value)
176+
```
177+
178+
## Worker Thread Pattern
179+
180+
TSignal provides a worker thread pattern that combines thread management with signal/slot communication and task queuing:
181+
182+
```python
183+
from tsignal import t_with_worker
184+
185+
@t_with_worker
186+
class DataProcessor:
187+
async def initialize(self, config=None):
188+
# Setup worker (called in worker thread)
189+
self.config = config or {}
190+
191+
async def process_data(self, data):
192+
# Heavy processing in worker thread
193+
result = await heavy_computation(data)
194+
self.processing_done.emit(result)
195+
196+
async def finalize(self):
197+
# Cleanup worker (called before thread stops)
198+
await self.cleanup()
199+
200+
@t_signal
201+
def processing_done(self):
202+
pass
203+
204+
# Usage
205+
processor = DataProcessor()
206+
processor.start(config={'threads': 4}) # Starts worker thread
207+
208+
# Queue task in worker thread
209+
await processor.queue_task(processor.process_data(some_data))
210+
211+
# Stop worker
212+
processor.stop() # Graceful shutdown
213+
```
214+
215+
The worker pattern provides:
216+
- Dedicated worker thread with event loop
217+
- Built-in signal/slot support
218+
- Async task queue
219+
- Graceful initialization/shutdown
220+
- Thread-safe communication

docs/api.md

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,88 @@ async def on_async_signal(self, *args, **kwargs):
3636
pass
3737
```
3838

39+
### `@t_with_worker`
40+
Class decorator that creates a worker thread with signal support and task queue.
41+
42+
**Requirements:**
43+
- Class must implement async `initialize(self, *args, **kwargs)` method
44+
- Class must implement async `finalize(self)` method
45+
46+
**Added Methods:**
47+
##### `start(*args, **kwargs) -> None`
48+
Starts the worker thread and calls initialize with given arguments.
49+
50+
##### `stop() -> None`
51+
Stops the worker thread gracefully, calling finalize.
52+
53+
##### `async queue_task(coro) -> None`
54+
Queues a coroutine for execution in the worker thread.
55+
56+
**Example:**
57+
```python
58+
@t_with_worker
59+
class Worker:
60+
async def initialize(self):
61+
print("Worker initialized")
62+
63+
async def finalize(self):
64+
print("Worker cleanup")
65+
66+
async def process(self):
67+
await asyncio.sleep(1)
68+
print("Processing done")
69+
70+
worker = Worker()
71+
worker.start()
72+
await worker.queue_task(worker.process())
73+
worker.stop()
74+
```
75+
3976
## Classes
4077

4178
### `TSignal`
4279
Base class for signals.
4380

4481
#### Methods
4582

46-
##### `connect(receiver: object, slot: Callable, connection_type: Optional[TConnectionType] = None) -> None`
83+
##### `connect(receiver_or_slot: Union[object, Callable], slot: Optional[Callable] = None) -> None`
4784
Connects the signal to a slot.
4885

4986
**Parameters:**
50-
- `receiver`: Object that contains the slot
51-
- `slot`: Callable that will receive the signal
52-
- `connection_type`: Optional connection type (DirectConnection or QueuedConnection)
87+
- When connecting to a QObject slot:
88+
- `receiver_or_slot`: The receiver object
89+
- `slot`: The slot method of the receiver
90+
91+
- When connecting to a function/lambda:
92+
- `receiver_or_slot`: The callable (function, lambda, or method)
93+
- `slot`: None
94+
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
53121

54122
##### `disconnect(receiver: Optional[object] = None, slot: Optional[Callable] = None) -> int`
55123
Disconnects one or more slots from the signal.
@@ -104,18 +172,8 @@ Emits the signal with the given arguments.
104172
Enum defining connection types.
105173

106174
#### Values:
107-
- `DirectConnection`: Slot is called directly in the emitting thread
108-
- `QueuedConnection`: Slot is queued in the receiver's event loop
109-
110-
## Constants
111-
112-
### `TSignalConstants`
113-
Constants used by the TSignal system.
114-
115-
#### Values:
116-
- `FROM_EMIT`: Key for emission context
117-
- `THREAD`: Key for thread storage
118-
- `LOOP`: Key for event loop storage
175+
- `DIRECT_CONNECTION`: Slot is called directly in the emitting thread
176+
- `QUEUED_CONNECTION`: Slot is queued in the receiver's event loop
119177

120178
## Usage Examples
121179

@@ -164,14 +222,14 @@ asyncio.run(main())
164222
sender.value_changed.connect(
165223
receiver,
166224
receiver.on_value_changed,
167-
connection_type=TConnectionType.DirectConnection
225+
connection_type=TConnectionType.DIRECT_CONNECTION
168226
)
169227

170228
# Force queued connection
171229
sender.value_changed.connect(
172230
receiver,
173231
receiver.on_value_changed,
174-
connection_type=TConnectionType.QueuedConnection
232+
connection_type=TConnectionType.QUEUED_CONNECTION
175233
)
176234
```
177235

docs/logging.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Logging Guidelines
22

3+
## Requirements
4+
TSignal requires Python 3.10 or higher.
5+
36
TSignal uses Python's standard logging module with the following levels:
47

58
- DEBUG: Detailed information about signal-slot connections and emissions

0 commit comments

Comments
 (0)