Skip to content

Major Documentation and Code Quality Updates #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 94 additions & 130 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,39 @@
# TSignal

Are you looking for a signal/slot pattern in Python without Qt dependencies? TSignal provides a lightweight, thread-safe, and asyncio-compatible implementation that gives you all the power of Qt's signal/slot pattern without the heavyweight dependencies. Perfect for:
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.

- Async applications needing event handling
- Thread communication in Python applications
- Event-driven architectures
- Decoupled component communication
## Key Features

- **Pure Python**: No Qt or external GUI frameworks needed.
- **Async/Await Friendly**: Slots can be synchronous or asynchronous, and integrate seamlessly with asyncio.
- **Thread-Safe**: Signal emissions and slot executions are automatically managed for thread safety.
- **Flexible Connection Types**: Direct or queued connections, automatically chosen based on the caller and callee threads.
- **Worker Thread Pattern**: Simplify background task execution with a built-in worker pattern that provides an event loop and task queue in a dedicated thread.
- **Familiar Decorators**: Inspired by Qt’s pattern, `@t_with_signals`, `@t_signal`, and `@t_slot` let you define signals and slots declaratively.

## Why TSignal?
- 🚀 Pure Python implementation - no Qt or external dependencies required
- ⚡ Async/await support out of the box
- 🔒 Thread-safe signal emission and slot execution
- 🎯 Simple, decorator-based API similar to Qt
- 🔄 Automatic thread handling for cross-thread signals

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:

- A minimal, dependency-free solution for event-driven architectures.
- Smooth integration with asyncio for modern async Python code.
- Automatic thread-affinity handling so cross-thread signals "just work."
- Decorator-based API that’s intuitive and maintainable.

## Installation

TSignal requires Python 3.10 or higher.

```bash
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]
```

## Quick Start

Expand Down Expand Up @@ -46,7 +67,7 @@ counter.count_changed.connect(display, display.on_count_changed)
counter.increment() # Will print: "Count is now: 1"
```

### Async Example
### Asynchronous Slot Example
```python
@t_with_signals
class AsyncDisplay:
Expand All @@ -69,51 +90,79 @@ async def main():
asyncio.run(main())
```

## Features
- Requires Python 3.10+
- Easy-to-use signal-slot mechanism with decorators
- Support for both synchronous and asynchronous slots
- Thread-safe signal emissions
- Automatic connection type detection (direct/queued)
- Compatible with Python's asyncio
## Core Concepts

## Installation
### Signals and Slots
- Signals: Declared with `@t_signal`. Signals are attributes of a class that can be emitted to notify interested parties.
- Slots: Declared with `@t_slot`. Slots are methods that respond to signals. Slots can be synchronous or async functions.
- Connections: Use `signal.connect(receiver, slot)` to link signals to slots. Connections can also be made directly to functions or lambdas.

TSignal requires Python 3.10 or higher. You can install it directly from the repository:
### Thread Safety and Connection Types
TSignal automatically detects whether the signal emission and slot execution occur in the same thread or different threads:

```bash
git clone https://github.com/tsignal/tsignal-python.git
cd tsignal-python
pip install -e .
```
- **Direct Connection**: If signal and slot share the same thread affinity, the slot is invoked directly.
- **Queued Connection**: If they differ, the call is queued to the slot’s thread/event loop, ensuring thread safety.

For development installation (includes test dependencies):
```bash
pip install -e ".[dev]"
```
This mechanism frees you from manually dispatching calls across threads.

## Documentation
- [Detailed Usage Guide](docs/usage.md)
- [API Reference](docs/api.md)
- [Examples](docs/examples.md)
- [Logging Guidelines](docs/logging.md)
- [Testing Guide](docs/testing.md)
### Worker Threads
For background work, TSignal provides a `@t_with_worker` decorator that:

## Development
- Spawns a dedicated event loop in a worker thread.
- Allows you to queue async tasks to this worker.
- Enables easy start/stop lifecycle management.
- Integrates with signals and slots for thread-safe updates to the main

### Logging
TSignal uses Python's standard logging module. For detailed logging configuration,
please see [Logging Guidelines](docs/logging.md).
**Worker Example**
```python
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()
```

## Documentation and Example
- [Usage Guide](docs/usage.md): Learn how to define signals/slots, manage threads, and structure your event-driven code.
- [API Reference](docs/api.md): Detailed documentation of classes, decorators, and functions.
- [Examples](docs/examples.md): Practical use cases, including UI integration, async operations, and worker pattern usage.
- [Logging Guidelines](docs/logging.md): Configure logging levels and handlers for debugging.
- [Testing Guide](docs/testing.md): earn how to run tests and contribute safely.

## Logging
Configure logging to diagnose issues:

Basic usage:
```python
import logging
logging.getLogger('tsignal').setLevel(logging.INFO)
logging.getLogger('tsignal').setLevel(logging.DEBUG)
```

For more details, see the [Logging Guidelines](docs/logging.md).

## Testing

TSignal includes a comprehensive test suite using pytest. For basic testing:
TSignal uses `pytest` for testing:

```bash
# Run all tests
Expand All @@ -126,95 +175,10 @@ pytest -v
pytest tests/unit/test_signal.py
```

For detailed testing instructions and guidelines, see [Testing Guide](docs/testing.md).
See the [Testing Guide](docs/testing.md) for more details.

## Contributing
Please see [Contributing Guidelines](CONTRIBUTING.md) for details on how to contribute to this project.
We welcome contributions. Please read the [Contributing Guidelines](CONTRIBUTING.md) before submitting PRs.

## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Connecting Signals and Slots

### Classic Object-Member Connection
```python
@t_with_signals
class Counter:
@t_signal
def count_changed(self):
pass

@t_with_signals
class Display:
@t_slot
def on_count_changed(self, value):
print(f"Count is now: {value}")

counter = Counter()
display = Display()
counter.count_changed.connect(display, display.on_count_changed)
```

### Function Connection
```python
# Connect to a simple function
def print_value(value):
print(f"Value: {value}")

counter.count_changed.connect(print_value)

# Connect to a lambda
counter.count_changed.connect(lambda x: print(f"Lambda received: {x}"))

# Connect to an object method without @t_slot
class Handler:
def process_value(self, value):
print(f"Processing: {value}")

handler = Handler()
counter.count_changed.connect(handler.process_value)
```

## Worker Thread Pattern

TSignal provides a worker thread pattern that combines thread management with signal/slot communication and task queuing:

```python
from tsignal import t_with_worker

@t_with_worker
class DataProcessor:
async def initialize(self, config=None):
# Setup worker (called in worker thread)
self.config = config or {}

async def process_data(self, data):
# Heavy processing in worker thread
result = await heavy_computation(data)
self.processing_done.emit(result)

async def finalize(self):
# Cleanup worker (called before thread stops)
await self.cleanup()

@t_signal
def processing_done(self):
pass

# Usage
processor = DataProcessor()
processor.start(config={'threads': 4}) # Starts worker thread

# Queue task in worker thread
await processor.queue_task(processor.process_data(some_data))

# Stop worker
processor.stop() # Graceful shutdown
```

The worker pattern provides:
- Dedicated worker thread with event loop
- Built-in signal/slot support
- Async task queue
- Graceful initialization/shutdown
- Thread-safe communication
TSignal is licensed under the MIT License. See [LICENSE](LICENSE) for details.
Loading
Loading