portfolio / portfolio-webapp / webapp / app.py
app.py
Raw
from flask import current_app, Flask, g, request, session
from pathlib import Path
from typing import Iterable, Union

from . import api, constants, jinjafilters, models, utils, views

def create_app(config_paths:Iterable[Union[str, Path]]=None, **config_overrides) -> Flask:
    """
    App factory. Returns Flask application object.
    Application configuration is defined by files in :config_paths:.
    :config_paths: 
        Iterable of absolute paths to config files. 
        These will be loaded sequentially. Settings in the first file may be overridden by subsequent files.
    :config_overrides: 
        Kwargs become configuration overrides that take precedence over anything in :config_paths:. 
        These should typically only be used in tests.
    """
    config_paths = config_paths or []
    app = Flask(__name__, root_path=Path(__file__).parent.parent)
    for i, absolute_path in enumerate(config_paths):
        print('{} config from {}'.format('Loading' if i == 0 else 'Extending', absolute_path))
        app.config.from_pyfile(absolute_path)
    for key, val in config_overrides.items():
        app.config[key] = val

    # Jinja globals
    # Each given name here becomes available as a variable anywhere within templates
    app.jinja_env.globals['models'] = models
    app.jinja_env.globals['constants'] = constants
    app.jinja_env.globals['datums'] = constants

    # Initialize database
    with app.app_context():
        models.db.init_app(app)

    # Register jinja filters
    app.register_blueprint(jinjafilters.bp)

    # Register view blueprints
    app.register_blueprint(views.public.bp)

    app.register_blueprint(views.admin.bp, url_prefix='/admin')
    app.register_blueprint(views.setup.bp, url_prefix='/setup')

    # Register API blueprints
    app.register_blueprint(api.public.bp, url_prefix='/api')

    app.register_blueprint(api.admin.bp, url_prefix='/api/admin')
    app.register_blueprint(api.auth.bp, url_prefix='/api/auth')
    

    # Before/after request registration
    app.before_request(_before_request)

    # Register error handlers
    #TODO Better error handling
    #app.register_error_handler(401, views.errorhandling.error_40x)
    #app.register_error_handler(403, views.errorhandling.error_40x)
    #app.register_error_handler(404, views.errorhandling.error_40x)
    #app.register_error_handler(405, views.errorhandling.error_40x)
    #app.register_error_handler(500, views.errorhandling.error_500)
    #app.register_error_handler(Exception, views.errorhandling.unhandled_exception)

    # Register core function routes
    #TODO Serve static resources
    #app.add_url_rule('/<path:resource>', 'serve_static_resource', serve_static_resource)

    return app


def _before_request():
    g.user = None
    g.timezone = current_app.config['TIMEZONE']
    g.root_url_full = f"{current_app.config['SCHEME']}://{current_app.config['ROOT_URL']}"

    if any(session.get(key, None) for key in ('logged_in', 'user_id', 'user_type')):
        if all(session.get(key, None) for key in ('logged_in', 'user_id', 'user_type')):
            g.user = models.User.query.filter_by(
                active=True,
                user_id=session['user_id'],
                user_type=session['user_type'],
            ).first()
        if not g.user:
            session.pop('logged_in')
            session.pop('user_id')
            session.pop('user_type')
            g.user = None