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:
1config.add_route('helloLogin', '/hello/login')
2config.add_route('helloFoo', '/hello/foo')
3config.add_route('helloList', '/hello/listDirectory')
4config.add_route('list', '/hello/{subdir}/listDirectory')
5
6config.add_view('myapp.views.login', route_name='helloLogin')
7config.add_view('myapp.views.foo', route_name='helloFoo')
8config.add_view('myapp.views.listHelloDirectory', route_name='helloList')
9config.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:
1class MyResource(dict):
2 def __init__(self, name, parent):
3 self.__name__ = name
4 self.__parent__ = parent
5
6class MySubdirResource(MyResource):
7 def __init__(self, name, parent):
8 self.__name__ = name
9 self.__parent__ = parent
10
11 # returns a MyResource object when the key is the name
12 # of a subdirectory
13 def __getitem__(self, key):
14 return MySubdirResource(key, self)
15
16class MyHelloResource(MySubdirResource):
17 pass
18
19def myRootFactory(request):
20 rootResource = MyResource('', None)
21 helloResource = MyHelloResource('hello', rootResource)
22 rootResource['hello'] = helloResource
23 return rootResource
24
25config.add_view('myapp.views.login', name='login')
26config.add_view('myapp.views.foo', name='foo')
27config.add_view('myapp.views.listHelloDirectory', context=MyHelloResource,
28 name='listDirectory')
29config.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:
1class MyResource(dict):
2 def __init__(self, name, parent):
3 self.__name__ = name
4 self.__parent__ = parent
5
6 # returns a MyResource object unconditionally
7 def __getitem__(self, key):
8 return MyResource(key, self)
9
10def myRootFactory(request):
11 return MyResource('', None)
12
13config = Configurator()
14
15config.add_route('helloLogin', '/hello/login')
16config.add_route('helloFoo', '/hello/foo')
17config.add_route('helloList', '/hello/listDirectory')
18config.add_route('list', '/hello/*traverse', factory=myRootFactory)
19
20config.add_view('myapp.views.login', route_name='helloLogin')
21config.add_view('myapp.views.foo', route_name='helloFoo')
22config.add_view('myapp.views.listHelloDirectory', route_name='helloList')
23config.add_view('myapp.views.listSubDirectory', route_name='list',
24 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.