0% found this document useful (0 votes)
68 views65 pages

Ua Pycon 2012

Flask is a microframework that provides the core functionality for building web applications in Python. It is designed to have minimal dependencies and allow extensions to be added to provide additional functionality like object-relational mappers (ORMs), form validation, and various open-source extensions. Blueprints allow grouping related views and other code, but are not full applications themselves and rely on the application and its context to access resources like the database. Requests are handled within the application context to provide access to the request object and other resources during request processing.

Uploaded by

Master Mind
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
68 views65 pages

Ua Pycon 2012

Flask is a microframework that provides the core functionality for building web applications in Python. It is designed to have minimal dependencies and allow extensions to be added to provide additional functionality like object-relational mappers (ORMs), form validation, and various open-source extensions. Blueprints allow grouping related views and other code, but are not full applications themselves and rely on the application and its context to access resources like the database. Requests are handled within the application context to provide access to the request object and other resources during request processing.

Uploaded by

Master Mind
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 65

In Flask

we Trust

Igor Davydenko
UA PyCon 2012
Flask is not
a new Django
Flask is a micro-framework

• It’s only Werkzeug (WSGI toolkit), Jinja2 (template


engine) and bunch of good things on top
• No unnecessary batteries included by default
• The idea of Flask is to build a good foundation for all
applications. Everything else is up to you or extensions
• So no more projects. All you need is Flask application
No ORM, no forms, no contrib
• Not every application needs a SQL database
• People have different preferences and requirements
• Flask could not and don’t want to apply those differences
• Flask itself just bridges to Werkzeug to implement a
proper WSGI application and to Jinja2 to handle
templating
• And yeah, most of web applications really need a template
engine in some sort
But actually we prepared well
• Blueprints as glue for views (but blueprint is not a
reusable app)
• Extensions as real batteries for our application
• And yeah, we have ORM (Flask-SQLAlchemy, Flask-
Peewee, Flask-MongoEngine and many others)
• We have forms (Flask-WTF)
• We have anything we need (Flask-Script, Flask-Testing,
Flask-Dropbox, Flask-FlatPages, Frozen-Flask, etc)
Application structure
From documentation From real world

$ 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

# Add lazy views, blueprints, error


# handlers to app

# Import and setup anything which needs


# initialized app instance
How to run?
From documentation From real world

(env)$ python manage.py runserver -p 4321


...

(env)$ gunicorn appname.app:app -b 0.0.0.0:5000 -w 4


...
(env)$ python app.py
* Running on http://127.0.0.1:5000/ (env)$ cat /etc/uwsgi/sites-available/appname.ini
chdir = /path/to/appname
venv = %(chdir)/env/
pythonpath = /path/to/appname
module = appname.app:app
touch-reload = %(chdir)/appname/app.py
(env)$ sudo service uwsgi full-reload
...
From request
to response
Routing
• Hail to the Werkzeug routing!
app = Flask(__name__)
app.add_url_rule(‘/’, index_view, endpoint=‘index’)
app.add_url_rule(‘/page’, page_view, defaults={‘pk’: 1},
endpoint=‘default_page’)
app.add_url_rule(‘/page/<int:pk>’, page_view, endpoint=‘page’)

@app.route(‘/secret’, methods=(‘GET’, ‘POST’))


@app.route(‘/secret/<username>’)
def secret(username=None):
...

• All application URL rules storing in app.url_map instance.


No more manage.py show_urls, just print(app.url_map)
URL routes in code
• Just url_for it!
>>> from flask import url_for
>>> url_for(‘index’)
‘/’
>>> url_for(‘default_page’)
‘/page’
>>> url_for(‘page’, pk=1)
‘/page/1’
>>> url_for(‘secret’, _external=True)
‘http://127.0.0.1:5000/secret’
>>> url_for(‘secret’, username=‘user’, foo=‘bar’)
‘/secret/user?foo=bar’

• And in templates too,


{{ url_for(“index”) }}
{{ url_for(“secret”, _external=True) }}
Request

• View doesn’t need a request arg!


• There is one request object per request which is read only
• The request object is available through local context
• Request is thread-safe by design
• When you need it, import it!
from flask import request

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

• Tuple with custom headers


from flask import make_response
from app import app

@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

• Rendered template with custom headers,


from flask import make_response, render_template
from app import app

@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

• Application setup state


• Runtime state
• Application runtime state
• Request runtime state
What is about?
In [1]: from flask import Flask, current_app, request

In [2]: app = Flask('appname')

In [3]: app
Out[3]: <flask.app.Flask at 0x1073139d0>

In [4]: current_app
Out[4]: <LocalProxy unbound>

In [5]: with app.app_context():


print(repr(current_app))
...:
<flask.app.Flask object at 0x1073139d0>

In [6]: request
Out[6]: <LocalProxy unbound>

In [7]: with app.test_request_context():


....: print(repr(request))
....:
<Request 'http://localhost/' [GET]>
Flask core

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

• Contexts are stacks


• So you can push to multiple contexts objects
• Request stack and application stack are independent
What depends on contexts?

• Application context
• flask._app_ctx_stack

• flask.current_app

• Request context
• flask._request_ctx_stack

• flask.g

• flask.request

• flask.session
More?

• Stack objects are shared


• There are context managers to use
• app.app_context

• app.test_request_context

• Working with shell


>>> ctx = app.test_request_context()
>>> ctx.push()
>>> ...
>>> ctx.pop()
Applications vs.
Blueprints
Blueprint is not an application

• Blueprint is glue for views


• Application is glue for blueprints and views
Blueprint uses data from app

• Blueprint hasn’t app attribute


• Blueprint doesn’t know about application state
• But in most cases blueprint needs to know about
application
Trivial example
$ cat appname/app.py
from flask import Flask
from .blueprintname import blueprint

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 = Blueprint(‘blueprintname’, ‘importname’)

@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 = Blueprint(‘blueprintname’, ‘importname’)

@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

• Application can created by factory, e.g. for using different


settings
• Blueprint can created by factory for same reasons
Application factory

$ cat appname/app.py
from flask import Flask

def create_app(name, settings):


app = Flask(name)
app.config.from_pyfile(settings)
register_blueprints(app.config[‘BLUEPRINTS’])

backend_app = create_app(‘backend’, ‘backend.ini’)


frontend_app = create_app(‘frontend’, ‘frontend.ini’)
Blueprint factory
$ cat appname/backend_app.py
from blueprintname import create_blueprint
...
app.register_blueprint(create_blueprint(app), url_prefix=’/blueprint’)

$ 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

• Just inherit flask.Flask or flask.Blueprint


class Appname(Flask):
def send_static_file(self, filename):
...

• Apply WSGI middleware to Flask.wsgi_app me


from werkzeug.wsgi import DispatcherMiddleware
thod

main_app.wsgi_app = DispatcherMiddleware(main_app.wsgi_app, {
‘/backend’: backend_app.wsgi_app,
})
Extensions
That’s what Flask about

• You need some code more than in one Flask app?


• Place it to flask_extname module or package
• Implement Extname class and provide init_app method
• Don’t forget to add your extension to app.extensions dict
• Volia!
Example. Flask-And-Redis

• Module flask_redis, class Redis


from redis import Redis

class Redis(object):
def __init__(self, app=None):
if app:
self.init_app(app)
self.app = app

def init_app(self, app):


config = self._read_config(app)

self.connection = redis = Redis(**config)


app.extensions[‘redis’] = redis

self._include_redis_methods(redis)
Usage. Singleton

• One Flask application, one Redis connection


from flask import Flask
from flask.ext.redis import Redis

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

• Provide init_app method to support multiple applications


• Don’t forget about app.extensions dict
• Do not assign self.app = app in init_app method
• Extension should have not-null self.app only for singleton
pattern
List of extensions
you should to know
and use
Database, forms, admin

• SQL ORM: Flask-SQLAlchemy, Flask-Peewee


• NoSQL: Flask-CouchDB, Flask-PyMongo, Flask-And-Redis
• NoSQL ORM: Flask-MongoEngine, Flask-MiniMongo
• Forms: Flask-WTF
• Admin: Flask-Admin, Flask-Dashed, Flask-Peewee
Authentication, REST

• Base: Flask-Auth, Flask-BasicAuth, Flask-Login


• Advanced: Flask-Security
• Social auth: Flask-GoogleAuth, Flask-OAuth,
Flask-OpenID, Flask-Social
• REST: Flask-REST, Flask-Restless, Flask-Snooze
Management

• 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

• Run tests with unittest2


(env)$ python -m unittest discover -fv -s appname/

• 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

• Yeah, it’s good idea to use application factories when you


have at least tests
• So appname.create_app better than appname.app, trust me :)
Deploy to Heroku

• Heroku perfectly fits staging needs


• One dyno, shared database, Redis, Mongo, email support,
Sentry for free
• Viva la gunicorn!
$ cat Procfile
web: gunicorn appname.app:app -b 0.0.0.0:$PORT -w 4
Deploy anywhere else

• 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

URL Bottle Django Flask Pyramid Tornado

/ 1327.99 416.83 806.86 1214.67 1930.96


13 bytes

/environ 1018.14 376.16 696.96 986.82 1430.54


~2900 bytes

/template 654.71 252.96 670.24 814.37 711.49


191 bytes
Time per request
$ ab -c 1 -n 1000 URL

URL Bottle Django Flask Pyramid Tornado

/ 0.748ms 2.360ms 1.248ms 0.826ms 0.521ms


13 bytes

/environ 0.963ms 2.672ms 1.425ms 1.007ms 0.715ms


~2900 bytes

/template 1.523ms 4.177ms 1.475ms 1.189ms 1.399ms


191 bytes
With concurrency
Average Max

2200

1650

1100

550

0
Bottle Django Flask Pyramid Tornado
Requests per second
$ ab -c 100 -n 1000 URL

URL Bottle Django Flask Pyramid Tornado

/ 553.02 228.91 826.34 703.82 2143.29


13 bytes

/environ 522.16 240.51 723.90 415.20 1557.62


~2900 bytes

/template 444.37 177.14 693.42 297.47 746.87


191 bytes
Additional notes

• Only Flask and Tornado can guarantee 100% responses on


100 concurrency requests
• Bottle, Django and Pyramid WSGI servers will have 2-10%
errors or will shutdown after 1000 requests
• Gunicorn will not help for sure :(
Questions?

I am Igor Davydenko
http://igordavydenko.com
http://github.com/playpauseandstop

You might also like