Let plugins decide if events should be processed or not (#5618)

* add wants_process_event to EventMixin

in register_event this function get called and the task is just offload if the plugin returns true

* add tests for wants_process_event

* fix comments for register_event after code changes

* add documentation for EventMixin.wants_process_event

* avoid pre-formatting log messages

---------

Co-authored-by: Oliver Lippert <oliver@lipperts-web.de>
This commit is contained in:
Oliver Lippert 2023-10-01 12:10:26 +02:00 committed by GitHub
parent 7fa0c6d280
commit 46b0d77418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 144 additions and 18 deletions

View File

@ -65,11 +65,16 @@ def register_event(event, *args, **kwargs):
with transaction.atomic():
for slug, plugin in registry.plugins.items():
if not plugin.mixin_enabled('events'):
continue
if plugin.mixin_enabled('events'):
if plugin.is_active():
# Only allow event registering for 'active' plugins
if not plugin.is_active():
continue
# Let the plugin decide if it wants to process this event
if not plugin.wants_process_event(event):
continue
logger.debug("Registering callback for plugin '%s'", slug)

View File

@ -9,6 +9,15 @@ class EventMixin:
Implementing classes must provide a "process_event" function:
"""
def wants_process_event(self, event):
"""Function to subscribe to events.
Return true if you're interested in the given event, false if not.
"""
# Default implementation always returns true (backwards compatibility)
return True
def process_event(self, event, *args, **kwargs):
"""Function to handle events.

View File

@ -0,0 +1,32 @@
"""Sample plugin which responds to events."""
import logging
from django.conf import settings
from plugin import InvenTreePlugin
from plugin.mixins import EventMixin
logger = logging.getLogger('inventree')
class FilteredEventPluginSample(EventMixin, InvenTreePlugin):
"""A sample plugin which provides supports for triggered events."""
NAME = "FilteredEventPlugin"
SLUG = "filteredsampleevent"
TITLE = "Triggered by test.event only"
def wants_process_event(self, event):
"""Return whether given event should be processed or not."""
return event == "test.event"
def process_event(self, event, *args, **kwargs):
"""Custom event processing."""
print(f"Processing triggered event: '{event}'")
print("args:", str(args))
print("kwargs:", str(kwargs))
# Issue warning that we can test for
if settings.PLUGIN_TESTING:
logger.debug('Event `%s` triggered in sample plugin', event)

View File

@ -0,0 +1,52 @@
"""Unit tests for event_sample sample plugins."""
from django.conf import settings
from django.test import TestCase
from common.models import InvenTreeSetting
from plugin import registry
from plugin.base.event.events import trigger_event
from .filtered_event_sample import logger
class FilteredEventPluginSampleTests(TestCase):
"""Tests for EventPluginSample."""
def test_run_event(self):
"""Check if the event is issued."""
# Activate plugin
config = registry.get_plugin('filteredsampleevent').plugin_config()
config.active = True
config.save()
InvenTreeSetting.set_setting('ENABLE_PLUGINS_EVENTS', True, change_user=None)
# Enable event testing
settings.PLUGIN_TESTING_EVENTS = True
# Check that an event is issued
with self.assertLogs(logger=logger, level="DEBUG") as cm:
trigger_event('test.event')
self.assertIn('DEBUG:inventree:Event `test.event` triggered in sample plugin', cm[1])
# Disable again
settings.PLUGIN_TESTING_EVENTS = False
def test_ignore_event(self):
"""Check if the event is issued."""
# Activate plugin
config = registry.get_plugin('filteredsampleevent').plugin_config()
config.active = True
config.save()
InvenTreeSetting.set_setting('ENABLE_PLUGINS_EVENTS', True, change_user=None)
# Enable event testing
settings.PLUGIN_TESTING_EVENTS = True
# Check that an event is issued
with self.assertLogs(logger=logger, level="DEBUG") as cm:
trigger_event('test.some.other.event')
self.assertNotIn('DEBUG:inventree:Event `test.some.other.event` triggered in sample plugin', cm[1])
# Disable again
settings.PLUGIN_TESTING_EVENTS = False

View File

@ -15,9 +15,9 @@ When a certain (server-side) event occurs, the background worker passes the even
{% include 'img.html' %}
{% endwith %}
### Example
### Example (all events)
Implementing classes must provide a `process_event` function:
Implementing classes must at least provide a `process_event` function:
```python
class EventPlugin(EventMixin, InvenTreePlugin):
@ -36,6 +36,34 @@ class EventPlugin(EventMixin, InvenTreePlugin):
print(f"Processing triggered event: '{event}'")
```
### Example (specific events)
If you want to process just some specific events, you can also implement the `wants_process_event` function to decide if you want to process this event or not. This function will be executed synchronously, so be aware that it should contain simple logic.
Overall this function can reduce the workload on the background workers significantly since less events are queued to be processed.
```python
class EventPlugin(EventMixin, InvenTreePlugin):
"""
A simple example plugin which responds to 'salesordershipment.completed' event on the InvenTree server.
This example simply prints out the event information.
A more complex plugin can run enhanced logic on this event.
"""
NAME = "EventPlugin"
SLUG = "event"
TITLE = "Triggered Events"
def wants_process_event(self, event):
"""Here you can decide if this event should be send to `process_event` or not."""
return event == "salesordershipment.completed"
def process_event(self, event, *args, **kwargs):
"""Here you can run you'r specific logic."""
print(f"Sales order was completely shipped: '{args}' '{kwargs}'")
```
### Events
Events are passed through using a string identifier, e.g. `build.completed`