<!-- README.md -->
[![PyPI Version](https://img.shields.io/pypi/v/pynnex.svg)](https://pypi.org/project/pynnex/)
[![License](https://img.shields.io/github/license/nexconnectio/pynnex.svg)](https://github.com/nexconnectio/pynnex/blob/main/LICENSE)
[![Build Status](https://img.shields.io/github/actions/workflow/status/nexconnectio/pynnex/tests.yml?branch=main)](https://github.com/nexconnectio/pynnex/actions)
[![Downloads](https://img.shields.io/pypi/dm/pynnex)](https://pypi.org/project/pynnex/)
# PynneX
**Looking for a lightweight alternative to heavier event-driven, signal-slot, or concurrency libraries in Python?**
PynneX is a pure-Python (asyncio-based) library that streamlines event-driven concurrency without forcing you to adopt large frameworks or external dependencies.
---
## Why PynneX?
Modern Python applications often combine async I/O and multithreading. Many existing event libraries or frameworks can bring in extra dependencies or complexities, especially if you only need clean, concurrency-focused event handling. PynneX offers a **focused** approach:
- **Decorator-based emitters and listeners** for writing succinct, event-driven code
- **Built-in thread safety**—no need to manually handle locks or queues
- **Easy background tasks** via `@nx_with_worker` decorator
- **Asyncio integration**: either async or sync listeners work seamlessly
- **No external dependencies** beyond Python 3.10+ (for improved asyncio support)
**PynneX** can also serve as a **lightweight** alternative to more complex concurrency or distributed event frameworks, letting you scale from simple local threads up to multi-threaded or async scenarios without overhead.
---
## Key Features
- **Pure Python**: No external dependencies needed
- **Event Decorators**: `@nx_emitter` and `@nx_listener` for intuitive event-based design
- **Multiple Aliases Available**: Prefer different terminology?
- Use `@nx_signal` and `@nx_slot` if you like Qt-style signal-slots
- Use `@nx_publisher` and `@nx_subscriber` if you’re coming from a Pub/Sub background
- All aliases share the same underlying mechanics
- Use `@emitter`, `@listener`, `@signal`, `@slot`, `@publisher`, `@subscriber` interchangeably without prefix `nx_`
- **Thread-Safe**: Automatic cross-thread invocation ensures concurrency safety
- **asyncio-Friendly**: Support for both synchronous and asynchronous listeners
- **Background Workers**: `@nx_with_worker` provides a dedicated event loop in a separate thread
- **Weak Reference**: If you connect a listener with `weak=True`, the connection is removed automatically once the receiver is garbage-collected
### **Requires an Existing Event Loop**
PynneX depends on Python’s `asyncio`. You **must** have a running event loop (e.g., `asyncio.run(...)`) for certain features like async listeners or cross-thread calls.
If no event loop is running, PynneX raises a `RuntimeError` instead of creating one behind the scenes—this ensures predictable concurrency behavior.
## Installation
```bash
pip install pynnex
```
PynneX requires **Python 3.10+**, leveraging newer asyncio improvements.
Alternatively, clone from GitHub and install locally:
```bash
git clone https://github.com/nexconnectio/pynnex.git
cd pynnex
pip install -e .
```
For development (includes tests and linting tools):
```
pip install -e ".[dev]
```
## Quick Hello (Emitters/Listeners)
Here’s the simplest “Hello, Emitters/Listeners” example. Once installed, run the snippet below:
```python
# hello_pynnex.py
from pynnex import with_emitters, emitter, listener
@with_emitters
class Greeter:
@emitter
def greet(self):
"""Emitter emitted when greeting happens."""
pass
def say_hello(self):
self.greet.emit("Hello from PynneX!")
@with_emitters
class Printer:
@listener
def on_greet(self, message):
print(message)
greeter = Greeter()
printer = Printer()
# Connect the emitter to the listener
greeter.greet.connect(printer, printer.on_greet)
# Fire the emitter
greeter.say_hello()
```
**Output:**
```
Hello from PynneX!
```
By simply defining `emitter` and `listener`, you can set up intuitive event handling that also works smoothly in multithreaded contexts.
If you come from a Qt background or prefer “signal-slot” naming, use:
```python
from pynnex import with_signals, signal, slot
@with_signals
class Greeter:
@signal
def greet(self):
"""Signal that fires a greeting event."""
pass
def say_hello(self):
self.greet.emit("Hello from PynneX!")
@with_signals
class Printer:
@slot
def on_greet(self, message):
print(f"Received: {message}")
```
If you prefer a Pub/Sub style, use:
```python
from pynnex import with_signals, signal, slot
@with_publishers
class Greeter:
@publisher
def greet(self):
"""Publisher that fires a greeting event."""
pass
def say_hello(self):
self.greet.emit("Hello from PynneX!")
@with_subscribers
class Printer:
@subscriber
def on_greet(self, message):
print(f"Received: {message}")
```
They’re all interchangeable aliases pointing to the same core functionality.
---
## Usage & Examples
Below are some brief examples. For more, see the [docs/](https://github.com/nexconnectio/pynnex/blob/main/docs/) directory.
### Basic Counter & Display
```python
from pynnex import with_emitters, emitter, listener
@with_emitters
class Counter:
def __init__(self):
self.count = 0
@emitter
def count_changed(self):
pass
def increment(self):
self.count += 1
self.count_changed.emit(self.count)
@with_emitters
class Display:
@listener
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 Listener Example
```python
@with_emitters
class AsyncDisplay:
@listener
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
### Emitters and Listeners
- Emitters: Declared with `@nx_emitter`. Emitters are attributes of a class that can be emitted to notify interested parties.
- Listeners: Declared with `@nx_listener`. Listeners are methods that respond to emitters. Listeners can be synchronous or async functions.
- Connections: Use `emitter.connect(receiver, listener)` to link emitters to listeners. Connections can also be made directly to functions or lambdas.
### Thread Safety and Connection Types
PynneX automatically detects whether the emitter emission and listener execution occur in the same thread or different threads:
- **Auto Connection**: When connection_type is AUTO_CONNECTION (default), PynneX checks whether the listener is a coroutine function or whether the caller and callee share the same thread affinity. If they are the same thread and listener is synchronous, it uses direct connection. Otherwise, it uses queued connection.
- **Direct Connection**: If emitter and listener share the same thread affinity, the listener is invoked directly.
- **Queued Connection**: If they differ, the call is queued to the listener’s thread/event loop, ensuring thread safety.
This mechanism frees you from manually dispatching calls across threads.
### Thread-Safe Properties
The `@nx_property` decorator provides thread-safe property access with automatic emitter emission:
```python
@with_emitters
class Example:
def __init__(self):
super().__init__()
self._data = None
@emitter
def updated(self):
"""Emitter emitted when data changes."""
pass
@nx_property(notify=updated)
def data(self):
"""Thread-safe property with change notification."""
return self._data
@data.setter
def data(self, value):
self._data = value
e = Example()
e.data = 42 # Thread-safe property set; emits 'updated' emitter on change
```
### Worker Threads
For background work, PynneX provides a `@nx_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 emitters and listeners for thread-safe updates to the main
**Worker Example**
```python
from pynnex import nx_with_worker, emitter
@with_worker
class DataProcessor:
@emitter
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 emitter
await self.wait_for_stop()
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/nexconnectio/pynnex/blob/main/docs/usage.md): Learn how to define emitters/listeners, manage threads, and structure your event-driven code.
- [API Reference](https://github.com/nexconnectio/pynnex/blob/main/docs/api.md): Detailed documentation of classes, decorators, and functions.
- [Examples](https://github.com/nexconnectio/pynnex/blob/main/docs/examples.md): Practical use cases, including UI integration, async operations, and worker pattern usage.
- [Logging Guidelines](https://github.com/nexconnectio/pynnex/blob/main/docs/logging.md): Configure logging levels and handlers for debugging.
- [Testing Guide](https://github.com/nexconnectio/pynnex/blob/main/docs/testing.md): earn how to run tests and contribute safely.
## Logging
Configure logging to diagnose issues:
```python
import logging
logging.getLogger('pynnex').setLevel(logging.DEBUG)
```
For more details, see the [Logging Guidelines](https://github.com/nexconnectio/pynnex/blob/main/docs/logging.md).
## Testing
PynneX uses `pytest` for testing:
```bash
# Run all tests
pytest
# Run with verbose output
pytest -v
# Run specific test file
pytest tests/unit/test_emitter.py
```
See the [Testing Guide](https://github.com/nexconnectio/pynnex/blob/main/docs/testing.md) for more details.
## Contributing
We welcome contributions! Please read our [Contributing Guidelines](https://github.com/nexconnectio/pynnex/blob/main/CONTRIBUTING.md) before submitting PRs.
## Sponsorship & Donations
If PynneX has helped simplify your async/multithreaded workflows, please consider [sponsoring us](https://github.com/nexconnectio/pynnex/blob/main/.github/FUNDING.yml). All funds go toward infrastructure, documentation, and future development.
Please note that financial contributions support only the project's maintenance and do not grant financial rewards to individual contributors.
## License
`PynneX` is licensed under the MIT License. See [LICENSE](https://github.com/nexconnectio/pynnex/blob/main/LICENSE) for details.
Raw data
{
"_id": null,
"home_page": null,
"name": "PynneX",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "emitter, listener, signals, slots, signal-slot, decorator, asyncio, thread-safe, multithreading, pubsub, publisher, subscriber",
"author": null,
"author_email": "San Kim <nexconnect.io@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/ed/b2/b7d41306f021230f8e874feab29857dfa22dc9bdf8f39bd0a1601ba6e7c1/pynnex-1.1.0.tar.gz",
"platform": null,
"description": "<!-- README.md -->\r\n\r\n[![PyPI Version](https://img.shields.io/pypi/v/pynnex.svg)](https://pypi.org/project/pynnex/)\r\n[![License](https://img.shields.io/github/license/nexconnectio/pynnex.svg)](https://github.com/nexconnectio/pynnex/blob/main/LICENSE)\r\n[![Build Status](https://img.shields.io/github/actions/workflow/status/nexconnectio/pynnex/tests.yml?branch=main)](https://github.com/nexconnectio/pynnex/actions)\r\n[![Downloads](https://img.shields.io/pypi/dm/pynnex)](https://pypi.org/project/pynnex/)\r\n\r\n# PynneX\r\n\r\n**Looking for a lightweight alternative to heavier event-driven, signal-slot, or concurrency libraries in Python?** \r\nPynneX is a pure-Python (asyncio-based) library that streamlines event-driven concurrency without forcing you to adopt large frameworks or external dependencies.\r\n\r\n---\r\n\r\n## Why PynneX?\r\n\r\nModern Python applications often combine async I/O and multithreading. Many existing event libraries or frameworks can bring in extra dependencies or complexities, especially if you only need clean, concurrency-focused event handling. PynneX offers a **focused** approach:\r\n\r\n- **Decorator-based emitters and listeners** for writing succinct, event-driven code \r\n- **Built-in thread safety**\u2014no need to manually handle locks or queues \r\n- **Easy background tasks** via `@nx_with_worker` decorator \r\n- **Asyncio integration**: either async or sync listeners work seamlessly \r\n- **No external dependencies** beyond Python 3.10+ (for improved asyncio support) \r\n\r\n**PynneX** can also serve as a **lightweight** alternative to more complex concurrency or distributed event frameworks, letting you scale from simple local threads up to multi-threaded or async scenarios without overhead.\r\n\r\n---\r\n\r\n## Key Features\r\n\r\n- **Pure Python**: No external dependencies needed\r\n- **Event Decorators**: `@nx_emitter` and `@nx_listener` for intuitive event-based design\r\n- **Multiple Aliases Available**: Prefer different terminology?\r\n - Use `@nx_signal` and `@nx_slot` if you like Qt-style signal-slots\r\n - Use `@nx_publisher` and `@nx_subscriber` if you\u2019re coming from a Pub/Sub background\r\n - All aliases share the same underlying mechanics\r\n - Use `@emitter`, `@listener`, `@signal`, `@slot`, `@publisher`, `@subscriber` interchangeably without prefix `nx_`\r\n- **Thread-Safe**: Automatic cross-thread invocation ensures concurrency safety\r\n- **asyncio-Friendly**: Support for both synchronous and asynchronous listeners\r\n- **Background Workers**: `@nx_with_worker` provides a dedicated event loop in a separate thread\r\n- **Weak Reference**: If you connect a listener with `weak=True`, the connection is removed automatically once the receiver is garbage-collected\r\n\r\n### **Requires an Existing Event Loop**\r\n\r\nPynneX depends on Python\u2019s `asyncio`. You **must** have a running event loop (e.g., `asyncio.run(...)`) for certain features like async listeners or cross-thread calls. \r\nIf no event loop is running, PynneX raises a `RuntimeError` instead of creating one behind the scenes\u2014this ensures predictable concurrency behavior.\r\n\r\n## Installation\r\n\r\n```bash\r\npip install pynnex\r\n```\r\n\r\nPynneX requires **Python 3.10+**, leveraging newer asyncio improvements.\r\nAlternatively, clone from GitHub and install locally: \r\n\r\n```bash\r\ngit clone https://github.com/nexconnectio/pynnex.git\r\ncd pynnex\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 Hello (Emitters/Listeners)\r\n\r\nHere\u2019s the simplest \u201cHello, Emitters/Listeners\u201d example. Once installed, run the snippet below:\r\n\r\n```python\r\n# hello_pynnex.py\r\nfrom pynnex import with_emitters, emitter, listener\r\n\r\n@with_emitters\r\nclass Greeter:\r\n @emitter\r\n def greet(self):\r\n \"\"\"Emitter emitted when greeting happens.\"\"\"\r\n pass\r\n\r\n def say_hello(self):\r\n self.greet.emit(\"Hello from PynneX!\")\r\n\r\n@with_emitters\r\nclass Printer:\r\n @listener\r\n def on_greet(self, message):\r\n print(message)\r\n\r\ngreeter = Greeter()\r\nprinter = Printer()\r\n\r\n# Connect the emitter to the listener\r\ngreeter.greet.connect(printer, printer.on_greet)\r\n\r\n# Fire the emitter\r\ngreeter.say_hello()\r\n```\r\n\r\n**Output:**\r\n```\r\nHello from PynneX!\r\n```\r\n\r\nBy simply defining `emitter` and `listener`, you can set up intuitive event handling that also works smoothly in multithreaded contexts.\r\n\r\nIf you come from a Qt background or prefer \u201csignal-slot\u201d naming, use:\r\n\r\n```python\r\nfrom pynnex import with_signals, signal, slot\r\n\r\n@with_signals\r\nclass Greeter:\r\n @signal\r\n def greet(self):\r\n \"\"\"Signal that fires a greeting event.\"\"\"\r\n pass\r\n\r\n def say_hello(self):\r\n self.greet.emit(\"Hello from PynneX!\")\r\n\r\n@with_signals\r\nclass Printer:\r\n @slot\r\n def on_greet(self, message):\r\n print(f\"Received: {message}\")\r\n```\r\n\r\nIf you prefer a Pub/Sub style, use:\r\n\r\n```python\r\nfrom pynnex import with_signals, signal, slot\r\n\r\n@with_publishers\r\nclass Greeter:\r\n @publisher\r\n def greet(self):\r\n \"\"\"Publisher that fires a greeting event.\"\"\"\r\n pass\r\n\r\n def say_hello(self):\r\n self.greet.emit(\"Hello from PynneX!\")\r\n\r\n@with_subscribers\r\nclass Printer:\r\n @subscriber\r\n def on_greet(self, message):\r\n print(f\"Received: {message}\")\r\n```\r\n\r\nThey\u2019re all interchangeable aliases pointing to the same core functionality.\r\n\r\n---\r\n\r\n## Usage & Examples\r\n\r\nBelow are some brief examples. For more, see the [docs/](https://github.com/nexconnectio/pynnex/blob/main/docs/) directory.\r\n\r\n### Basic Counter & Display\r\n```python\r\nfrom pynnex import with_emitters, emitter, listener\r\n\r\n@with_emitters\r\nclass Counter:\r\n def __init__(self):\r\n self.count = 0\r\n \r\n @emitter\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@with_emitters\r\nclass Display:\r\n @listener\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 Listener Example\r\n```python\r\n@with_emitters\r\nclass AsyncDisplay:\r\n @listener\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### Emitters and Listeners\r\n- Emitters: Declared with `@nx_emitter`. Emitters are attributes of a class that can be emitted to notify interested parties.\r\n- Listeners: Declared with `@nx_listener`. Listeners are methods that respond to emitters. Listeners can be synchronous or async functions.\r\n- Connections: Use `emitter.connect(receiver, listener)` to link emitters to listeners. Connections can also be made directly to functions or lambdas.\r\n\r\n### Thread Safety and Connection Types\r\nPynneX automatically detects whether the emitter emission and listener execution occur in the same thread or different threads:\r\n\r\n- **Auto Connection**: When connection_type is AUTO_CONNECTION (default), PynneX checks whether the listener is a coroutine function or whether the caller and callee share the same thread affinity. If they are the same thread and listener is synchronous, it uses direct connection. Otherwise, it uses queued connection.\r\n- **Direct Connection**: If emitter and listener share the same thread affinity, the listener is invoked directly.\r\n- **Queued Connection**: If they differ, the call is queued to the listener\u2019s thread/event loop, ensuring thread safety.\r\n\r\nThis mechanism frees you from manually dispatching calls across threads.\r\n\r\n### Thread-Safe Properties\r\nThe `@nx_property` decorator provides thread-safe property access with automatic emitter emission:\r\n\r\n```python\r\n@with_emitters\r\nclass Example:\r\n def __init__(self):\r\n super().__init__()\r\n self._data = None\r\n \r\n @emitter\r\n def updated(self):\r\n \"\"\"Emitter emitted when data changes.\"\"\"\r\n pass\r\n \r\n @nx_property(notify=updated)\r\n def data(self):\r\n \"\"\"Thread-safe property with change notification.\"\"\"\r\n return self._data\r\n \r\n @data.setter\r\n def data(self, value):\r\n self._data = value\r\n\r\ne = Example()\r\ne.data = 42 # Thread-safe property set; emits 'updated' emitter on change\r\n```\r\n\r\n### Worker Threads\r\nFor background work, PynneX provides a `@nx_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 emitters and listeners for thread-safe updates to the main \r\n\r\n**Worker Example**\r\n```python\r\nfrom pynnex import nx_with_worker, emitter\r\n\r\n@with_worker\r\nclass DataProcessor:\r\n @emitter\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 emitter\r\n await self.wait_for_stop()\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/nexconnectio/pynnex/blob/main/docs/usage.md): Learn how to define emitters/listeners, manage threads, and structure your event-driven code.\r\n- [API Reference](https://github.com/nexconnectio/pynnex/blob/main/docs/api.md): Detailed documentation of classes, decorators, and functions.\r\n- [Examples](https://github.com/nexconnectio/pynnex/blob/main/docs/examples.md): Practical use cases, including UI integration, async operations, and worker pattern usage.\r\n- [Logging Guidelines](https://github.com/nexconnectio/pynnex/blob/main/docs/logging.md): Configure logging levels and handlers for debugging.\r\n- [Testing Guide](https://github.com/nexconnectio/pynnex/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('pynnex').setLevel(logging.DEBUG)\r\n```\r\n\r\nFor more details, see the [Logging Guidelines](https://github.com/nexconnectio/pynnex/blob/main/docs/logging.md).\r\n\r\n## Testing\r\n\r\nPynneX 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_emitter.py\r\n```\r\n\r\nSee the [Testing Guide](https://github.com/nexconnectio/pynnex/blob/main/docs/testing.md) for more details.\r\n\r\n## Contributing\r\nWe welcome contributions! Please read our [Contributing Guidelines](https://github.com/nexconnectio/pynnex/blob/main/CONTRIBUTING.md) before submitting PRs.\r\n\r\n## Sponsorship & Donations\r\nIf PynneX has helped simplify your async/multithreaded workflows, please consider [sponsoring us](https://github.com/nexconnectio/pynnex/blob/main/.github/FUNDING.yml). All funds go toward infrastructure, documentation, and future development.\r\n\r\nPlease note that financial contributions support only the project's maintenance and do not grant financial rewards to individual contributors.\r\n\r\n## License\r\n`PynneX` is licensed under the MIT License. See [LICENSE](https://github.com/nexconnectio/pynnex/blob/main/LICENSE) for details.\r\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A Python Emitter-Listener library",
"version": "1.1.0",
"project_urls": {
"Documentation": "https://github.com/nexconnectio/pynnex#readme",
"Homepage": "https://github.com/nexconnectio/pynnex",
"Issues": "https://github.com/nexconnectio/pynnex/issues",
"Repository": "https://github.com/nexconnectio/pynnex"
},
"split_keywords": [
"emitter",
" listener",
" signals",
" slots",
" signal-slot",
" decorator",
" asyncio",
" thread-safe",
" multithreading",
" pubsub",
" publisher",
" subscriber"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "130db10bcf48b6a3c6a05b7dbd32d680dd6341f3dc1f18a14ecc5850ccdef3b5",
"md5": "3c90ae863fef2fffa326c73baaf556da",
"sha256": "348ca1ce18796f16f7448ef088887b7a9590c28a9ca6ee91081cd0eb66c0c46f"
},
"downloads": -1,
"filename": "PynneX-1.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "3c90ae863fef2fffa326c73baaf556da",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 20424,
"upload_time": "2025-01-16T04:50:51",
"upload_time_iso_8601": "2025-01-16T04:50:51.174034Z",
"url": "https://files.pythonhosted.org/packages/13/0d/b10bcf48b6a3c6a05b7dbd32d680dd6341f3dc1f18a14ecc5850ccdef3b5/PynneX-1.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "edb2b7d41306f021230f8e874feab29857dfa22dc9bdf8f39bd0a1601ba6e7c1",
"md5": "474902fb23bfb217279fd177ce4ec28e",
"sha256": "77fe1cf37e5e4bf419f110a195dbb43226f9d4dba6e04e928a28014b32cd8bb9"
},
"downloads": -1,
"filename": "pynnex-1.1.0.tar.gz",
"has_sig": false,
"md5_digest": "474902fb23bfb217279fd177ce4ec28e",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 19409,
"upload_time": "2025-01-16T04:50:55",
"upload_time_iso_8601": "2025-01-16T04:50:55.856304Z",
"url": "https://files.pythonhosted.org/packages/ed/b2/b7d41306f021230f8e874feab29857dfa22dc9bdf8f39bd0a1601ba6e7c1/pynnex-1.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-01-16 04:50:55",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "nexconnectio",
"github_project": "pynnex#readme",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pynnex"
}