Welcome to ConsenSys-Utils¶
Welcome to ConsenSys-Utils’s documentation.
User’s Guide¶
Guide¶
About ConsenSys-Utils¶
ConsenSys-Utils is a library including a set of utility resources used on a daily basis by ConsenSys France Engineering team.
Create a Flask Application with Factory pattern¶
Quickstart¶
ConsenSys-Utils provides multiple features to create a Flask application. In particular ConsenSys-Utils helps you implement the Application factory pattern
Create a
app.py
>>> from consensys_utils.flask import FlaskFactory >>> from consensys_utils.flask.cli import FlaskGroup # Create an application factory >>> app_factory = FlaskFactory(__name__) # Declares a click application using ConsenSys-Utils click group >>> cli = FlaskGroup(app_factory=app_factory)
Define an entry point in
setup.py
:from setuptools import setup setup( name='my-app', ..., entry_points={ 'console_scripts': [ 'my-app=app:cli' ], }, )
Install the application and start the application
$ pip install -e . $ my-app run --config config.yml
Note that
config.yml
is your .yml configuration file- you don’t need to set
FLASK_APP
environment variable run
command readsFLASK_ENV
environment variable. IfFLASK_ENV=production
the application will be run using agunicorn
server otherwise it useswerkzeug
default development server
Advanced usage¶
Class consensys_utils.flask.FlaskFactory
allows you to
- provide a specific yaml configuration loader
- provide specifics WSGI middlewares
- initialize specifics Flask extensions
- set application hooks
- register specifics Flask blueprints
Change configuration loader¶
By default consensys_utils.flask.FlaskFactory
uses a .yml configuration that
validates against consensys_utils.config.schema.flask.ConfigSchema
.
If you like you can define your own configuration loader.
>>> from consensys_utils.flask import FlaskFactory
>>> from consensys_utils.flask.cli import FlaskGroup
>>> from cfg_loader import ConfigSchema, YamlConfigLoader
>>> from marshmallow import fields
# Declare you configuration schema and config loader
>>> class MySchema(ConfigSchema):
... my_parameter = fields.Str()
>>> yaml_config_loader = YamlConfigLoader(config_schema=MySchema)
# Create an application factory
>>> app_factory = FlaskFactory(__name__, yaml_config_loader=yaml_config_loader)
# Declares a click application using ConsenSys-Utils click group
>>> cli = FlaskGroup(app_factory=app_factory)
Add WSGI Middlewares¶
You can define your own WSGI middlewares and have it automatically applied on your application
>>> from consensys_utils.flask import FlaskFactory
>>> from consensys_utils.flask.cli import FlaskGroup
>>> import base64
>>> class AuthMiddleware:
... def __init__(self, wsgi):
... self.wsgi = wsgi
...
... @staticmethod
... def is_authenticated(header):
... if not header:
... return False
... _, encoded = header.split(None, 1)
... decoded = base64.b64decode(encoded).decode('UTF-8')
... username, password = decoded.split(':', 1)
... return username == password
...
... def __call__(self, environ, start_response):
... if self.is_authenticated(environ.get('HTTP_AUTHORIZATION')):
... return self.wsgi(environ, start_response)
... start_response('401 Authentication Required',
... [('Content-Type', 'text/html'),
... ('WWW-Authenticate', 'Basic realm="Login"')])
... return [b'Login']
>>> middlewares = [AuthMiddleware]
# Create an application factory
>>> app_factory = FlaskFactory(__name__, middlewares=middlewares)
# Declares a click application using ConsenSys-Utils click group
>>> cli = FlaskGroup(app_factory=app_factory)
Add Flasks Extension¶
You can declare your own flask extensions
>>> from consensys_utils.flask import FlaskFactory
>>> from consensys_utils.flask.cli import FlaskGroup
>>> from flasgger import Swagger
>>> swag = Swagger(template={'version': '0.3.4-dev'})
>>> my_extensions = [swag]
# Create an application factory
>>> createapp_factory_app = FlaskFactory(__name__, extensions=my_extensions)
# Declares a click application using ConsenSys-Utils click group
>>> cli = FlaskGroup(app_factory=app_factory)
consensys_utils.flask.FlaskFactory
also extensions given as a
function taking a flask.Flask
application as an argument
>>> from consensys_utils.flask import FlaskFactory
>>> from consensys_utils.flask.cli import FlaskGroup
>>> def init_login_extension(app):
... if app.config.get('LOGIN'):
... from flask_login import LoginManager
...
... login_manager = LoginManager()
... login_manager.init_app(app)
>>> my_extensions = [init_login_extension]
# Create an application factory
>>> app_factory = FlaskFactory(__name__, extensions=my_extensions)
# Declares a click application using ConsenSys-Utils click group
>>> cli = FlaskGroup(app_factory=app_factory)
It allows you to implement advanced extension initialization based on application configuration.
In particular in the example above it allows to allows user having ‘Flask-Login’ installed on option,
only users having activated a LOGIN
configuration need to have ‘Flask-Login’ installed.
Set Application Hooks¶
>>> from consensys_utils.flask import FlaskFactory
>>> from consensys_utils.flask.cli import FlaskGroup
>>> def set_log_request_hook(app):
... @app.before_request
... def log_request():
... current_app.logger.debug(request)
>>> my_hook_setters = [set_log_request_hook]
# Create an application factory
>>> app_factory = FlaskFactory(__name__, hook_setters=my_hook_setters)
# Declares a click application using ConsenSys-Utils click group
>>> cli = FlaskGroup(app_factory=app_factory)
Register Blueprints¶
>>> from flask import Blueprint
>>> from consensys_utils.flask import FlaskFactory
>>> from consensys_utils.flask.cli import FlaskGroup
>>> my_bp1 = Blueprint('my-bp1', __name__)
>>> my_bp2 = Blueprint('my-bp2', __name__)
>>> blueprints = [
... my_bp1,
... lambda app: app.register_blueprint(my_bp2),
... ]
# Create an application factory
>>> app_factory = FlaskFactory(__name__, blueprints=blueprints)
# Declares a click application using ConsenSys-Utils click group
>>> cli = FlaskGroup(app_factory=app_factory)
Declare custom CLI commands¶
It is highly recommended that you declare custom CLI commands directly on the consensys_utils.flask.cli.FlaskGroup
object.
It automatically injects a --config
option to the command for configuration file.
>>> from flask import Blueprint
>>> from flask.cli import with_appcontext
>>> from consensys_utils.flask import FlaskFactory
>>> from consensys_utils.flask.cli import FlaskGroup
# Create an application factory
>>> app_factory = FlaskFactory(__name__)
# Declares a click application using ConsenSys-Utils click group
>>> cli = FlaskGroup(app_factory=app_factory)
>>> @cli.command('test')
... @with_appcontext
... def custom_command():
... click.echo('Test Command on %s' % current_app.import_name)
Properly manage process to execute an iterator¶
Quickstart¶
ConsenSys-Utils provides some resources to properly maintain the execution of an iterator. In particular it allows to
- Run the iterator with a Gunicorn worker in a properly maintained process
- Connect a Flask application to the iterator enabling external control on iterator state
It relies on two main resources
consensys_utils.flask.extensions.iterable.FlaskIterable()
that allows to transform a Flask application into an Iterableconsensys_utils.gunicorn.workers.SyncIterableWorker()
that allows to properly maintain a loop on an iterable WSGI object
Create a
app.py
>>> from flask import Flask >>> from consensys_utils.flask.extensions.iterable import FlaskIterable >>> from consensys_utils.flask import FlaskFactory >>> from consensys_utils.flask.cli import FlaskGroup # Create an iterator >>> iterator = iter(range(3)) # Create an app factory and extend it to make it with a FlaskIterable extension >>> iterable = FlaskIterable(iterator) >>> app_factory = FlaskFactory(__name__, extensions=[iterable]) # Declares a click application using ConsenSys-Utils click group >>> cli = FlaskGroup(app_factory=app_factory)
Set a
config.yml
choosing aconsensys_utils.gunicorn.workers.SyncIterableWorker()
Gunicorn worker allowing to iterate ono theflask: base: APP_NAME: Iterating-App gunicorn: worker-processes: worker_class: consensys_utils.gunicorn.workers.SyncIteratingWorker
Define application entry point and start application as described in Create Flask Application Quickstart
Advanced usage¶
For an advance use-case you can refer to the next example
"""
examples.iterable
~~~~~~~~~~~~~~~~~
Implement an example of properly managing an iterator using Flask-Iterable and Gunicorn
:copyright: Copyright 2017 by ConsenSys France.
:license: BSD, see LICENSE for more details.
"""
import logging
import os
from cfg_loader.utils import parse_yaml_file
from flask import current_app, jsonify
from gunicorn.app.base import BaseApplication
from consensys_utils.flask import Flask
from consensys_utils.flask.extensions.iterable import FlaskIterable
from consensys_utils.gunicorn.workers import PauseIteration
logger = logging.getLogger('examples.iterable')
LOGGING_FILE = os.path.join(os.path.dirname(__file__), 'logging.yml')
# Declare an iterator that we want to properly managed using Gunicorn
class Iterator:
def __init__(self):
self.meter = 0
def set_config(self, config):
self.meter = config['meter']
def __iter__(self):
return self
def __next__(self):
logger.info('Iterator.__next__ meter=%s' % self.meter)
self.meter += 1
if self.meter % 2 == 0:
# Indicating the running loop to pause iteration for 2 secs
raise PauseIteration(2)
if self.meter >= 100:
raise StopIteration()
# We declare a Flask application and extend it to make it iterable
iterable_app = Flask(__name__)
iterable_app.config['meter'] = 10
FlaskIterable(Iterator, iterable_app)
# We declare routes on Flask application to interact with the iterator
@iterable_app.route('/get')
def get():
"""Get current value of the iterator meter"""
logger.info('app.get meter=%s' % current_app.iterator.meter)
return jsonify({'data': current_app.iterator.meter})
@iterable_app.route('/set/<int:meter>')
def set(meter=0):
"""Set current value of the iterator meter"""
current_app.iterator.meter = rv = meter
logger.info('app.set meter=%s' % current_app.iterator.meter)
return jsonify({'data': rv})
# We declare a custom Gunicorn application for the only matter of the example
class Application(BaseApplication):
def load(self):
return iterable_app
def load_config(self):
self.cfg.set('logconfig_dict', parse_yaml_file(LOGGING_FILE))
# We use specific ConsenSys-Utils worker class
self.cfg.set('worker_class', 'consensys_utils.gunicorn.workers.SyncIteratingWorker')
if __name__ == "__main__":
# Run iterator
app = Application()
app.run()
Resources¶
ConsenSys-Utils Resources¶
Config¶
ConsenSys-Utils makes active use of cfg-loader for loading configuration. It is highly recommended you have some basic knowledge of it before using ConsenSys-Utils.
Schema¶
ConsenSys-Utils gathers a bench of useful ConfigSchema
that can be reused in any project.
Logging¶
Logging schema
Flask¶
Flask application configuration schemas
-
class
consensys_utils.config.schema.flask.
FlaskConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Flask application configuration schema
Describes and validates against
Key Comment Default value base
Required base configuration in BaseConfigSchema
formatBaseConfigSchema
defaultsession
Cookie session configuration in SessionConfigSchema
formatSessionConfigSchema
defaultPERMANENT_SESSION_LIFETIME
Cookie’s expiration in number of seconds 2678400 healthcheck
Healthcheck configuration in HealthCheckConfigSchema
formatswagger
Swagger configuration in SwaggerConfigSchema
format
-
class
consensys_utils.config.schema.flask.
SessionConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Flask Session configuration
Describes and validates against
Key Comment Default value cookie
Session cookie configuration in CookieConfigSchema
formatCookieConfigSchema
defaultREFRESH_EACH_REQUEST
Control whether the cookie is sent with every response True
-
class
consensys_utils.config.schema.flask.
CookieConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Flask Session cookie configuration
Describes and validates against
Key Comment Default value NAME
The name of the session cookie ‘session’ DOMAIN
The domain match rule that the session cookie will be valid for None
PATH
Path to the session cookie will be valid for None
HTTPONLY
Browsers will not allow JavaScript access to cookies marked as “HTTP only” for security True
SECURE
Browsers will only send cookies with requests over HTTPSd False
SAMESITE
Restrict how cookies are sent with requests from external sites None
-
class
consensys_utils.config.schema.flask.
SwaggerConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Swagger configuration
Key Comment Default value specs
List of Swagger-UI specs in SwaggerSpecConfigSchema
format[{‘ENDPOINT’: ‘apispec_1’, ‘ROUTE’: ‘/apispec_1.json’}] STATIC_URL_PATH
Endpoint for Swagger static files ‘/flasgger_static’ SWAGGER_UI
Boolean indicating if Swagger UI should be activated False
SPECS_ROUTE
Route to retrieve specifications ‘/apidocs/’
WSGI¶
Schema for WSGI middlewares
-
class
consensys_utils.config.schema.wsgi.
WSGIConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Configuration relative to wsgi middlewares
Describes and validates against
Key Comment Default value request_id
Request ID configuration in RequestIDConfigSchema
None
Gunicorn¶
Gunicorn configuration schemas
-
class
consensys_utils.config.schema.gunicorn.
GunicornConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Gunicorn configuration
Please refer to http://docs.gunicorn.org/en/stable/settings.html for exhaustive listing of Gunicorn settings.
Describes and validates against
Key Comment Default value config
Gunicorn config file path debugging
Debugging config in format DebuggingConfigSchema
DebuggingConfigSchema
defaultlogging
Gunicorn logging config in format LoggingConfigSchema
LoggingConfigSchema
defaultprocess-naming
Process naming config in format ProcessNamingConfigSchema
ProcessNamingConfigSchema
defaultssl
Debugging config in format SSLConfigSchema
SSLConfigSchema
defaultsecurity
Security config in format SecurityConfigSchema
SecurityConfigSchema
defaultserver-mechanics
Server mechanics config in format ServerMechanicsConfigSchema
ServerMechanicsConfigSchema
defaultserver-socket
Server Socket config in format ServerSocketConfigSchema
ServerSocketConfigSchema
defaultworker-processes
Worker processes config in format WorkerProcessesConfigSchema
WorkerProcessesConfigSchema
default
-
class
consensys_utils.config.schema.gunicorn.
ServerSocketConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Server Socket configuration
c.f http://docs.gunicorn.org/en/stable/settings.html#server-socket
-
class
consensys_utils.config.schema.gunicorn.
WorkerProcessesConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Worker Processes configuration
c.f http://docs.gunicorn.org/en/stable/settings.html#worker-processes
-
class
consensys_utils.config.schema.gunicorn.
LoggingConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Logging configuration
c.f http://docs.gunicorn.org/en/stable/settings.html#logging
-
class
consensys_utils.config.schema.gunicorn.
ServerMechanicsConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Server Mechanics configuration
c.f http://docs.gunicorn.org/en/stable/settings.html#server-mechanics
-
class
consensys_utils.config.schema.gunicorn.
ProcessNamingConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Process Naming configuration
c.f http://docs.gunicorn.org/en/stable/settings.html#process-naming
-
class
consensys_utils.config.schema.gunicorn.
SSLConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ SSL configuration
-
class
consensys_utils.config.schema.gunicorn.
SecurityConfigSchema
(*args, substitution_mapping=None, **kwargs)[source]¶ Security configuration
c.f http://docs.gunicorn.org/en/stable/settings.html#security
Loader¶
-
consensys_utils.config.loader.
create_yaml_config_loader
(config_schema, default_config_path='config.yml')[source]¶ Create a configuration loader that can read configuration from .yml file
Parameters: - config_schema (subclass of
cfg_loader.ConfigSchema
) – Configuration schema - default_config_path (str) – Default path where to load configuration from
- config_schema (subclass of
Flask¶
ConsenSys-Utils defines many resources for working with Flask application. It is highly recommended you have some basic knowledge of Flask before using ConsenSys-Utils.
Application Factory¶
ConsenSys-Utils provides useful resources to implement the Flask application factory pattern
-
class
consensys_utils.flask.
FlaskFactory
(import_name=None, yaml_config_loader=<cfg_loader.loader.YamlConfigLoader object>, default_config=None, config_path=None, middlewares=None, extensions=None, hook_setters=None, blueprints=None, **flask_kwargs)[source]¶ ConsenSys Flask factory. It inherits from
BaseFlaskFactory()
By default it applies
Middlewares
consensys_utils.flask.wsgi.apply_request_id_middleware()
: A middleware to inject a custom Request ID header
Extensions
consensys_utils.flask.extensions.initialize_health_extension()
: Init a Flask extension for health checkconsensys_utils.flask.extensions.initialize_web3_extension()
: Init a FlaskWeb3 extension
Hooks
consensys_utils.flask.hooks.set_request_id_hook()
: Hook injecting Request ID header on``flask.request``
-
class
consensys_utils.flask.
BaseFlaskFactory
(import_name=None, yaml_config_loader=<cfg_loader.loader.YamlConfigLoader object>, default_config=None, config_path=None, middlewares=None, extensions=None, hook_setters=None, blueprints=None, **flask_kwargs)[source]¶ A factory to create Flask application
>>> app_factory = BaseFlaskFactory(__name__)
When creating an application a
FlaskFactory
accomplishes next stepsInitialize Flask application
By default it creates a
consensys_utils.flask.Flask
applicationSet application configuration by using a .yml configuration loader
You can refer to
consensys_utils.flask.config.set_app_config()
for more informationApply WSGI middlewares on the application
You can refer to
consensys_utils.flask.wsgi.apply_middlewares()
for more informationInitialize extensions on the application
You can refer to
consensys_utils.flask.extensions.initialize_extensions()
for more informationSet hooks on the application
You can refer to
consensys_utils.flask.hooks.set_hooks()
for more informationRegister blueprints on the application
You can refer to
consensys_utils.flask.blueprints.register_blueprints()
for more information
It is possible to override default behavior by creating a new class that inherits from
FlaskFactory
Example: Adding default hooks
>>> 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__)
Parameters: - import_name (str) – The name of the application package
- yaml_config_loader (
cfg_loader.loader.YamlConfigLoader
) – Optional config loader - middlewares (list) – Middlewares to apply on the application
(c.f
consensys_utils.flask.wsgi.apply_middlewares()
) - extensions (list) – Extensions to initiate on the application
(c.f.
consensys_utils.flask.extensions.initialize_extensions()
) - hook_setters (list) – Hooks to set on the application
(c.f.
consensys_utils.flask.hooks.set_hooks()
) - blueprints (list) – Blueprints to register on the application
(c.f.
consensys_utils.flask.blueprints.register_blueprints()
)
-
create_app
(config_path=None, config=None, **kwargs)[source]¶ Create an application
Parameters: - config_path (str) – .yml configuration path
- config (dict) – Optional application config
- kwargs – Keyword arguments to provide to
flask_class
when instantiating the application object
-
init
(**kwargs)[source]¶ Instantiate Flask application
Parameters: kwargs (dict) – Keyword arguments to provide to the Flask application
-
load_config
(config_path=None)[source]¶ Load configuration
Parameters: config_path (str) – Configuration path
-
class
consensys_utils.flask.
Flask
(import_name, static_url_path=None, static_folder='static', static_host=None, host_matching=False, subdomain_matching=False, template_folder='templates', instance_path=None, instance_relative_config=False, root_path=None)[source]¶ 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
WSGI¶
ConsenSys-Utils implements functions to facilitate Flask app decoration with WSGI middlewares
-
consensys_utils.flask.wsgi.
apply_middlewares
(app, middlewares=None)[source]¶ Apply WSGI middlewares to a Flasks application
Example:
>>> app = Flask(__name__) >>> class AuthMiddleware: ... def __init__(self, wsgi): ... self.wsgi = wsgi ... ... @staticmethod ... def is_authenticated(header): ... if not header: ... return False ... _, encoded = header.split(None, 1) ... decoded = base64.b64decode(encoded).decode('UTF-8') ... username, password = decoded.split(':', 1) ... return username == password ... ... def __call__(self, environ, start_response): ... if self.is_authenticated(environ.get('HTTP_AUTHORIZATION')): ... return self.wsgi(environ, start_response) ... start_response('401 Authentication Required', ... [('Content-Type', 'text/html'), ... ('WWW-Authenticate', 'Basic realm="Login"')]) ... return [b'Login'] >>> middlewares = [AuthMiddleware] >>> apply_middlewares(app, middlewares)
Parameters: - app (
flask.Flask
) – Flask application - middlewares (list) –
WSGI middleware to apply on the application. Expects a list of elements which are either
- A class taking a wsgi as an argument
- A function that takes a
flask.Flask
as argument and even eventually apply a middleware on it
- app (
-
consensys_utils.flask.wsgi.
apply_request_id_middleware
(app)[source]¶ Apply a
consensys_utils.wsgi.RequestIDMiddleware()
on a Flask applicationParameters: app ( flask.Flask
) – Flask application
Extensions¶
ConsenSys-Utils implements functions to facilitate initialization of Flask extensions on an application
-
consensys_utils.flask.extensions.
initialize_extensions
(app, extensions=None)[source]¶ Initialize extensions on a Flask application
Example: Adding an extension
>>> app = Flask(__name__) >>> swag = Swagger(template={'version': '0.3.4-dev'}) >>> my_extensions = [swag] >>> initialize_extensions(app, my_extensions)
Parameters: - app (
flask.Flask
) – Flask application - extensions (list) –
Extensions to initialize on the application. Expects a list of elements which are either
- a Flask extension object (having a callable attribute
init_app
) - a function that takes a
flask.Flask
as argument and eventually initialize an extension on it
- a Flask extension object (having a callable attribute
- app (
-
consensys_utils.flask.extensions.
initialize_health_extension
(app)[source]¶ Initialize healthcheck extension
If
health
is missing in application configuration then this function has no effectParameters: app ( flask.Flask
) – Flask application
-
consensys_utils.flask.extensions.
initialize_web3_extension
(app)[source]¶ Initialize Web3 extension
If
web3
is missing in application configuration then this function has no effectParameters: app ( flask.Flask
) – Flask application
ConsenSys-Utils defines a bench of Flask extensions that can be smoothly re-used.
Healthcheck¶
-
class
consensys_utils.flask.extensions.health.
HealthCheck
(app=None, path=None, success_status=200, success_headers=None, success_handler=<function json_success_handler>, success_ttl=27, failed_status=500, failed_headers=None, failed_handler=<function json_failed_handler>, failed_ttl=9, exception_handler=<function basic_exception_handler>, checkers=None, log_on_failure=True, **options)[source]¶ Healthcheck extension that declares an health check route
Healthcheck URL can be set at application initialization by reading app configuration
Swagger¶
Web3¶
-
class
consensys_utils.flask.extensions.web3.
FlaskWeb3
(*args, app=None, create_provider=<function create_provider>, **kwargs)[source]¶ A Flask-Web3 class that supports initializing application with configuration in format
consensys_utils.config.schema.flask.ConfigSchema()
You can customize this class the same you would do with
flask_web3.FlaskWeb3
-
init_app
(app)[source]¶ Initialize application
Parameters: app (flask.Flask) – Flask application or blueprint object to extend
-
Config¶
-
consensys_utils.flask.config.
set_app_config
(app, config=None)[source]¶ Set application configuration
Parameters: - app (
flask.Flask
) – Flask application - config (dict) – Optional Application configuration
- app (
Hooks¶
ConsenSys-Utils implements functions to facilitate setting Flask hooks on an application
-
consensys_utils.flask.hooks.
set_hooks
(app, hook_setters=None)[source]¶ Set hooks on a Flask application
Example: Adding a hook
>>> app = Flask(__name__) >>> def set_log_request_hook(app): ... @app.before_request ... def log_request(): ... current_app.logger.debug(request) >>> my_hook_setters = [set_log_request_hook] >>> set_hooks(app, my_hook_setters)
Parameters: - app (
flask.Flask
) – Flask application - hook_setters (list) – Hooks to set on the application.
Expects a list of functions that takes a
flask.Flask
as argument
- app (
-
consensys_utils.flask.hooks.
set_request_id_hook
(app)[source]¶ Set a hook to inject request ID
It basis on application config to get the request header from which to retrieve request ID
Parameters: app ( flask.Flask
) – Flask application
Blueprints¶
ConsenSys-Utils implements functions to facilitate registering blueprints on an application
-
consensys_utils.flask.blueprints.
register_blueprints
(app, blueprints=None)[source]¶ Register blueprints on a Flask application
Example:
>>> app = Flask(__name__) >>> my_bp1 = Blueprint('my-bp1', __name__) >>> my_bp2 = Blueprint('my-bp2', __name__) >>> blueprints = [ ... lambda app: app.register_blueprint(my_bp1), ... my_bp2, ... ] >>> register_blueprints(app, blueprints)
Parameters: - app (
flask.Flask
) – Flask application - blueprints (list) –
Blueprints to register on the application. Expects a list of elements which elements are either
- a
flask.Blueprint
- a function that takes a
flask.Flask
as argument and eventually register a blueprint on it
- a
- app (
Gunicorn¶
ConsenSys-Utils slightly enhances gunicorn for better compatibility with its features
Application¶
Config¶
Logging¶
-
class
consensys_utils.gunicorn.logging.
Logger
(cfg)[source]¶ Enrich Gunicorn logger class
In particular it overrides the following methods
- setup to load logging configuration from a .yml file
-
class
consensys_utils.gunicorn.logging.
RequestIDLogger
(*args, **kwargs)[source]¶ Gunicorn logger that handles Request ID header
-
access
(resp, req, environ, request_time)[source]¶ See http://httpd.apache.org/docs/2.0/logs.html#combined for format details
-
Workers¶
-
class
consensys_utils.gunicorn.workers.
SyncIteratingWorker
(age, ppid, sockets, app, timeout, cfg, log)[source]¶ A Gunicorn synchronous worker that allows to run an iterable WSGI application.
It allows to run a loop process that iterates over a WSGI application object while allowing to process HTTP requests.
Since the worker is synchronous it is thread safe to modify the WSGI object either when iterating or when handling an HTTP request.
Remark
Such a worker should not be considered highly performing as HTTP server but for dealing with a few requests to control the iterable WSGI application it is well suited.
-
handle
(listener, client, address)[source]¶ Handle a request
Method is almost identical to
gunicorn.workers.sync.SyncWorker()
one.We need to overide this method because we use non blocking socket connections thus we are more sensitive to
errno.EAGAIN()
errors.
-
run
()[source]¶ Run the main worker loop
At each step of the loop it
- Handles entry socket request if available
- Iterate on the WSGI iterable object
If a
consensys_utils.exceptions.PauseIteration()
is caught when iterating on the WSGI object then the loop waits by entering a stale state freeing CPU usage.Receiving an HTTP request instantaneously gets the loop out of stale state.
-
Exceptions¶
-
class
consensys_utils.exceptions.
PauseIteration
(timeout=None)[source]¶ Error indicating to pause iteration
Useful when combined with
consensys_utils.gunicorn.workers.SyncIteratingWorker()
Parameters: timeout (float) – Maximum time to pause before re-starting iteration
Contributing¶
If you are interested in contributing to the project please refer to Contributing guidelines
Contributing guidelines¶
Feature Requests, Bug Reports, and Feedback…¶
…should all be reported on the GitHub Issue Tracker .
Reporting issues¶
- Describe what you expected to happen.
- If possible, include a minimal, complete, and verifiable example to help
- Describe what actually happened. Include the full traceback if there was an exception.
Setting-Up environment¶
Requirements¶
Having the latest version of
git
installed locallyHaving Python 3.6 installed locally
Having
virtualenv
installed locallyTo install
virtualenv
you can run the following command$ pip install virtualenv
Having
docker
anddocker-compose
installed locallyHaving
pip
environment variables correctly configuredSome of the package’s dependencies of the project could be hosted on a custom PyPi server. In this case you need to set some environment variables in order to make
pip
inspect the custom pypi server when installing packages.To set
pip
environment variables on a permanent basis you can add the following lines at the end of your\.bashrc
file (being careful to replace placeholders)# ~/.bashrc ... # Indicate to pip which pypi server to download from export PIP_TIMEOUT=60 export PIP_INDEX_URL=<custom_pypi_protocol>://<user>:<password>@<custom_pypi_host> export PIP_EXTRA_INDEX_URL=https://pypi.python.org/simple
First time setup¶
Clone the project locally
Create development environment using Docker or Make
$ make init
Project organisation¶
The project
.
├── consensys_utils/ # Main package source scripts (where all functional python scripts are stored)
├── docs/ # Docs module containing all scripts required by sphinx to build the documentation
├── tests/ # Tests folder where all test modules are stores
├── .coveragerc # Configuration file for coverage
├── .gitignore # List all files pattern excluded from git's tracking
├── .gitlab-ci.yml # GitLab CI script
├── AUTHORS # List of authors of the project
├── CHANGES # Changelog listing every changes from a release to another
├── CONTRIBUTING.rst # Indicate the guidelines that should be respected when contributing on this project
├── LICENSE # License of the project
├── Makefile # Script implement multiple commands to facilitate developments
├── README.rst # README.md of your project
├── setup.cfg # Configuration of extra commands that will be installed on package setup
├── setup.py # File used to setup the package
└── tox.ini # Configuration file of test suite (it runs test suite in both Python 3.5 and 3.6 environments)
Coding¶
Development Workflow¶
Please follow the next workflow when developing
- Create a branch to identify the feature or issue you will work on (e.g.
feature/my-feature
orhotfix/2287
) - Using your favorite editor, make your changes, committing as you go and respecting the AngularJS Commit Message Conventions
- Follow PEP8 and limit script’s line length to 120 characters. See testing-linting
- Include tests that cover any code changes you make. See running-test and running-coverage
- Update
setup.py
script with all dependencies you introduce. See adding-dependency for precisions - Write clear and exhaustive docstrings. Write docs to precise how to use the functionality you implement. See writing-docs
- Update changelog with the modifications you proceed to. See updating-changelog
- Your branch will soon be merged ! :-)
Testing¶
Running coverage¶
Please ensure that all the lines of source code you are writing are covered in your test suite. To generate the coverage report, please run
$ make coverage
Read more about coverage.
Running the full test suite with tox
will combine the coverage reports from all runs.
Testing linting¶
To test if your project is compliant with linting rules run
$ make test-lint
To automatically correct linting errors run
$ make lint
Running full test suite¶
Run test suite in multiple distinct python environment with following command
$ make tox
Writing documentation¶
Write clear and exhaustive docstrings in every functional scripts.
This project uses sphinx to build documentations, it requires docs file to be written in .rst
format.
To build the documentation, please run
$ make docs
Precisions¶
Updating changelog¶
Every implemented modifications on the project from a release to another should be documented in the changelog CHANGES.rst
file.
The format used for a release block is be the following
Version <NEW_VERSION>
---------------------
Released on <NEW_VERSION_RELEASED_DATE>, codename <NEW_VERSION_CODENAME>.
Features
- Feature 1
- Feature 2
- Feature 3
Fixes
- Hotfix 1 (``#134``)
- Hotfix 2 (``#139``)
.. _#134: https://github.com/ConsenSys/consensys-utils/issues/134
.. _#139: https://github.com/ConsenSys/consensys-utils/issues/139
Be careful to never touch the header line as well as the release’s metadata sentence.
Version <NEW_VERSION>
---------------------
Released on <NEW_VERSION_RELEASED_DATE>, codename <NEW_VERSION_CODENAME>.
Adding a new dependency¶
When adding a new package dependency it should be added in setup.py
file in the install_requires
list
The format should be dependency==1.3.2
.
- When adding a dev dependency (e.g. a testing dependency) it should be added in
setup.py
file in theextra_requires
dev
listtox.ini
file in the[testenv]
deps
Makefile commands¶
Makefile
implements multiple handful shell commands for development
make init¶
- Initialize development environment including
- venv creation
- package installation in dev mode
make clean¶
Clean the package project by removing some files such as .pyc
, .pyo
, *.egg-info
make coverage¶
Run the test suite and computes test coverage. It creates an html report that is automatically open after the commands terminates
make tox¶
Run the test suites in multiple environments
make docs¶
Build documentation from the docs
folder using sphinx.
It generates a build of the documentation in html format located in docs/_build/html
.
Additional Notes¶
Legal information and changelog are here for the interested.
Changelog¶
Here you can see the full list of changes between each releases of ConsenSys-Utils.
Version 0.2.0¶
Unreleased
Version 0.2.0b3¶
Released on August 9th 2018
Chore
- Requirements: add requirements for doc (required by readthedocs)
Version 0.2.0b2¶
Released on August 9th 2018
Fix
- Flask: remove swagger extension from default extensions
Version 0.2.0b1¶
Released on August 6th 2018
Feat
- Config: schema for web3 provider
- Web3: implement create_provider function
- Flask: implement Web3 extension
- Flask: implement Flask-Iterable extension
- Gunicorn: implement SyncIteratingWorker
Chore
- Examples: implement an example for an iterating worker
Version 0.1.0¶
Released on July 30th 2018
Fix
- Flask: Enhance consensys_utils.flask.cli.FlaskGroup
- Flask: Improve Factory pattern
Version 0.1.0b4¶
Released on July 27th 2018
Refactor
- Config: update default values of Gunicorn configuration schema
Version 0.1.0b3¶
Released on July 27th 2018
Fix
- Gunicorn: fix gunicorn application to use
consensys_utils.gunicorn.config.Config
Tests
- Gunicorn: add tests for
gunicorn.config.schema.GunicornConfigSchema
Version 0.1.0b1¶
Released July 26th 2018
Features
- Config: implement config package
- Flask: implement WSGI middlewares helpers
- Flask: implement application hooks helpers
- Flask: implement config features to integrate with cfg-loader
- Flask: implement flask extensions helpers
- Flask: implement default extension for healthcheck
- Flask: implement default extension for Swagger
- Flask: implement logging features
- Flask: implement blueprints helpers
- Gunicorn: implement custom Gunicorn application
- Flask: implement CLI resources in particular FlaskGroup that allows to smoothly integrates with Gunicorn
- Config: Implement Gunicorn config schema
License¶
Authors¶
ConsenSys-Utils is developed and maintained by the ConsenSys France team and community contributors. The core maintainers are:
- Nicolas Maurice (nmvalera)
General License Definitions¶
The following section contains the full license texts for ConsenSys-Utils and the documentation.
License¶
Copyright (c) 2017 by ConsenSys France and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.