Advanced Pyramid Design Features

Pyramid has been built from the ground up to avoid the problems that other frameworks can suffer.

You Don't Need Singletons

Have you ever struggled with parameterizing Django's settings.py file for multiple installations of the same Django application? Have you ever needed to monkey-patch a framework fixture to get it to behave properly for your use case? Have you ever tried to deploy your application using an asynchronous server and failed?

All these problems are symptoms of mutable global state, also known as import time side effects and arise from the use of singleton data structures.

Pyramid is written so that you don't run into these types of problems. It is even possible to run multiple copies of the same Pyramid application configured differently within a single Python process. This makes running Pyramid in shared hosting environments a snap.

Simplify your View Code with Predicates

How many times have you found yourself beginning the logic of your view code with something like this:

1if request.user.is_authenticated:
2    # do one thing
3else:
4    # do something else

Unlike many other systems, Pyramid allows you to associate more than one view with a single route. For example, you can create a route with the pattern /items and when the route is matched, you can send the request to one view if the request method is GET, another view if the request method is POST, and so on.

Pyramid uses a system of view predicates to allow this. Matching the request method is one basic thing you can do with a view predicate. You can also associate views with other request parameters, such as elements in the query string, the Accept header, whether the request is an AJAX (XHR) request or not, and lots of other things.

For our example above, you can do this instead:

1@view_config(route_name="items", effective_principals=pyramid.security.Authenticated)
2def auth_view(request):
3    # do one thing
4
5@view_config(route_name="items")
6def anon_view(request):
7    # do something else

This approach allows you to develop view code that is simpler, more easily understandable, and more directly testable.

See also

See also View Configuration Parameters.

Stop Worrying About Transactions

Pyramid's cookiecutter renders projects that include a transaction management system. When you use this system, you can stop worrying about when to commit your changes, Pyramid handles it for you. The system will commit at the end of a request or abort if there was an exception.

Why is that a good thing? Imagine a situation where you manually commit a change to your persistence layer. It's very likely that other framework code will run after your changes are done. If an error happens in that other code, you can easily wind up with inconsistent data if you're not extremely careful.

Using transaction management saves you from needing to think about this. Either a request completes successfully and all changes are committed, or it does not and all changes are aborted.

Pyramid's transaction management is extendable, so you can synchronize commits between multiple databases or databases of different kinds. It also allows you to do things like conditionally send email if a transaction is committed, but otherwise keep quiet.

See also

See also SQLAlchemy + URL dispatch wiki tutorial (note the lack of commit statements anywhere in application code).

Stop Worrying About Configuration

When a system is small, it's reasonably easy to keep it all in your head. But as systems grow large, configuration grows more complex. Your app may grow to have hundreds or even thousands of configuration statements.

Pyramid's configuration system keeps track of each of your statements. If you accidentally add two that are identical, or Pyramid can't make sense out of what it would mean to have both statements active at the same time, it will complain loudly at startup time.

Pyramid's configuration system is not dumb though. If you use the include() system, it can automatically resolve conflicts on its own. More local statements are preferred over less local ones. So you can intelligently factor large systems into smaller ones.

See also

See also Conflict Detection.

Compose Powerful Apps From Simple Parts

Speaking of the Pyramid structured include() mechanism, it allows you to compose complex applications from multiple, simple Python packages. All the configuration statements that can be performed in your main Pyramid application can also be used in included packages. You can add views, routes, and subscribers, and even set authentication and authorization policies.

If you need, you can extend or override the configuration of an existing application by including its configuration in your own and then modifying it.

For example, if you want to reuse an existing application that already has a bunch of routes, you can just use the include statement with a route_prefix. All the routes of that application will be available, prefixed as you requested:

1from pyramid.config import Configurator
2
3if __name__ == '__main__':
4    config = Configurator()
5    config.include('pyramid_jinja2')
6    config.include('pyramid_exclog')
7    config.include('some.other.package', route_prefix='/somethingelse')

Authenticate Users Your Way

Pyramid ships with prebuilt, well-tested authentication and authorization schemes out of the box. Using a scheme is a matter of configuration. So if you need to change approaches later, you need only update your configuration.

In addition, the system that handles authentication and authorization is flexible and pluggable. If you want to use another security add-on, or define your own, you can. And again, you need only update your application configuration to make the change.

Build Trees of Resources

Pyramid supports traversal, a way of mapping URLs to a concrete resource tree. If your application naturally consists of an arbitrary hierarchy of different types of content (like a CMS or a Document Management System), traversal is for you. If you have a requirement for a highly granular security model ("Jane can edit documents in this folder, but not that one"), traversal can be a powerful approach.

Take Action on Each Request with Tweens

Pyramid has a system for applying an arbitrary action to each request or response called a tween. The system is similar in concept to WSGI middleware, but can be more useful since tweens run in the Pyramid context, and have access to templates, request objects, and other niceties.

The Pyramid debug toolbar is a tween, as is the pyramid_tm transaction manager.

See also

See also Registering Tweens.

Return What You Want From Your Views

We have shown elsewhere (in the Pyramid Introduction) how using a renderer allows you to return simple Python dictionaries from your view code. But some frameworks allow you to return strings or tuples from view callables. When frameworks allow for this, code looks slightly prettier because there are fewer imports and less code. For example, compare this:

1from pyramid.response import Response
2
3def aview(request):
4    return Response("Hello world!")

To this:

1def aview(request):
2    return "Hello world!"

Nicer to look at, right?

Out of the box, Pyramid will raise an exception if you try to run the second example above. After all, a view should return a response, and "explicit is better than implicit".

But if you're a developer who likes the aesthetics of simplicity, Pyramid provides a way to support this sort of thing, the response adapter:

1from pyramid.config import Configurator
2from pyramid.response import Response
3
4def string_response_adapter(s):
5    response = Response(s)
6    response.content_type = 'text/html'
7    return response

A new response adapter is registered in configuration:

1if __name__ == '__main__':
2    config = Configurator()
3    config.add_response_adapter(string_response_adapter, str)

With that, you may return strings from any of your view callables, e.g.:

1def helloview(request):
2    return "Hello world!"
3
4def goodbyeview(request):
5    return "Goodbye world!"

You can even use a response adapter to allow for custom content types and return codes:

 1from pyramid.config import Configurator
 2
 3def tuple_response_adapter(val):
 4    status_int, content_type, body = val
 5    response = Response(body)
 6    response.content_type = content_type
 7    response.status_int = status_int
 8    return response
 9
10def string_response_adapter(body):
11    response = Response(body)
12    response.content_type = 'text/html'
13    response.status_int = 200
14    return response
15
16if __name__ == '__main__':
17    config = Configurator()
18    config.add_response_adapter(string_response_adapter, str)
19    config.add_response_adapter(tuple_response_adapter, tuple)

With this, both of these views will work as expected:

1def aview(request):
2    return "Hello world!"
3
4def anotherview(request):
5    return (403, 'text/plain', "Forbidden")

Use Global Response Objects

Views have to return responses. But constructing them in view code is a chore. And perhaps registering a response adapter as shown above is just too much work. Pyramid provides a global response object as well. You can use it directly, if you prefer:

1def aview(request):
2    response = request.response
3    response.body = 'Hello world!'
4    response.content_type = 'text/plain'
5    return response

Extend Configuration

Perhaps the Pyramid configurator's syntax feels a bit verbose to you. Or possibly you would like to add a feature to configuration without asking the core developers to change Pyramid itself?

You can extend Pyramid's configurator with your own directives. For example, let's say you find yourself calling pyramid.config.Configurator.add_view() repetitively. Usually you can get rid of the boring with existing shortcuts, but let's say that this is a case where there is no such shortcut:

 1from pyramid.config import Configurator
 2
 3config = Configurator()
 4config.add_route('xhr_route', '/xhr/{id}')
 5config.add_view('my.package.GET_view', route_name='xhr_route', xhr=True,
 6    permission='view', request_method='GET')
 7config.add_view('my.package.POST_view', route_name='xhr_route', xhr=True,
 8    permission='view', request_method='POST')
 9config.add_view('my.package.HEAD_view', route_name='xhr_route', xhr=True,
10    permission='view', request_method='HEAD')

Pretty tedious right? You can add a directive to the Pyramid configurator to automate some of the tedium away:

 1from pyramid.config import Configurator
 2
 3def add_protected_xhr_views(config, module):
 4    module = config.maybe_dotted(module)
 5    for method in ('GET', 'POST', 'HEAD'):
 6        view = getattr(module, 'xhr_%s_view' % method, None)
 7        if view is not None:
 8            config.add_view(view, route_name='xhr_route', xhr=True,
 9                            permission='view', request_method=method)
10
11config = Configurator()
12config.add_directive('add_protected_xhr_views', add_protected_xhr_views)

Once that's done, you can call the directive you've just added as a method of the configurator object:

1config.add_route('xhr_route', '/xhr/{id}')
2config.add_protected_xhr_views('my.package')

Much better!

You can share your configuration code with others, too. Add your code to a Python package. Put the call to add_directive() in a function. When other programmers install your package, they'll be able to use your configuration by passing your function to a call to include().

Introspect Your Application

If you're building a large, pluggable system, it's useful to be able to get a list of what has been plugged in at application runtime. For example, you might want to show users a set of tabs at the top of the screen based on a list of the views they registered.

Pyramid provides an introspector for just this purpose.

Here's an example of using Pyramid's introspector from within a view:

1from pyramid.view import view_config
2from pyramid.response import Response
3
4@view_config(route_name='bar')
5def show_current_route_pattern(request):
6    introspector = request.registry.introspector
7    route_name = request.matched_route.name
8    route_intr = introspector.get('routes', route_name)
9    return Response(str(route_intr['pattern']))