In our last post when did a bare bones introduction to CherryPy. Now let’s dig a little deeper into how GAE and CherryPy work together to map URLs to objects and methods, referred to as dispatching.
As discussed, CherryPy’s default behavior is to group together classes to create an Application, where classes and methods form a hierarchical tree of objects. CherryPy does this by virtue of the cherrypy.tree.mount( class, URL) method. Once you have a suitable hierarchy of objects, CherryPy will parse incoming URL requests into the path components. Each path component is then used to make a path through the Application’s root to a page handler method of one of the classes. If CherryPy get to a leaf node (a method) before all of the path components are used up, these are turned into positional arguments for that method. If the url path ends before a page handling method, CherryPy will call the index() method of the class. While this sounds a bit complex, in practice it is fairly straight forward. Let’s set up an example.
HelloWorld, Part Duex
In this example we’ll be creating two inter-related classes, a greeting of Hello World! and reply class Good Day!:
# main.py import cherrypy import wsgiref.handlers class HelloWorld: @cherrypy.expose def index(self): return "Hello world!" class GoodDay: @cherrypy.expose def index(self,num=None): reply = "Good Day!" if num is not None: for x in range(1,int(num)): reply += " Good Day!" return (reply) def main(): root = HelloWorld() root.reply = GoodDay() app = cherrypy.tree.mount(root,"/") wsgiref.handlers.CGIHandler().run(app) if __name__ == '__main__': main()
If you were to make a graph of these objects it would look something like this:
And, assuming you are running GAE locally on port 8080, the following table represents some URL to page handler mappings:
| http://localhost:8080/ | HelloWord().index() |
| http://localhost:8080/index | HelloWord().index() |
| http://localhost:8080/reply | GoodDay().index() |
| http://localhost:8080/reply/4 | ERROR PAGE |
| http://localhost:8080/reply/index/4 | GoodDay().index(4) |
| http://localhost:8080/reply/?num=4 | GoodDay().index(num=4) |
As expected, the root URL, “/”, maps to our first object HelloWorld(). Since no other path components are given, the default index() method is called. Note the second example where we make an explicit call to index().
For the third example, we continue to traverse the object tree, following the reply path to the GoodDay() and its default index() method. The forth example shows that by default, mapping of URL components are quite literal, thus “4″ is expected to be mapped to GoodDay().4(), and not being defined will result in an error. The 5th and 6th examples show the proper way to pass arguments to the GoodDay().index() method, using the positional and keyword styles, respectively.
Notice that we have yet to talk about GAE. In this example, we have pretty much left all routing to CherryPy. Other than copying the CherryPy modules into the directory and editing the main.py, nothing was touched in the default GAE application. If you would like to serve requests out of a none-root URL, then you would need to sync the mount points of app.yaml and cherrypy.tree.mount() like so:
# in app.yaml
handlers:
- url: /hello/.*
script: main.py
# in main.py
app = cherrypy.tree.mount(root,"/hello/")
# now accessible as http://localhost:8080/hello/
Also note that CherryPy’s default dispatcher does not differentiate between GET and POST requests. The MethodDispatcher class adds this capability on top of the default dispatcher, but if method parsing is of interest, then I suspect that you have more requirements on URL mapping than either of these dispatcher can grant. In our next article, we’ll cover the RoutesDispatcher, which is a very robust method of mapping URLs to page handlers taking a cue from the Ruby on Rails world.


2 comments
October 15, 2010 at 7:57 am
Egor Petrov
Why is this example can not work?
I create test2.py, run it and get:
d:\python>test2.py
[15/Oct/2010:14:44:23] Traceback (most recent call last):
File “C:\Python31\lib\site-packages\cherrypy\_cpwsgi.py”, line 138, in trap
return func(*args, **kwargs)
File “C:\Python31\lib\site-packages\cherrypy\_cpwsgi.py”, line 71, in __call__
return self.nextapp(environ, start_response)
File “C:\Python31\lib\site-packages\cherrypy\_cpwsgi.py”, line 309, in tail
return self.response_class(environ, start_response, self.cpapp)
File “C:\Python31\lib\site-packages\cherrypy\_cpwsgi.py”, line 183, in __init__
self.run()
File “C:\Python31\lib\site-packages\cherrypy\_cpwsgi.py”, line 223, in run
meth = self.environ['REQUEST_METHOD']
KeyError: ‘REQUEST_METHOD’
Traceback (most recent call last):
File “C:\Python31\lib\wsgiref\handlers.py”, line 75, in run
self.finish_response()
File “C:\Python31\lib\wsgiref\handlers.py”, line 116, in finish_response
self.write(data)
File “C:\Python31\lib\wsgiref\handlers.py”, line 210, in write
self.send_headers()
File “C:\Python31\lib\wsgiref\handlers.py”, line 266, in send_headers
self.send_preamble()
File “C:\Python31\lib\wsgiref\handlers.py”, line 196, in send_preamble
self._write(‘Status: %s\r\n’ % self.status)
File “C:\Python31\lib\wsgiref\handlers.py”, line 402, in _write
self.stdout.write(data)
TypeError: must be str, not bytes
d:\python>
What’s wrong? Cherrypy is 3.2.0rc1, Python is 3.1.2
October 15, 2010 at 2:04 pm
delagoya
This is a rather old article. Perhaps it is related to Python 3 v.s. Python 2.x differences. Or the older version of CherryPy vs. newer. In either case I can’t help you. Sorry.