From c0652a817a5d59601923df4e4fd39eb3ce32c9cc Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 2 Aug 2024 11:44:22 +0200 Subject: [PATCH] finishing notifcation docs and restrucutre imports a but --- docs/_static/exengine_bigpicture.svg | 4 +- docs/conf.py | 4 + docs/index.rst | 2 +- docs/usage/notifications.rst | 178 +++++++++++++++++- src/exengine/__init__.py | 3 + src/exengine/base_classes.py | 2 + src/exengine/examples/micromanager_example.py | 2 +- .../test_events_and_notifications.py | 2 +- .../integration_tests/test_imports.py | 22 +++ src/exengine/kernel/__init__.py | 2 + src/exengine/notifications.py | 5 + 11 files changed, 217 insertions(+), 9 deletions(-) create mode 100644 src/exengine/base_classes.py create mode 100644 src/exengine/integration_tests/test_imports.py create mode 100644 src/exengine/notifications.py diff --git a/docs/_static/exengine_bigpicture.svg b/docs/_static/exengine_bigpicture.svg index 419bb95..536e65b 100644 --- a/docs/_static/exengine_bigpicture.svg +++ b/docs/_static/exengine_bigpicture.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7dcee6a939c7712485eea89ec99e24963394dbf1df77af6f4485df1538594fc6 -size 10332659 +oid sha256:04766fb288bcc938371067e3edb3e90c87532ec28573d4eed8af956efc3982af +size 10337528 diff --git a/docs/conf.py b/docs/conf.py index 7f38767..1066bcf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,6 +31,10 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "sphinx_rtd_theme" +html_theme_options = { + 'collapse_navigation': True, + 'navigation_depth': 6, +} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/index.rst b/docs/index.rst index cc34895..a4d0a57 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -30,7 +30,7 @@ Key Features: .. toctree:: - :maxdepth: 2 + :maxdepth: 3 :caption: Contents: design diff --git a/docs/usage/notifications.rst b/docs/usage/notifications.rst index 1806ce2..4ef9944 100644 --- a/docs/usage/notifications.rst +++ b/docs/usage/notifications.rst @@ -1,9 +1,179 @@ .. _notifications: - -############## +============= Notifications -############## +============= + + +Overview +--------- + +Notifications in ExEngine provide a powerful mechanism for asynchronous communication between the Execution and user code. They allow devices, events, and other components to broadcast updates about their status or important occurrences. This enables reactive programming patterns, allowing your software to respond dynamically to changes in the system state or experimental conditions. + +Notifications can serve several purposes: + + - Inform about the completion of asynchronous operations (i.e. those occuring on a different thread) + - Alert about changes in device states + - Communicate errors or warnings + - Provide updates on the progress of long-running tasks + + + +Anatomy of a Notification +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Notifications in ExEngine are instances of classes derived from the base ``Notification`` class. Each notification has several components: + +1. **Category**: Defined by the ``NotificationCategory`` enum, this indicates the broad type of the notification (``Event``, ``Data``, ``Storage``, or ``Device``). + +2. **Description**: A string providing a explanation of what the notification represents. + +3. **Payload**: An optional piece of data associated with the notification, whose type depends on the particular notification. + +4. **Timestamp**: Automatically set to the time the notification was created. + + +Built-in Notification Types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +ExEngine provides built-in notification types, such as: + +1. ``EventExecutedNotification``: Posted when an ExecutionEvent completes. Its payload is None, or an Exception if the event didn't complete successfully + +2. ``DataStoredNotification``: Posted when data is stored by a Storage object. Its payload is the ``DataCoordinates`` of the stored data. + + + +Subscribing to Notifications +---------------------------- + +To subscribe to notifications from ExEngine, you can use the ``subscribe_to_notifications`` method of the ``ExecutionEngine`` instance: + +.. code-block:: python + + from exengine import ExecutionEngine + + def notification_handler(notification): + print(f'Got Notification: time {notification.timestamp} and payload {notification.payload}') + + engine = ExecutionEngine.get_instance() + + engine.subscribe_to_notifications(notification_handler) + + + # When finished, unsubscribe + engine.unsubscribe_from_notifications(notification_handler) + + + +Your ``notification_handler`` function will be called each time a new notification is posted. Since there may be many notifications produced by the ``ExecutionEngine``, these handler functions should not contain code that takes a long time to run. + + +Filtering Subscriptions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can filter notifications by type (i.e. a specific notification subclass) or category when subscribing, so that the handler function only gets called for a subset of notifications + +.. code-block:: python + + + # Subscribe to a specific notification type + # SpecificNotificationClass should be a subclass of exengine.base_classes.Notification + engine.subscribe_to_notifications(handler, SpecificNotificationClass) + + + # Subscribe to notifications of a specific category + from exengine.kernel.notification_base import NotificationCategory + + engine.subscribe_to_notifications(handler, NotificationCategory.Data) + +Multiple subscriptions with different filters can be set up: + +.. code-block:: python + + engine.subscribe_to_notifications(handler1, NotificationA) + engine.subscribe_to_notifications(handler2, NotificationCategory.Device) + engine.subscribe_to_notifications(handler3) # No filter, receives all notifications + + + + +Determining Available Notifications +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``ExecutorEvents`` declare the types of notifications they might emit through the ``notification_types`` class attribute. This attribute is a list of Notification types that the event may produce during its execution. + +To discover which notification types are supported by a particular event: + +.. code-block:: python + + print(MyEvent.notification_types) + + +All ExecutorEvents include the ``EventExecutedNotification`` by default. Subclasses can add their additional custom types of notifications. + + + +Awaiting Notifications from a Future +------------------------------------ + +Notifications can be awaited on an :ref:`ExecutionFuture ` in addition to subscribing to ExEngine notifications. This is useful for waiting on specific conditions related to a particular ``ExecutorEvent``: + +.. code-block:: python + + future = engine.submit(some_event) + notification = future.await_notification(SomeSpecificNotification) + +The Future tracks all notifications for its event. If called after a notification occurs, it returns immediately. + + + +Publishing Notifications +------------------------- + +Events can emit notifications using the ``publish_notification`` method: + +.. code-block:: python + + class MyEvent(ExecutorEvent): + notification_types = [MyCustomNotification] + + def execute(self): + # ... do something ... + self.publish_notification(MyCustomNotification(payload="Something happened")) + + + + +Creating Custom Notifications +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To create a custom notification: + +1. Subclass ``exengine.base_classes.Notification`` +2. Use Python's ``@dataclass`` decorator +3. Define ``category`` (from ``exengine.notifications.NotificationCategory`` enum) and ``description`` (string) as class variables +4. Optionally, specify a payload type using a type hint in the class inheritance. For example, ``class MyCustomNotification(Notification[str])`` indicates this notification's payload will be a string. + +Keep payloads lightweight for efficient processing. Example: + +.. code-block:: python + + from dataclasses import dataclass + from exengine.base_classes import Notification + from exengine.notifications import NotificationCategory + + @dataclass + class MyCustomNotification(Notification[str]): + category = NotificationCategory.Device + description = "A custom device status update" + + # Usage + notification = MyCustomNotification(payload="Device XYZ is ready") + + + + + -Notifications provide a mechanism for asynchronous communication within the system. They allow devices, events, and other components to broadcast updates about their status or important occurrences. This feature enables reactive programming patterns, allowing your software to respond dynamically to changes in the system state or experimental conditions. \ No newline at end of file diff --git a/src/exengine/__init__.py b/src/exengine/__init__.py index 93b2513..b4fe1b9 100644 --- a/src/exengine/__init__.py +++ b/src/exengine/__init__.py @@ -4,3 +4,6 @@ A flexible multi-backend execution engine for microscopy """ from ._version import __version__, version_info + +from . import kernel +from .kernel.executor import ExecutionEngine diff --git a/src/exengine/base_classes.py b/src/exengine/base_classes.py new file mode 100644 index 0000000..13e2fbd --- /dev/null +++ b/src/exengine/base_classes.py @@ -0,0 +1,2 @@ +from .kernel.notification_base import Notification +from .kernel.ex_event_base import ExecutorEvent \ No newline at end of file diff --git a/src/exengine/examples/micromanager_example.py b/src/exengine/examples/micromanager_example.py index 7052a86..8569f33 100644 --- a/src/exengine/examples/micromanager_example.py +++ b/src/exengine/examples/micromanager_example.py @@ -9,7 +9,7 @@ # download_and_install_mm() # If needed # Start Micro-Manager core instance with Demo config -create_core_instance(mm_app_path=, mm_config_path=) +create_core_instance() executor = ExecutionEngine() diff --git a/src/exengine/integration_tests/test_events_and_notifications.py b/src/exengine/integration_tests/test_events_and_notifications.py index 05cf96d..9c17d6d 100644 --- a/src/exengine/integration_tests/test_events_and_notifications.py +++ b/src/exengine/integration_tests/test_events_and_notifications.py @@ -2,7 +2,7 @@ Integration tests for events, notifications, futures, and the execution engine """ import pytest -from exengine.kernel.executor import ExecutionEngine +from exengine import ExecutionEngine from exengine.kernel.ex_event_base import ExecutorEvent from exengine.kernel.notification_base import Notification, NotificationCategory from dataclasses import dataclass diff --git a/src/exengine/integration_tests/test_imports.py b/src/exengine/integration_tests/test_imports.py new file mode 100644 index 0000000..ca41650 --- /dev/null +++ b/src/exengine/integration_tests/test_imports.py @@ -0,0 +1,22 @@ +# src/exengine/integration_tests/test_imports.py + +import pytest + + +def test_import_engine(): + try: + from exengine import ExecutionEngine + except ImportError as e: + pytest.fail(f"Import failed for ExecutionEngine: {e}") + +def test_import_base_classes(): + try: + from exengine.base_classes import Notification, ExecutorEvent + except ImportError as e: + pytest.fail(f"Import failed for base_classes: {e}") + +def test_import_notifications(): + try: + from exengine.notifications import NotificationCategory, DataStoredNotification, EventExecutedNotification + except ImportError as e: + pytest.fail(f"Import failed for notifications: {e}") diff --git a/src/exengine/kernel/__init__.py b/src/exengine/kernel/__init__.py index e69de29..76b09d3 100644 --- a/src/exengine/kernel/__init__.py +++ b/src/exengine/kernel/__init__.py @@ -0,0 +1,2 @@ +from .notification_base import NotificationCategory, Notification +from .ex_event_base import ExecutorEvent, EventExecutedNotification \ No newline at end of file diff --git a/src/exengine/notifications.py b/src/exengine/notifications.py new file mode 100644 index 0000000..8469328 --- /dev/null +++ b/src/exengine/notifications.py @@ -0,0 +1,5 @@ +""" +Convenience file for imports +""" +from .kernel.notification_base import NotificationCategory +from .kernel.notification_base import EventExecutedNotification, DataStoredNotification \ No newline at end of file