Flask Patterns
Flask Patterns
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello World!'
Separate States
• application state
• runtime state
>>> request
<LocalProxy unbound>
>>> with app.test_request_context():
... request
...
<Request 'http://localhost/' [GET]>
Contexts are Stacks
ctx = app.request_context()
ctx.push()
try:
...
finally:
ctx.pop()
ctx = flask._request_ctx_stack.top
Implicit Context Push
• flask._request_ctx_stack
• flask._app_ctx_stack
When to use them?
They are like sys._getframe
There are legitimate uses for them but be careful
How do they work?
• Request bound
• Test bound
• User controlled
• Early teardown
State Bound Data
app = Flask(__name__)
@app.before_request
def connect_to_db_on_request():
g.db = connect_to_db(app.config['DATABASE_URL'])
@app.teardown_request
def close_db_connection_after_request(error=None):
db = getattr(g, 'db', None)
if db is not None:
db.close()
Problems with that
app = Flask(__name__)
def get_db():
ctx = _app_ctx_stack.top
con = getattr(ctx, 'myapp_database', None)
if con is None:
con = connect_to_database(app.config['DATABASE_URL'])
ctx.myapp_database = con
return con
@app.teardown_appcontext
def close_database_connection(error=None):
con = getattr(_app_ctx_stack.top, 'myapp_database', None)
if con is not None:
con.close()
Multiple Apps!
from flask import _app_ctx_stack
def init_app(app):
app.teardown_appcontext(close_database_connection)
def get_db():
ctx = _app_ctx_stack.top
con = getattr(ctx, 'myapp_database', None)
if con is None:
con = connect_to_database(ctx.app.config['DATABASE_URL'])
ctx.myapp_database = con
return con
def close_database_connection(error=None):
con = getattr(_app_ctx_stack.top, 'myapp_database', None)
if con is not None:
con.close()
Using it
app = Flask(__name__)
yourdatabase.init_app(app)
@app.route('/')
def index():
db = yourdatabase.get_db()
db.execute_some_operation()
return '...'
Bring the proxies back
app = Flask(__name__)
yourdatabase.init_app(app)
db = LocalProxy(yourdatabase.get_db)
@app.route('/')
def index():
db.execute_some_operation()
return '...'
2 Teardown Management
How Teardown Works
@app.teardown_request
def release_resource(error=None):
g.resource.release()
Good Teardown
@app.teardown_request
def release_resource(error=None):
res = getattr(g, 'resource', None)
if res is not None:
res.release()
Responsive Teardown
@app.teardown_appcontext
def handle_database_teardown(error=None):
db_con = getattr(_app_ctx_stack.top, 'myapp_database', None)
if db_con is None:
return
if error is None:
db_con.commit_transaction()
else:
db_con.rollback_transaction()
db_con.close()
3 Response Object Creation
Requests and Responses
• Response objects on the other hand are passed down the call stack
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
Explicit Response Creation
app = Flask(__name__)
@app.route('/')
def index():
string = render_template('index.html')
response = make_response(string)
return response
Manual Response Creation
app = Flask(__name__)
@app.route('/')
def index():
string = render_template('index.html')
response = Response(string)
return response
Response Object Creation
def add_timing_information(f):
def timed_function(*args, **kwargs):
now = time.time()
rv = make_response(f(*args, **kwargs))
rv.headers['X-Runtime'] = str(time.time() - now)
return rv
return update_wrapper(timed_function, f)
@app.route('/')
@add_timing_information
def index():
return 'Hello World!'
Custom Return Types
from flask import Flask, jsonify
class MyFlask(Flask):
def make_response(self, rv):
if hasattr(rv, 'to_json'):
return jsonify(rv.to_json())
return Flask.make_response(self, rv)
class User(object):
def __init__(self, id, username):
self.id = id
self.username = username
def to_json(self):
return {'username': self.username, 'id': self.id}
app = MyFlask(__name__)
@app.route('/')
def index():
return User(42, 'john')
4 Blueprints
Problem: Multiple Apps
• For instance an app that shares everything with another one except
for the configuration settings and one view.
Attempt #1
app1 = Flask(__name__)
app1.config.from_pyfile('config1.py')
app2 = Flask(__name__)
app2.config.from_pyfile('config2.py')
???
Won't work :-(
def make_app(filename):
app = Flask(__name__)
app.config.from_pyfile(filename)
@app.route('/')
def index():
return 'Hello World!'
return app
app1 = make_app('config1.py')
app2 = make_app('config2.py')
Problems with that
bp = Blueprint('common', __name__)
@bp.route('/')
def index():
return 'Hello World!'
def make_app(filename):
app = Flask(__name__)
app.config.from_pyfile(filename)
app.register_blueprint(bp)
return app
app1 = make_app('config1.py')
app2 = make_app('config2.py')
Ugly?
Beauty is in the eye of the beholder
A “better” solution is hard — walk up to me
The name says it all
def register_jinja_stuff(sstate):
sstate.app.jinja_env.globals['some_variable'] = 'some_value'
bp = Blueprint('common', __name__)
bp.record_once(register_jinja_stuff)
5 Multi-Register Blueprints
Simple Example
bp = Blueprint('common', __name__)
@bp.route('/')
def index(username):
return 'Resource for user %s' % username
app.register_blueprint(bp, url_prefix='/<username>')
app.register_blueprint(bp, url_prefix='/special/admin', url_defaults={
'username': 'admin'
})
URL Value Pulling
@bp.url_defaults
def add_language_code(endpoint, values):
values.setdefault('lang_code', g.lang_code)
@bp.url_value_preprocessor
def pull_lang_code(endpoint, values):
g.lang_code = values.pop('lang_code')
@bp.route('/')
def index():
return 'Looking at language %s' % g.lang_code
Hidden URL Values
bp = Blueprint('section', __name__)
@bp.url_defaults
def add_section_name(endpoint, values):
values.setdefault('section', g.section)
@bp.url_value_preprocessor
def pull_section_name(endpoint, values):
g.section = values.pop('section')
@bp.route('/')
def index():
return 'Looking at section %s' % g.section
Registering Hidden URL Values
• They can modify the Flask application in any way they want
app = Flask(__name__)
db = SQLlite3(app)
@app.route('/')
def show_users():
cur = db.connection.cursor()
cur.execute('select * from users')
...
A Bad Extension
class SQLite3(object):
@property
def connection(self):
ctx = _app_ctx_stack.top
if not hasattr(ctx, 'sqlite3_db'):
ctx.sqlite3_db = sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
return ctx.sqlite3_db
Better Extension (1)
class SQLite3(object):
...
@property
def connection(self):
ctx = _app_ctx_stack.top
if not hasattr(ctx, 'sqlite3_db'):
ctx.sqlite3_db = self.connect(ctx.app)
return ctx.sqlite3_db
Alternative Usages
from flask import Flask, Blueprint
from flask_sqlite3 import SQLite3
db = SQLite3()
bp = Blueprint('common', __name__)
@bp.route('/')
def show_users():
cur = db.connection.cursor()
cur.execute('select * from users')
...
def make_app(config=None):
app = Flask(__name__)
app.config.update(config or {})
app.register_blueprint(bp)
db.init_app(app)
return app
App-Specific Extension Config
You can either place config values in app.config
or you can store arbitrary data in app.extensions[name]
Binding Application Data
def get_config_value(self):
ctx = _app_ctx_stack.top
return ctx.app.extensions['myext']['config_value']
Bound Data for Bridging
app = Flask(__name__)
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(30))
@app.route('/')
def user_list():
users = User.query.all()
return render_template('user_list.html', users=users)
7 Keeping the Context Alive
Default Context Lifetime
def stream_with_context(gen):
def wrap_gen():
with _request_ctx_stack.top:
yield None
try:
for item in gen:
yield item
finally:
if hasattr(gen, 'close'):
gen.close()
wrapped_g = wrap_gen()
wrapped_g.next()
return wrapped_g
Built into Flask 0.9
8 Sign&Roundtrip instead of Store
Flask's Sessions
def get_activation_link(user):
return url_for('activate', code=serializer.dumps(user.user_id, salt=ACTIVATION_SALT))
@app.route('/activate/<code>')
def activate(code):
try:
user_id = serializer.loads(code, salt=ACTIVATION_SALT)
except itsdangerous.BadSignature:
abort(404)
activate_the_user_with_id(user_id)
Signature Expiration
• Also you can put more information into the data you're dumping to
make it expire with certain conditions (for instance md5() of
password salt. If password changes, redeem link gets invalidated)
• Don't build monolithic codebases. If you do, you will not enjoy Flask
• Embrace SOA