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.