Fab Magic

Recently Jamie and I did some fairly major refactoring of fab, our "invented here" Python web framework. I think we made some interesting additions and I'm fairly excited about developing our future web applications with it.

Most of the new features are supported by some pretty magic python hackery, which always evokes strong feelings of pride amongst programmers. I particularly like the uniform handling of HTTP GET/POST and our so-called "path arguments". Path arguments are really just segments of the requested URL that we treat as parameters -- this method usually looks a lot cleaner than GET arguments and can often accomplish the same thing. All of this is facilitated by our favorite HTTP wrapper, CherryPy, but I think the layers we've added on top of CherryPy are significant and helpful.

This is probably best explained by a small example -- a blog. A request to read posts for today might look like this:

http://www.mrshoe.org/blog/2005/11/16

Assuming this blog is kept entirely in memory fab can handle this request in a few lines.

class BlogPage(fab.FabPage):
   template = blogTemplate
   def control(self, page, year, month, day):
      page.blogtext = blogdata[year][month][day]


Now imagine you want to make a search phrase bold throughout the post. Such a request might look like this:

http://www.mrshoe.org/blog/2005/11/16?bold=python

To handle this you could write:

class BlogPage(fab.FabPage):
   template = blogTemplate
   def control(self, page, year, month, day, bold=''):
      text = blogdata[year][month][day]
      page.blogtext = text.replace(bold,
         '<strong>%s</strong>' % bold)


This is a simple example, but it demonstrates the use of both path arguments and GET arguments. The interesting part is how fab passes these arguments to the application. They are passed to the control method. Path arguments are normal positional arguments (which are a direct analog to segments of the URL, really), and GET/POST arguments are keyword arguments (also a very natural mapping). It gets better. Fab is smart enough to know what you do and don't care about. Any arguments you specify in the control method's signature will get passed along. Anything else will be ignored. Any arguments you expect but are absent in the request are assigned the value of None. Initially this system struck me as being a little too magic, but in practice it's very useful. If someone requests

http://www.mrshoe.org/blog/2005/11/16?
    bold=python&foo=bar


I don't care about the foo argument. My control method doesn't use it for anything. If they request

http://www.mrshoe.org/blog/2005/11/16/baz?
    bold=python&foo=bar


I don't care about the baz. Why display an annoying error message? Just give them the blog post they're looking for. If, in the future, I decide to add more fields to the form, all I have to do is add more keyword arguments to the control method and voila, fab will pass along the relevant data -- no more, no less.

I've been using this method for a couple weeks now and it has proven to be easier and more elegant than any web development I've done in the past.

I hope I didn't just provide you all with a starting point for hacking MrShoe.org... of course you would never try something so sinister, right?