Edit me on GitHub

Comparing and Combining Traversal and URL Dispatch

(adapted from Bayle Shank’s contribution at https://github.com/bshanks/pyramid/commit/c73b462c9671b5f2c3be26cf088ee983952ab61a).

Here’s is an example which compares URL dispatch to traversal.

Let’s say we want to map

/hello/login to a function login in the file myapp/views.py

/hello/foo to a function foo in the file myapp/views.py

/hello/listDirectory to a function listHelloDirectory in the file myapp/views.py

/hello/subdir/listDirectory to a function listSubDirectory in the file myapp/views.py

With URL dispatch, we might have:

1
2
3
4
5
6
7
8
9
config.add_route('helloLogin', '/hello/login')
config.add_route('helloFoo', '/hello/foo')
config.add_route('helloList', '/hello/listDirectory')
config.add_route('list', '/hello/{subdir}/listDirectory')

config.add_view('myapp.views.login', route_name='helloLogin')
config.add_view('myapp.views.foo', route_name='helloFoo')
config.add_view('myapp.views.listHelloDirectory', route_name='helloList')
config.add_view('myapp.views.listSubDirectory', route_name='list')

When the listSubDirectory function from myapp/views.py is called, it can tell what the subdirectory’s name was by checking request.matchdict['subdir']. This is about all you need to know for URL-dispatch-based apps.

With traversal, we have a more complex setup:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MyResource(dict):
    def __init__(self, name, parent):
        self.__name__ = name
        self.__parent__ = parent

class MySubdirResource(MyResource):
    def __init__(self, name, parent):
        self.__name__ = name
        self.__parent__ = parent

    # returns a MyResource object when the key is the name
    # of a subdirectory
    def __getitem__(self, key):
        return MySubdirResource(key, self)

class MyHelloResource(MySubdirResource):
    pass

def myRootFactory(request):
    rootResource = MyResource('', None)
    helloResource = MyHelloResource('hello', rootResource)
    rootResource['hello'] = helloResource
    return rootResource

config.add_view('myapp.views.login', name='login')
config.add_view('myapp.views.foo', name='foo')
config.add_view('myapp.views.listHelloDirectory', context=MyHelloResource,
                name='listDirectory')
config.add_view('myapp.views.listSubDirectory', name='listDirectory')

In the traversal example, when a request for /hello/@@login comes in, the framework calls myRootFactory(request), and gets back the root resource. It calls the MyResource instance’s __getitem__('hello'), and gets back a MyHelloResource. We don’t traverse the next path segment (@@login`), because the ``@@ means the text that follows it is an explicit view name, and traversal ends. The view name ‘login’ is mapped to the login function in myapp/views.py, so this view callable is invoked.

When a request for /hello/@@foo comes in, a similar thing happens.

When a request for /hello/@@listDirectory comes in, the framework calls myRootFactory(request), and gets back the root resource. It calls MyRootResource’s __getitem__('hello'), and gets back a MyHelloResource instance. It does not call MyHelloResource’s __getitem__('listDirectory') (due to the @@ at the lead of listDirectory). Instead, ‘listDirectory’ becomes the view name and traversal ends. The view name ‘listDirectory’ is mapped to myapp.views.listRootDirectory, because the context (the last resource traversed) is an instance of MyHelloResource.

When a request for /hello/xyz/@@listDirectory comes in, the framework calls myRootFactory(request), and gets back an instance of MyRootResource. It calls MyRootResource’s __getitem__('hello'), and gets back a MyHelloResource instance. It calls MyHelloResource’s __getitem__('xyz'), and gets back another MySubdirResource instance. It does not call __getitem__('listDirectory') on the MySubdirResource instance. ‘listDirectory’ becomes the view name and traversal ends. The view name ‘listDirectory’ is mapped to myapp.views.listSubDirectory, because the context (the final traversed resource object) is not an instance of MyHelloResource. The view can access the MySubdirResource via request.context.

At we see, traversal is more complicated than URL dispatch. What’s the benefit? Well, consider the URL /hello/xyz/abc/listDirectory. This is handled by the above traversal code, but the above URL dispatch code would have to be modified to describe another layer of subdirectories. That is, traversal can handle arbitrarily deep, dynamic hierarchies in a general way, and URL dispatch can’t.

You can, if you want to, combine URL dispatch and traversal (in that order). So, we could rewrite the above as:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MyResource(dict):
    def __init__(self, name, parent):
        self.__name__ = name
        self.__parent__ = parent

    # returns a MyResource object unconditionally
    def __getitem__(self, key):
        return MyResource(key, self)

def myRootFactory(request):
    return MyResource('', None)

config = Configurator()

config.add_route('helloLogin', '/hello/login')
config.add_route('helloFoo', '/hello/foo')
config.add_route('helloList', '/hello/listDirectory')
config.add_route('list', '/hello/*traverse', factory=myRootFactory)

config.add_view('myapp.views.login', route_name='helloLogin')
config.add_view('myapp.views.foo', route_name='helloFoo')
config.add_view('myapp.views.listHelloDirectory', route_name='helloList')
config.add_view('myapp.views.listSubDirectory', route_name='list',
                name='listDirectory')

You will be able to visit e.g. http://localhost:8080/hello/foo/bar/@@listDirectory to see the listSubDirectory view.

This is simpler and more readable because we are using URL dispatch to take care of the hardcoded URLs at the top of the tree, and we are using traversal only for the arbitrarily nested subdirectories.

Table Of Contents

Previous topic

Routing: Traversal and URL Dispatch

Next topic

Using Traversal in Pyramid Views