From 485bf412ce803416ddcec1b21e79f718470e98ac Mon Sep 17 00:00:00 2001 From: nexconnectio Date: Sun, 5 Jan 2025 00:14:58 +0900 Subject: [PATCH] Refactor/nx prefix and move to thread (#2) * refactor: remove nx_ prefix from public API imports This commit removes the nx_ prefix from public API imports to maintain consistency with decorator usage in examples and tests. The change was prompted by an inconsistency in the README where nx_signal was imported but used as @signal. Changes include: - Update imports in tests to use unprefixed versions (signal, with_signals, etc.) - Keep nx_ prefix in internal implementation files (core.py, etc.) - Update README examples to use consistent import style - Add move_to_thread documentation to api.md - Add emit documentation to signal decorator in api.md - Add start/stop/queue_task documentation to worker decorator in api.md The change makes the API more intuitive while maintaining the internal naming convention for better code organization. * Bump version to 1.0.2 for release --------- Co-authored-by: San --- CHANGELOG.md | 8 ++ LICENCSE.md => LICENSE.md | 0 README.md | 2 +- docs/api.md | 67 +++++++++ pyproject.toml | 2 +- src/pynnex/__init__.py | 10 +- .../contrib/patterns/worker/decorators.py | 81 +++++++++-- src/pynnex/core.py | 6 + tests/conftest.py | 12 +- tests/integration/test_thread_safety.py | 6 +- tests/integration/test_threading.py | 10 +- tests/integration/test_worker.py | 3 +- tests/integration/test_worker_queue.py | 7 +- tests/integration/test_worker_signal.py | 9 +- tests/performance/test_memory.py | 10 +- tests/performance/test_stress.py | 10 +- tests/unit/test_move_to_thread.py | 130 ++++++++++++++++++ tests/unit/test_property.py | 11 +- tests/unit/test_signal.py | 40 +++--- tests/unit/test_slot.py | 12 +- tests/unit/test_weak.py | 10 +- 21 files changed, 362 insertions(+), 84 deletions(-) rename LICENCSE.md => LICENSE.md (100%) create mode 100644 tests/unit/test_move_to_thread.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c7356..99c9c40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.2] - 2025-01-04 + +### Changed +- Removed nx_ prefix from public API imports for better consistency: + - Updated imports in examples and tests to use unprefixed versions (signal, with_signals, etc.) + - Maintained nx_ prefix in internal implementation files + - Improved API documentation with detailed method descriptions + ## [1.0.1] - 2025-01-02 ### Changed diff --git a/LICENCSE.md b/LICENSE.md similarity index 100% rename from LICENCSE.md rename to LICENSE.md diff --git a/README.md b/README.md index 4db73bf..1f90f13 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ For background work, PynneX provides a `@nx_with_worker` decorator that: **Worker Example** ```python -from pynnex import nx_with_worker, nx_signal +from pynnex import nx_with_worker, signal @with_worker class DataProcessor: diff --git a/docs/api.md b/docs/api.md index 78cdb60..368f725 100644 --- a/docs/api.md +++ b/docs/api.md @@ -23,6 +23,45 @@ Enables signal-slot functionality on a class. Classes decorated with `@nx_with_s **Important**: `@nx_with_signals` expects that you already have an `asyncio` event loop running (e.g., via `asyncio.run(...)`) unless you only rely on synchronous slots in a single-thread scenario. When in doubt, wrap your main logic in an async function and call `asyncio.run(main())`. +**Key Methods**: + +`move_to_thread(target_worker)` +Moves the instance to another thread by copying thread affinity from a worker. This allows dynamic thread reassignment of signal-slot objects. + +- **Parameters:** + - **target_worker:** A worker instance decorated with `@nx_with_worker`. The instance will adopt this worker's thread affinity. +- **Raises:** + - **RuntimeError:** If the target worker's thread is not started. + - **TypeError:** If the target is not compatible (not decorated with `@nx_with_worker`). + +**Example:** +```python +@nx_with_worker +class Worker: + async def run(self): + await self.wait_for_stop() + +@nx_with_signals +class SignalEmitter: + @nx_signal + def value_changed(self): + pass + +worker = Worker() +worker.start() + +emitter = SignalEmitter() +emitter.move_to_thread(worker) # Now emitter runs in worker's thread +``` + +**Usage:** +```python +@nx_with_signals +class MyClass: + @nx_signal + def my_signal(self): + pass + **Usage:** ```python @nx_with_signals @@ -35,6 +74,15 @@ class MyClass: ### `@nx_signal` Defines a signal within a class that has `@nx_with_signals`. Signals are callable attributes that, when emitted, notify all connected slots. +**Key Methods**: + +`emit(*args, **kwargs)` +Emits the signal, invoking all connected slots with the provided arguments. + +- **Parameters:** + - ***args:** Positional arguments to pass to the connected slots. + - ****kwargs:** Keyword arguments to pass to the connected slots. + **Usage:** ```python @@ -72,6 +120,25 @@ Decorates a class to run inside a dedicated worker thread with its own event loo - The `run(*args, **kwargs)` coroutine as the main entry point. - A built-in async task queue via `queue_task`. +**Key Methods**: + +`start(*args, **kwargs)` +Starts the worker thread and its event loop. +- **Parameters:** + - ***args:** Positional arguments passed to the worker's `run()` method. + - ****kwargs:** Keyword arguments passed to the worker's `run()` method. + +`stop()` +Stops the worker thread and its event loop gracefully. +- Cancels any running tasks and waits for the thread to finish. + +`queue_task(coro)` +Schedules a coroutine to run on the worker's event loop. +- **Parameters:** + - **coro:** A coroutine object to be executed in the worker thread. +- **Raises:** + - **RuntimeError:** If the worker is not started. + **Key Points:** `run(*args, **kwargs)` is an async method that you can define to perform long-running operations or await a stopping event. diff --git a/pyproject.toml b/pyproject.toml index fc47fdf..5aea1ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pynnex" -version = "1.0.1" +version = "1.0.2" description = "A Python Signal-Slot library" readme = "README.md" requires-python = ">=3.10" diff --git a/src/pynnex/__init__.py b/src/pynnex/__init__.py index 30f904f..cd0bbee 100644 --- a/src/pynnex/__init__.py +++ b/src/pynnex/__init__.py @@ -8,7 +8,10 @@ nx_slot, nx_graceful_shutdown, NxConnectionType, - NxSignalConstants + NxConnection, + NxSignalConstants, + NxSignal, + _determine_connection_type ) from .utils import nx_log_and_raise_error from .contrib.patterns.worker.decorators import nx_with_worker @@ -29,5 +32,8 @@ 'nx_log_and_raise_error', 'nx_graceful_shutdown', 'NxConnectionType', - 'NxSignalConstants' + 'NxConnection', + 'NxSignalConstants', + 'NxSignal', + '_determine_connection_type' ] diff --git a/src/pynnex/contrib/patterns/worker/decorators.py b/src/pynnex/contrib/patterns/worker/decorators.py index e56aab7..8e7a45d 100644 --- a/src/pynnex/contrib/patterns/worker/decorators.py +++ b/src/pynnex/contrib/patterns/worker/decorators.py @@ -15,7 +15,7 @@ class has the required asynchronous `initialize` and `finalize` methods, import inspect import logging import threading -from pynnex.core import nx_signal +from pynnex.core import nx_signal, NxSignalConstants logger = logging.getLogger(__name__) @@ -185,7 +185,27 @@ async def start_queue(self): ) def queue_task(self, coro): - """Method to add a task to the queue""" + """ + Schedules a coroutine to run on the worker's event loop in a thread-safe manner. + + Parameters + ---------- + coro : coroutine + A coroutine object to be executed in the worker thread. + + Raises + ------ + RuntimeError + If the worker is not started. + ValueError + If the provided argument is not a coroutine object. + + Notes + ----- + - Thread-safe: Can be called from any thread. + - Tasks are processed in FIFO order. + - Failed tasks are logged but don't stop queue processing. + """ if not asyncio.iscoroutine(coro): logger.error( @@ -200,7 +220,23 @@ def queue_task(self, coro): loop.call_soon_threadsafe(lambda: self._nx_task_queue.put_nowait(coro)) def start(self, *args, **kwargs): - """Start the worker thread.""" + """ + Starts the worker thread and its event loop, initializing the worker's processing environment. + + Parameters + ---------- + *args : Any + Positional arguments passed to the worker's `run()` method. + **kwargs : Any + Keyword arguments passed to the worker's `run()` method. + - run_coro: Optional coroutine to run instead of the default `run()` method. + + Notes + ----- + - Creates a new thread with its own event loop. + - Automatically starts task queue processing if no `run()` method is defined. + - Emits `started` signal when initialization is complete. + """ run_coro = kwargs.pop(_WorkerConstants.RUN_CORO, None) @@ -277,7 +313,16 @@ async def runner(): self._nx_thread.start() def stop(self): - """Stop the worker thread.""" + """ + Gracefully stops the worker thread and its event loop. + + Notes + ----- + - Cancels any running tasks including the main `run()` coroutine. + - Waits for task queue to finish processing. + - Emits `stopped` signal before final cleanup. + - Thread is joined with a 2-second timeout. + """ logger.debug("[WorkerClass][stop] Starting worker shutdown") @@ -297,12 +342,28 @@ def stop(self): self._nx_loop = None self._nx_thread = None - def move_to_thread(self, target): + def _copy_affinity(self, target): """ - Move target object to this worker's thread and loop. - target must be an object created by nx_with_signals or nx_with_worker, - and the worker must be started with start() method. + Copy this worker's thread affinity (thread, loop, and affinity object) to the target. + This is an internal method used by move_to_thread() and should not be called directly. + + The method copies thread, event loop, and affinity object references from this worker + to the target object, effectively moving the target to this worker's thread context. + + Parameters + ---------- + target : object + Target object that will receive this worker's thread affinity. + Must be decorated with @nx_with_signals or @nx_with_worker. + + Raises + ------ + RuntimeError + If the worker thread is not started. + TypeError + If the target is not compatible (not decorated with @nx_with_signals or @nx_with_worker). """ + with self._nx_lifecycle_lock: if not self._nx_thread or not self._nx_loop: raise RuntimeError( @@ -312,8 +373,8 @@ def move_to_thread(self, target): # Assume target is initialized with nx_with_signals # Reset target's _nx_thread, _nx_loop, _nx_affinity - if not hasattr(target, "_nx_thread") or not hasattr( - target, "_nx_loop" + if not hasattr(target, NxSignalConstants.THREAD) or not hasattr( + target, NxSignalConstants.LOOP ): raise TypeError( "[WorkerClass][move_to_thread] Target is not compatible. " diff --git a/src/pynnex/core.py b/src/pynnex/core.py index af6f527..abbcea8 100644 --- a/src/pynnex/core.py +++ b/src/pynnex/core.py @@ -935,7 +935,13 @@ def __init__(self, *args, **kwargs): # Call the original __init__ original_init(self, *args, **kwargs) + def move_to_thread(self, target_thread): + """Change thread affinity of the instance to targetThread""" + + target_thread._copy_affinity(self) + cls.__init__ = __init__ + cls.move_to_thread = move_to_thread return cls diff --git a/tests/conftest.py b/tests/conftest.py index b527183..7b8cd99 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,17 +17,17 @@ import logging import pytest import pytest_asyncio -from pynnex import nx_with_signals, nx_signal, nx_slot +from pynnex import with_signals, signal, slot # Only creating the logger without configuration logger = logging.getLogger(__name__) -@nx_with_signals +@with_signals class Sender: """Sender class""" - @nx_signal + @signal def value_changed(self, value): """Signal for value changes""" @@ -36,7 +36,7 @@ def emit_value(self, value): self.value_changed.emit(value) -@nx_with_signals +@with_signals class Receiver: """Receiver class""" @@ -50,7 +50,7 @@ def __init__(self): self.id = id(self) logger.info("Created Receiver[%d]", self.id) - @nx_slot + @slot async def on_value_changed(self, value: int): """Slot for value changes""" logger.info( @@ -67,7 +67,7 @@ async def on_value_changed(self, value: int): self.received_count, ) - @nx_slot + @slot def on_value_changed_sync(self, value: int): """Sync slot for value changes""" logger.info( diff --git a/tests/integration/test_thread_safety.py b/tests/integration/test_thread_safety.py index 10d502a..4d842f0 100644 --- a/tests/integration/test_thread_safety.py +++ b/tests/integration/test_thread_safety.py @@ -9,16 +9,16 @@ import unittest import threading import gc -from pynnex.core import nx_with_signals, nx_signal +from pynnex import with_signals, signal -@nx_with_signals +@with_signals class SafeSender: """ A class that sends events. """ - @nx_signal + @signal def event(self, value): """ Event signal. diff --git a/tests/integration/test_threading.py b/tests/integration/test_threading.py index acfa6cd..747d7a0 100644 --- a/tests/integration/test_threading.py +++ b/tests/integration/test_threading.py @@ -12,7 +12,7 @@ import time import logging import pytest -from pynnex.core import nx_with_signals, nx_signal, nx_slot +from pynnex import with_signals, signal, slot logger = logging.getLogger(__name__) @@ -77,15 +77,15 @@ async def call_slot(): async def test_connection_type_with_different_threads(): """Test connection type is determined correctly for different thread scenarios""" - @nx_with_signals + @with_signals class Sender: """Sender class""" - @nx_signal + @signal def value_changed(self): """Signal emitted when value changes""" - @nx_with_signals + @with_signals class Receiver: """Receiver class""" @@ -93,7 +93,7 @@ def __init__(self): super().__init__() self.received = False - @nx_slot + @slot def on_value_changed(self, value): """Slot called when value changes""" diff --git a/tests/integration/test_worker.py b/tests/integration/test_worker.py index af17443..0d56e47 100644 --- a/tests/integration/test_worker.py +++ b/tests/integration/test_worker.py @@ -11,7 +11,6 @@ import asyncio import logging import pytest -from pynnex.contrib.patterns.worker.decorators import nx_with_worker from pynnex import NxSignalConstants, with_worker logger = logging.getLogger(__name__) @@ -27,7 +26,7 @@ async def worker(): if getattr(w, NxSignalConstants.THREAD, None) and w._nx_thread.is_alive(): w.stop() -@nx_with_worker +@with_worker class TestWorker: """Test worker class""" diff --git a/tests/integration/test_worker_queue.py b/tests/integration/test_worker_queue.py index 379cf2b..9dd0312 100644 --- a/tests/integration/test_worker_queue.py +++ b/tests/integration/test_worker_queue.py @@ -10,8 +10,7 @@ import asyncio import logging import pytest -from pynnex import nx_signal -from pynnex.contrib.patterns.worker.decorators import nx_with_worker +from pynnex import signal, with_worker logger = logging.getLogger(__name__) @@ -27,7 +26,7 @@ async def queue_worker(): w.stop() -@nx_with_worker +@with_worker class QueueWorker: """Queue worker class""" @@ -147,7 +146,7 @@ async def test_mixed_signal_and_queue(queue_worker): """Test for simultaneous use of signals and task queue""" # Add a signal - @nx_signal + @signal def task_completed(): pass diff --git a/tests/integration/test_worker_signal.py b/tests/integration/test_worker_signal.py index df506e7..1ed322f 100644 --- a/tests/integration/test_worker_signal.py +++ b/tests/integration/test_worker_signal.py @@ -12,8 +12,7 @@ import asyncio import logging import pytest -from pynnex.contrib.patterns.worker.decorators import nx_with_worker -from pynnex import nx_signal, NxSignalConstants +from pynnex import with_worker, signal, NxSignalConstants logger = logging.getLogger(__name__) @@ -37,7 +36,7 @@ async def signal_worker(): w.stop() -@nx_with_worker +@with_worker class SignalWorker: """Signal worker class""" @@ -45,11 +44,11 @@ def __init__(self): self.value = None super().__init__() - @nx_signal + @signal def worker_event(self): """Signal emitted when the worker event occurs""" - @nx_signal + @signal def value_changed(self): """Signal emitted when the value changes""" diff --git a/tests/performance/test_memory.py b/tests/performance/test_memory.py index 082470d..eaa7bbf 100644 --- a/tests/performance/test_memory.py +++ b/tests/performance/test_memory.py @@ -10,25 +10,25 @@ """ import pytest -from pynnex import nx_with_signals, nx_signal, nx_slot +from pynnex import with_signals, signal, slot def create_complex_signal_chain(): """Create a complex signal chain""" - @nx_with_signals + @with_signals class Sender: """Sender class""" - @nx_signal + @signal def signal(self): """Signal method""" - @nx_with_signals + @with_signals class Receiver: """Receiver class""" - @nx_slot + @slot def slot(self, value): """Slot method""" diff --git a/tests/performance/test_stress.py b/tests/performance/test_stress.py index 65ba87f..bdc3629 100644 --- a/tests/performance/test_stress.py +++ b/tests/performance/test_stress.py @@ -11,7 +11,7 @@ import asyncio import logging import pytest -from pynnex import nx_with_signals, nx_signal, nx_slot, nx_graceful_shutdown +from pynnex import with_signals, signal, slot, nx_graceful_shutdown logger = logging.getLogger(__name__) @@ -35,19 +35,19 @@ async def graceful_shutdown(): async def test_heavy_signal_load(): """Test heavy signal load""" - @nx_with_signals + @with_signals class Sender: """Sender class""" - @nx_signal + @signal def signal(self): """Signal method""" - @nx_with_signals + @with_signals class Receiver: """Receiver class""" - @nx_slot + @slot async def slot(self): """Slot method""" await asyncio.sleep(0.001) diff --git a/tests/unit/test_move_to_thread.py b/tests/unit/test_move_to_thread.py new file mode 100644 index 0000000..9539c03 --- /dev/null +++ b/tests/unit/test_move_to_thread.py @@ -0,0 +1,130 @@ +# tests/unit/test_move_to_thread.py + +# pylint: disable=no-member +# pylint: disable=unnecessary-lambda +# pylint: disable=useless-with-lock +# pylint: disable=unused-argument +# pylint: disable=redefined-outer-name +# pylint: disable=import-outside-toplevel + +""" +Move to thread test. +""" + +import asyncio +import logging +import threading +import pytest +from pynnex import signal, slot, with_signals, with_worker + +logger = logging.getLogger(__name__) + + +@with_worker +class WorkerA: + """First worker thread.""" + + async def run(self, *args, **kwargs): + """Run the worker thread.""" + + logger.info("[WorkerA] run() started") + await self.start_queue() + + +@with_worker +class WorkerB: + """Second worker thread.""" + + async def run(self, *args, **kwargs): + """Run the worker thread.""" + + logger.info("[WorkerB] run() started") + await self.start_queue() + + +@with_signals +class Mover: + """ + move_to_thread test object. + Created in main thread, then moved to WorkerA -> WorkerB, + to check signal behavior. + """ + + @signal + def data_ready(self, value): + """Signal for data ready.""" + + def __init__(self): + super().__init__() + self.emitted_values = [] + + def do_work(self, value): + """ + Assume some work is done in a separate thread (or main thread), + and emit a signal. + """ + + logger.info("[Mover][do_work] value=%s (thread=%s)", value, threading.current_thread().name) + self.data_ready.emit(value) + + @slot + def on_data_ready(self, value): + """ + Slot for data_ready signal. + """ + + logger.info("[Mover][on_data_ready] value=%s (thread=%s)", value, threading.current_thread().name) + self.emitted_values.append(value) + + +@pytest.mark.asyncio +async def test_move_to_thread(): + """ + 1) Create Mover object in main thread + 2) Move to WorkerA thread + 3) Move to WorkerB thread + Check if signal is emitted/received correctly in each step + """ + + logger.info("=== test_move_to_thread START ===") + + # 1) Create Mover object in main thread + mover = Mover() + + # Connect signal to mover's on_data_ready method + mover.data_ready.connect(mover, mover.on_data_ready) + + # 2) Prepare WorkerA + worker_a = WorkerA() + worker_a.start() # Start thread + event loop + await asyncio.sleep(0.2) # Wait for worker_a run() to start + + # move_to_thread + mover.move_to_thread(worker_a) + logger.info("Mover moved to WorkerA thread") + + # Call do_work -> WorkerA thread emits signal + mover.do_work("from WorkerA") + await asyncio.sleep(0.3) # Wait for signal to be processed + + assert "from WorkerA" in mover.emitted_values, "The data emitted from WorkerA should be received" + + # 3) Prepare WorkerB + worker_b = WorkerB() + worker_b.start() + await asyncio.sleep(0.2) + + mover.move_to_thread(worker_b) + logger.info("Mover moved to WorkerB thread") + + # do_work -> Now WorkerB emits signal + mover.do_work("from WorkerB") + await asyncio.sleep(0.3) + + assert "from WorkerB" in mover.emitted_values, "The data emitted from WorkerB should be received" + + # Clean up + worker_a.stop() + worker_b.stop() + + logger.info("=== test_move_to_thread DONE ===") diff --git a/tests/unit/test_property.py b/tests/unit/test_property.py index fc650cb..fc97603 100644 --- a/tests/unit/test_property.py +++ b/tests/unit/test_property.py @@ -12,14 +12,13 @@ import threading import logging import pytest -from pynnex.contrib.extensions.property import nx_property -from pynnex import nx_signal, nx_with_signals +from pynnex import signal, with_signals, nx_property logger = logging.getLogger(__name__) -@nx_with_signals +@with_signals class Temperature: """Temperature class for testing""" @@ -27,7 +26,7 @@ def __init__(self): super().__init__() self._celsius = -273 - @nx_signal + @signal def celsius_changed(self): """Signal for celsius change""" @@ -42,7 +41,7 @@ def celsius(self, value: float): self._celsius = value -@nx_with_signals +@with_signals class ReadOnlyTemperature: """ReadOnlyTemperature class for testing""" @@ -50,7 +49,7 @@ def __init__(self): super().__init__() self._celsius = 0 - @nx_signal + @signal def celsius_changed(self): """Signal for celsius change""" diff --git a/tests/unit/test_signal.py b/tests/unit/test_signal.py index 61b932f..f543663 100644 --- a/tests/unit/test_signal.py +++ b/tests/unit/test_signal.py @@ -3,6 +3,9 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable # pylint: disable=too-many-locals +# pylint: disable=redefined-outer-name +# pylint: disable=import-outside-toplevel +# pylint: disable=reimported """ Test cases for the PynneX signal pattern. @@ -11,15 +14,14 @@ import asyncio import logging import pytest -from pynnex.core import ( - nx_with_signals, - nx_signal, - nx_slot, +from pynnex import ( + with_signals, + signal, + slot, NxSignal, NxConnectionType, _determine_connection_type, ) -from pynnex.contrib.patterns.worker.decorators import nx_with_worker from ..conftest import Receiver logger = logging.getLogger(__name__) @@ -164,14 +166,14 @@ async def test_signal_disconnect_nonexistent(sender, receiver): async def test_signal_disconnect_during_emit(sender, receiver): """Test disconnecting slots while emission is in progress""" - @nx_with_signals + @with_signals class SlowReceiver: """Receiver class for slow slot""" def __init__(self): self.received_value = None - @nx_slot + @slot async def on_value_changed(self, value): """Slot for value changed""" await asyncio.sleep(0.1) @@ -268,7 +270,7 @@ async def test_method_connection_with_signal_attributes(sender): received_values = [] - @nx_with_signals + @with_signals class SignalReceiver: """Receiver class for signal attributes""" @@ -318,6 +320,8 @@ def collect_value(self, value): async def test_connection_type_determination(): """Test connection type is correctly determined for different scenarios""" + from pynnex import signal, with_worker + # Regular function should use DIRECT_CONNECTION def regular_handler(value): """Regular handler""" @@ -334,37 +338,37 @@ def handler(self, value): """Handler""" # Regular class with thread/loop attributes - @nx_with_signals + @with_signals class RegularClassWithSignal: """Regular class with signal""" - @nx_signal + @signal def test_signal(self): """Signal""" # Class with thread/loop but not worker - @nx_with_signals + @with_signals class ThreadedClass: """Threaded class""" - @nx_slot + @slot def sync_handler(self, value): """Sync handler""" - @nx_slot + @slot async def async_handler(self, value): """Async handler""" # Worker class - @nx_with_worker + @with_worker class WorkerClass: """Worker class""" - @nx_slot + @slot def sync_handler(self, value): """Sync handler""" - @nx_slot + @slot async def async_handler(self, value): """Async handler""" @@ -438,13 +442,13 @@ async def test_one_shot(): then removed automatically upon the first call. """ - @nx_with_signals + @with_signals class OneShotSender: """ A class that sends one-shot events. """ - @nx_signal + @signal def one_shot_event(self, value): """ One-shot event signal. diff --git a/tests/unit/test_slot.py b/tests/unit/test_slot.py index fedc0c8..0eaf6f5 100644 --- a/tests/unit/test_slot.py +++ b/tests/unit/test_slot.py @@ -12,7 +12,7 @@ import time import logging import pytest -from pynnex import nx_with_signals, nx_slot +from pynnex import with_signals, slot logger = logging.getLogger(__name__) @@ -44,11 +44,11 @@ async def test_directly_call_slot(receiver): async def test_slot_exception(sender, receiver): """Test exception handling in slots""" - @nx_with_signals + @with_signals class ExceptionReceiver: """Receiver class for exception testing""" - @nx_slot + @slot async def on_value_changed(self, value): """Slot for value changed""" raise ValueError("Test exception") @@ -69,7 +69,7 @@ async def on_value_changed(self, value): async def test_slot_thread_safety(): """Test slot direct calls from different threads""" - @nx_with_signals + @with_signals class ThreadTestReceiver: """Receiver class for thread safety testing""" @@ -79,7 +79,7 @@ def __init__(self): self.received_count = 0 self.execution_thread = None - @nx_slot + @slot async def async_slot(self, value): """Async slot for thread safety testing""" self.execution_thread = threading.current_thread() @@ -87,7 +87,7 @@ async def async_slot(self, value): self.received_value = value self.received_count += 1 - @nx_slot + @slot def sync_slot(self, value): """Sync slot for thread safety testing""" self.execution_thread = threading.current_thread() diff --git a/tests/unit/test_weak.py b/tests/unit/test_weak.py index 92d4608..48611e1 100644 --- a/tests/unit/test_weak.py +++ b/tests/unit/test_weak.py @@ -11,7 +11,7 @@ import gc import asyncio import weakref -from pynnex.core import nx_with_signals, nx_signal +from pynnex import with_signals, signal class WeakRefReceiver: @@ -31,13 +31,13 @@ def on_signal(self, value): print(f"WeakRefReceiver got value: {value}") -@nx_with_signals(weak_default=True) +@with_signals(weak_default=True) class WeakRefSender: """ A class that sends weak reference events. """ - @nx_signal + @signal def event(self): """ Event signal. @@ -65,13 +65,13 @@ def on_signal(self, value): print(f"StrongRefReceiver got value: {value}") -@nx_with_signals(weak_default=True) +@with_signals(weak_default=True) class MixedSender: """ A class that sends mixed reference events. """ - @nx_signal + @signal def event(self, value): """ Event signal.