Notes on Flask

python flask

Notes from Armin Ronacher’s Flask for Fun and Profit

create_app
from flask import Flask

def create_app(config=None):
    app = Flask(__name__)
    app.config.update(config or {})
    register_blueprints(app)
    register_other_things(app)
    return app
register_blueprints
from werkzeug.utils import find_modules, import_string

def register_blueprints(app):
    for name in find_modules('myapp.blueprints'):
        mod = import_string(name)
        if hasattr(mod, 'blueprint'):
            app.register_blueprint(mod.blueprint)
Optional Contained App

The idea here is you can separate top level config and functionality that is clearly separated from internal flask related config and functionality. I.e. in the flask app this object is exposed as an attribute on the app, and visa versa.

from flask import Flask

class MyThing:

    def __init__(self, config):
        self.flask_app = create_app(config)
        self.flask_app.my_thing = self

    def __call__(self, environ, start_response):
        return self.flask_app(environ, start_response)
Development Runner

Basically don’t use flask.run(debug=True) in your code. Run flask run from the commandline instead.

Global Variable Context Object

Thread local variables are kind of unavoidable, flask explicitly exposes this stuff so it’s clear where they are used.

These object exist for duration of the request only:

flask.request
flask.session
flask.g
flask.current_app
REST API

Ronacher doesn’t use external libs for this, just writes his own:

from flask import Flask, json, Response, Blueprint

class ApiResult:

    def __init__(self, value, status=200):
        self.value = value
        self.status = status

    def to_response(self):
        return Response(
            json.dumps(self.value),
            status=self.status,
            mimetype='application/json'
        )


class ApiFlask(Flask):

    def make_response(self, rv):
        if isinstance(rv, ApiResult):
            return rv.to_response()
        return Flask.make_response(self, rv)


class ApiException:

    def __init__(self, message, status=400):
        self.message = message
        self.status = status

    def to_result(self):
        return ApiResult({'message': self.message}, status=self.status)


def register_error_handlers(app):
    app.register_error_handler(
        ApiException, lambda err: err.to_result()
    )


bp = Blueprint('demo', __name__)

@bp.route('/add')
def add_nubmers():
    a = request.args.get('a', type=int)
    b = request.args.get('b', type=int)
    if a is None or b is None:
        raise ApiException('Numbers must be integers')
    return ApiResult({'sum': a + b})
Validation

Too many validation/type libraries… Ronacher’s talk is a bit old, so his libraries are not really in use, but the overall idea is good.

Good example of wrapping up their usage in a decorator:

def dataschema(schema):
    def decorator(f):
        def new_func(*args, **kwargs):
            try:
                kwargs.update(schema(request.get_json()))
            except Invalid as e:
                raise ApiException(f'Invalid data: {e.msg} (path "{'.'.join(e.path)}")')
            return f(*args, **kwargs)
        # update_wrapper must be his own @wraps function?
        return  update_wrapper(new_func, f)
    return decorator
Security

The idea here is restricting authorization at a central point to make security easier to track.

from myapp import db
from myapp.security import get_available_organizations

class Project(db.Model):

    @property
    def query(self):
        org_query = get_available_organizations()
        return db.Query(self).filter(
            Project.organization.in_(org_query)
        )
Testing

Ronacher uses pytest. That’s all I ever use. Sold!

His example for creating a root fixture for use in flask tests:

import pytest

@pytest.fixture(scope='module')
def app(request):
    from yourapp import create_app
    app = create_app()
    ctx = app.app_context()
    ctx.push()
    request.addfinalizer(ctx.pop)
    return app
Web Sockets?

He’s not crazy about websockets with Flask, rather use redis pub/sub and have a separate little web server that pushes out events from redis, that is has received from the main Flask app.

Comments

Ronacher says:

  • werkzeug adhere’s to the WSGI standard much closer than other folks did.

  • Jinja has a lot of warts in his opinion.

  • He’d remove a lot of functionality that is current in Flask

  • Rust - pencil web framework similar to Flask