Ua Pycon 2012
Ua Pycon 2012
we Trust
Igor Davydenko
UA PyCon 2012
Flask is not
a new Django
Flask is a micro-framework
$ tree -L 1 $ tree -L 2
. .
├── app.py └── appname/
└── requirements.txt ├── blueprintname/
├── onemoreblueprint/
├── static/
├── templates/
├── tests/
├── __init__.py
├── app.py
├── manage.py
├── models.py
├── settings.py
├── views.py
└── utils.py
└── requirements.txt
Application source
From documentation From real world
$ cat app.py $ cat appname/app.py
from flask import Flask from flask import Flask
# Import extensions and settings
app = Flask(__name__)
app = Flask(__name__)
@app.route(‘/’) app.config.from_object(settings)
def hello():
return ‘Hello, world!’ # Setup context processors, template
# filters, before/after requests handlers
if __name__ == ‘__main__’:
app.run() # Initialize extensions
def page_view(pk):
return ‘Page #{0:d} @ {1!r} host’.format(pk, request.host)
Response
• There is no flask.response
• Can be implicitly created
• Can be replaced by other response objects
Implicitly created response
• Could be a text
def index_view():
return ‘Hello, world!’
Implicitly created response
• A tuple
from app import app
@app.errorhandler(404)
@app.errorhandler(500)
def error(e):
code = getattr(e, ‘code’, 500)
return ‘Error {0:d}’.format(code), code
Implicitly created response
• Or rendered template
from flask import render_template
from models import Page
def page_view(pk):
page = Page.query.filter_by(id=pk).first_or_404()
return render_template(‘page.html’, page=page)
Explicitly created response
• Text or template
from flask import make_response, render_template
def index_view():
response = make_response(‘Hello, world!’)
return response
def page_view(pk):
output = render_template(‘page.html’, page=pk)
response = make_response(output)
return response
Explicitly created response
@app.errorhandler(404)
def error(e):
response = make_response(‘Page not found!’, e.code)
response.headers[‘Content-Type’] = ‘text/plain’
return response
Explicitly created response
@app.errorhandler(404)
def error(e):
output = render_template(‘error.html’, error=e)
return make_response(
output, e.code, {‘Content-Language’: ‘ru’}
)
The application and
the request contexts
All starts with states
In [3]: app
Out[3]: <flask.app.Flask at 0x1073139d0>
In [4]: current_app
Out[4]: <LocalProxy unbound>
In [6]: request
Out[6]: <LocalProxy unbound>
class Flask(_PackageBoundObject):
...
def wsgi_app(self, environ, start_response):
with self.request_context(environ):
try:
response = self.full_dispatch_request()
except Exception, e:
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)
Hello to contexts
• Application context
• flask._app_ctx_stack
• flask.current_app
• Request context
• flask._request_ctx_stack
• flask.g
• flask.request
• flask.session
More?
• app.test_request_context
app = Flask(__name__)
app.register_blueprint(blueprint, url_prefix=’/blueprint’)
@app.route(‘/’)
def hello():
return ‘Hello from app!’
$ cat appname/blueprintname/__init__.py
from .blueprint import blueprint
$ cat appname/blueprintname/blueprint.py
from flask import Blueprint
@blueprint.route(‘/’)
def hello():
return ‘Hello from blueprint!’
Real example
$ cat appname/app.py
...
app = Flask(__name__)
db = SQLAlchemy(app)
...
from .blueprintname import blueprint
app.register_blueprint(blueprint, url_prefix=’/blueprint’)
$ cat appname/models.py
from app import db
class Model(db.Model):
...
$ cat appname/blueprintname/blueprint.py
from flask import Blueprint
from appname.models import Model
@blueprint.route(‘/’)
def hello():
# Work with model
return ‘something...’
Sharing data with blueprint
$ cat appname/app.py
from flask import Flask
from blueprintname import blueprint
class Appname(Flask):
def register_bluepint(self, blueprint, **kwargs):
super(Appname, self).register_blueprint(blueprint, **kwargs)
blueprint.extensions = self.extensions
app = Appname(__name__)
app.register_blueprint(blueprint)
$ cat blueprintname/deferred.py
from .blueprint import blueprint
db = blueprint.extensions[‘sqlalchemy’].db
More canonical way
$ cat appname/app.py
from flask import Flask
from blueprintname import blueprint
app = Flask(__name__)
app.register_blueprint(blueprint)
$ cat blueprintname/deferred.py
from appname.app import db
Factories
$ cat appname/app.py
from flask import Flask
$ cat appname/frontend_app.py
from blueprintname import create_blueprint
...
app.register_blueprint(create_blueprint(app), url_prefix=’/blueprint’)
$ cat blueprintname/blueprint.py
from flask import Blueprint
from flask.ext.lazyviews import LazyViews
def create_blueprint(app):
blueprint = Blueprint(__name__)
views = LazyViews(blueprint)
if app.name == ‘backend’:
blueprint.add_app_template_filter(backend_filter)
views.add(‘/url’, ‘view’)
return blueprint
Customizing
main_app.wsgi_app = DispatcherMiddleware(main_app.wsgi_app, {
‘/backend’: backend_app.wsgi_app,
})
Extensions
That’s what Flask about
class Redis(object):
def __init__(self, app=None):
if app:
self.init_app(app)
self.app = app
self._include_redis_methods(redis)
Usage. Singleton
app = Flask(‘appname’)
app.config[‘REDIS_URL’] = ‘redis://localhost:6379/0’
redis = Redis(app)
@app.route(‘/counter’)
def counter():
number = redis.incr(‘counter_key’)
return ‘This page viewed {:d} time(s)’.format(number)
Usage. Advanced
• Initializing without app object (multiple apps to one
extension)
$ cat extensions.py
from flask.ext.redis import Redis
redis = Redis()
$ cat backend_app.py
from flask import Flask
from extensions import redis
app = Flask(‘backend’)
app.config[‘REDIS_URL’] = ‘redis://localhost:6379/0’
redis.init_app(app)
@app.route(‘/counter’)
def counter():
number = redis.incr(‘counter_key’)
return ‘This page viewed {:d} time(s)’.format(number)
So, one more time
• Internationalization: Flask-Babel
• Management commands: Flask-Actions, Flask-Script
• Assets: Flask-Assets, Flask-Collect
• Testing: flask-fillin, Flask-Testing
• Debug toolbar: Flask-DebugToolbar
Other
• Cache: Flask-Cache
• Celery: Flask-Celery
• Lazy views: Flask-LazyViews
• Dropbox API: Flask-Dropbox
• Flat pages: Flask-FlatPages, Frozen-Flask
• Mail: Flask-Mail
• Uploads: Flask-Uploads
Debugging,
testing and
deployment
Werkzeug debugger
pdb, ipdb
• Just import pdb (ipdb) in code and set trace
def view():
...
import pdb
pdb.set_trace()
...
• That’s all!
• Works with development server
(env)$ python app.py
(env)$ python manage.py runserver
• Or gunicorn
(env)$ gunicorn app:app -b 0.0.0.0:5000 -t 9000 --debug
Debug toolbar
Flask-Testing
• Inherit test case class from flask.ext.testing.TestCase
• Implement create_app method
from flask.ext.testing import TestCase
from appname.app import app
class TestSomething(TestCase):
def create_app(self):
app.testing = True
return app
• Or with nosetests
(env)$ nosetests -vx -w appname/
WebTest
• Setup app and wrap it with TestApp class
• Don’t forget about contexts
from unittest import TestCase
from webtest import TestApp
from appname.app import app
class TestSomething(TestCase):
def setUp(self):
app.testing = True
self.client = TestApp(app)
self._ctx = app.test_request_context()
self._ctx.push()
def tearDown(self):
if self._ctx is not None:
self._ctx.pop()
Application factories & tests
• nginx + gunicorn
• nginx + uwsgi
• And don’t forget that you can wrap your Flask app with
Tornado, gevent, eventlet, greenlet or any other WSGI
container
Funny numbers
Without concurrency
Average Max
2000
1500
1000
500
0
Bottle Django Flask Pyramid Tornado
Requests per second
$ ab -c 1 -n 1000 URL
2200
1650
1100
550
0
Bottle Django Flask Pyramid Tornado
Requests per second
$ ab -c 100 -n 1000 URL
I am Igor Davydenko
http://igordavydenko.com
http://github.com/playpauseandstop