PyZine
 


Article Finder
People
Issue 5 - Revision 7  /   April 20, 2004 


 
  Py Links:
Latest Issue
Issue 08
Issue 07
Issue 06
Issue 05
Issue 04
Issue 02
Issue 01
 
 
Downloads
     
  Articles:
Throughout the quarter we cover topics of interest to Python developers.

  RSOAP

  Simple Code Generation

  4ss

  Pyro

  PyCon 2004

  Kaa & Firedrop

  XML-RPC for Python

  Applied XML-RPC

 
 
 
     


REST, Quixote, and Python

- - - - - - - - - - - -

By Dave Kuhlman | February 3, 2003

print
Representational State Transfer (REST)

REST is an architectural style for the design of message-passing systems; most notably, it's the architectural style expressed by HTTP and the World Wide Web. Knowledge of and familiarity with REST priniciples is useful for the design of web applications and web services.

This document describes a specific application delivery model, REST, and techniques for implementing web applications that follow the REST model on top of AOLserver, PyWX (a Python plug-in for AOLserver), and Quixote (a Python web application framework). But most of this article will apply equally well to Quixote applications deployed on other web servers, such as Apache or medusa.

The guidance REST offers to building web applications comes mostly in the form of restrictions, which is appropriate because one of the goals is to develop web applications that are more simple and more understandable both by the developers of the application and by its clients and users.

Here are some of those guidelines or restrictions:

  • Everything is a resource. The client requests a resource. The web application delivers a representation of that resource. Even submitting information is done via requesting a resource. For example, the user might submit information by requesting a form (the representation of a form resource), filling out the form, then requesting the updated resource by clicking a link in the form to submit the form.
  • Every resource has a URI. Think of the design process as determining which things to identify or name. URI's are the names.
  • The application produces representations, not resources. Furthermore, for any given resource, your application can expose multiple representations: for example, HTML and XML representations and even multiple HTML or multiple XML representations. Think of a representation as the content you generate from a resource; the representation/content is what you send to the client/requestor.
  • State transitions are managed by the client, not the server. One implication of this guideline is that each request from a client must contain all the information needed by the application (server) to satisfy the request.

And here are some less important guidelines:

  • Submit content (the HTTP body) in preference to an HTTP query (CGI variables).
  • Think of HTTP methods as verbs: GET, POST, PUT, and DELETE. Use many resource identifiers, i.e. the URIs that name your resources. Why? Restricting your design to a small number of verbs reduces complexity.

Conceptually, what's the big deal anyway? Is it a big deal? I believe the shift from standard Web application to a RESTful application is a significant shift:

  • Sequencing. Think about the shift we made from command line style application to GUI application. In a command-line application, the application tended to control the sequence of events. In a GUI application, the user controls the sequence of actions, e.g. by selecting menu items in a particular order. The application imposes restrictions of the sort: action A must be performed before action B. In an analogous way, the client controls the sequencing of actions in a RESTful application. The server (or application) is less restrictive about what requests must be made in what order. There is a common agreement that if the client has a URI that names a resource, then the client can use the URI to request the resource.
  • Fragility. REST encourages an application developer to support a fixed set of operations (HTTP methods) over a practically unlimited set of resources. The application can examine the request and the input submitted with the request and can ask whether it is valid. It's similar to preconditions that are validated when a function is called in an Eiffel program. We hope that this approach to development will encourage the developer to design requests that can be validated.
PyWX and Quixote

In a number of respects, PyWX is an especially strong platform for developing applications guided by the REST architecture. In particular, PyWX allows manipulation of resources and representations. PyWX provides especially strong access to resources. And REST is all about delivering representations of resources. Relational databases are supported through the database API provided by AOLserver and exposed through PyWX. And, Python is an excellent glue language, providing access to C libraries, XML parcers, XML-RPC, and much more. Python provides exceptionally strong features, both in terms of the logic and formatting capabilities, needed to generate representations. We will also see below how Quixote adds even stronger formatting capabilities for producing representations.

This section provides some vague guidelines for implementing RESTful applications on top of PyWX (and possibly Quixote). REST is an architectural style. It is not, for example, a toolset or framework that would force you to iron clad set of rules. The web application you will construct will be more or less RESTful, depending on how closely you follow REST principles. And if you already know the tools you will build on (AOLserver, PyWX, and Quixote, for example), then building your application in the REST style will be mostly obvious.

With those reservations in mind, consider these guidelines:

Identify your significant resources. REST is all about delivering resources, about making those resources accessible across the Web. Your first step should be to identify the resources that you wish to expose and to specify which information in those resources you are attempting to make visible.

Implement the Python code that provides access to the resources. You should also explore the AOLserver API that is exposed by PyWX, looking for the access to you resources that it provides. And, remember, PyWX also exposes AOLserver's Tcl API.

Implement the Python code that generates the representations of the resources. Factoring your code into reusable Python modules and classes is a good way to go here. Try to think in terms of user interface classes: for example, a class that encapsulates a resource and contains methods that produce representations of different kinds.

Quixote has three elements that help to develop RESTful applications.

  • Mapping URIs to resources -- Quixote maps URIs to Python modules in a simple way. But it also enables the developer to insert Python logic in this mapping.
  • A template language -- Quixote's template language (PTL) is simple and Python-centric. It is well-suited for inserting snippets of Python logic in boilerplate. This makes Quixote especially well suited for producing representations of resource.
  • Structure -- Because a Quixote application is structured as a hierarchy of Python packages and modules, Quixote is particularly convenient for exposing resources that are (conceptually) organized as tree.

So how do you develop a RESTful application with Quixote?

  • Identify each resource to be exposed.
  • For each resource, coin a URI.
  • For each URI , implement a Python module to generate the representation of the resource identified by the URI. You can also consider implementing access to a resource in a Python class, then returning an instance of that class for Quixote to traverse.
  • For each resource that accepts a POST, you will need to map the URI for that resource to a function or method that reads and processes what's submitted by the client as the content (body) of the HTTP request. Here is an example:
    import Ns
    import updateformsub
    
    def update(request, name):
        conn = Ns.GetConn()
        server = conn.Server()
        pool = Ns.DbPoolDefault(server)
        dbHandle = Ns.DbHandle(pool)
        response = request.response
        response.set_header('Content-type', 'text/xml; charset=iso-8859-1')
        # The PyWX way to get request content.
        #buff = PyWX_buffer.GetBuff()
        # The Quixote way to get request content.
        buff = request.stdin
        content = buff.read()
        root = updateformsub.parseString(content)
        name = root.getName()
        descrip = root.getDescription()
        # Update the database.
        updateDB(dbHandle, name, descrip)
        if descrip == '[delete]':
            root.setDescription('[deleted]')
        contentstream = StringIO.StringIO()
        root.export(contentstream, 0)
        content = contentstream.getvalue()
        contentstream.close()
        return content
    

In some cases Quixote templates will enable you to write cleaner code than plain Python functions, although Python makes it very easy to generate text. I use PTL and templates whenever there is a high ratio of boilerplate to generated text, or when you can make things convenient by composing templates. For example, here is a template that composes a header, a body, and a footer:

template genHeader(request, name):
    """<html>
    ...
    """
template genFooter(request, name):
    """
    ...
    </html>
    """
template genContent(request, name):
    genHeader(request, name)
    genBody(request, name)
    genFooter(request, name)

where each of genHeader, genBody, and genFooter return its own bit of content.

Some Simple Examples

This section provides explanation and commentary on these examples.

DrillDown is a standard REST pattern. This example shows a list of food recipes and enables the user to drill down by selecting a recipe for display. The user can also select and display either (1) the ingredients or (2) the instructions for a recipe. The recipes are stored in the file data.py from which they can be imported:

# data.py

recipes = {
    'greensalad': {
        'name': "Green Salad",
        'ingredients': ['lettuce', 'parsley', 'bellpepper',
            'radish', 'dressing'],
        'instructions': [
            'Wash all ingredients.',
            'Cut up lettuce.',
            'Chop parsley, bellpepper, and radish.',
            'Add dressing.',
            'Toss',
            ]
        },
    'fruitsalad': {
        'name': "Fruit Salad",
        'ingredients': ['apples', 'oranges', 'banana', 'plum', 'pomegranate'],
        'instructions': [
            'Wash apples and plums. Cut into chunks.',
            'Peel oranges. Break in sections and cut in half.',
            'Shell pomegranates, submerging under water to avoid splatters and
            'Mix and serve',
            ]
        }
    }

A set of recipes is are stored in a dictionary. The keys in the dictionary are names of recipes. The values are recipes. Each recipe is a dictionary with three entries: (1) the name of the recipe; (2) the ingredients; and (3) instructions for preparing the recipe.

The Quixote dispatcher is in __init__.py. It exports the modules that serve up a list of recipes (module recipelist), (2) serves up a recipe (module recipe), and (3) provides recipe details (ingredients and instructions).

# __init__.py

_q_exports = ['recipelist', 'recipe', 'details']


def _q_index(request):
    return IndexMsg

IndexMsg = """\
<html>
<head><title>Index</title></head>
<body>
<div align="center"><h1>Index</h1></div>
<p><a href="recipelist">Show list of recipes</a></p>
</body>
</html>
"""

The __init__ module dispatches requests to submodules by exporting recipelist and recipe in the magic variable _q_exports. Quixote does the rest. The index page is produced by function _q_index, which is automatically exposed to Quixote and which Quixote uses as the default page. The recipelist module is a Quixote template file, with a ptl extension. It displays a list of recipes and URIs for each one. The URIs are used to drill down to the recipe itself, to a list of ingredients, or to a list of instructions.

# recipelist.ptl

from data import recipes

_q_exports = []


template _q_index(request):
    """\
<html>
<head><title>Recipe List</title></head>
<body>
<p>A list of recipes:</p>
<table border="1">
  <tr>
    <th>Recipe</th>
    <th>Ingredients</th>
    <th>Instructions</th>
"""
    for key in recipes.keys():
        '  <tr>\n'
        '    <td><a href="/DrillDown/recipe/%s">%s</a></td>\n' % \
            (key, recipes[key]['name'])
        '    <td><a href="/DrillDown/details/ingredients/%s">%s</a></td>\n' % \
            (key, recipes[key]['name'])
        '    <td><a href="/DrillDown/details/instructions/%s">%s</a></td>\n' % \
            (key, recipes[key]['name'])
        '  </tr>\n'
    """\
</table>
<hr>
<p><a href="/DrillDown/">Return to index</a></p>
</body>
</html>
"""

Since _q_index is a template, there is no need to explicitly return its content. Any text in a Quixote template is automatically returned. Embedded in the template is a Python for-loop that inserts the recipe name and URIs to be used to access the recipe, the ingredient list, and the instruction list. The recipe module displays a single recipe.

# recipe.py

import string
from data import recipes

_q_exports = []

def _q_getname(request, name):
    if not name.lower() in recipes.keys():
        return NosuchrecipeMsg % name
    return showRecipe(request, name)

NosuchrecipeMsg = """\
<html>
<head><title>Error</title></head>
<body>
<div align="center"><h1>Error</h1></div>
<p>No such recipe: %s</p>
</body>
</html>
"""

def showRecipe(request, name):
    recipe = recipes[name]
    rname = recipe['name']
    doc = ['<html>',
           '<head><title>Recipe -- %s</title><head>' % rname,
           '<body>'
           '<div align="center"><h1>Recipe -- %s</h1></div>' % rname,
           '<h2>Ingredients</h2>',
           '<ul>'
           ]
    for item in recipe['ingredients']:
        doc.append('  <li>%s</li>' % item)
    doc.append('</ul>')
    doc.append('<hr>')
    doc.append('<h2>Instructions</h2>')
    doc.append('  <ol>')
    for item in recipe['instructions']:
        doc.append('  <li>%s</li>' % item)
    doc.append('  </ol>')
    doc.append('  <hr>')
    doc.append('  <p><a href="/DrillDown/recipelist">Return to recipe list</a></p>')
    doc.append('  <p><a href="/DrillDown/">Return to index</a></p>')
    doc.append('</body>')
    doc.append('</html>')
    content = string.join(doc, '\n')
    return content

recipe provides a _q_getname function that takes the last (right-most) component of the URI and uses it to select a recipe. This function, in effect, dispatches to individual recipes. showRecipe() formats and displays the recipe. It creates a list of lines of text, then joins them into a single string (inserting a newline between each line). Using list append and join is faster that string append. Other alternatives would be to use either module StringIO or module cStringIO from the Python standard library. The details module is a Python file that displays either> (1) the ingredients or (2) the instructions for a recipe. In effect, it drills down into either ingredients or instructions.

# details.py

import string
from quixote.errors import TraversalError

from data import recipes

_q_exports = []

def _q_getname(request, name):
    if name == 'ingredients':
        return Ingredients()
    elif name == 'instructions':
        return Instructions()
    else:
        raise TraversalError('No such detail: %s' % name)


class Ingredients:
    _q_exports = []
    def __init__(self):
        pass
    def show(self, request, name):
        recipe = recipes[name]
        rname = recipe['name']
        doc = [
            '<html>'
            '<head><title>Drilldown - Details - Ingredientss</title></head>',
            '<body>',
            '  <div align="center"><h1>Recipe -- %s</h1></div>' % rname,
            '  <h2>Ingredients</h2>',
            '  <ol>'
            ]
        for item in recipe['ingredients']:
            doc.append('    <li>%s</li>' % item)
        doc.append('  </ol>')
        doc.append('  <hr>')
        doc.append('  <p><a href="/DrillDown/recipelist">Return to recipe list</a></p>')
        doc.append('  <p><a href="/DrillDown/">Return to index</a></p>')
        doc.append('</body>')
        doc.append('</html>')
        content = string.join(doc, '\n')
        return content
    def _q_getname(self, request, name):
        if not recipes.has_key(name):
            raise TraversalError('No such recipe: %s' % name)
        return self.show(request, name)


class Instructions:
    _q_exports = []
    def __init__(self):
        pass
    def show(self, request, name):
        recipe = recipes[name]
        rname = recipe['name']
        doc = [
            '<html>'
            '<head><title>Drilldown - Details - Instructions</title></head>',
            '<body>'
            '<div align="center"><h1>Recipe -- %s</h1></div>' % rname,
            '<h2>Instructions</h2>',
            '<ol>'
            ]
        for item in recipe['instructions']:
            doc.append('  <li>%s</li>' % item)
        doc.append('</ol>')
        doc.append('  <hr>')
        doc.append('  <p><a href="/DrillDown/recipelist">Return to recipe list</a></p>')
        doc.append('  <p><a href="/DrillDown/">Return to index</a></p>')
        doc.append('</body>')
        doc.append('</html>')
        content = string.join(doc, '\n')
        return content
    def _q_getname(self, request, name):
        if not recipes.has_key(name):
            raise TraversalError('No such recipe: %s' % name)
        return self.show(request, name)

Comments:

  • The top-level function _q_getname dispatches by returning an instance of class Ingredients or class Instructions (into which Quixote will continue traversing) or by raising a Quixote TraversalError exception.
  • The class Ingredients contains a _q_getname method that checks to determine the requested recipe exists, and then calls method show to produce the content to be sent to the client.
  • Class Instructions is similar to class Ingredients except that it shows the instructions (instead of the ingredients) for a recipe.
REST Client Development

For the purposes of this discussion, a REST client is written in Python; communicates with a server via HTTP; sends and receives XML messages, that is, the server passes a representation of a resource in XML and the client passes content to the server by filling in an XML "form" and returning the XML form as the content of a POST request. The client's building blocks include:

  • httplib -- Enables us to send HTTP requests to the server, to receive XML content (the representation of a resource) from the server, and to send XML content to the server. (httplib is in the standard Python library.)
  • generateDS.py -- We want to enable clients to consume and to produce XML documents as readily as possible. generateDS.py helps us do that by generating Python classes for XML documents and elements from an XML Schema definition of the document. You can find generateDS.py and more information about it at my site.

I'm assuming several things. First, that the server responds to HTTP requests with XML content. Second, that the implementor of the server (application) provides an W3C XML Schema definition of the XML messages passed between server and client.These XML Schema definitions are compatible with generateDS.py.

In order to build a client, given these assumptions, take the following steps.

  1. For each unique XML message type to be received from the server, obtain its XML Schema definition. Pass the XML Schema through generateDS.py to produce a super-class module and a sub-class module.
  2. Then, for each desired resource, implement a function or method that requests the resource. Here is a sample request:
    import plantlistsub
    
    def showlist(line):
        host = 'warbler:8081'
        url = '/UpdateRecord/recordlist/'
        headers = {'Accept': 'text/xml'}
        params = {}
        try:
            conn = httplib.HTTPConnection(host)
            req = conn.request('GET', url, params, headers)
        except socket.error:
            print "Can't connect to server: warbler:8081"
            return
        res = conn.getresponse()
        status = res.status
        content = res.read()
        conn.close()
        if status != 200:
            print "Can't get plant list.  status: %d" % status
            return
        root = plantlistsub.parseString(content)
        print 'Plant list:'
        for plant in root.getPlant():
            print '    Plant # %s:' % plant.getIndex()
            print '        Name:         %s' % plant.getName()
            print '        Description:  "%s"' % plant.getDescription()
            print '        Record link:  %s' % plant.getRecordlink()
            print '        Update link:  %s' % plant.getUpdatelink()
    
  3. Parse the message received from the server. For example, given the module plantlistsub generated by generateDS.py, the following will parse the document and create instances the generated classes:
    root = plantlistsub.parseString(content)
    
  4. Extract data items from the constructed instances. The details of this depend on the XML document definition and the classes generated by generateDS.py. Here is an example:
    for plant in root.getPlant():
        print '    Plant # %s:' % plant.getIndex()
        print '        Name:         %s' % plant.getName()
        print '        Description:  "%s"' % plant.getDescription()
        print '        Record link:  %s' % plant.getRecordlink()
        print '        Update link:  %s' % plant.getUpdatelink()
    
  5. In order to POST an update to the server, first, update the instance using the set functions (e.g. setDescription below), then generate the content to be sent to the server. You can use the export function generated by generateDS.py. The example code below captures the content to be sent to the server by writing (exporting) the content to a stream that is an instance of StringIO. It then POSTs the content to the server.
    newdescrip = raw_input('New description: ')
    if newdescrip != '[quit]':
        root.setDescription(newdescrip)
        contentstream = StringIO.StringIO()
        root.export(contentstream, 0)
        content = contentstream.getvalue()
        contentstream.close()
        url = root.getSubmitlink()
        length = len(content)
        conn = httplib.HTTPConnection(host)
        req = conn.request('POST', url, content, headers)
        res = conn.getresponse()
        status = res.status
        content = res.read()
        conn.close()
    
Conclusion

Because of its flexibility and expressiveness, Python is a good fit for building web services and applications. Using Quixote is a particularly good fit for building them in a RESTful way.


Dave Kuhlman

shim
shim

 Py is committed to bringing you great Python Articles.

shim
shim


Home   Subscribe   Migration FAQ   Contact PyZine   Write for PyZine   ZopeMag   opensourcexperts.com  

Reproduction of material from any of PyZine's pages without prior written permission is strictly prohibited. Copyright 2003 - 2005 PyZine Zope/Plone hosting by Nidelven IT