17.7. RESTful Resources in TurboGears
REST is an architectural style, or a way of thinking about application development, first described by Roy Fielding in 2000. Roy was one of the principal authors of HTTP, and REST is a distillation of why he thinks the web works so well in allowing people to create reusable resources.
Here are a few key principles of the REST way of thinking:
In HTTP, the URL provides the universal resource identification syntax, Get and Post provide the initial set of well-defined operations, and there is no provision for stateful transactions.
So, to a certain extent, all web applications are built on a RESTful corebut it is easy to create URLs that don't represent particular resources, or that don't make it easy to define a limited set of actions. For example, you could have a URL for editing an existing bookmark that looks like this: http://localhost.com/bookmarker/edit_bookmark/form$id=15, which jumbles actions, values, configuration options, and miscellaneous information all into the URL in a pretty much random order.
The REST way of thinking is to make the URL into two sections: the resource locator, and the "verb" or action you want taken. So, the preceding URL would become /bookmark/15/edit, which indicates the resource you want is a bookmark, the particular one you want is 15, and the action you want to take is to edit that bookmark. This is certainly cleaner looking, but there are lots of other advantages because it is easier to understand and easier to program. You have specific elements that can be handled separately (for example, the resource location, and the choice of what action to take on that resource).
This way of thinking isn't a particularly natural way of working with CherryPy out of the box. CherryPy publishes objects that represent user actions, and these objects are passed parameters that tell CherryPy what resources that action ought to be taken on. Which is exactly the opposite way of thinking of REST.
Fortunately, creative use of the default method can make RESTful interfaces easy. For example, if you want to implement a simple interface for four actionsCreate, Read, Update, or Delete (CRUD) on an itemyou could write methods for each of those, and use them with this default method to give you a REST-style interface:
@expose def default(self, *vpath, **params): if len(vpath==1): item=vpath return read(item) if len(vpath)==2 item, verb = vpath action = getattr(self, verb, None) return action(item, **params) expose() def read(self, item, **params): pass expose() def add(self, item, **params) pass expose() def update(self, item, **params): pass expose() def delete(self, item, **params): pass
The default method checks the number of positional parameters sent in; and if there's only one, it assumes that the read method should be called. If not, it calls the correct method for you, based on the value in the second positional parameter. The key to making this happen is hidden in the action = getattr(self, verb, None) line.
The getattr is a built-in function in Python that takes an object and any string and if that string matches any of the attributes of that object, returns a reference to that object. So, calling getattr(self, "add") is the same as self.addboth provide a reference to the add object, which can be assigned another name. This does not call the referenced object (which might not in fact be a callableas you might guess from the name, the getattr function works on attributes, too).
In this case, getattr is used to find out whether the verb value (which, as you will remember, is a string pulled off of the end of the URL) matches any of the methods in this class. We can't call the add method directly because all we have is a string passed in from the URL; but with getattr, that's no problem.
The optional third parameter allows you to pick a default to return if the string does not match any attribute name. In this case, we are just returning None. You could skip this, but having a sane default makes handling malformed input easier.
Which brings us to a problem with our little default dispatcher: It doesn't handle invalid user input very well. It's also likely that you'll want to use something like this on more than one set of objects in your application. To help with both of those problems, the TurboGears wiki has described this nice little content class, which you can use as a base class for any number of controller classes:
class content(): @turbogears.expose() def default(self, *vpath, **params): if len(vpath) == 1: identifier = vpath action = self.read elif len(vpath) == 2: identifier, verb = vpath verb = verb.replace('.', '_') action = getattr(self, verb, None) if not action: raise cherrypy.NotFound if not action.exposed: raise cherrypy.NotFound else: raise cherrypy.NotFound items = self.query(identifier) if items.count() == 0: raise cherrypy.NotFound else: return action(items, **params)
As you can see, this is pretty much the same thing as we did in our default method, but with better error handling. But, now we can get this default method in our class by including it in as one of the superclasses of our controller class, as follows:
class Root(controllers.RootController, content): @turbogears.expose(template='myapp.templates.show_thing') def read(self, thing): return dict(text=thing.text)
With this implementation, you could have any number of verbs available for this resource. And as long as you define the proper methods in your controller class, they will work automatically.
There is a whole world of reuse possibilities if you create resources that implement a consistent set of verbs. This is particularly true when you couple this API similarity with the ability to expose the same method in multiple ways (through a Kid Template, as a JSON struct, or via XML RPC, and so on). You could leave the default parameter out, but it's always a good idea to have a sane default when you're handling untrusted input.