REST for AOLserver, PyWX, and Quixote

Dave Kuhlman

http://www.rexx.com/~dkuhlman
Email:

 
Front Matter

Copyright (c) 2003 Dave Kuhlman

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Abstract:

This document provides an introduction to REST plus suggestions and instructions on how to deliver RESTful applications on top of AOLserver, PyWX, and Quixote, possibly with help of a relational database such as PostgreSQL



Contents

 
1 Introduction

REST is an architecture 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, and Quixote. Much of this document should be meaningful and useful for those who intend to develop using Quixote on top of other Web servers, e.g. Apache.

2 REST -- The Representation State Transfer Architecture

2.1 A brief description of REST

What is REST? -- REST is a set of guidelines for constructing Web applications. Those guidelines are mostly restrictions. This (restricting our options) is appropriate because one of our 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.

A central document on REST is Architectural Styles and the Design of Network-based Software Architectures.

And, here are several other links that will lead you to plenty of additional information on REST:

And, here are some of those guidelines or restrictions:

And here are some less important guidelines:

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. Let me try to show why.

3 REST and PyWX

3.1 Why do REST on AOLserver+PyWX?

In a number of respects, PyWX is an especially strong platform for developing applications guided by the REST architecture. In particular:

Summary -- PyWX (and Python) provide strengths in both (1) access to resources and (2) formatting representations.

3.2 How to Do REST on PyWX

This section provides some vague guidelines for implementing RESTful applications on top of PyWX (and possibly Quixote). The guidelines are vague for at least two reasons:

With those reservations, here are a few guidelines:

4 REST and Quixote

4.1 Why do REST on Quixote?

Quixote provides (at least) two capabilities that are of value in developing REST servers:

4.2 How to do REST on Quixote

The simple answer is to write a Quixote application, but to eliminate the dependence on session information. (All state information is carried on the client, not the server, remember.)

A more complete answer would involve the following steps, most of which are standard REST strategy:

A note about PTL/templates vs. vanilla Python functions -- 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, also. I'd look to using PTL and templates whenever (1) there is a high ratio of boilerplate to calculated text or (2) 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. For more on this, see the comments on refactoring in Greg Ward's article: http://www.linuxjournal.com/article.php?sid=6178.

5 REST Quixote/PyWX Examples

The following server-side examples are available in rest_examples.zip. These examples have been tested with AOLserver 3.5.1, PyWX 1.0b2, and Quixote 0.5.1.

Handler scripts are included for AOLserver and PyWX. Look under directory handlers. These won't mean much to you if you do not use AOLserver.

If you attempt to use these examples with Apache, you will most likely need to create your own Quixote driver scripts.

This section provides explanation and commentary on these examples.

5.1 REST Quixote example -- DrillDown

DrillDown is a standard REST pattern. This example shows a list of 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.

5.2 REST Quixote example -- ListAndDetail

ListAndDetail is another ``drill down'' example. This one retrieves its list of items from a relational database.

This example shows the combination of features from Quixote and PyWX:

The database itself is stored in PostgreSQL. The plants database table has two columns: (1) plant name and (2) plant description.

Here is the implementation along with some explanation and commentary.

6 REST Client Development

This section describes a small amount of support for developing REST clients in Python.

For the purposes of this discussion, a REST client:

What to build upon:

Assumptions:

Client development strategy -- Here are a few steps that you can follow::

  1. For each XML document to be received from the server, obtain the XML Schema definition of that document. Pass the XML Schema through generateDS.py to produce a super-class module and a sub-class module.

  2. For each desired resource, implement a function/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 document -- 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 from it (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/form 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 POST's 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()
    

6.1 A client example

Here is a more complete (though still trivial) example which combines the fragments above into a client program.

#!/usr/bin/env python

import sys
import cmd
import httplib, socket
import updateformsub
import plantlistsub
import StringIO


HELP_TEXT = """\
Commands:
    showlist      -- Show list of plant names.
    show <name>   -- Show plant <name>.
    update <name> -- Update description for plant <name>.
    quit          -- Exit.
    help          -- Show this help.
"""

class RestCmd(cmd.Cmd):
    prompt = '>>> '
    intro = "Type '?' for help."
    def __init__(self):
        cmd.Cmd.__init__(self)

    def do_help(self, line):
        print HELP_TEXT

    def do_quit(self, line):
        sys.exit(1)

    def emptyline(self):
        print "Enter a command or type '?' for help."

    def request_http(self, function, host, url, params, headers):
        try:
            conn = httplib.HTTPConnection(host)
            req = conn.request(function, url, params, headers)
        except socket.error:
            print "Can't connect to server: %s" % host
            return
        res = conn.getresponse()
        status = res.status
        content = None
        if status == 200:
            content = res.read()
        conn.close()
        return (status, content)

    def do_showlist(self, line):
        host = 'warbler:8081'
        url = '/UpdateRecord/recordlist/'
        headers = {'Accept': 'text/xml'}
        params = {}
        status, content = self.request_http('GET', host, url, params, headers)
        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()

    def do_show(self, line):
        name = None
        if line:
            args = line.split()
            if len(args) > 0:
                name = args[0]
        if not name:
            print '*** Missing plant name.'
            return
        host = 'warbler:8081'
        url = '/UpdateRecord/updateform/%s' % name
        headers = {'Accept': 'text/xml'}
        params = {}
        status, content = self.request_http('GET', host, url, params, headers)
        if status != 200:
            print "Can't get description for name: %s" % name
        root = updateformsub.parseString(content)
        descrip = root.getDescription()
        print 'Name:        %s' % name
        print 'Description: %s' % descrip

    def do_update(self, line):
        name = None
        if line:
            args = line.split()
            if len(args) > 0:
                name = args[0]
        if not name:
            print '*** Missing plant name.'
            return
        host = 'warbler:8081'
        url = '/UpdateRecord/updateform/%s' % name
        headers = {'Accept': 'text/xml'}
        params = {}
        status, content = self.request_http('GET', host, url, params, headers)
        if status != 200:
            print "Can't get description for name: %s" % name
        root = updateformsub.parseString(content)
        olddescrip = root.getDescription()
        print 'Old description: %s' % olddescrip
        print 'Enter new description ("[quit]" to skip update).'
        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()
            status, content = self.request_http('POST', host, url, \
                content, headers)
            print 'status: %d' % status


USAGE_TEXT = """
Usage: python client1.py
"""

def usage():
    print USAGE_TEXT
    sys.exit(-1)


def main():
    args = sys.argv[1:]
    if len(args) != 0:
        usage()
    interp = RestCmd()
    interp.cmdloop()


if __name__ == '__main__':
    main()
    #import pdb
    #pdb.run('main()')

Comments and explanation:

 
End Matter

Acknowledgements and Thanks

Titus Brown, Brent Fulgham, Michael Haggerty -- The developers of PyWX. Titus is currently actively supporting PyWX and makes this possible.

And, thanks to the implementors of Quixote for producing an exceptionally usable application server that is so well suited for REST.

See Also:

The RESTWiki
for more information and links on REST

The main AOLserver Web site
for more information on AOLserver

The PyWX Web site
for more information on PyWX

Quixote home page
for more information on the Quixote Python Web application framework

The PostgreSQL Web site
for information on PostgreSQL

Beginner's How-to for AOLserver, PyWX, and PostgreSQL
for more information on using AOLserver, PyWX, PostgreSQL and Quixote

generateDS.py -- Generate Python data structures from XML Schema
for more information on generateDS.py

About this document ...

REST for AOLserver, PyWX, and Quixote

This document was generated using the LaTeX2HTML translator.

LaTeX2HTML is Copyright © 1993, 1994, 1995, 1996, 1997, Nikos Drakos, Computer Based Learning Unit, University of Leeds, and Copyright © 1997, 1998, Ross Moore, Mathematics Department, Macquarie University, Sydney.

The application of LaTeX2HTML to the Python documentation has been heavily tailored by Fred L. Drake, Jr. Original navigation icons were contributed by Christopher Petrilli.