Though Pyramid is my web framework of choice, I've been doing more Django development recently at work. I honestly want to do Django "the right way", and I'm the kind of person who benefits greatly from twitter links, blog posts and github code browsing, so I try to follow Django developers and companies. I also figured it wouldn't hurt to buy the best Django book on the market.

Django has been very good for us and I like it a lot, but of course I can't help comparing how some things are done between the frameworks. I don't do this because I want to bash Django, I simply want to let people know about some really cool Pyramid features and Django makes an excellent point of reference because it has been so successful that most Python developers know about it.

Anyway, that should explain why I was paying attention when Jacob Kaplan-Moss tweeted last week that he had just created django-multiurl, a Django plug-in for allowing multiple views to match the same URL.

Django stops resolving URLs at the first match, so it's not possible to do it "out of the box". With django-multiurl you can do this:

1
2
3
4
5
6
7
8
 from multiurl import multiurl

 urlpatterns = patterns('',
     multiurl(
         url('/app/(\w+)/$', app.views.people),
         url('/app/(\w+)/$', app.views.place),
     )
 )

This sets things up so Django can try one URL after another until it gets to the correct one, but this is not automatic. In your views, you need to raise a multiurl.ContinueResolving exception to let Django know that this was not the right view and it needs to continue resolving. That looks like this:

1
2
3
4
5
6
7
8
 from multiurl import ContinueResolving

 def people(request, name):
     try:
         person = Person.objects.get(name=name)
     except Person.DoesNotExist:
         raise ContinueResolving
     return render(...)

If all the views included in the multiurl call end up raising the ContinueResolving exception, a 404 error will be raised.

This is a good thing to have in your Django toolbox, but it got me thinking about how great Pyramid's view configuration system is. Not only does it easily allow similar functionality to the multiurl extension for Django, but goes way beyond that in both flexibility and extensibility.

For example, I'm sure everyone has seen code like this before:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 def a_view(request):
     if request.method == 'POST':
         form = ExampleForm(request.POST)
         if form.is_valid():
             process_data(form)
             return HttpResponseRedirect('/success/')
     else:
         form = ExampleForm()

     return render(request, 'example.html', {'form': form})

This is a single view, but we actually have code for two different actions. If the request method is POST, we want to do validate the form and redirect; if the method is GET we want to render an empty form. In this example the code for each action is very short, which makes the example easy to understand, but in a real application each action could be several lines long.

In Pyramid we could do that, but there's another way:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 from pyramid.httpexceptions import HTTPFound
 from pyramid.view import view_config

 @view_config(route_name='example', request_method='GET',
     renderer='form.pt')
 def a_view_get(request):
     form = ExampleForm()
     return {'form': form}

 @view_config(route_name='example', request_method='POST')
 def a_view_post(request):
     form = ExampleForm(request)
     process_data(form)
     return HTTPFound(location='/success/')

In this example, we have one view for the GET action and one for the POST action. If you look closely, you'll see that both views use the same route, which is similar to the multiurl thing, except no exceptions need to be thrown. Pyramid analyzes the request and finds the view that matches best. This last step is known in Pyramid as view lookup.

What do we gain by this? Well, the code is less complex for each view and it's simpler to test. We also get more readability. This style of view configuration comes in very handy for REST applications, for example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 from pyramid.view import view_config
 from pyramid.response import Response

 class RESTView(object):
     def __init__(self, request):
         self.request = request

     @view_config(route_name='rest', request_method='GET')
     def get(self):
         return Response('get')

     @view_config(route_name='rest', request_method='POST')
     def post(self):
         return Response('post')

     @view_config(route_name='rest', request_method='DELETE')
     def delete(self):
         return Response('delete')

In the example above, we have one view for each REST verb that we need. They all use the same route, so the URL would be the same. By the way, notice that here we have a class where each method is a view. In the previous example we just used functions as views. To Pyramid it's all the same, any callable can be a view, even an instance of a class. Also, look at the __init__ method. All the views in our REST class share that common initialization, so for example all of them could use self.request.

Now, I'll be the first to admit that there's nothing specially innovative about using the request method to find the correct view to execute. Flask can do that. For Django, both Django REST framework and Django Piston offer similar mechanisms. Outside the Python world, Ruby on Rails has REST as a default route configuration method, and its magic config process even creates seven routes and controller actions for each declaration, one for each REST verb.

What sets Pyramid and its view configuration system apart is that unlike most frameworks, it can use much more than the request method to determine which view to invoke for a request. Look at it this way: the request has lots of information available, why limit a framework's view configuration mechanism to just the request method?

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. In the REST example above, both route_name and request_method are predicates.

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.

It's no accident that the REST example we discussed above uses route_name and request_method as predicates. Since those are the most commonly used view routing mechanisms across frameworks, they should make the concept easier to understand. At least, I hope they do. Let's take a look now at some of the more generally interesting predicates that Pyramid accepts in view configuration declarations.

The request_param predicate configures a view to be called only if the GET or POST variables of the request contain the specified variable name. This can be useful if for example we want one view to be used if a certain option is selected and a different one if it's not:

1
2
3
4
5
6
7
 @view_config(route_name='subscribe', request_param='level=normal')
 def normal_subscription(request):
     return Response('Subscribed!')

 @view_config(route_name='subscribe', request_param='level=premium')
 def premium_subscription(request):
     return Response('Subscribed, Sir!')

The match_param predicate is similar, but looks at the match dict for the route. If you have a route like this:

config.add_route('blog_action', '/blog/{action}')

You can use match_param to get away with using a single route declaration and having a bunch of action views for that, rather than creating seven different routes for your seven views (for example).

1
2
3
4
5
6
7
 @view_config(route_name='blog_action', match_param='action=create')
 def create_blog(request):
     return Response('Created.')

 @view_config(route_name='blog_action', match_param='action=edit')
 def edit_blog(request):
     return Response('Edited.')

Note that both request_param and match_param allow passing in a sequence of strings if you want to take account of more than one parameter. Also, adding a value is not mandatory, so you could also check merely for the presence of a variable name, regardless of its value.

Lots of web applications nowadays need to do a lot of work for Javascript clients that require JSON responses. Web frameworks like jQuery know to add an HTTP_X_REQUESTED_WITH header with the value set to XMLHttpRequest to signal an AJAX request. The xhr predicate is used to declare this kind of view.

1
2
3
4
5
6
7
 @view_config(route_name='ultimate_answer')
 def answer(request):
     return Response('The answer is 42.')

 @view_config(route_name='ultimate_answer', xhr=True, renderer='json')
 def answer_json(request):
     return {'answer': 42}

In the xhr example, the renderer bit is not a predicate. It's just a view configuration setting telling Pyramid to return a JSON response. The predicate is the thing responsible for getting to that view.

Another interesting predicate is accept, which looks at the Accept HTTP header. You can do content negotiation using that, but be very careful, since some clients return */* in that header and thus results may vary from what you expect. It's best to try out the different clients for what you want to do first and adjust the predicate use according to the results. An example:

1
2
3
4
5
6
7
 @view_config(route_name='content', accept='text/html')
 def normal_view(request):
     return Response('This is text.')

 @view_config(route_name='ultimate_answer', accept='application/rdf+xml')
 def rdf_view(request):
     return Response('This is not text, it's RDF.')

The last two predicates are commonly used ways to look at HTTP headers, but you might have a less general need. That's why the header predicate exists:

1
2
3
 @view_config(route_name='content', header='any-header-even-custom')
 def my_special_view(request):
     return Response('This is special.')

The header predicate accepts a sequence of headers as well, and you can use the equals sign to look for a specific value, like in the request_param example above.

Of course, we have seen each predicate on its own for simplicity, but they can be combined in any way you choose. Just remember that as you add predicates to a view configuration, it becomes more specific and will take precedence over views with fewer predicates if everything else is the same.

Here's an example of using multiple predicates:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 @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.')

This is by no means the full list of predicates, but it should be enough to give a good idea of their use and potential. You can take a look at Pyramid's documentation if you want to see the full list.

If you are hooked and start using them for everything, you might find that even the full list is no longer enough for your ambitious needs. Fear not, Pyramid allows you to easily use your own custom predicates, known as third party predicates in Pyramid.

One other really nice feature of Pyramid that I would like to talk about some time, is its configuration system. Third party predicates can be added using this system. First, you need a predicate factory (it's OK, that just means a class that is used to construct the predicate):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 class IPAddressPredicate(object):
     def __init__(self, val, config):
         self.val = val

     def text(self):
         return "ip_address = %s" % val

     phash = text

     def __call__(self, context, request):
         addr = request.remote_addr
         return addr.startswith(self.val)

The __init__ method must expect as parameters the value passed in the view_config declaration and the current configurator instance. Its main job is to save the passed in value for use during the actual predicate test.

The text method must return a description of the predicate behavior which is used in error messages.

phash must be a unique description of the predicate, used by the internal view configuration process. Usually it's just the same as what text returns.

The main predicate functionality is in the __call__ method. It receives a context and a request, which can be examined to determine if the predictae is True or False.

In the example above, our predicate receives an IP address or a portion of one and if the current client IP starts with this value, returns True. This could be used to test if a given request comes from inside a network, for example.

To use this predicate we need to add it during Pyramid's configuration stage:

config.add_view_predicate('ip_address', IPAddressPredicate)

Once we do this, we can use it in our view_config calls:

1
2
3
 @view_config(route_name='internal', ip_address='192.168.1')
 def internal_view(request):
     return Response('only called if IP is internal.')

One final thing about predicates. They can be used in route configuration as well, not just views. Route configuration is a different topic, though, so we won't go into detail about that here.

This was only an introduction to predicates, we didn't really go over all view configuration options for Pyramid. Please consult Pyramid's documentation for a much more detailed overview of its powerful view configuration system