Web applications with Flask
A Tasty Drop
If you are considering writing a web application with Python, one obvious choice is Django, which enjoys popularity within the Python world that is similar to Rails in the Ruby community. An alternative to Django is Flask [1], which has become increasingly popular since its inception in 2010.
The advantage of Django is that it comes with more or less everything you need for many web applications: templates, a database connection, user authentication, and so on. However, it is difficult to leave out or replace components. If you prefer a more flexible approach, you should take a look at Flask. Out of the box, Flask relies on underpinnings provided by a WSGI library by the name of Werkzeug [2], which was written by Flask programmer Armin Ronacher. The same applies to the Jinja2 template library, which Flask uses by default.
WSGI [3] is a specification that defines how a web server software and a web application written in Python communicate. This could be the Apache or Nginx web server with their WSGI modules or special WSGI web servers, such as Gunicorn or uWSGI. The Flask library also contains a separate web server for development.
The latest version of Flask, 0.9, was released in July 2012, and it is uncertain when version 0.10 will be released. Flask requires at least Python 2.5; Python 3 is not currently supported. The easiest approach is to install Flask with Virtualenv [4], which maintains a local version of Python and its modules in a local directory, thus avoiding conflicts with the other projects on a server. Virtualenv itself is available with most Linux distributions. Alternatively, you can install it with the Python package management tools pip
and easy_install
.
The following commands create a new project directory; change to this directory and parse the shell variables that define the virtual Python environment (Figure 1):
virtualenv webproject cd webproject source bin/activate
You can return to your normal Python environment at system level by typing deactivate
. As long as you are in Virtualenv, which is shown by the (webproject)
prompt prefix, you can install Python modules for the local directory structure only. If you issue the pip install Flask
command to install the library, you will find it in webproject/lib/python2.7/site-packages/flask
. Tools and Jinja2 are then installed by Pip.
Routing
Flask itself is little more than the glue that keeps the WSGI layer and the template library together. In particular, it handles routing that maps URLs to functions. The blueprint for this comes from the Sinatra Ruby framework [5], which has been copied in many variants for many different programming languages. Besides Flask, another option is Bottle, which is covered by Admin Story in this issue.
With a Flask application, you basically assign functions to individual URLs using Python decorators; the URLs then rely on templates to render individual websites. A simple example looks like this:
@app.route('/') def homepage(): return "This is the homepage"
For this approach to work, you obviously need to import the Flask module at the start of the script. The function name is not important at this point. The only important thing is that the URL specified in the @app.route
decorator, /
, is linked to a function. The complete minimalist web application is given in Listing 1. The typical Python idiom ensures that app.run()
only runs if the script is called by the interpreter and not imported by another module.
Listing 1: Simple Flask App
01 from flask import Flask 02 app = Flask("My app") 03 04 @app.route('/') 05 def homepage(): 06 return 'This is the homepage' 07 08 if __name__ == '__main__': 09 app.run()
Note the final slash in pathnames in the listing. If you type a slash, as in @app.route("/article/")
, the URL will work either with or without it. But if you just write "/article"
, trying to access http://Server/article/ will result in a 404 error message. If you want to differentiate by the HTTP verb in the call to the URL, which is typical of REST applications, you can use the methods
keyword parameter to do so. The following URL can only be called by a POST:
@app.route('/login', methods=['POST'])
By default, the built-in web server with local host IP 127.0.0.1 runs on port 5000. To change this, pass in the keyword parameters host=<IP address>
and port=<port number>
to the run()
method. Make sure that your own application is not available via an address that is reachable from the Internet while it is still running in debug mode. In this case, you can run your own Python instructions on the server from your browser!
Templates
The bad old days in which websites were still output using Print statements should be a thing of the past. This legacy approach not only led to unmanageable code, but typically also to security holes.
The alternative is the use of templates, which isolate the code from the presentation. If you have Flask and the Jinja2 library, you can do this in a single line:
render_template("homepage.html")
Because this approach would only let you serve up a static page, render_template
naturally also lets you pass in variables, which the template outputs using {{ Variable }}
. Additionally, you can use control structures, such as for
loops, that iterate over sequences (Listing 2). They lie between {%
and %}
, but you can change the syntax if needed.
Listing 2: Templates
01 <!doctype html> 02 <head> 03 {% block head %} 04 <link rel="stylesheet" href="style.css" /> 05 {% endblock %} 06 </head> 07 <body> 08 <ul> 09 {% for item in seq %} 10 <li>{{ item }}</li> 11 {% endfor %} 12 </ul> 13 </body> 14 </html>
If a variable contains HTML code, Jinja2 automatically masks it before output – again this is protection against undesirable injection of exploit code. If you can rely on the content of a variable, you can prevent this behavior manually.
What is particularly interesting here is that templates also support inheritance. Thus, you can outsource elements of all of your websites into a basic template, from which you can then derive all other templates. The basic template can define default blocks that the derivate templates "overwrite" with their own content. Alternatively, templates can integrate other templates, thus allowing programmers to create a modular structure for a website, if needed.
Variables
To make web applications from static HTML pages, they need to respond dynamically to user input that the web browser sends in the form of GET or POST requests. For example, if the directory for the blog entries is followed by an ID, Flask can process it with the following code block:
@app.route('/blog/<blog_id>') def show_blog(blog_id): render_template("blog.html", blog_id)
Also, you can convert the variable directly from a string to another type (e.g., integer: <int:blog_id>
). To access variables transferred by POSTing an HTML form, Flask accesses the HTTP Request object, which imports the instruction from flask import request
into the script. The Python form
dictionary for the request object contains the form variables:
login = request.form['login']
If the variable is not found, the method creates a type KeyError
exception. If you don't handle this exception yourself, the web application outputs an HTTP 400 "Bad Request" error.
To store variables between requests, Flask uses a session object, which also works like a Python dictionary. For security reasons, it is important to use a robust (i.e., random) key to sign the sessions.
Databases
Although Flask does not support a database internally, it has several extensions that let you store data permanently. Of course, you do not need a special Flask module for this; for example, you can access MySQL and PostgreSQL with standard Python modules in your own code. However, a couple of Flask extensions could make everything more convenient.
The most important module here is Flask-SQLAlchemy, which integrates one of the most common modules for cross-database, object-oriented database access (ORM) into Flask. More extensions are available for popular NoSQL databases such as CouchDB and MongoDB.
Conclusions
Flask is an interesting alternative to Django for any Python web developer who does not want or need to use the complete feature scope of the Django framework. The clean code and the good documentation take the pain out of the learning curve. Additionally, a pretty hard-working Flask community offers support if you have problems, and third-party extensions are available (Figure 2) [6]. It's just a pity that the program's developer, Armin Ronacher, can't spend as much time as he would like on maintaining the framework due to work obligations.