mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Adds the ability for 'scheduled tasks' to be member functions of plugins
This commit is contained in:
parent
7f31e08c56
commit
934de1f772
@ -75,10 +75,18 @@ class ScheduleMixin:
|
|||||||
'schedule': "I", # Schedule type (see django_q.Schedule)
|
'schedule': "I", # Schedule type (see django_q.Schedule)
|
||||||
'minutes': 30, # Number of minutes (only if schedule type = Minutes)
|
'minutes': 30, # Number of minutes (only if schedule type = Minutes)
|
||||||
'repeats': 5, # Number of repeats (leave blank for 'forever')
|
'repeats': 5, # Number of repeats (leave blank for 'forever')
|
||||||
}
|
},
|
||||||
|
'member_func': {
|
||||||
|
'func': 'my_class_func', # Note, without the 'dot' notation, it will call a class member function
|
||||||
|
'schedule': "H", # Once per hour
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Note: 'schedule' parameter must be one of ['I', 'H', 'D', 'W', 'M', 'Q', 'Y']
|
Note: 'schedule' parameter must be one of ['I', 'H', 'D', 'W', 'M', 'Q', 'Y']
|
||||||
|
|
||||||
|
Note: The 'func' argument can take two different forms:
|
||||||
|
- Dotted notation e.g. 'module.submodule.func' - calls a global function with the defined path
|
||||||
|
- Member notation e.g. 'my_func' (no dots!) - calls a member function of the calling class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ALLOWABLE_SCHEDULE_TYPES = ['I', 'H', 'D', 'W', 'M', 'Q', 'Y']
|
ALLOWABLE_SCHEDULE_TYPES = ['I', 'H', 'D', 'W', 'M', 'Q', 'Y']
|
||||||
@ -94,11 +102,14 @@ class ScheduleMixin:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.add_mixin('schedule', 'has_scheduled_tasks', __class__)
|
self.scheduled_tasks = self.get_scheduled_tasks()
|
||||||
self.scheduled_tasks = getattr(self, 'SCHEDULED_TASKS', {})
|
|
||||||
|
|
||||||
self.validate_scheduled_tasks()
|
self.validate_scheduled_tasks()
|
||||||
|
|
||||||
|
self.add_mixin('schedule', 'has_scheduled_tasks', __class__)
|
||||||
|
|
||||||
|
def get_scheduled_tasks(self):
|
||||||
|
return getattr(self, 'SCHEDULED_TASKS', {})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_scheduled_tasks(self):
|
def has_scheduled_tasks(self):
|
||||||
"""
|
"""
|
||||||
@ -158,18 +169,46 @@ class ScheduleMixin:
|
|||||||
|
|
||||||
task_name = self.get_task_name(key)
|
task_name = self.get_task_name(key)
|
||||||
|
|
||||||
# If a matching scheduled task does not exist, create it!
|
if Schedule.objects.filter(name=task_name).exists():
|
||||||
if not Schedule.objects.filter(name=task_name).exists():
|
# Scheduled task already exists - continue!
|
||||||
|
continue
|
||||||
|
|
||||||
logger.info(f"Adding scheduled task '{task_name}'")
|
logger.info(f"Adding scheduled task '{task_name}'")
|
||||||
|
|
||||||
|
func_name = task['func'].strip()
|
||||||
|
|
||||||
|
if '.' in func_name:
|
||||||
|
"""
|
||||||
|
Dotted notation indicates that we wish to run a globally defined function,
|
||||||
|
from a specified Python module.
|
||||||
|
"""
|
||||||
|
|
||||||
Schedule.objects.create(
|
Schedule.objects.create(
|
||||||
name=task_name,
|
name=task_name,
|
||||||
func=task['func'],
|
func=func_name,
|
||||||
schedule_type=task['schedule'],
|
schedule_type=task['schedule'],
|
||||||
minutes=task.get('minutes', None),
|
minutes=task.get('minutes', None),
|
||||||
repeats=task.get('repeats', -1),
|
repeats=task.get('repeats', -1),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
"""
|
||||||
|
Non-dotted notation indicates that we wish to call a 'member function' of the calling plugin.
|
||||||
|
|
||||||
|
This is managed by the plugin registry itself.
|
||||||
|
"""
|
||||||
|
|
||||||
|
slug = self.plugin_slug()
|
||||||
|
|
||||||
|
Schedule.objects.create(
|
||||||
|
name=task_name,
|
||||||
|
func='plugin.registry.registry.call_plugin_function',
|
||||||
|
args=f'{slug}, {func_name}',
|
||||||
|
schedule_type=task['schedule'],
|
||||||
|
minutes=task.get('minutes', None),
|
||||||
|
repeats=task.get('repeats', -1),
|
||||||
|
)
|
||||||
|
|
||||||
except (ProgrammingError, OperationalError):
|
except (ProgrammingError, OperationalError):
|
||||||
# Database might not yet be ready
|
# Database might not yet be ready
|
||||||
logger.warning("register_tasks failed, database not ready")
|
logger.warning("register_tasks failed, database not ready")
|
||||||
|
@ -59,6 +59,22 @@ class PluginsRegistry:
|
|||||||
# mixins
|
# mixins
|
||||||
self.mixins_settings = {}
|
self.mixins_settings = {}
|
||||||
|
|
||||||
|
def call_plugin_function(self, slug, func, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Call a member function (named by 'func') of the plugin named by 'slug'.
|
||||||
|
|
||||||
|
As this is intended to be run by the background worker,
|
||||||
|
we do not perform any try/except here.
|
||||||
|
|
||||||
|
Instead, any error messages are returned to the worker.
|
||||||
|
"""
|
||||||
|
|
||||||
|
plugin = self.plugins[slug]
|
||||||
|
|
||||||
|
plugin_func = getattr(plugin, func)
|
||||||
|
|
||||||
|
plugin_func(*args, **kwargs)
|
||||||
|
|
||||||
# region public functions
|
# region public functions
|
||||||
# region loading / unloading
|
# region loading / unloading
|
||||||
def load_plugins(self):
|
def load_plugins(self):
|
||||||
|
@ -3,7 +3,7 @@ Sample plugin which supports task scheduling
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from plugin import IntegrationPluginBase
|
from plugin import IntegrationPluginBase
|
||||||
from plugin.mixins import ScheduleMixin
|
from plugin.mixins import ScheduleMixin, SettingsMixin
|
||||||
|
|
||||||
|
|
||||||
# Define some simple tasks to perform
|
# Define some simple tasks to perform
|
||||||
@ -15,7 +15,7 @@ def print_world():
|
|||||||
print("World")
|
print("World")
|
||||||
|
|
||||||
|
|
||||||
class ScheduledTaskPlugin(ScheduleMixin, IntegrationPluginBase):
|
class ScheduledTaskPlugin(ScheduleMixin, SettingsMixin, IntegrationPluginBase):
|
||||||
"""
|
"""
|
||||||
A sample plugin which provides support for scheduled tasks
|
A sample plugin which provides support for scheduled tasks
|
||||||
"""
|
"""
|
||||||
@ -25,6 +25,11 @@ class ScheduledTaskPlugin(ScheduleMixin, IntegrationPluginBase):
|
|||||||
PLUGIN_TITLE = "Scheduled Tasks"
|
PLUGIN_TITLE = "Scheduled Tasks"
|
||||||
|
|
||||||
SCHEDULED_TASKS = {
|
SCHEDULED_TASKS = {
|
||||||
|
'member': {
|
||||||
|
'func': 'member_func',
|
||||||
|
'schedule': 'I',
|
||||||
|
'minutes': 30,
|
||||||
|
},
|
||||||
'hello': {
|
'hello': {
|
||||||
'func': 'plugin.samples.integration.scheduled_task.print_hello',
|
'func': 'plugin.samples.integration.scheduled_task.print_hello',
|
||||||
'schedule': 'I',
|
'schedule': 'I',
|
||||||
@ -35,3 +40,21 @@ class ScheduledTaskPlugin(ScheduleMixin, IntegrationPluginBase):
|
|||||||
'schedule': 'H',
|
'schedule': 'H',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SETTINGS = {
|
||||||
|
'T_OR_F': {
|
||||||
|
'name': 'True or False',
|
||||||
|
'description': 'Print true or false when running the periodic task',
|
||||||
|
'validator': bool,
|
||||||
|
'default': False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def member_func(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
A simple member function to demonstrate functionality
|
||||||
|
"""
|
||||||
|
|
||||||
|
t_or_f = self.get_setting('T_OR_F')
|
||||||
|
|
||||||
|
print(f"Called member_func - value is {t_or_f}")
|
||||||
|
@ -14,4 +14,4 @@ INVENTREE_DB_USER=pguser
|
|||||||
INVENTREE_DB_PASSWORD=pgpassword
|
INVENTREE_DB_PASSWORD=pgpassword
|
||||||
|
|
||||||
# Enable plugins?
|
# Enable plugins?
|
||||||
INVENTREE_PLUGINS_ENABLED=False
|
INVENTREE_PLUGINS_ENABLED=True
|
||||||
|
Loading…
Reference in New Issue
Block a user