Files
snikket-web-portal/snikket_web/__init__.py
Jonas Schäfer e0cfcc6aaa 12-factorize application a little
snikket_web can now be fully configured via the environment alone,
no extra files needed. It is still supported to inject a python
file to generate environment variables though, which may be
useful for generating and reloading a secret key.
2021-01-17 20:11:26 +01:00

114 lines
3.0 KiB
Python

import base64
import binascii
import logging
import os
import pathlib
import typing
import quart.flask_patch
import quart
from quart import (
url_for,
)
import environ
from . import colour, infra
from ._version import version, version_info # noqa:F401
def proc() -> typing.Dict[str, typing.Any]:
def url_for_avatar(entity: str, hash_: str,
**kwargs: typing.Any) -> str:
return url_for(
"main.avatar",
from_=base64.urlsafe_b64encode(
entity.encode("utf-8"),
).decode("ascii").rstrip("="),
code=base64.urlsafe_b64encode(
binascii.a2b_hex(hash_)[:8],
).decode("ascii").rstrip("="),
**kwargs
)
return {
"url_for_avatar": url_for_avatar,
"text_to_css": colour.text_to_css,
"lang": infra.selected_locale(),
}
def autosplit(s: typing.Union[str, typing.List[str]]) -> typing.List[str]:
if isinstance(s, str):
return s.split()
return s
@environ.config(prefix="SNIKKET_WEB")
class AppConfig:
secret_key = environ.var()
prosody_endpoint = environ.var()
domain = environ.var()
avatar_cache_ttl = environ.var(1800, converter=int)
languages = environ.var(["de", "en"], converter=autosplit)
_UPPER_CASE = "".join(map(chr, range(ord("A"), ord("Z")+1)))
def create_app() -> quart.Quart:
try:
env_init = os.environ["SNIKKET_WEB_PYENV"]
except KeyError:
pass
else:
import runpy
init_vars = runpy.run_path(env_init)
for name, value in init_vars.items():
if not name:
continue
if name[0] not in _UPPER_CASE:
continue
os.environ[name] = value
config = environ.to_config(AppConfig)
app = quart.Quart(__name__)
app.config["LANGUAGES"] = config.languages
app.config["SECRET_KEY"] = config.secret_key
app.config["PROSODY_ENDPOINT"] = config.prosody_endpoint
app.config["SNIKKET_DOMAIN"] = config.domain
app.config["AVATAR_CACHE_TTL"] = config.avatar_cache_ttl
app.context_processor(proc)
logging_config = app.config.get("LOGGING_CONFIG")
if logging_config is not None:
if isinstance(logging_config, dict):
logging.config.dictConfig(logging_config)
elif isinstance(logging_config, (bytes, str, pathlib.Path)):
import toml
with open(logging_config, "r") as f:
logging_config = toml.load(f)
logging.config.dictConfig(logging_config)
else:
logging.basicConfig(level=logging.WARNING)
if app.debug:
logging.getLogger("snikket_web").setLevel(logging.DEBUG)
infra.babel.init_app(app)
infra.client.init_app(app)
infra.init_templating(app)
from .main import bp as main_bp
from .user import bp as user_bp
from .admin import bp as admin_bp
app.register_blueprint(main_bp)
app.register_blueprint(user_bp, url_prefix="/user")
app.register_blueprint(admin_bp, url_prefix="/admin")
return app