feat(scheduler): Scheduler is now a plugin

Scheduler allows to schedule tasks, like cron does, but requires special configuration.
This commit is contained in:
Ferdinand Thiessen 2021-12-06 23:48:05 +01:00
parent dca52b764c
commit 348adefb7c
6 changed files with 103 additions and 18 deletions

View File

@ -77,6 +77,7 @@ def configure_app(app, test_config=None):
"auth": {"enabled": True},
"roles": {"enabled": True},
"users": {"enabled": True},
"scheduler": {"enabled": True},
},
)

View File

@ -11,7 +11,11 @@ root = "/api"
# Set secret key
secret_key = "V3ryS3cr3t"
# Domain used by frontend
#domain = "flaschengeist.local"
[scheduler]
# Possible values are: "passive_web" (default), "active_web" and "system"
# See documentation
# cron = "passive_web"
[LOGGING]
# You can override all settings from the logging.toml here

View File

@ -0,0 +1,83 @@
import pkg_resources
from datetime import datetime, timedelta
from flask import Blueprint
from flaschengeist import logger
from flaschengeist.utils.HTTP import no_content
from . import Plugin
class __Task:
def __init__(self, function, **kwags):
self.function = function
self.interval = timedelta(**kwags)
_scheduled_tasks: dict[__Task] = dict()
def scheduled(id: str, replace=False, **kwargs):
"""
kwargs: days, hours, minutes
"""
def real_decorator(function):
if id not in _scheduled_tasks or replace:
logger.info(f"Registered task: {id}")
_scheduled_tasks[id] = __Task(function, **kwargs)
else:
logger.debug(f"Skipping already registered task: {id}")
return function
return real_decorator
class SchedulerPlugin(Plugin):
id = "dev.flaschengeist.scheduler"
name = "scheduler"
blueprint = Blueprint(name, __name__)
def __init__(self, config=None):
"""Constructor called by create_app
Args:
config: Dict configuration containing the plugin section
"""
def __view_func():
self.run_tasks()
return no_content()
def __passiv_func(v):
try:
self.run_tasks()
except:
logger.error("Error while executing scheduled tasks!", exc_info=True)
self.version = pkg_resources.get_distribution(self.__module__.split(".")[0]).version
cron = None if config is None else config.get("cron", "passive_web").lower()
if cron is None or cron == "passive_web":
self.blueprint.teardown_app_request(__passiv_func)
elif cron == "active_web":
self.blueprint.add_url_rule("/cron", view_func=__view_func)
def run_tasks(self):
changed = False
now = datetime.now()
status = self.get_setting("status", default=dict())
for id, task in _scheduled_tasks.items():
last_run = status.setdefault(id, now)
if last_run + task.interval <= now:
logger.debug(
f"Run task {id}, was scheduled for {last_run + task.interval}, next iteration: {now + task.interval}"
)
task.function()
changed = True
if changed:
# Remove not registered tasks
for id in status.keys():
if id not in _scheduled_tasks.keys():
del status[id]
self.set_setting("status", status)

View File

@ -1,17 +0,0 @@
from flask import current_app
from flaschengeist.utils.HTTP import no_content
_scheduled = set()
def scheduled(func):
_scheduled.add(func)
return func
@current_app.route("/cron")
def __run_scheduled():
for function in _scheduled:
function()
return no_content()

View File

@ -39,6 +39,19 @@ Configuration is done within the a `flaschengeist.toml`file, you can copy the on
Uncomment and change at least all the database parameters!
#### CRON
Some functionality used by some plugins rely on regular updates,
but as flaschengeists works as an WSGI app it can not controll when it gets called.
So you have to configure one of the following options to call flaschengeists CRON tasks:
1. Passive Web-CRON: Every time an users calls flaschengeist a task is scheduled (**NOT RECOMMENDED**)
- Pros: No external configuration needed
- Cons: Slower user experience, no guaranteed execution time of tasks
2. Active Web-CRON: You configure a webworker to call `<flaschengeist>/cron`
- Pros: Guaranteed execution interval, no impact on user experience (at least if you do not limit wsgi worker threads)
- Cons: Uses one of the webserver threads while executing
### Database installation
The user needs to have full permissions to the database.
If not you need to create user and database manually do (or similar on Windows):

View File

@ -68,6 +68,7 @@ setup(
"balance = flaschengeist.plugins.balance:BalancePlugin",
"mail = flaschengeist.plugins.message_mail:MailMessagePlugin",
"pricelist = flaschengeist.plugins.pricelist:PriceListPlugin",
"scheduler = flaschengeist.plugins.scheduler:SchedulerPlugin",
],
},
cmdclass={