2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-05-02 21:38:48 +00:00

Fix task register (#3805)

* fix schedule registration

* add collection step for tasks

* make tasks register configurable

* extend docs

* Also run InvenTree setup in testing

* fix import loading method

* fix wrong task registration

* do not test

* do only distinct testing

* ignore import error for coverage
This commit is contained in:
Matthias Mair 2022-10-18 07:54:10 +02:00 committed by GitHub
parent 3956a45c48
commit 269b269de3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 58 deletions

View File

@ -1,8 +1,10 @@
"""AppConfig for inventree app.""" """AppConfig for inventree app."""
import logging import logging
from importlib import import_module
from pathlib import Path
from django.apps import AppConfig from django.apps import AppConfig, apps
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core.exceptions import AppRegistryNotReady from django.core.exceptions import AppRegistryNotReady
@ -23,10 +25,11 @@ class InvenTreeConfig(AppConfig):
def ready(self): def ready(self):
"""Setup background tasks and update exchange rates.""" """Setup background tasks and update exchange rates."""
if canAppAccessDatabase(): if canAppAccessDatabase() or settings.TESTING_ENV:
self.remove_obsolete_tasks() self.remove_obsolete_tasks()
self.collect_tasks()
self.start_background_tasks() self.start_background_tasks()
if not isInTestMode(): # pragma: no cover if not isInTestMode(): # pragma: no cover
@ -54,68 +57,31 @@ class InvenTreeConfig(AppConfig):
def start_background_tasks(self): def start_background_tasks(self):
"""Start all background tests for InvenTree.""" """Start all background tests for InvenTree."""
try:
from django_q.models import Schedule
except AppRegistryNotReady: # pragma: no cover
logger.warning("Cannot start background tasks - app registry not ready")
return
logger.info("Starting background tasks...") logger.info("Starting background tasks...")
# Remove successful task results from the database for task in InvenTree.tasks.tasks.task_list:
InvenTree.tasks.schedule_task( ref_name = f'{task.func.__module__}.{task.func.__name__}'
'InvenTree.tasks.delete_successful_tasks', InvenTree.tasks.schedule_task(
schedule_type=Schedule.DAILY, ref_name,
) schedule_type=task.interval,
minutes=task.minutes,
)
# Check for InvenTree updates logger.info("Started background tasks...")
InvenTree.tasks.schedule_task(
'InvenTree.tasks.check_for_updates',
schedule_type=Schedule.DAILY
)
# Heartbeat to let the server know the background worker is running def collect_tasks(self):
InvenTree.tasks.schedule_task( """Collect all background tasks."""
'InvenTree.tasks.heartbeat',
schedule_type=Schedule.MINUTES,
minutes=15
)
# Keep exchange rates up to date for app_name, app in apps.app_configs.items():
InvenTree.tasks.schedule_task( if app_name == 'InvenTree':
'InvenTree.tasks.update_exchange_rates', continue
schedule_type=Schedule.DAILY,
)
# Delete old error messages if Path(app.path).joinpath('tasks.py').exists():
InvenTree.tasks.schedule_task( try:
'InvenTree.tasks.delete_old_error_logs', import_module(f'{app.module.__package__}.tasks')
schedule_type=Schedule.DAILY, except Exception as e: # pragma: no cover
) logger.error(f"Error loading tasks for {app_name}: {e}")
# Delete old notification records
InvenTree.tasks.schedule_task(
'common.tasks.delete_old_notifications',
schedule_type=Schedule.DAILY,
)
# Check for overdue purchase orders
InvenTree.tasks.schedule_task(
'order.tasks.check_overdue_purchase_orders',
schedule_type=Schedule.DAILY
)
# Check for overdue sales orders
InvenTree.tasks.schedule_task(
'order.tasks.check_overdue_sales_orders',
schedule_type=Schedule.DAILY,
)
# Check for overdue build orders
InvenTree.tasks.schedule_task(
'build.tasks.check_overdue_build_orders',
schedule_type=Schedule.DAILY
)
def update_exchange_rates(self): # pragma: no cover def update_exchange_rates(self): # pragma: no cover
"""Update exchange rates each time the server is started. """Update exchange rates each time the server is started.

View File

@ -4,7 +4,9 @@ import json
import logging import logging
import re import re
import warnings import warnings
from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from typing import Callable
from django.conf import settings from django.conf import settings
from django.core import mail as django_mail from django.core import mail as django_mail
@ -126,6 +128,79 @@ def offload_task(taskname, *args, force_async=False, force_sync=False, **kwargs)
_func(*args, **kwargs) _func(*args, **kwargs)
@dataclass()
class ScheduledTask:
"""A scheduled task.
- interval: The interval at which the task should be run
- minutes: The number of minutes between task runs
- func: The function to be run
"""
func: Callable
interval: str
minutes: int = None
MINUTES = "I"
HOURLY = "H"
DAILY = "D"
WEEKLY = "W"
MONTHLY = "M"
QUARTERLY = "Q"
YEARLY = "Y"
TYPE = [MINUTES, HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY]
class TaskRegister:
"""Registery for periodicall tasks."""
task_list: list[ScheduledTask] = []
def register(self, task, schedule, minutes: int = None):
"""Register a task with the que."""
self.task_list.append(ScheduledTask(task, schedule, minutes))
tasks = TaskRegister()
def scheduled_task(interval: str, minutes: int = None, tasklist: TaskRegister = None):
"""Register the given task as a scheduled task.
Example:
```python
@register(ScheduledTask.DAILY)
def my_custom_funciton():
...
```
Args:
interval (str): The interval at which the task should be run
minutes (int, optional): The number of minutes between task runs. Defaults to None.
tasklist (TaskRegister, optional): The list the tasks should be registered to. Defaults to None.
Raises:
ValueError: If decorated object is not callable
ValueError: If interval is not valid
Returns:
_type_: _description_
"""
def _task_wrapper(admin_class):
if not isinstance(admin_class, Callable):
raise ValueError('Wrapped object must be a function')
if interval not in ScheduledTask.TYPE:
raise ValueError(f'Invalid interval. Must be one of {ScheduledTask.TYPE}')
_tasks = tasklist if tasklist else tasks
_tasks.register(admin_class, interval, minutes=minutes)
return admin_class
return _task_wrapper
@scheduled_task(ScheduledTask.MINUTES, 15)
def heartbeat(): def heartbeat():
"""Simple task which runs at 5 minute intervals, so we can determine that the background worker is actually running. """Simple task which runs at 5 minute intervals, so we can determine that the background worker is actually running.
@ -149,6 +224,7 @@ def heartbeat():
heartbeats.delete() heartbeats.delete()
@scheduled_task(ScheduledTask.DAILY)
def delete_successful_tasks(): def delete_successful_tasks():
"""Delete successful task logs which are more than a month old.""" """Delete successful task logs which are more than a month old."""
try: try:
@ -168,6 +244,7 @@ def delete_successful_tasks():
results.delete() results.delete()
@scheduled_task(ScheduledTask.DAILY)
def delete_old_error_logs(): def delete_old_error_logs():
"""Delete old error logs from the server.""" """Delete old error logs from the server."""
try: try:
@ -190,6 +267,7 @@ def delete_old_error_logs():
return return
@scheduled_task(ScheduledTask.DAILY)
def check_for_updates(): def check_for_updates():
"""Check if there is an update for InvenTree.""" """Check if there is an update for InvenTree."""
try: try:
@ -232,6 +310,7 @@ def check_for_updates():
) )
@scheduled_task(ScheduledTask.DAILY)
def update_exchange_rates(): def update_exchange_rates():
"""Update currency exchange rates.""" """Update currency exchange rates."""
try: try:
@ -273,6 +352,7 @@ def update_exchange_rates():
logger.error(f"Error updating exchange rates: {e}") logger.error(f"Error updating exchange rates: {e}")
@scheduled_task(ScheduledTask.DAILY)
def run_backup(): def run_backup():
"""Run the backup command.""" """Run the backup command."""
from common.models import InvenTreeSetting from common.models import InvenTreeSetting

View File

@ -144,6 +144,7 @@ def notify_overdue_build_order(bo: build.models.Build):
trigger_event(event_name, build_order=bo.pk) trigger_event(event_name, build_order=bo.pk)
@InvenTree.tasks.scheduled_task(InvenTree.tasks.ScheduledTask.DAILY)
def check_overdue_build_orders(): def check_overdue_build_orders():
"""Check if any outstanding BuildOrders have just become overdue """Check if any outstanding BuildOrders have just become overdue

View File

@ -5,9 +5,12 @@ from datetime import datetime, timedelta
from django.core.exceptions import AppRegistryNotReady from django.core.exceptions import AppRegistryNotReady
from InvenTree.tasks import ScheduledTask, scheduled_task
logger = logging.getLogger('inventree') logger = logging.getLogger('inventree')
@scheduled_task(ScheduledTask.DAILY)
def delete_old_notifications(): def delete_old_notifications():
"""Remove old notifications from the database. """Remove old notifications from the database.

View File

@ -6,9 +6,9 @@ from django.utils.translation import gettext_lazy as _
import common.notifications import common.notifications
import InvenTree.helpers import InvenTree.helpers
import InvenTree.tasks
import order.models import order.models
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
from InvenTree.tasks import ScheduledTask, scheduled_task
from plugin.events import trigger_event from plugin.events import trigger_event
@ -55,6 +55,7 @@ def notify_overdue_purchase_order(po: order.models.PurchaseOrder):
) )
@scheduled_task(ScheduledTask.DAILY)
def check_overdue_purchase_orders(): def check_overdue_purchase_orders():
"""Check if any outstanding PurchaseOrders have just become overdue: """Check if any outstanding PurchaseOrders have just become overdue:
@ -117,6 +118,7 @@ def notify_overdue_sales_order(so: order.models.SalesOrder):
) )
@scheduled_task(ScheduledTask.DAILY)
def check_overdue_sales_orders(): def check_overdue_sales_orders():
"""Check if any outstanding SalesOrders have just become overdue """Check if any outstanding SalesOrders have just become overdue