tsignal


Nametsignal JSON
Version 0.4.2 PyPI version JSON
download
home_pageNone
SummaryA Python Signal-Slot library inspired by Qt
upload_time2024-12-21 13:45:07
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseMIT
keywords signal-slot decorator multithreading asyncio
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # TSignal

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.

## 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.
- **Weak Reference**: 
  - 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.

## Why TSignal?

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

### Basic Example
```python
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"
```

### Asynchronous Slot Example
```python
@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())
```

## Core Concepts

### 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.

### Thread Safety and Connection Types
TSignal automatically detects whether the signal emission and slot execution occur in the same thread or different threads:

- **Auto Connection**: When connection_type is AUTO_CONNECTION (default), TSignal checks whether the slot is a coroutine function or whether the caller and callee share the same thread affinity. If they are the same thread and slot is synchronous, it uses direct connection. Otherwise, it uses queued connection.
- **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.

This mechanism frees you from manually dispatching calls across threads.

### Worker Threads
For background work, TSignal provides a `@t_with_worker` decorator that:

- 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 

**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](https://github.com/TSignalDev/tsignal-python/blob/main/docs/usage.md): Learn how to define signals/slots, manage threads, and structure your event-driven code.
- [API Reference](https://github.com/TSignalDev/tsignal-python/blob/main/docs/api.md): Detailed documentation of classes, decorators, and functions.
- [Examples](https://github.com/TSignalDev/tsignal-python/blob/main/docs/examples.md): Practical use cases, including UI integration, async operations, and worker pattern usage.
- [Logging Guidelines](https://github.com/TSignalDev/tsignal-python/blob/main/docs/logging.md): Configure logging levels and handlers for debugging.
- [Testing Guide](https://github.com/TSignalDev/tsignal-python/blob/main/docs/testing.md): earn how to run tests and contribute safely.

## Logging
Configure logging to diagnose issues:

```python
import logging
logging.getLogger('tsignal').setLevel(logging.DEBUG)
```

For more details, see the [Logging Guidelines](https://github.com/TSignalDev/tsignal-python/blob/main/docs/logging.md).

## Testing

TSignal uses `pytest` for testing:

```bash
# Run all tests
pytest

# Run with verbose output
pytest -v

# Run specific test file
pytest tests/unit/test_signal.py
```

See the [Testing Guide](https://github.com/TSignalDev/tsignal-python/blob/main/docs/testing.md) for more details.

## Contributing
We welcome contributions. Please read the [Contributing Guidelines](https://github.com/TSignalDev/tsignal-python/blob/main/CONTRIBUTING.md) before submitting PRs.

## License
TSignal is licensed under the MIT License. See [LICENSE](https://github.com/TSignalDev/tsignal-python/blob/main/LICENSE) for details.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "tsignal",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "signal-slot, decorator, multithreading, asyncio",
    "author": null,
    "author_email": "San Kim <tsignal.dev@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/9b/ec/d24e694152e0f67c396eeabf66f05d1a6159df4f3a389af40f513862d043/tsignal-0.4.2.tar.gz",
    "platform": null,
    "description": "# TSignal\r\n\r\nTSignal is a lightweight, pure-Python signal/slot library that provides thread-safe, asyncio-compatible event handling inspired by the Qt signal/slot pattern\u2014but without the heavyweight Qt dependencies. It enables clean decoupling of components, seamless thread-to-thread communication, and flexible asynchronous/synchronous slot handling.\r\n\r\n## Key Features\r\n\r\n- **Pure Python**: No Qt or external GUI frameworks needed.\r\n- **Async/Await Friendly**: Slots can be synchronous or asynchronous, and integrate seamlessly with asyncio.\r\n- **Thread-Safe**: Signal emissions and slot executions are automatically managed for thread safety.\r\n- **Flexible Connection Types**: Direct or queued connections, automatically chosen based on the caller and callee threads.\r\n- **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.\r\n- **Familiar Decorators**: Inspired by Qt\u2019s pattern, `@t_with_signals`, `@t_signal`, and `@t_slot` let you define signals and slots declaratively.\r\n- **Weak Reference**: \r\n  - 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.\r\n\r\n## Why TSignal?\r\n\r\nModern 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:\r\n\r\n- A minimal, dependency-free solution for event-driven architectures.\r\n- Smooth integration with asyncio for modern async Python code.\r\n- Automatic thread-affinity handling so cross-thread signals \"just work.\"\r\n- Decorator-based API that\u2019s intuitive and maintainable.\r\n\r\n## Installation\r\n\r\nTSignal requires Python 3.10 or higher.\r\n\r\n```bash\r\ngit clone https://github.com/TSignalDev/tsignal-python.git\r\ncd tsignal-python\r\npip install -e .\r\n```\r\n\r\nFor development (includes tests and linting tools):\r\n```\r\npip install -e \".[dev]\r\n```\r\n\r\n## Quick Start\r\n\r\n### Basic Example\r\n```python\r\nfrom tsignal import t_with_signals, t_signal, t_slot\r\n\r\n@t_with_signals\r\nclass Counter:\r\n    def __init__(self):\r\n        self.count = 0\r\n    \r\n    @t_signal\r\n    def count_changed(self):\r\n        pass\r\n    \r\n    def increment(self):\r\n        self.count += 1\r\n        self.count_changed.emit(self.count)\r\n\r\n@t_with_signals\r\nclass Display:\r\n    @t_slot\r\n    async def on_count_changed(self, value):\r\n        print(f\"Count is now: {value}\")\r\n\r\n# Connect and use\r\ncounter = Counter()\r\ndisplay = Display()\r\ncounter.count_changed.connect(display, display.on_count_changed)\r\ncounter.increment()  # Will print: \"Count is now: 1\"\r\n```\r\n\r\n### Asynchronous Slot Example\r\n```python\r\n@t_with_signals\r\nclass AsyncDisplay:\r\n    @t_slot\r\n    async def on_count_changed(self, value):\r\n        await asyncio.sleep(1)  # Simulate async operation\r\n        print(f\"Count updated to: {value}\")\r\n\r\n# Usage in async context\r\nasync def main():\r\n    counter = Counter()\r\n    display = AsyncDisplay()\r\n    \r\n    counter.count_changed.connect(display, display.on_count_changed)\r\n    counter.increment()\r\n    \r\n    # Wait for async processing\r\n    await asyncio.sleep(1.1)\r\n\r\nasyncio.run(main())\r\n```\r\n\r\n## Core Concepts\r\n\r\n### Signals and Slots\r\n- Signals: Declared with `@t_signal`. Signals are attributes of a class that can be emitted to notify interested parties.\r\n- Slots: Declared with `@t_slot`. Slots are methods that respond to signals. Slots can be synchronous or async functions.\r\n- Connections: Use `signal.connect(receiver, slot)` to link signals to slots. Connections can also be made directly to functions or lambdas.\r\n\r\n### Thread Safety and Connection Types\r\nTSignal automatically detects whether the signal emission and slot execution occur in the same thread or different threads:\r\n\r\n- **Auto Connection**: When connection_type is AUTO_CONNECTION (default), TSignal checks whether the slot is a coroutine function or whether the caller and callee share the same thread affinity. If they are the same thread and slot is synchronous, it uses direct connection. Otherwise, it uses queued connection.\r\n- **Direct Connection**: If signal and slot share the same thread affinity, the slot is invoked directly.\r\n- **Queued Connection**: If they differ, the call is queued to the slot\u2019s thread/event loop, ensuring thread safety.\r\n\r\nThis mechanism frees you from manually dispatching calls across threads.\r\n\r\n### Worker Threads\r\nFor background work, TSignal provides a `@t_with_worker` decorator that:\r\n\r\n- Spawns a dedicated event loop in a worker thread.\r\n- Allows you to queue async tasks to this worker.\r\n- Enables easy start/stop lifecycle management.\r\n- Integrates with signals and slots for thread-safe updates to the main \r\n\r\n**Worker Example**\r\n```python\r\nfrom tsignal import t_with_worker, t_signal\r\n\r\n@t_with_worker\r\nclass DataProcessor:\r\n    @t_signal\r\n    def processing_done(self):\r\n        \"\"\"Emitted when processing completes\"\"\"\r\n\r\n    async def run(self, *args, **kwargs):\r\n        # The main entry point for the worker thread\u2019s event loop\r\n        # Wait for tasks or stopping signal\r\n        await self._tsignal_stopping.wait()\r\n\r\n    async def process_data(self, data):\r\n        # Perform heavy computation in the worker thread\r\n        result = await heavy_computation(data)\r\n        self.processing_done.emit(result)\r\n\r\nprocessor = DataProcessor()\r\nprocessor.start()\r\n\r\n# Queue a task to run in the worker thread:\r\nprocessor.queue_task(processor.process_data(some_data))\r\n\r\n# Stop the worker gracefully\r\nprocessor.stop()\r\n```\r\n\r\n## Documentation and Example\r\n- [Usage Guide](https://github.com/TSignalDev/tsignal-python/blob/main/docs/usage.md): Learn how to define signals/slots, manage threads, and structure your event-driven code.\r\n- [API Reference](https://github.com/TSignalDev/tsignal-python/blob/main/docs/api.md): Detailed documentation of classes, decorators, and functions.\r\n- [Examples](https://github.com/TSignalDev/tsignal-python/blob/main/docs/examples.md): Practical use cases, including UI integration, async operations, and worker pattern usage.\r\n- [Logging Guidelines](https://github.com/TSignalDev/tsignal-python/blob/main/docs/logging.md): Configure logging levels and handlers for debugging.\r\n- [Testing Guide](https://github.com/TSignalDev/tsignal-python/blob/main/docs/testing.md): earn how to run tests and contribute safely.\r\n\r\n## Logging\r\nConfigure logging to diagnose issues:\r\n\r\n```python\r\nimport logging\r\nlogging.getLogger('tsignal').setLevel(logging.DEBUG)\r\n```\r\n\r\nFor more details, see the [Logging Guidelines](https://github.com/TSignalDev/tsignal-python/blob/main/docs/logging.md).\r\n\r\n## Testing\r\n\r\nTSignal uses `pytest` for testing:\r\n\r\n```bash\r\n# Run all tests\r\npytest\r\n\r\n# Run with verbose output\r\npytest -v\r\n\r\n# Run specific test file\r\npytest tests/unit/test_signal.py\r\n```\r\n\r\nSee the [Testing Guide](https://github.com/TSignalDev/tsignal-python/blob/main/docs/testing.md) for more details.\r\n\r\n## Contributing\r\nWe welcome contributions. Please read the [Contributing Guidelines](https://github.com/TSignalDev/tsignal-python/blob/main/CONTRIBUTING.md) before submitting PRs.\r\n\r\n## License\r\nTSignal is licensed under the MIT License. See [LICENSE](https://github.com/TSignalDev/tsignal-python/blob/main/LICENSE) for details.\r\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A Python Signal-Slot library inspired by Qt",
    "version": "0.4.2",
    "project_urls": {
        "Documentation": "https://github.com/TSignalDev/tsignal-python#readme",
        "Homepage": "https://github.com/TSignalDev/tsignal-python",
        "Issues": "https://github.com/TSignalDev/tsignal-python/issues",
        "Repository": "https://github.com/TSignalDev/tsignal-python"
    },
    "split_keywords": [
        "signal-slot",
        " decorator",
        " multithreading",
        " asyncio"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f15e8bd352506e4118296fa5ebf6cfe0560634d1f496bd84dd2f47c0c3ca59ae",
                "md5": "ec8070d1b07c53ea328cdadb626235b0",
                "sha256": "29357944756d37f94b5168633f7c642f785ab196629f61554cc6f028b5be5410"
            },
            "downloads": -1,
            "filename": "tsignal-0.4.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ec8070d1b07c53ea328cdadb626235b0",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 19724,
            "upload_time": "2024-12-21T13:45:04",
            "upload_time_iso_8601": "2024-12-21T13:45:04.571317Z",
            "url": "https://files.pythonhosted.org/packages/f1/5e/8bd352506e4118296fa5ebf6cfe0560634d1f496bd84dd2f47c0c3ca59ae/tsignal-0.4.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9becd24e694152e0f67c396eeabf66f05d1a6159df4f3a389af40f513862d043",
                "md5": "8340e28f4cc87d08c8bdfe0edeffff37",
                "sha256": "cdd41704d881ce5eeed91cd8fee23c891d61909b5868891e46637fa474bc1adf"
            },
            "downloads": -1,
            "filename": "tsignal-0.4.2.tar.gz",
            "has_sig": false,
            "md5_digest": "8340e28f4cc87d08c8bdfe0edeffff37",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 21119,
            "upload_time": "2024-12-21T13:45:07",
            "upload_time_iso_8601": "2024-12-21T13:45:07.024400Z",
            "url": "https://files.pythonhosted.org/packages/9b/ec/d24e694152e0f67c396eeabf66f05d1a6159df4f3a389af40f513862d043/tsignal-0.4.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-12-21 13:45:07",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "TSignalDev",
    "github_project": "tsignal-python#readme",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "tsignal"
}
        
Elapsed time: 0.41394s