Author: | Dave Kuhlman |
---|---|
Address: | dkuhlman@rexx.com http://www.rexx.com/~dkuhlman |
Revision: | 1.0b |
Date: | Jan. 2, 2004 |
Copyright: | Copyright (c) 2003 Dave Kuhlman. This documentation is covered by The MIT License: http://www.opensource.org/licenses/mit-license. |
Abstract
This document explains how to organize and implement a simple Quixote application. It is intended to enable the first-time Quixote user to start writing her/his Quixote application as quickly as possible.
The Quixote documentation is good on details, but a little lite on information of the type: "Put this here", "Put that there", "Next call this", etc. This document attempts to give you a way to get past a few early steps in writing your first Quixote application.
This document attempts to lead you through a sequence of first steps toward implementing a basic Quixote application. Those steps are:
We'll assume that your have installed Quixote and that you have set up your Web server to forward requests to your Quixote application. If you have not already done so, you can look at Notes on Using Quixote with SCGI for help.
Also note that parts of what follows assumes that your have enabled PTL. The above mentioned "Notes ..." document shows how to enable PTL for SCGI.
I'm going to describe a particular application organization. There is nothing magical or required about this organization. It's merely an easy way to get started. After you've learned and perhaps used this one, you will be able to change or extend it easily.
Our application will live in two Python packages:
Our only requirement is that this code can be imported and called from the user interface in myapp.ui. So, for example, we can put our code in:
In a single class in a single module in myapp.services, for example, myapp.services.mymodule.myclass. In this case, the user interface (in myapp.ui) might call this code as follows:
from myapp.services import services service = services.MyClass() results = service.service1(arg1, arg2)
In several classes in a single module in myapp.services, for example, myapp.services.mymodule.myclass1 and myapp.services.mymodule.myclass2. In this case, the user interface (in myapp.ui) might call this code as follows:
from myapp.services import services service = services.MyClass() otherService = services.MyOtherClass() results = service.service1(arg1, arg2) otherResults = otherService.anotherservice(arg3, arg4)
In several classes in separate modules in myapp.services, for example, myapp.services.mymodule1.myclass1 and myapp.services.mymodule2.myclass2. In this case, the user interface (in myapp.ui) might call this code as follows:
from myapp.services import firstModule, secondModule firstService = firstModule.SomeClass() secondService = secondModule.AnotherClass() firstResults = firstService.some_method(arg1, arg2) firstResults = secondService.another_method(arg3, arg4)
And so on ...
Our primary goals here are the following:
The following describes several alternative ways to do this. And, for more explanation about how this works, see: Quixote Programming Overview.
Suppose that you want your application to respond to requests specified by the following URIs:
Here is a simple way to process requests:
In __init__.py, place the following code:
# Map requests/URI to the user interface code. _q_exports = ['requestinfo', 'serverinfo'] from servicesui import ServicesUI # # Respond to the requestinfo request -- http://localhost/requestinfo # def requestinfo(request): service = ServicesUI() return service.do_requestinfo(request) # # Respond to the serverinfo request -- http://localhost/serverinfo # def serverinfo(request): service = ServicesUI() return service.do_serverinfo(request) # # Respond to a generic request -- http://localhost/ # def _q_index(request): service = ServicesUI() return service.index(request)
Explanation:
And in myapp/ui/servicesui.py, you could put something like the following:
class ServicesUI: def index [html] (self, request): header('Services') '<ul>\n' '<li><a href="requestinfo">request object info</a></li>\n' '<li><a href="serverinfo">Server info</a></li>\n' '</ul>\n' footer() def do_requestinfo [html] (self, request): header('requestinfo') o o o footer() def do_serverinfo [html] (self, request): header('requestinfo') o o o footer()
To add a new service, do the following:
This style is appropriate in the following situations:
Since the following example has a relatively small number of methods, the lookup style is not necessary, but as an example ...
This style looks up request handler methods in the UI class.
Put something like the following in myapp/ui/__init__.py:
# Map requests/URI to the user interface code. _q_exports = [] from servicesui import ServicesUI def _q_lookup(request, name): services = ServicesUI() meth = getattr(services, 'do_' + name, services.index) return meth(request)
Explanation:
And myapp/ui/servicesui.py could be the same as in the previous example.
In order to add a new request, we only need to add the appropriately name method to the class ServicesUI. Given the above _q_lookup function, that new method should have a name of the form "do_xxxx".
What to do:
Enable PTL -- The following must be done sometime before the module containing the PTL code is imported:
from quixote import enable_ptl enable_ptl()
If you are using SCGI/mod_scgi, you can put these lines of code in your driver script.
Name the file that implements the module with extension ".ptl".
Mark PTL functions and methods with either "[html]" or "[plain]" in the header. See the example below.
Import your PTL module as you would a normal Python module.
There is more explanation of PTL in PTL: Python Template Language.
A few notes and cautions:
Here is a sample that generates content to be returned to the Web browser/client:
def header [html] (subtitle): '<html>\n' '<head>\n' '<title>Quixote Test #2 -- %s</title>\n' % subtitle '</head>\n' '<body>\n' '<div align="center"><h1>Quixote Test #2 -- %s</h1></div>\n' % subtitle '<a href="/">menu</a>\n' '<hr/>\n' def footer [html] (): '<hr/>\n' '<p><a href="/">menu</a></p>\n' '<p>Generated on: %s</p>\n' % time.ctime() '<hr />\n' '</body>\n' '</html>\n' o o o class InfoUI: def do_serverinfo [html] (self, request): header('Server Information') '<ul>\n' ' <li>Server: %s</li>\n' % request.get_server() browserInfo = request.guess_browser_version() ' <li>Browser info: %s -- %s</li>\n' % (browserInfo[0], browserInfo[1]) ' <li>Path: %s</li>\n' % request.get_path() ' <li>Environment:\n' ' <ul>\n' for key, item in request.environ.items(): ' <li>%s: %s</li>\n' % (key, item) ' </ul>\n' ' </li>\n' '</ul>\n' footer()
Explanation:
Here we describe a simple way to get values from an HTML form. But, note that a more powerful and complete way to produce and process forms is described in the next sub-section: Quixote forms and widgets.
Suppose the HTTP request contains CGI variables from an HTML form. Here is how you can get the values of those variables:
value1 = request.get_form_var('arg1') value2 = request.get_form_var('arg2', 'no-value') '<p>value1: %s</p>\n' % value1 '<p>value2: %s</p>\n' % value2
Explanation:
The first argument is the name of the variable in the HTML form.
The second, optional argument is a default value, which is returned if the variable does not exist in the request. If you do not specify a default value and the variable does not exist, then None is returned.
A request/URI containing variables arg1 and arg2 might look something like the following:
http://thrush:8081/requestinfo?arg1=111&arg2=222
Quixote forms and widgets enable us to:
You can find more documentation on Quixote forms and widgets in Quixote Widget Classes.
A couple of notes about what follows:
Here is a basic sequence of actions that you can use when you need to repeatedly process and re-display a form.
Here is an example that creates several text entry fields, a set of radio buttons, and a submit button:
def do_plant_db [html] (self, request): plantName = widget.StringWidget('plant_name') plantDesc = widget.StringWidget('plant_desc') plantRating = widget.StringWidget('plant_rating') action = widget.RadiobuttonsWidget('command', value='View', allowed_values=['View', 'Add', 'Delete', 'Modify'], delim='</td>\n<td>') submit = widget.SubmitButtonWidget(value='Do it') o o o
Use the parse method to get the values for widgets in an HTTP request. For example:
def do_plant_db [html] (self, request): o o o if request.form: plantNameValue = plantName.parse(request) plantDescValue = plantDesc.parse(request) plantRatingValue = plantRating.parse(request) actionValue = action.parse(request) else: plantNameValue = '' plantDescValue = '' plantRatingValue = '' actionValue = 'View'
In order to generate the HTML content, call the render method for each widget. Here is some sample code. Remember that this code is from a PTL file:
def do_plant_db [html] (self, request): o o o '<form method="POST" action="plant_db">\n' '<table width="50%">' '<tr><td>Plant name:</td><td>' plantName.render(request) '</td></tr>\n<tr><td>Plant description:</td><td>' plantDesc.render(request) '</td></tr>\n<tr><td>Plant rating:</td><td>' plantRating.render(request) '</td></tr>\n' '</table>\n' '<table border="1" width="100%">\n' '<tr><td>' action.render(request) '</td></tr>\n' '</table>\n' '<p />\n<div align="center">' submit.render(request) '</div>\n' '</form>\n' o o o
When I first started using Quixote forms and widgets, I found it convenient to be able to render widgets and inspect the generated HTML outside of the Quixote/Web environment.
Here is a quick, simple Python script that you can run from the command line and which shows the content generated by your widgets:
#!/usr/bin/env python from quixote.form import widget from quixote import http_request request = http_request.HTTPRequest(sys.stdin, {}) print widget.StringWidget('f1').render(request) print '\n', widget.RadiobuttonsWidget('r1', value='two', allowed_values=['one', 'two', 'three']).render(request) value = """ Line #1 Line #2 Line #3 """ print '\n', widget.TextWidget('t1', value=value).render(request) print '\n', widget.CheckboxWidget('cb1', value='maybe').render(request) vals = [101, 102, 103] descs = ['First', 'Second', 'Third'] print '\n', widget.SingleSelectWidget('ss1', value=102, allowed_values=vals, descriptions=descs).render(request) print '\n', widget.HiddenWidget('h1', value='some secret stuff').render(request) print '\n', widget.FloatWidget('float1').render(request)
Explanation:
Suppose you want to sub-divide your request name space, in effect providing sub-categories of requests. For example:
http://myserver/info/requestinfo http://myserver/info/serverinfo
and:
http://myserver/service/qotd http://myserver/service/temperature http://myserver/service/plant_db
There are a variety of ways to do this. We'll combine several in a single example.
This example uses the explicit style shown above to handle "info" requests and the lookup style to handle "service" requests:
from servicesui import MenuUI, InfoUI, ServicesUI def _q_lookup(request, name): if name == 'info': handler = InfoHandler() return handler elif name == 'service': handler = ServiceHandler() return handler else: menu = MenuUI() return menu.index(request) class InfoHandler: _q_exports = ['requestinfo', 'serverinfo'] def requestinfo(self, request): info = InfoUI() return info.do_requestinfo(request) def serverinfo(self, request): info = InfoUI() return info.do_serverinfo(request) def _q_index(self, request): menu = MenuUI() return menu.index(request) class ServiceHandler: _q_exports = [] def _q_lookup(self, request, name): services = ServicesUI() meth = getattr(services, 'do_' + name, None) if meth: return meth(request) else: menu = MenuUI() return menu.index(request)
Explanation:
Once again, notice that we will be organizing the "user interface" and the application code that responds to the requests in this user interface. However, the logic in our services or model package does not need to follow that same organization or structure.
If there is a relational database in the back-end of your Web application, you are likely to want to maintain and reuse persistent database connections. Quixote makes this especially easy. But, it requires a bit of explanation.
First, I'm using the SCGI handler. I'm not sure whether what follows applies to other handlers such FastCGI, CGI, etc. More information on using the Quixote SCGI server is available at http://www.mems-exchange.org/software/scgi/ and http://www.mems-exchange.org/software/quixote/doc/web-server.html. I've also written a short how-to document on using Quixote with Apache and SCGI, which is available at http://www.rexx.com/~dkuhlman/quixote_scgi.html.
And, as you can see from the example below, I'm using the pyPgSQL implementation of the database interface to PostgreSQL, which is available at: http://pypgsql.sourceforge.net/. If you are using some other implementation of the Python DB API, you will have to tweak the sample code slightly.
A little explanation -- With the SCGI server handler, at least, child processes are created to handle multiple, concurrent requests. And, at most, one request is handled at one time (concurrently) in any one child process. Therefore, if we create one database connection in each child process, we are safe using that connection in the request handler executed in that child process, since only one request handler will be active at any time in any one process.
I've used this same technique to create and reuse an XML-RPC proxy and a SOAP proxy.
Below is an example of how to open, use, and close one database connection for each child process created by the SCGI server.
Here is a sample SCGI driver script:
#!/usr/bin/env python from scgi.quixote_handler import QuixoteHandler, main from quixote.publish import Publisher from pyPgSQL import PgSQL from quixote import enable_ptl enable_ptl() CONNECT_ARGS = 'localhost:5432:test:postgres:xxxxxxxx' class MyPublisher(Publisher): def __init__(self, root_namespace, config=None): Publisher.__init__(self, root_namespace, config) self.dbConnection = PgSQL.connect(CONNECT_ARGS) def __del__(self): self.dbConnection.close() def start_request(self, request): Publisher.start_request(self, request) request.dbConnection = self.dbConnection class MyAppHandler(QuixoteHandler): publisher_class = MyPublisher root_namespace = "test2.ui" prefix = "" if __name__ == '__main__': main(MyAppHandler)
Explanation:
Then in our application code, we can retrieve and use the connection. For example:
def do_plant_db [html] (self, request): o o o service = services.Services() dbDonnection = request.dbConnection msg, rowSet = service.db_query(dbConnection, actionValue, sortbyValue, plantNameValue, plantDescValue, plantRatingValue) o o o
Explanation:
And, here is an example of using the connection to perform an action on the database:
class Services: o o o def db_query(self, dbConnection, action, sortby, plantName, plantDesc, plantRating): cursor = dbConnection.cursor() if action == 'View': msg = 'Viewing' rowSet = self._db_retrieve(cursor, sortby) elif action == 'Add': cursor.execute("select * from Plant_DB where p_name = '%s'" % plantName) row = cursor.fetchone() if row: msg = 'Plant (%s) already exists.' % plantName else: sql = "insert into Plant_DB values ('%s', '%s', '%s')" % \ (plantName, plantDesc, plantRating) cursor.execute(sql) dbConnection.commit() msg = 'Added' rowSet = self._db_retrieve(cursor, sortby) elif action == 'Delete': sql = "select * from Plant_DB where p_name = '%s'" % plantName cursor.execute(sql) row = cursor.fetchone() if row: cursor.execute("delete from Plant_DB where p_name='%s'" % plantName) dbConnection.commit() msg = 'Plant (%s) deleted.' % plantName else: msg = 'Plant (%s) does not exist.' % plantName rowSet = self._db_retrieve(cursor, sortby) elif action == 'Modify': cursor.execute("select * from Plant_DB where p_name = '%s'" % plantName) row = cursor.fetchone() if row: sql = "update Plant_DB set p_desc='%s', p_rating='%s' where p_name='%s'" % \ (plantDesc, plantRating, plantName) cursor.execute(sql) dbConnection.commit() msg = 'Updated plant (%s).' % plantName else: msg = 'Plant (%s) does not exist.' % plantName rowSet = self._db_retrieve(cursor, sortby) else: rowSet = None cursor.close() return (msg, rowSet) def _db_retrieve(self, cursor, sortby): if sortby == 'Name': sql = "select * from Plant_DB order by p_name" else: sql = "select * from Plant_DB order by p_rating" cursor.execute(sql) rows = cursor.fetchall() return rows
The Web is an incredible and huge resource. It's also the "Mother of all REST applications".
In this section we will explore ways to treat the Web as a resource or as a feed for content to be filled into the Web pages generated by your own Web applications.
Retrieval -- We can use urllib from the Python standard library to retrieve Web pages.
Data extraction -- Then, we will use several techniques to extract data items from Web pages. Here are several important ones:
Here is the link to the document that describes how to do this in more detail: HTML Screen Scraping: A How-To Document.
http://www.mems-exchange.org/software/quixote/: The Quixote support Web site.
Quixote Programming Overview: This document explains how Quixote applications map requests to the code that responds to those requests.
PTL: Python Template Language: More information on PTL.
Quixote Widget Classes: More information on Quixote forms and HTML widgets.
Introducing Quixote: A Simple Link Display: Another tutorial to help you get up-to-speed with Quixote quickly.
White Paper: Quixote for Web Development: This white paper has a helpful graphic that shows the relationship between (1) the HTTP client, (2) Apache and the mod_scgi, and (3) the Quixote application receiving its requests from mod_scgi.
Python home: The Python Web site.
pyPgSQL: pyPgSQL is a package that provides a Python DB-API 2.0 compliant interface to PostgreSQL databases.
SIG on Tabular Databases in Python: Provides specification of and implementations of the Python database API.
HTML Screen Scraping: A How-To Document: How to treat the Web as a resource.
Notes on Using Quixote with SCGI: This document explains how to install, set-up, and run SCGI support for Quixote.
Docutils: Python Documentation Utilities -- This document was formatted with Docutils.