XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.


Implementing the Atom Publishing Protocol
by Joe Gregorio | Pages: 1, 2, 3

A Word About WSGI

Please read PEP 333, a nicely written and detailed account of the Web Services Gateway Interface (WSGI). WSGI is an API for writing web services or components in Python. It also allows you to write applications in a platform independent manner. Wsgiref is a library that will be making it's Python core library debut in Python 2.5; it includes a reference implementation of a server, some middleware, and a WSGI application. We'll write our APP implementation as a WSGI application, which gives us more portability and opens up possibilities to write less code, which is always a good thing.

WSGI is simple and simply explained:

The WSGI interface has two sides: the "server" or "gateway" side, and the "application" or "framework" side. The server side invokes a callable object that is provided by the application side. The specifics of how that object is provided are up to the server or gateway. It is assumed that some servers or gateways will require an application's deployer to write a short script to create an instance of the server or gateway, and supply it with the application object. Other servers and gateways may use configuration files or other mechanisms to specify where an application object should be imported from, or otherwise obtained. [PEP 333]

So the application side is a callable object, and if you are familiar with Python you realize soon that you can start doing functional type things with callable objects, like composing them. That observation leads to a concept of "middleware." Not to be confused with high-priced enterprisey solutions, this kind of middleware is made of Python objects that wrap themselves around application objects to provide enhanced behavior.

In addition to "pure" servers/gateways and applications/frameworks, it is also possible to create "middleware" components that implement both sides of this specification. Such components act as an application to their containing server, and as a server to a contained application, and can be used to provide extended APIs, content transformation, navigation, and other useful functions. [PEP 333]

Here is an example of a simple WSGI application, straight from PEP 333:

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

I won't go into any further detail on WSGI here. PEP 333 does a very good job of describing it, and I heartily suggest you go read the PEP if you are at all curious.


Selector is a piece of WSGI middleware from Luke Arno that, "...provides WSGI middleware for 'RESTful' mapping of URL paths to WSGI applications." So if we know our URI structure and have built WSGI applications for each of the resources in our application, Selector lets us map all those pieces together in a completely natural way, by mapping from URI Templates and method names into WSGI applications. Let's take Table 2 from above and redo it one more time to drop the resource and representation columns and instead plug in our WSGI application names (see Table 3).

Table 3.



WSGI Application



















Selector makes it easy to specify such a service. Assuming our applications are already defined, we can create a selector object that does the mapping:

import selector

s = selector.Selector()
s.add('/collection/introspection/', GET=introspection)
s.add('/collection/', POST=create_new_entry, GET=enumerate_collection)
s.add('/collection/member/{id}', GET=member_get, PUT=member_update, DELETE=member_delete)

If we wanted to run our service as a CGI application we can use the wrapper provided in the wsgirf library.

 from wsgiref.handlers import CGIHandler CGIHandler().run(s)

So, all that's left is the individual applications themselves -- the ones that do the work and provide an interface into our Store class. Let's look at the implementation of the WSGI application to create a new entry, remembering that a WSGI application is just a function or callable object that implements the WSGI interface. In this case the application is implemented as a function.

Create an Entry

def create_new_entry(environ, start_response):
    # 1. Check for a good Content-Type: header.
    content_type = environ.get('CONTENT_TYPE', '') 
    content_type = content_type.split(';')[0]
    if content_type and content_type != 'application/atom+xml':
        start_response("400 Bad Request", [('Content-Type','text/plain')])
        return ["Wrong media type."] 

    # 2. Read in the entry
    length = int(environ['CONTENT_LENGTH'])
    content = environ['wsgi.input'].read(length)
    # 3. Store the entry
    store = getstore(environ)
    id = store.post(content)
    # 4. Response includes a Location: header
    start_response("201 Created", 
          ('Location', urljoin(
               expand_uri_template('/collection/member/{id}', {'id': id}))
    return [store.get(id).encode('utf-8')]

First, we do some basic checks (1) to ensure we have been sent the right kind of data.

Then we read in the entry that was sent (2) and place it in the store (3). If successful, we send a 201 Created response that includes a Location: header that points to the newly created resource. The response needs to include a Location: header with the URI of the newly created entry, and from the spec of HTTP (RFC 2616) we know that the URI returned must be an absolute URI.

The call to wsgiref.util.application_uri() gets us our base URI and then we use expand_uri_template() to expand the URI Template with the id we just assigned the new entry. The expand_uri_template() function is described fully in the XML.com article, "Constructing or Traversing URIs?".

If the store has problems with the entry -- for example, it isn't well-formed -- it will throw an exception that our WSGI wrapper will catch and turn into an appropriate error response. Note that the default error response isn't very helpful and a future enhancement will be to add more informative status codes and error messages.

Pages: 1, 2, 3

Next Pagearrow