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