flaschengeist/flaschengeist/app.py

100 lines
3.4 KiB
Python

import enum
from flask import Flask
from flask_cors import CORS
from datetime import datetime, date
from flask.json import JSONEncoder, jsonify
from sqlalchemy.exc import OperationalError
from werkzeug.exceptions import HTTPException
from flaschengeist.utils.plugin import get_plugins
from . import logger
from .plugins import Plugin
from .config import config, configure_app
from .utils.hook import Hook
class CustomJSONEncoder(JSONEncoder):
def default(self, o):
try:
# Check if custom model
return o.serialize()
except AttributeError:
pass
if isinstance(o, datetime) or isinstance(o, date):
return o.isoformat()
if isinstance(o, enum.Enum):
return o.value
try:
# Check if iterable
iterable = iter(o)
except TypeError:
pass
else:
return list(iterable)
return JSONEncoder.default(self, o)
@Hook("plugins.loaded")
def load_plugins(app: Flask):
def load_plugin(cls: type[Plugin]):
logger.debug(f"Load plugin {cls.id}")
# Initialize plugin with config section
plugin = cls(config.get(plugin_class.id, config.get(plugin_class.id.split(".")[-1], {})))
# Register blueprint if provided
if plugin.blueprint is not None:
app.register_blueprint(plugin.blueprint)
# Save plugin application context
app.config.setdefault("FG_PLUGINS", {})[plugin.id] = plugin
return plugin
for plugin_class in get_plugins():
names = [plugin_class.id, plugin_class.id.split(".")[-1]]
if config["FLASCHENGEIST"]["auth"] in names:
# Load authentification plugin
app.config["FG_AUTH_BACKEND"] = load_plugin(plugin_class)
logger.info(f"Using authentication plugin: {plugin_class.id}")
elif any([i in config and config[i].get("enabled", False) for i in names]):
# Load all other enabled plugins
load_plugin(plugin_class)
logger.info(f"Using plugin: {plugin_class.id}")
else:
logger.debug(f"Skip disabled plugin {plugin_class.id}")
if "FG_AUTH_BACKEND" not in app.config:
logger.fatal("No authentication plugin configured or authentication plugin not found")
raise RuntimeError("No authentication plugin configured or authentication plugin not found")
def create_app(test_config=None, cli=False):
app = Flask(__name__)
app.json_encoder = CustomJSONEncoder
CORS(app)
with app.app_context():
from flaschengeist.database import db, migrate
configure_app(app, test_config, cli)
db.init_app(app)
migrate.init_app(app, db, compare_type=True)
load_plugins(app)
@app.route("/", methods=["GET"])
def __get_state():
from . import __version__ as version
return jsonify({"plugins": app.config["FG_PLUGINS"], "version": version})
@app.errorhandler(Exception)
def handle_exception(e):
if isinstance(e, HTTPException):
logger.debug(e.description, exc_info=True)
return jsonify({"error": e.description}), e.code
if isinstance(e, OperationalError):
logger.error(e, exc_info=True)
return {"error": "Database unavailable"}, 504
logger.error(str(e), exc_info=True)
return jsonify({"error": "Internal server error occurred"}), 500
return app