Compare commits
3 Commits
653c1c584c
...
348adefb7c
Author | SHA1 | Date |
---|---|---|
Ferdinand Thiessen | 348adefb7c | |
Ferdinand Thiessen | dca52b764c | |
Ferdinand Thiessen | f6c229d2ef |
|
@ -36,18 +36,21 @@ class CustomJSONEncoder(JSONEncoder):
|
|||
|
||||
|
||||
def __load_plugins(app):
|
||||
logger.info("Search for plugins")
|
||||
logger.debug("Search for plugins")
|
||||
|
||||
app.config["FG_PLUGINS"] = {}
|
||||
for entry_point in pkg_resources.iter_entry_points("flaschengeist.plugins"):
|
||||
logger.debug("Found plugin: >{}<".format(entry_point.name))
|
||||
plugin = None
|
||||
if entry_point.name in config and config[entry_point.name].get("enabled", False):
|
||||
logger.debug(f"Found plugin: >{entry_point.name}<")
|
||||
|
||||
if entry_point.name == config["FLASCHENGEIST"]["auth"] or (
|
||||
entry_point.name in config and config[entry_point.name].get("enabled", False)
|
||||
):
|
||||
logger.debug(f"Load plugin {entry_point.name}")
|
||||
try:
|
||||
logger.info(f"Load plugin {entry_point.name}")
|
||||
plugin = entry_point.load()
|
||||
if not hasattr(plugin, "name"):
|
||||
setattr(plugin, "name", entry_point.name)
|
||||
plugin = plugin(config[entry_point.name])
|
||||
plugin = plugin(config.get(entry_point.name, {}))
|
||||
if hasattr(plugin, "blueprint") and plugin.blueprint is not None:
|
||||
app.register_blueprint(plugin.blueprint)
|
||||
except:
|
||||
|
@ -55,16 +58,17 @@ def __load_plugins(app):
|
|||
f"Plugin {entry_point.name} was enabled, but could not be loaded due to an error.",
|
||||
exc_info=True,
|
||||
)
|
||||
del plugin
|
||||
continue
|
||||
if isinstance(plugin, AuthPlugin):
|
||||
logger.debug(f"Found authentication plugin: {entry_point.name}")
|
||||
if entry_point.name == config["FLASCHENGEIST"]["auth"]:
|
||||
app.config["FG_AUTH_BACKEND"] = plugin
|
||||
else:
|
||||
if entry_point.name != config["FLASCHENGEIST"]["auth"]:
|
||||
logger.debug(f"Unload not configured AuthPlugin {entry_point.name}")
|
||||
del plugin
|
||||
continue
|
||||
if plugin:
|
||||
else:
|
||||
logger.info(f"Using authentication plugin: {entry_point.name}")
|
||||
app.config["FG_AUTH_BACKEND"] = plugin
|
||||
else:
|
||||
logger.info(f"Using plugin: {entry_point.name}")
|
||||
app.config["FG_PLUGINS"][entry_point.name] = plugin
|
||||
if "FG_AUTH_BACKEND" not in app.config:
|
||||
logger.error("No authentication plugin configured or authentication plugin not found")
|
||||
|
|
|
@ -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
|
||||
|
@ -44,12 +48,8 @@ allowed_mimetypes = [
|
|||
"image/webp"
|
||||
]
|
||||
|
||||
[auth_plain]
|
||||
enabled = true
|
||||
|
||||
[auth_ldap]
|
||||
# Full documentation https://flaschengeist.dev/Flaschengeist/flaschengeist/wiki/plugins_auth_ldap
|
||||
enabled = false
|
||||
# host = "localhost"
|
||||
# port = 389
|
||||
# base_dn = "dc=example,dc=com"
|
||||
|
|
|
@ -96,6 +96,9 @@ class Plugin:
|
|||
.one_or_none()
|
||||
)
|
||||
if setting is not None:
|
||||
if value is None:
|
||||
db.session.delete(setting)
|
||||
else:
|
||||
setting.value = value
|
||||
else:
|
||||
db.session.add(_PluginSetting(plugin=self.name, name=name, value=value))
|
||||
|
|
|
@ -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