Navigation

Pyramid lets you start small and finish big. This Quick Tour of Pyramid is
for those who want to evaluate Pyramid, whether you are new to Python
web frameworks, or a pro in a hurry. For more detailed treatment of
each topic, give the Quick Tutorial for Pyramid a try.

Of course Pyramid runs fine on Python 2.6+, as do the examples in this
Quick Tour. We're just showing Python 3 a little love (Pyramid had
production support in October 2011.)

Note

Why easy_install and not pip? Pyramid encourages use of namespace
packages which, until recently, pip didn't permit. Also, Pyramid has
some optional C extensions for performance. With easy_install, Windows
users can get these extensions without needing a C compiler.

As shown in this example, the configurator plays a central role
in Pyramid development. Building an application from loosely-coupled
parts via Application Configuration is a central idea in Pyramid,
one that we will revisit regurlarly in this Quick Tour.

Developing for the web means processing web requests. As this is a
critical part of a web application, web developers need a robust,
mature set of software for web requests.

Pyramid has always fit nicely into the existing world of Python web
development (virtual environments, packaging, scaffolding,
first to embrace Python 3, etc.) For request handling, Pyramid turned
to the well-regarded WebOb Python library for request and
response handling. In our example
above, Pyramid hands hello_world a request that is
based on WebOb.

Let's see some features of requests and responses in action:

defhello_world(request):# Some parameters from a request such as /?name=lisaurl=request.urlname=request.params.get('name','No Name Provided')body='URL %s with name: %s'%(url,name)returnResponse(content_type="text/plain",body=body)

In this Pyramid view, we get the URL being visited from request.url.
Also, if you visited http://localhost:6543/?name=alice,
the name is included in the body of the response:

importcgifrompyramid.httpexceptionsimportHTTPFoundfrompyramid.responseimportResponsefrompyramid.viewimportview_config# First view, available at http://localhost:6543/@view_config(route_name='home')defhome_view(request):returnResponse('<p>Visit <a href="/howdy?name=lisa">hello</a></p>')# /howdy?name=alice which links to the next view@view_config(route_name='hello')defhello_view(request):name=request.params.get('name','No Name')body='<p>Hi %s, this <a href="/goto">redirects</a></p>'# cgi.escape to prevent Cross-Site Scripting (XSS) [CWE 79]returnResponse(body%cgi.escape(name))# /goto which issues HTTP redirect to the last view@view_config(route_name='redirect')defredirect_view(request):returnHTTPFound(location="/problem")# /problem which causes a site error@view_config(route_name='exception')defexception_view(request):raiseException()

We have 4 views, each leading to the other. If you start at
http://localhost:6543/, you get a response with a link to the next
view. The hello_view (available at the URL /howdy) has a link
to the redirect_view, which issues a redirect to the final
view.

Earlier we saw config.add_view as one way to configure a view. This
section introduces @view_config. Pyramid's configuration supports
imperative configuration, such as the config.add_view in
the previous example. You can also use declarative
configuration, in which a Python decorator is placed on the
line above the view. Both approaches result in the same final
configuration, thus usually, it is simply a matter of taste.

Writing web applications usually means sophisticated URL design. We
just saw some Pyramid machinery for requests and views. Let's look at
features that help in routing.

Above we saw the basics of routing URLs to views in Pyramid:

Your project's "setup" code registers a route name to be used when
matching part of the URL

Elsewhere, a view is configured to be called for that route name

Note

Why do this twice? Other Python web frameworks let you create a
route and associate it with a view in one step. As
illustrated in Routes Need Relative Ordering, multiple routes might
match the same URL pattern. Rather than provide ways to help guess,
Pyramid lets you be explicit in ordering. Pyramid also gives
facilities to avoid the problem.

What if we want part of the URL to be available as data in my view? This
route declaration:

config.add_route('hello','/howdy/{first}/{last}')

With this, URLs such as /howdy/amy/smith will assign amy to
first and smith to last. We can then use this data in our
view:

Ouch. We have been making our own Response and filling the response
body with HTML. You usually won't embed an HTML string directly in
Python, but instead, will use a templating language.

Pyramid doesn't mandate a particular database system, form library,
etc. It encourages replaceability. This applies equally to templating,
which is fortunate: developers have strong views about template
languages. That said, the Pylons Project officially supports bindings for
Chameleon, Jinja2, and Mako, so in this step, let's use Chameleon.

Let's add pyramid_chameleon, a Pyramid add-on which enables
Chameleon as a renderer in our Pyramid applications:

$ easy_install pyramid_chameleon

With the package installed, we can include the template bindings into
our configuration:

Ahh, that looks better. We have a view that is focused on Python code.
Our @view_config decorator specifies a renderer that points
to our template file. Our view then simply returns data which is then
supplied to our template:

We just said Pyramid doesn't prefer one templating language over
another. Time to prove it. Jinja2 is a popular templating system,
modelled after Django's templates. Let's add pyramid_jinja2,
a Pyramid add-on which enables Jinja2 as a renderer in
our Pyramid applications:

$ easy_install pyramid_jinja2

With the package installed, we can include the template bindings into
our configuration:

config.include('pyramid_jinja2')

The only change in our view is to point the renderer at the .jinja2 file:

Pyramid's templating add-ons register a new kind of renderer into your
application. The renderer registration maps to different kinds of
filename extensions. In this case, changing the extension from .pt
to .jinja2 passed the view response through the pyramid_jinja2
renderer.

Of course the Web is more than just markup. You need static assets:
CSS, JS, and images. Let's point our web app at a directory where
Pyramid will serve some static assets. First, another call to the
configurator:

All we need to do now is point to it in the <head> of our Jinja2
template:

<linkrel="stylesheet"href="/static/app.css"/>

This link presumes that our CSS is at a URL starting with /static/.
What if the site is later moved under /somesite/static/? Or perhaps
a web developer changes the arrangement on disk? Pyramid provides a helper
to allow flexibility on URL generation:

<linkrel="stylesheet"href="${request.static_url('static/app.css')}"/>

By using request.static_url to generate the full URL to the static
assets, you both ensure you stay in sync with the configuration and
gain refactoring flexibility later.

So far our views have been simple, free-standing functions. Many times
your views are related: different ways to look at or work on the same
data or a REST API that handles multiple operations. Grouping these
together as a
view class makes sense:

Group views

Centralize some repetitive defaults

Share some state and helpers

The following shows a "Hello World" example with three operations: view
a form, save a change, or press the delete button:

# One route, at /howdy/amy, so don't repeat on each @view_config@view_defaults(route_name='hello')classHelloWorldViews:def__init__(self,request):self.request=request# Our templates can now say {{ view.name }}self.name=request.matchdict['name']# Retrieving /howdy/amy the first time@view_config(renderer='hello.jinja2')defhello_view(self):returndict()# Posting to /howdy/amy via the "Edit" submit button@view_config(request_param='form.edit',renderer='edit.jinja2')defedit_view(self):print('Edited')returndict()# Posting to /howdy/amy via the "Delete" submit button@view_config(request_param='form.delete',renderer='delete.jinja2')defdelete_view(self):print('Deleted')returndict()

As you can see, the three views are logically grouped together.
Specifically:

The first view is returned when you go to /howdy/amy. This URL is
mapped to the hello route that we centrally set using the optional
@view_defaults.

The second view is returned when the form data contains a field with
form.edit, such as clicking on
<inputtype="submit"name="form.edit"value="Save"/>. This rule
is specified in the @view_config for that view.

The third view is returned when clicking on a button such
as <inputtype="submit"name="form.delete"value="Delete"/>.

Only one route needed, stated in one place atop the view class. Also,
the assignment of the name is done in the __init__. Our
templates can then use {{view.name}}.

Pyramid view classes, combined with built-in and custom predicates,
have much more to offer:

All the same view configuration parameters as function views

One route leading to multiple views, based on information in the
request or data such as request_param, request_method,
accept, header, xhr, containment, and
custom_predicates

So far we have done all of our Quick Tour as a single Python file.
No Python packages, no structure. Most Pyramid projects, though,
aren't developed this way.

To ease the process of getting started, Pyramid provides scaffolds
that generate sample projects from templates in Pyramid and Pyramid
add-ons. Pyramid's pcreate command can list the available scaffolds:

Prior to scaffolds, our project mixed a number of operations details
into our code. Why should my main code care which HTTP server I want and
what port number to run on?

pserve is Pyramid's application runner, separating operational
details from your code. When you install Pyramid, a small command
program called pserve is written to your bin directory. This
program is an executable Python module. It's very small, getting most
of its brains via import.

You can run pserve with --help to see some of its options.
Doing so reveals that you can ask pserve to watch your development
files and reload the server when they change:

$ pserve development.ini --reload

The pserve command has a number of other options and operations.
Most of the work, though, comes from your project's wiring, as
expressed in the configuration file you supply to pserve. Let's
take a look at this configuration file.

Earlier in Quick Tour we first met Pyramid's configuration system.
At that point we did all configuration in Python code. For example,
the port number chosen for our HTTP server was right there in Python
code. Our scaffold has moved this decision, and more, into the
development.ini file:

Let's take a quick high-level look. First, the .ini file is divided
into sections:

[app:hello_world] configures our WSGI app

[pipeline:main] sets up our WSGI "pipeline"

[server:main] holds our WSGI server settings

Various sections afterwards configure our Python logging system

We have a few decisions made for us in this configuration:

Choice of web server. The use=egg:pyramid#wsgiref tells
pserve to use the wsgiref server that is wrapped in the Pyramid
package.

Port number. port=6543 tells wsgiref to listen on port
6543.

WSGI app. What package has our WSGI application in it?
use=egg:hello_world in the app section tells the
configuration what application to load.

Easier development by automatic template reloading. In development
mode, you shouldn't have to restart the server when editing a Jinja2
template. reload_templates=true sets this policy,
which might be different in production.

Additionally, the development.ini generated by this scaffold wired
up Python's standard logging. We'll now see in the console, for example,
a log on every request that comes in, as well as traceback information.

As we introduce the basics, we also want to show how to be productive in
development and debugging. For example, we just discussed template
reloading and earlier we showed --reload for application reloading.

pyramid_debugtoolbar is a popular Pyramid add-on which makes
several tools available in your browser. Adding it to your project
illustrates several points about configuration.

First, change your setup.py to say:

requires=['pyramid>=1.0.2','pyramid_jinja2','pyramid_debugtoolbar']

...and re-run your setup:

$ python ./setup.py develop

The Python package was now installed into our environment. The package
is a Pyramid add-on, which means we need to include its configuration
into our web application. We could do this with imperative
configuration, as we did above for the pyramid_jinja2 add-on:

config.include('pyramid_jinja2')

Now that we have a configuration file, we can use the
pyramid.includes facility and place this in our
development.ini instead:

[app:hello_world]pyramid.includes=pyramid_debugtoolbar

You'll now see an attractive (and
collapsible) menu in the right of your browser, providing introspective
access to debugging information. Even better, if your web application
generates an error, you will see a nice traceback on the screen. When
you want to disable this toolbar, no need to change code: you can
remove it from pyramid.includes in the relevant .ini
configuration file.

Yikes! We got this far and we haven't yet discussed tests. Particularly
egregious, as Pyramid has had a deep commitment to full test coverage
since before it was released.

Our pyramid_jinja2_starter scaffold generated a tests.py module
with one unit test in it. To run it, let's install the handy nose
test runner by editing setup.py. While we're at it, we'll throw in
the coverage tool which yells at us for code that isn't tested:

It's important to know what is going on inside our web application.
In development we might need to collect some output. In production,
we might need to detect situations when other people use the site. We
need logging.

Fortunately Pyramid uses the normal Python approach to logging. The
scaffold generated in your development.ini has a number of lines that
configure the logging for you to some reasonable defaults. You then see
messages sent by Pyramid (for example, when a new request comes in).

Maybe you would like to log messages in your code? In your Python
module, import and set up the logging:

importlogginglog=logging.getLogger(__name__)

You can now, in your code, log messages:

log.debug('Some Message')

This will log SomeMessage at a debug log level,
to the application-configured logger in your development.ini. What
controls that? These sections in the configuration file:

When people use your web application, they frequently perform a task
that requires semi-permanent data to be saved. For example, a shopping
cart. This is called a session.

Pyramid has basic built-in support for sessions. Third party packages such as
pyramid_redis_sessions provide richer session support. Or you can create
your own custom sessioning engine. Let's take a look at the
built-in sessioning support. In our
__init__.py we first import the kind of sessioning we want:

frompyramid.sessionimportSignedCookieSessionFactory

Warning

As noted in the session docs, this example implementation is
not intended for use in settings with security implications.

Now make a "factory" and pass it to the configurator's
session_factory argument:

We now have a working sample SQLAlchemy application with all
dependencies installed. The sample project provides a console script to
initialize a SQLite database with tables. Let's run it and then start
the application:

$ initialize_sqla_demo_db development.ini
$ pserve development.ini

The ORM eases the mapping of database structures into a programming
language. SQLAlchemy uses "models" for this mapping. The scaffold
generated a sample model:

Developers have lots of opinions about web forms, and thus there are many
form libraries for Python. Pyramid doesn't directly bundle a form
library, but Deform is a popular choice for forms,
along with its related Colander schema system.

As an example, imagine we want a form that edits a wiki page. The form
should have two fields on it, one of them a required title and the
other a rich text editor for the body. With Deform we can express this
as a Colander schema:

With this in place, we can render the HTML for a form,
perhaps with form data from an existing page:

form=self.wiki_form.render()

We'd like to handle form submission, validation, and saving:

# Get the form data that was postedcontrols=self.request.POST.items()try:# Validate and either raise a validation error# or return deserialized data from widgetsappstruct=wiki_form.validate(controls)exceptdeform.ValidationFailurease:# Bail out and render form with errorsreturndict(title=title,page=page,form=e.render())# Change the content and redirect to the viewpage['title']=appstruct['title']page['body']=appstruct['body']

Deform and Colander provide a very flexible combination for forms,
widgets, schemas, and validation. Recent versions of Deform also
include a retail mode for gaining Deform
features on custom forms.

Also, the deform_bootstrap Pyramid add-on restyles the stock Deform
widgets using attractive CSS from Bootstrap and more powerful widgets
from Chosen.