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},
|
"auth": {"enabled": True},
|
||||||
"roles": {"enabled": True},
|
"roles": {"enabled": True},
|
||||||
"users": {"enabled": True},
|
"users": {"enabled": True},
|
||||||
|
"scheduler": {"enabled": True},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,11 @@ root = "/api"
|
||||||
# Set secret key
|
# Set secret key
|
||||||
secret_key = "V3ryS3cr3t"
|
secret_key = "V3ryS3cr3t"
|
||||||
# Domain used by frontend
|
# Domain used by frontend
|
||||||
#domain = "flaschengeist.local"
|
|
||||||
|
[scheduler]
|
||||||
|
# Possible values are: "passive_web" (default), "active_web" and "system"
|
||||||
|
# See documentation
|
||||||
|
# cron = "passive_web"
|
||||||
|
|
||||||
[LOGGING]
|
[LOGGING]
|
||||||
# You can override all settings from the logging.toml here
|
# 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!
|
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
|
### Database installation
|
||||||
The user needs to have full permissions to the database.
|
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):
|
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",
|
"balance = flaschengeist.plugins.balance:BalancePlugin",
|
||||||
"mail = flaschengeist.plugins.message_mail:MailMessagePlugin",
|
"mail = flaschengeist.plugins.message_mail:MailMessagePlugin",
|
||||||
"pricelist = flaschengeist.plugins.pricelist:PriceListPlugin",
|
"pricelist = flaschengeist.plugins.pricelist:PriceListPlugin",
|
||||||
|
"scheduler = flaschengeist.plugins.scheduler:SchedulerPlugin",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
cmdclass={
|
cmdclass={
|
||||||
|
|
Loading…
Reference in New Issue