BitWorking

wsgicollection

The idea of RESTful "Collections", i.e. doing CRUD over HTTP correctly, has been percolating for years now. A Collection is nothing more than a list, a container for resources. While the APP defines a Collection in terms of Atom Feed and Entry documents we don't have to be limited to that. It's time to complete a virtuous circle; RESTLog inspired the Atom Publishing Protocol which inspired David Heinemeier Hansson's World of Resources (pdf) and now it's time to come full circle and get that world of resources in Python.

In particular look at page 18 of that slide deck, where dispatching to a collection of people, the following URIs are to be handled:

  GET    /people         
  POST   /people
  GET    /people/1
  PUT    /people/1
  DELETE /people/1
  GET    /people;new
  GET    /people/1;edit

Now the 'new' and 'edit' URIs can be a bit ambiguous, only in the sense that you might not guess right away that they are nouns, and remember, URIs always identify nouns. I prefer to make the noun-ishness of them more apparent.

  GET    /people;create_form
  GET    /people/1;edit_form

In general, using the notation of Selector, we are looking at URIs of the form:

 /...people/[{id}][;{noun}] 

And dispatching requests to URIs of that form to functions with nice names:

  GET    /people               list()
  POST   /people               create()
  GET    /people/1             retrieve()
  PUT    /people/1             update()
  DELETE /people/1             delete()
  GET    /people;create_form   get_create_form()
  GET    /people/1;edit_form   get_edit_form()
  

Introducing wsgicollection, a Python library that does just that, simplifying implementing such a Collection under WSGI.

Wsgicollection uses Selector indirectly, relying on it to parse the URIs for {id} and {noun}. In theory it will work with any WSGI middleware that sets values for 'id' and 'noun' in environ['selector.vars'] environ['wsgiorg.routing_args']. Here is how you would define a WSGI application that implements a collection:

from wsgicollection import Collection

class RecipeCollection(Collection):

    # GET /cookbook/
def list(environ, start_response):
        pass
# POST /cookbook/
def create(environ, start_response):
        pass
# GET /cookbook/1
def retrieve(environ, start_response):
        pass
# PUT /cookbook/1
def update(environ, start_response):
        pass
# DELETE /cookbook/1
def delete(environ, start_response):
        pass
# GET /cookbook/;create_form
def get_create_form(environ, start_response):
        pass
# POST /cookbook/1;comment_form
def post_comment_form(environ, start_response):
        pass

And this class can be easily hooked up to Selector:

import selector

urls = selector.Selector()

urls.add('/cookbook/[{id}][;{noun}]', _ANY_=RecipeCollection())

Now that I have this Collection class it will ease implementing the APP, but as I indicated earlier, the collection (CRUD) model goes beyond that of just Atom, and we'll dig into that next.

You can find the code here.

Update: Fixed a bug where wsgicollection directly imported selector, which it does not need to do. You will, however, need selector installed to run the unit tests.

Update 2: Updated to support routing_args

2006-09-28