As we have seen, there are many Python web frameworks to choose from. In fact, there are too many to be able to cover every one in detail in this report. Instead, we will take a deeper look at six of the most popular. There is enough diversity here to give the reader some idea about how different frameworks work and what a web application’s code looks like when using them.
For each framework, we are going to give a general description, discuss some key features, look at some sample code, and talk a bit about when it should be used. When possible, code for a simple single-file application will be shown. Quick start instructions assume Python and pip
or easy_install
are present on the system. It is also recommended that you use virtualenv
(or pyvenv
for Python 3.3+) to create an isolated environment for your application. For simplicity, the examples do not show the setup of pip
and virtualenv
. See Appendix A for help with any of these tools.
Django is without a doubt the most popular web framework for Python at the time of this writing. Django is a high-level framework, designed to take care of most common web application needs.
Django makes a lot of decisions for you, from code layout to security. It’s also very well documented, so it’s very easy to get a project off the ground quickly. There are also many third-party applications that can complement its many features nicely.
Django is very well-suited for database-driven web applications. Not only does it include its own object-relational mapping (ORM), but it can do automatic form generation based on the schemas and even helps with migrations. Once your models are defined, a rich Python API can be used to access your data.
Django also offers a dynamic administrative interface that lets authenticated users add, change, and delete objects. This makes it possible to get a nice-looking admin site up very early in the development cycle, and start populating the data and testing the models while the user-facing parts of the application are taking shape.
In addition to all this, Django has a clean and simple way of mapping URLs to code (views), and deals with things like caching, user profiles, authentication, sessions, cookies, internationalization, and more. Its templating language is simple, but it’s possible to create custom tags for more advanced needs. Also, Django now supports Jinja2, so there’s that option if you require a bit more powerful templating.
To install Django:
$ pip install Django
Unlike the other frameworks discussed in this chapter, Django is a full-stack framework, so we won’t show a code listing for a Hello World application. While it’s possible to create a Django application in a single file, this goes against the way Django is designed, and would actually require more knowledge about the various framework pieces than a complete application.
Django organizes code inside a project. A project has a configuration, or settings, plus a set of URL declarations. Since Django is intended for working with relational databases, the settings usually include database configuration information. Inside the project, there is a command-line utility, named manage.py
, for interacting with it in various ways. To create a project:
$ django-admin startproject mysite
A project can contain one or more applications. An application is an ordinary Python package where Django looks for some things. An application can contain the database models, views, and admin site registrations that will make your models be part of the automatic admin interface. The basic idea in Django is that an application performs one defined task.
Django’s database integration is one of its strong suits, so let’s take a look at a few examples of that.
from
django.db
import
models
class
Author
(
models
.
Model
):
first_name
=
models
.
CharField
(
max_length
=
70
)
last_name
=
models
.
CharField
(
max_length
=
70
)
def
__str__
(
self
):
return
self
.
full_name
@property
def
full_name
(
self
):
return
'{} {}'
.
format
(
self
.
first_name
,
self
.
last_name
)
class
Book
(
models
.
Model
):
author
=
models
.
ForeignKey
(
Author
)
title
=
models
.
CharField
(
max_length
=
200
)
description
=
models
.
TextField
()
pub_date
=
models
.
DateField
()
def
__str__
(
self
):
return
self
.
title
A model contains data about one single part of your application. It will usually map to a single database table. A model is defined in a class that subclasses django.db.models.Model
. There are several types of fields, like CharField
, TextField
, DateField
, etc. A model can have as many fields as needed, which are added simply by assigning them to attributes of the model. Relationships can be expressed easily, like in the author
field in the Book
model above, which uses a ForeignKey
field to model a many-to-one relationship.
In addition to fields, a model can have behaviors, or “business logic.” This is done using instance methods, like full_name
in our sample code. All models also include automatic methods, which can be overriden if desired, like the __str__
method in the example, which gives a unicode representation for a model in Python 3.
from
django.contrib
import
admin
from
mysite.myapp.models
import
Book
class
BookAdmin
(
admin
.
ModelAdmin
):
list_display
=
[
'title'
,
'author'
,
'pub_date'
]
list_filter
=
[
'pub_date'
]
search_fields
=
[
'title'
,
'description'
]
admin
.
site
.
register
(
Book
,
BookAdmin
)
To make your models appear in the Django admin site, you need to register them. This is done by inheriting from django.contrib.admin.ModelAdmin
, customizing the display and behavior of the admin, and registering the class, like we do in the last line of the previous example. That’s all the code needed to get a polished interface for adding, changing, and removing books from your site.
Django’s admin is very flexible, and has many customization hooks. For example, the list_display
attribute takes a list of fields and displays their values in columns for each row of books, rather than just showing the result of the __str__()
method. The hooks are not just for display purposes. You can add custom validators, or define actions that operate on one or more selected instances and perform domain specific transformations on the data.
from
django.shortcuts
import
render
from
.models
import
Book
def
publication_by_year
(
request
,
year
):
books
=
Book
.
objects
.
filter
(
pub_date__year
=
year
)
context
=
{
'year'
:
year
,
'book_list'
:
books
}
return
render
(
request
,
'books/by_year.html'
,
context
)
A view in Django can be a simple method that takes a request and zero or more URL parameters. The view is mapped to a URL using Django’s URL patterns. For example, the view above might be associated with a pattern like this:
url(r'^books/([0-9]{4})/$, views.publication_by_year)
This is a simple regular expression pattern that will match any four-digit number after “books/” in the URL. Let’s say it’s a year. This number is passed to the view, where we use the model API to filter all existing books with this year in the publication date. Finally, the render
method is used to generate an HTML page with the result, using a context
object that contains any results that need to be passed to the template.
Django recommends using the unittest
module for writing tests, though any testing framework can be used. Django provides some tools to help write tests, like TestCase
subclasses that add Django-specific assertions and testing mechanisms. It has a test client that simulates requests and lets you examine the responses.
Django’s documentation has several sections dedicated to testing applications, giving detailed descriptions of the tools it provides and examples of how to test your applications.
Django is very good for getting a database-driven application done really quickly. Its many parts are very well integrated and the admin site is a huge time saver for getting site administrators up and running right away.
If your data is not relational or you have fairly simple requirements, Django’s features and parts can be left just sitting there, or even get in the way. In that case, a lighter framework might be better.
Flask is a micro framework. Micro refers to the small core of the framework, not the ability to create single-file applications. Flask basically provides routing and templating, wrapped around a few configuration conventions. Its objective is to be flexible and allow the user to pick the tools that are best for their project. It provides many hooks for customization and extensions.
Flask curiously started as an April Fool’s joke, but it’s in fact a very serious framework, heavily tested and extensively documented. It features integrated unit testing support and includes a development server with a powerful debugger that lets you examine values and step through the code using the browser.
Flask is unicode-based and supports the Jinja2 templating engine, which is one of the most popular for Python web applications. Though it can be used with other template systems, Flask takes advantage of Jinja2’s unique features, so it’s really not advisable to do so.
Flask’s routing system is very well-suited for RESTful request dispatching, which is really a fancy name for allowing specific routes for specific HTTP verbs (methods). This is very useful for building APIs and web services.
Other Flask features include sessions with secure cookies, pluggable views, and signals (for notifications and subscriptions to them). Flask also uses the concept of blueprints for making application components.
To install Flask:
$ pip install Flask
from
flask
import
Flask
app
=
Flask
(
__name__
)
@app.route
(
"/"
)
def
hello
():
return
"Hello World!"
if
__name__
==
"__main__"
:
app
.
run
()
The Flask
class is used to create an instance of a WSGI application, passing in the name of the application’s package or module. Once we have a WSGI application object, we can use Flask’s specific methods and decorators.
The route
decorator is used to connect a view function with a URL (in this case, the root of the site). This is a very simple view that just returns a string of text.
Finally, we use the common Python idiom for executing some code when a script is called directly by the interpreter, where we call app.run()
to start the development server.
Let’s look at a few examples of what Flask code looks like inside real applications.
@app.before_request
def
before_request
():
g
.
db
=
connect_db
()
@app.teardown_request
def
teardown_request
(
exception
):
db
=
getattr
(
g
,
'db'
,
None
)
if
db
is
not
None
:
db
.
close
()
It’s common to need some resources present on a per request basis such as service connections to things like redis, Salesforce, or databases. Flask provides various decorators to set this up easily. In the example above, we assume that the connect_db
method is defined somewhere else and takes care of connecting to an already initialized database. Any function decorated with before_request
will be called before a request, and we use that call to store the database connection in the special g
object provided by Flask.
To make sure that the connection is closed at the end of the request, we can use the teardown_request
decorator. Functions decorated with this are guaranteed to be executed even if an exception occurs. In fact, if this happens, the exception is passed in. In this example, we don’t care if there’s an exception; we just try to get the connection from g
, and if there is one, we close it.
There’s also an after_request
decorator, which gets called with the response as a parameter and must return that or another response object.
from
flask
import
Flask
,
session
,
redirect
,
url_for
,
escape
,
request
app
=
Flask
(
__name__
)
@app.route
(
'/'
)
def
index
():
if
'username'
in
session
:
return
'Logged in as
%s
'
%
escape
(
session
[
'username'
])
return
'You are not logged in'
@app.route
(
'/login'
,
methods
=
[
'GET'
,
'POST'
])
def
login
():
if
request
.
method
==
'POST'
:
session
[
'username'
]
=
request
.
form
[
'username'
]
return
redirect
(
url_for
(
'index'
))
return
render_template
(
'login.html'
)
@app.route
(
'/logout'
)
def
logout
():
session
.
pop
(
'username'
,
None
)
return
redirect
(
url_for
(
'index'
))
app
.
secret_key
=
'A secret'
The session
object allows you to store information specific to a user from one request to the next. Sessions are implemented using secure cookies, and thus need a key to be used.
The index
view checks the session for the presence of a user name, and shows the logged-in state accordingly. The login
view is a bit more interesting. It renders the login template if called with the GET method, and sets the session username
variable if called with POST. The logout
view simply removes the variable from the session, in effect logging out the user.
@app.route
(
'/'
)
def
show_entries
():
cur
=
g
.
db
.
execute
(
'select title, text from entries order by id desc'
)
entries
=
[
dict
(
title
=
row
[
0
],
text
=
row
[
1
])
for
row
in
cur
.
fetchall
()]
return
render_template
(
'show_entries.html'
,
entries
=
entries
)
@app.route
(
'/add'
,
methods
=
[
'POST'
])
def
add_entry
():
if
not
session
.
get
(
'username'
):
abort
(
401
)
g
.
db
.
execute
(
'insert into entries (title, text) values (?, ?)'
,
[
request
.
form
[
'title'
],
request
.
form
[
'text'
]])
g
.
db
.
commit
()
flash
(
'New entry was successfully posted'
)
return
redirect
(
url_for
(
'show_entries'
))
Here we show how to define views. The route
decorator that we saw in the quickstart application is used to connect the add_entry
method with the /add URL. Note the use of the methods
parameter to restrict that view to POST requests.
We examine the session to see if the user is logged in, and if not, abort the request. We assume that the request comes from a form that includes the title
and text
parameters, which we extract from the request to use in an insert statement. The request
object referenced here has to be imported from flask
, as in the sessions example.
Finally, a flash message is set up to display the change to the user, and the browser is redirected to the main show_entries
view. This last view is simple, but it shows how to render a template, calling it with the template name and the context data required for rendering.
Flask exposes the Werkzeug test client to make testing applications easier. It also provides test helpers to let tests access the request context as if they were views.
The documentation has a long section about testing applications. The examples use unittest
, but any other testing tool can be used. Since Werkzeug is fully documented itself, there is very good information available about the test client too.
Flask can be used to write all kinds of applications, but by design it’s better for small- to medium-sized systems. It is also not ideal for composing multiple applications, because of its use of global variables. It’s especially good for web APIs and services. Its small core allows it to be used as “glue” code for many data backends, and it’s a very powerful companion for SQLAlchemy when dealing with database-driven web applications.
Tornado is a combination of an asynchronous networking library and a web framework. It is intended for use in applications that require long-lived connections to their users.
Tornado has its own HTTP server based on its asynchronous library. While it’s possible to use the web framework part of Tornado with WSGI, to take advantage of its asynchronous nature it’s necessary to use it together with the web server.
In addition to typical web framework features, Tornado has libraries and utilities to make writing asynchronous code easier. Instead of depending on callbacks, Tornado’s coroutines library allows a programming style more similar to synchronous code.
Tornado includes a simple templating language. Unlike other templating languages discussed here, in Tornado templates there are no restrictions on the kind of expressions that you can use. Tornado also has the concept of UI modules, which are special function calls to render UI widgets that can include their own CSS and JavaScript.
Tornado also offers support for authentication and security, including secure cookies and CSRF protection. Tornado authentication includes support for third-party login systems, like Google, Facebook, and Twitter.
To install Tornado:
$ pip install tornado
import
tornado.ioloop
import
tornado.web
class
MainHandler
(
tornado
.
web
.
RequestHandler
):
def
get
(
self
):
self
.
write
(
"Hello, world"
)
application
=
tornado
.
web
.
Application
([
(
r"/"
,
MainHandler
),
])
if
__name__
==
"__main__"
:
application
.
listen
(
8888
)
tornado
.
ioloop
.
IOLoop
.
current
()
.
start
()
First, we define a request handler, which will simply write our “Hello World” message in the response. A Tornado application usually consists of one or more handlers. The only prerequisite for defining a handler is to subclass from the tornado.web.RequestHandler
class.
To route requests to the appropriate handlers and take care of global configuration options, Tornado uses an application object. In the example above, we can see how the application is passed the routing table, which in this case includes only one route. This route assigns the root URL of the site to the MainHandler
created above.
Once we have an application object, we configure it to listen to port 8888 and start the asynchronous loop to serve our application. Note that there’s no specific association of the application object we created and the ioloop
, because the listen call actually creates an HTTP server behind the scenes.
Since Tornado’s asynchronous nature is its main feature, let’s see some examples of that.
from
tornado.httpclient
import
HTTPClient
def
synchronous_fetch
(
url
):
http_client
=
HTTPClient
()
response
=
http_client
.
fetch
(
url
)
return
response
.
body
from
tornado.httpclient
import
AsyncHTTPClient
def
asynchronous_fetch
(
url
,
callback
):
http_client
=
AsyncHTTPClient
()
def
handle_response
(
response
):
callback
(
response
.
body
)
http_client
.
fetch
(
url
,
callback
=
handle_response
)
from
tornado
import
gen
@gen.coroutine
def
fetch_coroutine
(
url
):
http_client
=
AsyncHTTPClient
()
response
=
yield
http_client
.
fetch
(
url
)
raise
gen
.
Return
(
response
.
body
)
In these three short examples, we can see how Tornado uses asynchronous calls and how that compares with the normal, synchronous calls that we would use in a WSGI application.
In the first example, we use tornado.HTTPClient
to fetch a URL from somewhere in the cloud. This is the regular case, and the synchronous_fetch
call will not return until the client gets the response back.
The second example uses the AsyncHTTPClient
. The call will return immediately after the fetch call, which is why Tornado can scale more. The fetch method is passed a callback, which is a function that will be executed when the client gets a response back. This works, but it can lead to situations where you have to chain callbacks together, which can quickly become confusing.
For this reason, coroutines are the recommended way to write asynchronous code in Tornado. Coroutines take advantage of Python generators to be able to return immediately with no callbacks. In the fetch_coroutine
method above, the gen.coroutine
decorator takes care of waiting without blocking for the client to finish fetching the URL, and then passes the result to the yield.
class
BaseHandler
(
tornado
.
web
.
RequestHandler
):
def
get_current_user
(
self
):
return
self
.
get_secure_cookie
(
"user"
)
class
MainHandler
(
BaseHandler
):
def
get
(
self
):
if
not
self
.
current_user
:
self
.
redirect
(
"/login"
)
return
name
=
tornado
.
escape
.
xhtml_escape
(
self
.
current_user
)
self
.
render
(
"hello.html"
,
title
=
"Welcome"
,
name
)
class
LoginHandler
(
BaseHandler
):
def
get
(
self
):
self
.
render
(
"login.html"
,
title
=
"Login Form"
)
def
post
(
self
):
self
.
set_secure_cookie
(
"user"
,
self
.
get_argument
(
"name"
))
self
.
redirect
(
"/"
)
application
=
tornado
.
web
.
Application
([
(
r"/"
,
MainHandler
),
(
r"/login"
,
LoginHandler
)],
cookie_secret
=
"__TODO:_GENERATE_A_RANDOM_VALUE_HERE__"
)
Since request handlers are classes, you can use inheritance to define a base request handler that can have all the basic behavior needed for your application. In BaseHandler
in the previous example, the get_current_user
call will be available for both handlers defined in the next example.
A handler should have a method for every HTTP method that it can handle. In MainHandler
, the GET
method gets a look at the current user and redirects to the login handler if it is not set (remember that get_current_user
is inherited from the base handler). If there’s a user, its name is escaped before being passed to the template. The render method of a handler gets a template by name, optionally passes it some arguments, and renders it.
LoginHandler
has both GET
and POST
methods. The first renders the login form, and the second sets a secure cookie with the name and redirects to the MainHandler
. The Tornado handlers have several utility methods to help with requests. For example, the self.get_argument
method gets a parameter from the request. The request itself can be accessed with self.request
.
class
Entry
(
tornado
.
web
.
UIModule
):
def
embedded_css
(
self
):
return
".entry { margin-bottom: 1em; }"
def
render
(
self
,
entry
,
show_comments
=
False
):
return
self
.
render_string
(
"module-entry.html"
,
entry
=
entry
,
show_comments
=
show_comments
)
UI modules are reusable UI widgets that you can use across your application. They make it easy to design your page layouts using independent components. UI modules subclass from tornado.web.UIModule
and must include a render method. In the example above, we define a UI module that represents a blog entry.
The render method can include arbitrary parameters, which usually will be passed on to the module template, like in the example above. A UI module can also include its own CSS and JavaScript. In our example, we use the embedded_css
method to return some CSS to use for the entry class. There are also methods for embedding JavaScript and for pointing to CSS and JavaScript files.
Once the UI module is defined, we can call it within a template with:
{ % module Entry(entry, show_comments=True) % }
Tornado offers support classes for automated testing that allow developers to test asynchronous code. It has a simple test runner, which wraps unittest.main
. It also has a couple of test helper functions.
Tornado’s test module is documented, but there is no specific tutorial or narrative section devoted to testing.
Tornado is a bit different to the other web frameworks discussed here, in that it goes hand in hand with asynchronous networking. It’s ideal to use when you need websockets, long polling, or any other kind of long-lived connections. It can also help you scale your application to tens of thousands of open connections, provided your code is written to be asynchronous and nonblocking.
For more “regular” applications, like database-driven sites, using a WSGI framework is probably a better choice. Some of those frameworks also include a lot of features that the Tornado web framework does not have.
Bottle is a true Python micro framework, in that it’s actually distributed as a single file and has no dependencies outside of the Python standard library. It’s lightweight and fast.
Bottle focuses on providing clean and simple URL routing and templating. It includes utilities for many web development needs, like access to form data, cookies, headers, and file uploads. It also includes its own HTTP server, so you don’t need to set up anything else to get your application running.
Even though its core is very small and it’s designed for small applications, Bottle has a plugin architecture that allows it to be expanded to fit more complex needs.
To install Bottle:
$ pip install bottle
from
bottle
import
route
,
run
,
template
@route
(
'/hello/<name>'
)
def
index
(
name
):
return
template
(
'Hello {{name}}!'
,
name
=
name
)
run
(
host
=
'localhost'
,
port
=
8080
)
The Bottle quick start is very simple. The route
decorator
connects the index
method to the /hello/<name> URL. Here, name is part of a dynamic route. Its value is added to the hello message using a simple string template.
Note that there is some magic going on in this example, because Bottle
creates an application object the first time route
is used.
This default application object is the one that is served when the web
server is run in the last line. It’s also possible to create a different application object explicitly, by instantiating Bottle and assigning the result to the new application. In that case, the run
method is passed to the application as the first argument.
Even though Bottle is minimalistic, it does have a few nifty features, as the following examples show.
%
name
=
"Bob"
# a line of python code
<
p
>
Some
plain
text
in
between
</
p
>
<%
# A block of python code
name
=
name
.
title
()
.
strip
()
%>
<
p
>
More
plain
text
</
p
>
<
ul
>
%
for
item
in
basket
:
<
li
>
{{
item
}}
</
li
>
%
end
</
ul
>
Bottle uses a fast, simple template language that accepts both lines
and blocks of Python code. Indentation is ignored so that code can
be properly aligned, but in exchange for that it’s necessary to add
an end
keyword after a normally indented block, like the for
loop above.
from
bottle
import
hook
,
response
,
route
@hook
(
'after_request'
)
def
enable_cors
():
response
.
headers
[
'Access-Control-Allow-Origin'
]
=
'*'
@route
(
'/foo'
)
def
say_foo
():
return
'foo!'
@route
(
'/bar'
)
def
say_bar
():
return
{
'type'
:
'friendly'
,
'content'
:
'Hi!'
}
Bottle supports after_request
and before_request
hooks, which can be used to modify the response or request. In the
example above, we add a hook that will add a cross-origin resource sharing (CORS) header to the response.
Once we define the hook, it will be used in all routes. This example
also shows how Bottle automatically converts a response to JSON when
the callback returns a dictionary, like say_bar
above.
def
list_filter
(
config
):
''' Matches a comma separated list of numbers. '''
delimiter
=
config
or
','
regexp
=
r'd+(
%s
d)*'
%
re
.
escape
(
delimiter
)
def
to_python
(
match
):
return
map
(
int
,
match
.
split
(
delimiter
))
def
to_url
(
numbers
):
return
delimiter
.
join
(
map
(
str
,
numbers
))
return
regexp
,
to_python
,
to_url
app
=
Bottle
()
app
.
router
.
add_filter
(
'list'
,
list_filter
)
@app.route
(
'/follow/<ids:list>'
)
def
follow_users
(
ids
):
for
id
in
ids
:
follow
(
id
)
Bottle’s router supports filters to modify the value of a URL parameter before it’s passed to the corresponding callback. Default filters include :int
, :float
, :path
, and :re
. You can add your own filters to the router, like in the example above.
A filter can do anything, but has to return three things: a regular expression string, a callable to convert the URL fragment to a Python
object, and a callable to turn a Python object into a URL fragment. In the list_filter
example, we will match a list of numbers in the URL and pass the converted list to the callback.
The regexp
matches a series of one or more numbers separated by a delimiter. The to_python
function turns them into integers, and the to_url
function uses str()
to turn the numbers back to strings.
Next, we explicitly create an application, and use the add_filter
method of its router to include our filter. We can now use it in a route expression, like we do in follow_users
at the bottom of the code.
Bottle has no specific tools for testing applications. Its documentation does not have a specific section devoted to testing, though it does offer a couple of short recipes on how to do unit and functional testing.
If you are working on a small application or a prototype, Bottle can be a good way to get started quickly and painlessly. However, Bottle does not have many advanced features that other frameworks do offer. While it’s possible to add these features and build a large application with Bottle, it is designed for small applications and services, so you should definitely at least take a look at the larger frameworks if you have a complex application in mind.
Pyramid is a general web application development framework. It’s designed for simplicity and speed. One of its main objectives is to make it possible to start small without having to have a lot of knowledge about the framework, but allow an application to grow organically as you learn more about Pyramid without sacrificing performance and organization to do so.
Pyramid focuses on the most basic web application needs: mapping URLs to code, supporting the best Python templating systems, serving static assets, and providing security features. In addition to that, it offers powerful and extensible configuration, extension, and add-on systems. With Pyramid, it’s possible to override and customize core code and add-ons from the outside, making it ideal for reusable subsystems and even specialized frameworks.
Pyramid is the fruit of the merger between the Pylons and repoze.bfg web frameworks. It is developed under the Pylons Project brand, which sometimes causes some confusion with the old framework name.
To install Pyramid:
$ pip install pyramid
from
wsgiref.simple_server
import
make_server
from
pyramid.config
import
Configurator
from
pyramid.response
import
Response
def
hello
(
request
):
return
Response
(
'Hello World!'
)
if
__name__
==
'__main__'
:
config
=
Configurator
()
config
.
add_route
(
'hello_world'
,
'/'
)
config
.
add_view
(
hello
,
route_name
=
'hello_world'
)
app
=
config
.
make_wsgi_app
()
server
=
make_server
(
'0.0.0.0'
,
8080
,
app
)
server
.
serve_forever
()
First, we define a view, using a function named hello
. In Pyramid, views can be any kind of callable, including functions, classes, and even class instances. The view has to return a response (in this case, just the text “Hello World!”).
To configure the application, Pyramid uses a configurator, to allow structured and extensible configuration. The configurator knows about several statements that can be performed at startup time, such as adding a route and a view that will be called when the route matches, as in the example above. Using a configurator might seem like an extra step in this example, but the configuration system is one of the things that make Pyramid a good fit for applications of any size, and particularly good for evolving applications.
The last step is to create an app and serve it. The configurator allows us to create the app using the make_wsgi_app
statement. We then pass it on to a WSGI server. Pyramid doesn’t have its own server, so in the example we use the simple server included with Python.
Pyramid has a few unique features, like view predicates, renderers, and asset specifications. Take a look at the following examples.
from
pyramid.view
import
view_config
@view_config
(
route_name
=
'blog_action'
,
match_param
=
'action=create'
,
request_method
=
'GET'
)
def
show_create_page
(
request
):
return
Response
(
'Creating...'
)
@view_config
(
route_name
=
'blog_action'
,
match_param
=
'action=create'
,
request_method
=
'POST'
)
def
create_blog
(
request
):
return
Response
(
'Created.'
)
@view_config
(
route_name
=
'blog_action'
,
match_param
=
'action=edit'
,
request_method
=
'GET'
)
def
show_edit_page
(
request
):
return
Response
(
'Editing...'
)
@view_config
(
route_name
=
'blog_action'
,
match_param
=
'action=edit'
,
request_method
=
'POST'
)
def
edit_blog
(
request
):
return
Response
(
'Edited.'
)
The first thing to note in the example above is the use of the
view_config
decorator to configure views. In the Pyramid quick start example we used config.add_view
for the same purpose, but using the decorator is a way to get the view declarations near the actual code they call.
Next, look at the different parameters passed in to the view decorator. What sets Pyramid’s view configuration system apart is that unlike most frameworks, Pyramid lets developers use several pieces of information from the request to determine which views will be called by the view lookup mechanism. This is done by adding predicates to the view configuration. Think of a predicate as a True or False statement about the current request.
A view configuration declaration can have zero or more predicates, and all things being equal, the view that has more predicates that all evaluate to True will be called by the view lookup mechanism over other views with fewer predicates. In other words, more specific view configuration declarations have preference over less specific ones.
For our example, this means that all four defined views will match the same blog_action
route (another useful Pyramid feature), but the view lookup will look at the request method and at the request parameters to find the correct view to call. For example, if the request is using the POST method and there is a request parameter named “action” and that parameter has the value “edit,” then the edit_blog
view will be called.
from
pyramid.view
import
view_config
@view_config
(
renderer
=
'json'
)
def
hello_world
(
request
):
return
{
'content'
:
'Hello!'
}
Instead of always returning some kind of response object or assuming that a view will return a template, Pyramid uses the concept of a renderer for converting the result of the view to a response. When using a renderer, the view can return a dictionary and Pyramid will pass it to the renderer for generating the response.
In the example above, the view is configured with a “json” renderer, which will turn the dictionary into a response with the correct “application/json” content type header and a JSON body with the dictionary value.
Out of the box, Pyramid supports string, json, and jsonp renderers. All the supported templating add-ons for Pyramid also include a renderer. It’s also very easy to create custom renderers. One benefit of this approach is simplicity, because the view code can focus on getting the correct result without worrying about generating the response and adding headers. Views that use renderers are also more easily tested, since simple unit tests will be enough to assert that the correct values are returned. Also, because rendering a response to HTML doesn’t assume a specific templating engine, it’s possible to easily use several engines in the same application, just by choosing the corresponding renderer (for example, “mako” or “jinja2”).
from
pyramid.view
import
view_config
@view_config
(
route_name
=
'hello_world'
,
renderer
=
'myapp:templates/hello.jinja2'
)
def
hello
(
request
):
return
{
'message'
:
"Hello World!"
}
Python web applications use many types of files in addition to Python code, like images, stylesheets, HTML templates, and JavaScript code. These files are known as assets in Pyramid. An asset specification is a mechanism for referring to these files in Python code in order to get an absolute path for a resource.
In the example above, the renderer mentioned in the configuration is in fact an asset specification that refers to a Jinja2 template that can be found inside the myapp package. Asset specifications have two parts: a package name, and an asset name, which is a file path relative to the package directory. This tells Pyramid that the template to be used for rendering the view is the hello.jinja2 file inside the templates directory in the myapp package. Pyramid resolves that into an absolute file path, and passes it to the Jinja2 renderer for generating the HTML.
Pyramid supports serving static assets, which use the same specifications for pointing at the assets to be served. This mechanism allows Pyramid to easily find and serve assets without having to depend on configuration globals or template hierarchies, like other frameworks do.
Asset specifications also make it possible to override any asset. This includes template files, template directories, static files, and static directories. For example, suppose the view declaration in the example is part of a reusable application that is meant to be mostly used as is, but with a different look. Pyramid allows you to reuse the entire application from another package and simply override the template, without requiring that the application be forked. All that would be needed is to add the following code to the customized application’s configuration:
config
.
override_asset
(
to_override
=
'myapp:templates/hello.jinja2'
,
override_with
=
'another.package:othertemplates/anothertemplate.jinja2'
)
Pyramid has its own testing
module for supporting tests. Like other frameworks, it uses unittest
in the documentation. Key Pyramid components, like the configurator, offer test helpers to simulate some of their functionality.
Pyramid’s documentation includes a chapter about unit, integration, and functional testing. The pyramid.testing
module is also documented, as are the various testing helpers. All official tutorials include a testing section.
Pyramid is designed to be used for small to large applications, so unlike some of the frameworks discussed here, size is not a criterion for deciding on its use. Pyramid has a few features that work differently from other frameworks, so being comfortable with those features is important if you want to use it.
Pyramid is very well-suited for writing extensible applications, and is a very good candidate for writing domain-specific frameworks due to its many extension points and override mechanisms.
CherryPy is the oldest framework discussed here. It is perhaps the original Python micro framework, as it focuses on matching a URL to some view, and leaves everything else to the developer. It does include its own web server, so it can serve static content as well. In addition, CherryPy has built-in tools for caching, encoding, sessions, authorization, and more.
CherryPy has a fine-grained configuration system, which allows several configuration settings to be modified. Global configuration is separate from application configuration, so it’s possible to run several applications in one process, each with its own configuration.
CherryPy is designed to be extensible, and offers several mechanisms for extensions and hook points. Since it has its own server, these extension points include server-wide functions outside of the regular request/response cycle. This gives CherryPy an added level of extensibility.
To install CherryPy:
$ pip install cherrypy
import
cherrypy
class
HelloWorld
(
object
):
def
index
(
self
):
return
"Hello World!"
index
.
exposed
=
True
cherrypy
.
quickstart
(
HelloWorld
())
CherryPy applications are usually written as classes. The views of the application are the methods of its class, but by default only explicitly selected methods will be turned into views, or exposed, as CherryPy calls it. In the example, we have a single class named HelloWorld
, with a single method in it that simply returns the text “Hello World!”. The index method is explicitly exposed by setting that attribute to True.
Then, we use the cherrypy.quickstart
call to direct CherryPy to host our application. By default, it will be accessible on http://127.0.0.1:8080/.
Note that unlike other frameworks, CherryPy does not associate a URL to a view using mappings or routes. By default, the index
method goes to “/”. Other exposed methods of a class are located by using the method name as a URL segment. For example, a goodbye
method in the example above would be called by a request to http://127.0.0.1:8080/goodbye (if exposed, of course). CherryPy does provide other ways to handle parameters in routes.
Here are a couple of examples that showcase some of CherryPy’s features.
import
random
import
string
import
cherrypy
class
StringGeneratorWebService
(
object
):
exposed
=
True
def
GET
(
self
):
return
cherrypy
.
session
[
'mystring'
]
def
POST
(
self
,
length
=
8
):
some_string
=
''
.
join
(
random
.
sample
(
string
.
hexdigits
,
int
(
length
)))
cherrypy
.
session
[
'mystring'
]
=
some_string
return
some_string
if
__name__
==
'__main__'
:
conf
=
{
'/'
:
{
'request.dispatch'
:
cherrypy
.
dispatch
.
MethodDispatcher
(),
'tools.sessions.on'
:
True
,
}
}
cherrypy
.
quickstart
(
StringGeneratorWebService
(),
'/'
,
conf
)
First, we define a class. Instead of exposing every method individually, we set exposed
to True for the whole class, which exposes all its methods. We define separate methods for each HTTP “verb.” They are very simple. GET will get the latest generated string, and POST
will generate a new one. Note the use of cherrypy.session
to store information for the current user from request to request.
A REST API uses the request methods to decide which views to call in an
application. The default CherryPy dispatcher does not know how to do that, so we need to use a different dispatcher. This is done in the configuration dictionary that will be passed to cherrypy.quickstart
. As you can see, CherryPy already has a dispatcher for this, so we just set it and everything will work.
class
Root
(
object
):
@cherrypy.expose
@cherrypy.tools.json_in
()
def
decode_json
(
self
):
data
=
cherrypy
.
request
.
json
@cherrypy.expose
@cherrypy.tools.json_out
()
def
encode_json
(
self
):
return
{
'key'
:
'value'
}
These days, JavaScript is used extensively in web applications, and JSON is the standard way to get information to and from JavaScript applications. CherryPy has a simple way of encoding and decoding JSON.
In the first example, we can treat a request as JSON using the
cherrypy.tools.json_in
decorator. It attaches a “json” attribute to the request, which contains the decoded JSON.
In the second example, we use the corresponding cherrypy.tools.json_out
decorator, which encodes the JSON and generates the correct “application/json” header.
import
time
import
cherrypy
class
TimingTool
(
cherrypy
.
Tool
):
def
__init__
(
self
):
cherrypy
.
Tool
.
__init__
(
self
,
'before_handler'
,
self
.
start_timer
)
def
_setup
(
self
):
cherrypy
.
Tool
.
_setup
(
self
)
cherrypy
.
request
.
hooks
.
attach
(
'before_finalize'
,
self
.
end_timer
)
def
start_timer
(
self
):
cherrypy
.
request
.
_time
=
time
.
time
()
def
end_timer
(
self
):
duration
=
time
.
time
()
-
cherrypy
.
request
.
_time
cherrypy
.
log
(
"Page handler took
%.4f
"
%
duration
)
cherrypy
.
tools
.
timeit
=
TimingTool
()
class
Root
(
object
):
@cherrypy.expose
@cherrypy.tools.timeit
()
def
index
(
self
):
return
"hello world"
It’s very common in a web application to need some code to be executed at some point for every request. CherryPy uses tools for that purpose. A tool is a callable piece of code that is attached into some specific point of the request, or hook. CherryPy offers several hooks, from the very beginning of the request until it is completed.
In the example, we define a tool for computing the time taken by a request from start to finish. A tool needs an __init__
method to initialize itself, which includes attaching to a hook point. In this case, the tool is attached to the before_handler
hook, which is processed right before the view is called. This starts the timer, saving the start time as an attribute of the request.
After a hook is initialized and attached to the request, its _setup
method is called. Here, we use it to attach the tool to yet another hook, before_finalize
. This is called before CherryPy formats the response to be sent to the client. Our tool will then stop the timer by calculating the difference between the start and end times, and logging the result.
To initialize the tool, we instance it and add it as an attribute of
cherrypy.tools
. We can now use it in our code by adding it as a
decorator to any class method, like this: @cherrypy.tools.timeit()
.
CherryPy provides a helper class for functional tests. It is a test case class with some helpers. The documentation for this class is brief, but includes an example.
CherryPy is a very flexible framework, which can be used in applications of all kinds. The fact that it includes its own web server and doesn’t have many dependencies makes it a good choice for shared web hosting sites.
Though it’s very easy to get started with, CherryPy does leave in the developer’s hands many of the tasks that some frameworks handle for them. This is flexible but might require more work. Also, some of its configuration and extensibility features need a more thorough understanding, so beginners trying to use it right away for a big undertaking might find its learning curve a bit steep.