canaille-globuzma/canaille/__init__.py

214 lines
6.5 KiB
Python

import datetime
import logging
import sys
from logging.config import dictConfig
from logging.config import fileConfig
from flask import Flask
from flask import request
from flask import session
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect()
def setup_sentry(app): # pragma: no cover
if not app.config["CANAILLE"]["SENTRY_DSN"]:
return None
try:
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
except Exception:
return None
sentry_sdk.init(
dsn=app.config["CANAILLE"]["SENTRY_DSN"], integrations=[FlaskIntegration()]
)
return sentry_sdk
def setup_logging(app):
conf = app.config["CANAILLE"]["LOGGING"]
security_level_name = "SECURITY"
if not hasattr(logging, security_level_name):
addLoggingLevel(security_level_name, logging.INFO + 5)
if conf is None:
log_level = "DEBUG" if app.debug else "INFO"
dictConfig(
{
"version": 1,
"formatters": {
"default": {
"format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
}
},
"handlers": {
"wsgi": {
"class": "logging.StreamHandler",
"stream": "ext://flask.logging.wsgi_errors_stream",
"formatter": "default",
}
},
"root": {"level": log_level, "handlers": ["wsgi"]},
"loggers": {
"faker": {"level": "WARNING"},
"mail.log": {"level": "WARNING"},
},
"disable_existing_loggers": False,
}
)
elif isinstance(conf, dict):
dictConfig(conf)
else:
fileConfig(conf, disable_existing_loggers=False)
def setup_jinja(app):
app.jinja_env.filters["len"] = len
app.jinja_env.policies["ext.i18n.trimmed"] = True
def setup_blueprints(app):
import canaille.core.endpoints
app.url_map.strict_slashes = False
app.register_blueprint(canaille.core.endpoints.bp)
if "CANAILLE_OIDC" in app.config:
import canaille.oidc.endpoints
app.register_blueprint(canaille.oidc.endpoints.bp)
def setup_flask(app):
csrf.init_app(app)
@app.before_request
def make_session_permanent():
session.permanent = True
app.permanent_session_lifetime = datetime.timedelta(days=365)
@app.context_processor
def global_processor():
from canaille.app.flask import current_user
return {
"debug": app.debug or app.config.get("TESTING", False),
"logo_url": app.config["CANAILLE"]["LOGO"],
"favicon_url": app.config["CANAILLE"]["FAVICON"]
or app.config["CANAILLE"]["LOGO"],
"website_name": app.config["CANAILLE"]["NAME"],
"user": current_user(),
"menu": True,
"is_boosted": request.headers.get("HX-Boosted", False),
"features": app.features,
}
def setup_flask_converters(app):
from canaille.app import models
from canaille.app.flask import model_converter
for model_name, model_class in models.MODELS.items():
app.url_map.converters[model_name.lower()] = model_converter(model_class)
def create_app(
config=None, validate=True, backend=None, env_file=".env", env_prefix=""
):
from .app.configuration import setup_config
from .app.features import setup_features
from .app.i18n import setup_i18n
from .app.themes import setup_themer
from .backends import setup_backend
app = Flask(__name__)
with app.app_context():
if not setup_config(
app=app,
config=config,
test_config=validate,
env_file=env_file,
env_prefix=env_prefix,
): # pragma: no cover
sys.exit(1)
sentry_sdk = setup_sentry(app)
try:
setup_logging(app)
backend = setup_backend(app, backend)
setup_features(app)
setup_flask_converters(app)
setup_blueprints(app)
setup_jinja(app)
setup_i18n(app)
setup_themer(app)
setup_flask(app)
if "CANAILLE_OIDC" in app.config:
from .oidc.oauth import setup_oauth
setup_oauth(app)
except Exception as exc: # pragma: no cover
if sentry_sdk:
sentry_sdk.capture_exception(exc)
raise
return app
def addLoggingLevel(levelName, levelNum, methodName=None):
"""Comprehensively adds a new logging level to the `logging` module and the
currently configured logging class.
`levelName` becomes an attribute of the `logging` module with the value
`levelNum`. `methodName` becomes a convenience method for both `logging`
itself and the class returned by `logging.getLoggerClass()` (usually just
`logging.Logger`). If `methodName` is not specified, `levelName.lower()` is
used.
To avoid accidental clobberings of existing attributes, this method will
raise an `AttributeError` if the level name is already an attribute of the
`logging` module or if the method name is already present
Example
-------
>>> addLoggingLevel("TRACE", logging.DEBUG - 5)
>>> logging.getLogger(__name__).setLevel("TRACE")
>>> logging.getLogger(__name__).trace("that worked")
>>> logging.trace("so did this")
>>> logging.TRACE
5
"""
if not methodName:
methodName = levelName.lower()
if hasattr(logging, levelName):
raise AttributeError(f"{levelName} already defined in logging module")
if hasattr(logging, methodName):
raise AttributeError(f"{methodName} already defined in logging module")
if hasattr(logging.getLoggerClass(), methodName):
raise AttributeError(f"{methodName} already defined in logger class")
# This method was inspired by the answers to Stack Overflow post
# http://stackoverflow.com/q/2183233/2988730, especially
# http://stackoverflow.com/a/13638084/2988730
def logForLevel(self, message, *args, **kwargs):
if self.isEnabledFor(levelNum):
self._log(levelNum, message, args, **kwargs)
def logToRoot(message, *args, **kwargs):
logging.log(levelNum, message, *args, **kwargs)
logging.addLevelName(levelNum, levelName)
setattr(logging, levelName, levelNum)
setattr(logging.getLoggerClass(), methodName, logForLevel)
setattr(logging, methodName, logToRoot)