"""
consensys_utils.flask.app
~~~~~~~~~~~~~~~~~~~~~~~~~
This module implements a WSGI application object.
:copyright: Copyright 2017 by ConsenSys France.
:license: BSD, see :ref:`license` for more details.
"""
import flask
from .blueprints import register_blueprints
from .config import set_app_config
from .extensions import initialize_extensions, DEFAULT_EXTENSIONS
from .hooks import set_hooks, DEFAULT_HOOK_SETTERS
from .logging import create_logger
from .wsgi import apply_middlewares, DEFAULT_MIDDLEWARES
from ..config import DEFAULT_FLASK_CONFIG_LOADER
[docs]class Flask(flask.Flask):
"""ConsenSys-Utils Flask class
It applies a light overriding on top of :class`flask.Flask` to enable
- usage of a logger that can be configured from .yml file
.. _`cfg-loader`: https://cfg-loader.readthedocs.io/en/stable/
"""
@flask.helpers.locked_cached_property
def logger(self):
return create_logger(self)
[docs]class BaseFlaskFactory:
"""A factory to create Flask application
.. doctest::
>>> from consensys_utils.flask.app import BaseFlaskFactory
>>> app_factory = BaseFlaskFactory(__name__)
When creating an application a :class:`FlaskFactory` accomplishes next steps
#. Initialize Flask application
By default it creates a :class:`consensys_utils.flask.Flask` application
#. Set application configuration by using a .yml configuration loader
You can refer to :meth:`consensys_utils.flask.config.set_app_config` for more information
#. Apply WSGI middlewares on the application
You can refer to :meth:`consensys_utils.flask.wsgi.apply_middlewares` for more information
#. Initialize extensions on the application
You can refer to :meth:`consensys_utils.flask.extensions.initialize_extensions` for more information
#. Set hooks on the application
You can refer to :meth:`consensys_utils.flask.hooks.set_hooks` for more information
#. Register blueprints on the application
You can refer to :meth:`consensys_utils.flask.blueprints.register_blueprints` for more information
It is possible to override default behavior by creating a new class that inherits from :class:`FlaskFactory`
Example: Adding default hooks
.. doctest::
>>> from flask import request
>>> def set_foo_request_id_hook(app):
... @app.before_request
... def set_request_id():
... request.id = 'foo'
>>> class CustomFlaskFactory(BaseFlaskFactory):
... default_hook_setters = [set_foo_request_id_hook]
>>> app_factory = CustomFlaskFactory(__name__)
:param import_name: The name of the application package
:type import_name: str
:param yaml_config_loader: Optional config loader
:type yaml_config_loader: :class:`cfg_loader.loader.YamlConfigLoader`
:param middlewares: Middlewares to apply on the application
(c.f :meth:`consensys_utils.flask.wsgi.apply_middlewares`)
:type middlewares: list
:param extensions: Extensions to initiate on the application
(c.f. :meth:`consensys_utils.flask.extensions.initialize_extensions`)
:type extensions: list
:param hook_setters: Hooks to set on the application
(c.f. :meth:`consensys_utils.flask.hooks.set_hooks`)
:type hook_setters: list
:param blueprints: Blueprints to register on the application
(c.f. :meth:`consensys_utils.flask.blueprints.register_blueprints`)
:type blueprints: list
"""
# Flask class to use to instantiate applications
flask_class = Flask
# Default WSGI middleware to apply
default_middlewares = []
# Default Flask extensions to initialize
default_extensions = []
# Default hooks to set on the application
default_hook_setters = []
# Default blueprints to register on the application
default_blueprints = []
def __init__(self, import_name=None,
yaml_config_loader=DEFAULT_FLASK_CONFIG_LOADER, default_config=None, config_path=None,
middlewares=None, extensions=None, hook_setters=None, blueprints=None,
**flask_kwargs):
self.import_name = flask_kwargs.pop('import_name', import_name)
self.flask_kwargs = flask_kwargs
self.yaml_config_loader = yaml_config_loader
self.default_config = default_config or {}
self.config_path = config_path
self.middlewares = self.default_middlewares + (middlewares or [])
self.extensions = self.default_extensions + (extensions or [])
self.hook_setters = self.default_hook_setters + (hook_setters or [])
self.blueprints = self.default_blueprints + (blueprints or [])
self._config = None
self._app = None
[docs] def init(self, **kwargs):
"""Instantiate Flask application
:param kwargs: Keyword arguments to provide to the Flask application
:type kwargs: dict
"""
# Declare Flask application
kwargs.update(self.flask_kwargs)
kwargs.setdefault('import_name', self.import_name)
self._app = self.flask_class(**kwargs)
return self._app
[docs] def load_config(self, config_path=None):
"""Load configuration
:param config_path: Configuration path
:type config_path: str
"""
config = self.default_config.copy()
config.update(self.yaml_config_loader.load(config_path or self.config_path))
return config
[docs] def set_config(self, config=None):
"""Set application config
:param raw_config: Optional application config
:type raw_config: dict
"""
self._config = self.default_config.copy()
self._config.update(config or {})
# Set application configuration
set_app_config(self._app, config=self._config)
[docs] def apply_middlewares(self):
"""Apply middlewares on application"""
apply_middlewares(self._app, self.middlewares)
[docs] def initialize_extensions(self):
"""Initialize extensions on application"""
initialize_extensions(self._app, self.extensions)
[docs] def set_hooks(self):
"""Set hooks on application"""
set_hooks(self._app, self.hook_setters)
[docs] def register_blueprints(self):
"""Register blueprints on application"""
register_blueprints(self._app, self.blueprints)
[docs] def create_app(self, config_path=None, config=None, **kwargs):
"""Create an application
:param config_path: .yml configuration path
:type config_path: str
:param config: Optional application config
:type config: dict
:param kwargs: Keyword arguments to provide to :attr:`flask_class` when
instantiating the application object
:type kwargs dict:
"""
# Load configuration
config = config or self.load_config(config_path)
if config == self._config:
return self._app
# Declare new Flask application
self.init(**kwargs)
# Set configuration
self.set_config(config)
# Apply middlewares
self.apply_middlewares()
# Initialize extensions
self.initialize_extensions()
# Set hooks
self.set_hooks()
# Register blueprints
self.register_blueprints()
return self._app
def __call__(self, *args, **kwargs):
return self.create_app(*args, **kwargs)
[docs]class FlaskFactory(BaseFlaskFactory):
"""ConsenSys Flask factory. It inherits from :meth:`BaseFlaskFactory`
By default it applies
**Middlewares**
- :meth:`consensys_utils.flask.wsgi.apply_request_id_middleware`: A middleware to inject a custom Request ID header
**Extensions**
- :meth:`consensys_utils.flask.extensions.initialize_health_extension`: Init a Flask extension for health check
- :meth:`consensys_utils.flask.extensions.initialize_web3_extension`: Init a FlaskWeb3 extension
**Hooks**
- :meth:`consensys_utils.flask.hooks.set_request_id_hook`: Hook injecting Request ID header on``flask.request``
"""
# Default WSGI middleware to apply
default_middlewares = DEFAULT_MIDDLEWARES
# Default Flask extensions to initialize
default_extensions = DEFAULT_EXTENSIONS
# Default hooks to set on the application
default_hook_setters = DEFAULT_HOOK_SETTERS