Slide 1

Slide 1 text

(NCUMHQT(WPCPF2TQHKV Armin @mitsuhiko Ronacher

Slide 2

Slide 2 text

Hello I'm Armin, Hailing from wonderful Vienna Austria

Slide 3

Slide 3 text

I do Open Source Things :)

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Where does Flask come from?

Slide 7

Slide 7 text

Iteration … • Before Flask there was Werkzeug • Before Werkzeug there was WSGITools • Before WSGITools there was Colubrid • Before Colubrid there was a lot of PHP and “Pocoo”

Slide 8

Slide 8 text

Why? • I wanted to build software to distribute • Originally I wanted to write a version of phpBB • The inspiration was utilities to build “trac” and never “django” • Put programmer into control of configuration, do not impose configuration on the framework users

Slide 9

Slide 9 text

Why do people like it?

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

The API seems to resonate with people

Slide 12

Slide 12 text

Small overall footprint

Slide 13

Slide 13 text

What's it good at

Slide 14

Slide 14 text

small HTML heavy CRUD sites

Slide 15

Slide 15 text

JSON APIs :)

Slide 16

Slide 16 text

Iteration Speed

Slide 17

Slide 17 text

Testing :)

Slide 18

Slide 18 text

What's it bad at

Slide 19

Slide 19 text

High Performance Async IO

Slide 20

Slide 20 text

My Favorite Flask App Structure

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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)

Slide 23

Slide 23 text

Optional Contained App from flask import Flask class MyThing(object): 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)

Slide 24

Slide 24 text

Development Runner # devapp.py from myapp import create_app app = create_app({ 'DATABASE_URI': 'sqlite:////tmp/my-appdb.db',
 })

Slide 25

Slide 25 text

Development Runner $ export FLASK_APP=`pwd`/devapp.py $ export FLASK_DEBUG=1 $ flask run * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 236-726-332

Slide 26

Slide 26 text

The Improved Runner app.run(debug=True) $ export FLASK_APP=/path/to/file.py $ export FLASK_DEBUG=True $ flask run

Slide 27

Slide 27 text

Context Locals

Slide 28

Slide 28 text

Basics from flask import Flask, current_app app = Flask(__name__) with app.app_context(): assert current_app.name == app.name

Slide 29

Slide 29 text

Other Context Objects • request context bound: • flask.request • flask.session • app context bound: • flask.g • flask.current_app

Slide 30

Slide 30 text

app context tears down end of request!

Slide 31

Slide 31 text

Cron Stuff from myapp import create_app from werkzeug.utils import import_string def run_cron(import_name, config): func = import_string(import_name) app = create_app(config=config) with app.app_context():
 func()

Slide 32

Slide 32 text

Resource Management import sqlite3 from flask import g def get_db(): db = getattr(g, '_database_con', None) if db is None: db = g._database_con = sqlite3.connect(DATABASE) return db @app.teardown_appcontext def close_connection(exception): db = getattr(g, '_database_con', None) if db is not None: db.close()

Slide 33

Slide 33 text

User Management from flask import g def get_user():
 user = getattr(g, 'user', None) if user is None: user = load_user_from_request() g.user = user return user

Slide 34

Slide 34 text

JSON APIs

Slide 35

Slide 35 text

Result Wrapper from flask import json, Response class ApiResult(object): 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')

Slide 36

Slide 36 text

Response Converter from flask import Flask class ApiFlask(Flask): def make_response(self, rv): if isinstance(rv, ApiResult):
 return rv.to_response() return Flask.make_response(self, rv)

Slide 37

Slide 37 text

API Errors from flask import json, Response class ApiException(object): def __init__(self, message, status=400): self.message = message self.status = status def to_result(self): return ApiResult({'message': self.message}, status=self.status)

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Demo Api from flask import Blueprint bp = Blueprint('demo', __name__) @bp.route('/add') def add_numbers():
 a = request.args('a', type=int) b = request.args('b', type=int) if a is None or b is None: raise ApiException('Numbers must be integers') return ApiResult({'sum': a + b})

Slide 40

Slide 40 text

Validation / Serialization

Slide 41

Slide 41 text

Finding the Balance • Most validation systems in Python are in a weird spot • Either very powerful but opinionated and fun to use • Or powerful and a pain to use • Or weak and sooner or later shape your API a ton

Slide 42

Slide 42 text

Finding the Right Library • There are so many • jsonschema anyone? • One that works for me: voluptuous

Slide 43

Slide 43 text

voluptuous 101 from flask import request from voluptuous import Invalid def dataschema(schema): def decorator(f): def new_func(*args, **kwargs): try: kwargs.update(schema(request.get_json())) except Invalid as e: raise ApiException('Invalid data: %s (path "%s")' % (e.msg, '.'.join(e.path))) return f(*args, **kwargs) return update_wrapper(new_func, f) return decorator

Slide 44

Slide 44 text

voluptuousified view from voluptuous import Schema, REMOVE_EXTRA @app.route('/add', methods=['POST']) @dataschema(Schema({ 'a': int, 'b': int, }, extra=REMOVE_EXTRA)) def add_numbers(a, b): return ApiResult({'sum': a + b})

Slide 45

Slide 45 text

extensions vs hand rolled

Slide 46

Slide 46 text

Extensions • They are nice for a lot of things (like database APIs) • However they are very opinionated about data in/out • Often these things fight with how I want APIs to work • In particular serialization/deserialization/errors

Slide 47

Slide 47 text

Control the API: Pagination from werkzeug.urls import url_join class ApiResult(object): def __init__(self, …, next_page=None):
 … self.next_page = next_page def to_response(self): rv = Response(…) if self.next_page is not None: rv.headers['Link'] = '<%s>; rel="next"' % \ url_join(request.url, self.next_page) return rv

Slide 48

Slide 48 text

Security!

Slide 49

Slide 49 text

Context, context, context • Write good abstractions for security related APIs • Make code aware of the context it's executed at

Slide 50

Slide 50 text

context for improved security 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))

Slide 51

Slide 51 text

JSON Escaping >>> from flask.json import htmlsafe_dumps >>> print htmlsafe_dumps("var x = 'foo';") "\u003cem\u003evar x = \u0027foo\u0027;\u003c/em\u003e"

Slide 52

Slide 52 text

Testing!

Slide 53

Slide 53 text

best paired with py.test

Slide 54

Slide 54 text

Basic Example 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

Slide 55

Slide 55 text

Example Test def test_app_name(app):
 assert app.name == 'mypackage'

Slide 56

Slide 56 text

More Fixtures def test_client(request, app):
 client = app.test_client() client.__enter__() request.addfinalizer( lambda: client.__exit__(None, None, None)) return client

Slide 57

Slide 57 text

Example View Test def test_welcome_view(test_client): rv = test_client.get('/welcome') assert 'set-cookie' not in rv.headers assert b'Welcome' in rv.data assert rv.status_code == 200

Slide 58

Slide 58 text

Websockets and Stuff

Slide 59

Slide 59 text

no amazing answer

Slide 60

Slide 60 text

What I do: • redis broker with pub/sub • custom server that sends those events via SSE to the browser • push events from the Flask backend to this redis broker • use signing (itsdangerous) for authentication of the channel

Slide 61

Slide 61 text

QA &