feat(scheduler): Scheduler is now a plugin
Scheduler allows to schedule tasks, like cron does, but requires special configuration.
This commit is contained in:
parent
dca52b764c
commit
348adefb7c
|
@ -77,6 +77,7 @@ def configure_app(app, test_config=None):
|
|||
"auth": {"enabled": True},
|
||||
"roles": {"enabled": True},
|
||||
"users": {"enabled": True},
|
||||
"scheduler": {"enabled": True},
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
13
readme.md
13
readme.md
|
@ -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):
|
||||
|
|
1
setup.py
1
setup.py
|
@ -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={
|
||||
|
|
Loading…
Reference in New Issue