[Python-modules-commits] r532 - in /packages/quixote1: ./ branches/ branches/upstream/ branches/upstream/current/ branches/upstream/current/demo/ branches/upstream/current/doc/ branches/upstream/current/form/ branches/upstream/current/form2/ branches/upstream/current/server/ branches/upstream/current/src/ branches/upstream/current/test/ tags/

santiago at users.alioth.debian.org santiago at users.alioth.debian.org
Mon May 8 19:18:13 UTC 2006


Author: santiago
Date: Mon May  8 19:16:16 2006
New Revision: 532

URL: http://svn.debian.org/wsvn/python-modules/?sc=1&rev=532
Log:
[svn-inject] Installing original source of quixote1

Added:
    packages/quixote1/
    packages/quixote1/branches/
    packages/quixote1/branches/upstream/
    packages/quixote1/branches/upstream/current/
    packages/quixote1/branches/upstream/current/ACKS
    packages/quixote1/branches/upstream/current/CHANGES
    packages/quixote1/branches/upstream/current/LICENSE
    packages/quixote1/branches/upstream/current/MANIFEST
    packages/quixote1/branches/upstream/current/MANIFEST.in
    packages/quixote1/branches/upstream/current/PKG-INFO
    packages/quixote1/branches/upstream/current/README
    packages/quixote1/branches/upstream/current/TODO
    packages/quixote1/branches/upstream/current/__init__.py
    packages/quixote1/branches/upstream/current/_py_htmltext.py
    packages/quixote1/branches/upstream/current/config.py
    packages/quixote1/branches/upstream/current/demo/
    packages/quixote1/branches/upstream/current/demo/__init__.py
    packages/quixote1/branches/upstream/current/demo/demo.cgi   (with props)
    packages/quixote1/branches/upstream/current/demo/demo.conf
    packages/quixote1/branches/upstream/current/demo/demo_scgi.py   (with props)
    packages/quixote1/branches/upstream/current/demo/demo_scgi.sh   (with props)
    packages/quixote1/branches/upstream/current/demo/forms.ptl
    packages/quixote1/branches/upstream/current/demo/integer_ui.py
    packages/quixote1/branches/upstream/current/demo/pages.ptl
    packages/quixote1/branches/upstream/current/demo/run_cgi.py
    packages/quixote1/branches/upstream/current/demo/session.ptl
    packages/quixote1/branches/upstream/current/demo/session_demo.cgi   (with props)
    packages/quixote1/branches/upstream/current/demo/upload.cgi   (with props)
    packages/quixote1/branches/upstream/current/demo/widgets.ptl
    packages/quixote1/branches/upstream/current/doc/
    packages/quixote1/branches/upstream/current/doc/INSTALL.html
    packages/quixote1/branches/upstream/current/doc/INSTALL.txt
    packages/quixote1/branches/upstream/current/doc/Makefile
    packages/quixote1/branches/upstream/current/doc/PTL.html
    packages/quixote1/branches/upstream/current/doc/PTL.txt
    packages/quixote1/branches/upstream/current/doc/ZPL.txt
    packages/quixote1/branches/upstream/current/doc/default.css
    packages/quixote1/branches/upstream/current/doc/demo.html
    packages/quixote1/branches/upstream/current/doc/demo.txt
    packages/quixote1/branches/upstream/current/doc/form2conversion.html
    packages/quixote1/branches/upstream/current/doc/form2conversion.txt
    packages/quixote1/branches/upstream/current/doc/multi-threaded.html
    packages/quixote1/branches/upstream/current/doc/multi-threaded.txt
    packages/quixote1/branches/upstream/current/doc/programming.html
    packages/quixote1/branches/upstream/current/doc/programming.txt
    packages/quixote1/branches/upstream/current/doc/session-mgmt.html
    packages/quixote1/branches/upstream/current/doc/session-mgmt.txt
    packages/quixote1/branches/upstream/current/doc/static-files.html
    packages/quixote1/branches/upstream/current/doc/static-files.txt
    packages/quixote1/branches/upstream/current/doc/upgrading.html
    packages/quixote1/branches/upstream/current/doc/upgrading.txt
    packages/quixote1/branches/upstream/current/doc/upload.html
    packages/quixote1/branches/upstream/current/doc/upload.txt
    packages/quixote1/branches/upstream/current/doc/web-server.html
    packages/quixote1/branches/upstream/current/doc/web-server.txt
    packages/quixote1/branches/upstream/current/doc/web-services.html
    packages/quixote1/branches/upstream/current/doc/web-services.txt
    packages/quixote1/branches/upstream/current/doc/widgets.html
    packages/quixote1/branches/upstream/current/doc/widgets.txt
    packages/quixote1/branches/upstream/current/errors.py
    packages/quixote1/branches/upstream/current/fcgi.py
    packages/quixote1/branches/upstream/current/form/
    packages/quixote1/branches/upstream/current/form/__init__.py
    packages/quixote1/branches/upstream/current/form/form.py
    packages/quixote1/branches/upstream/current/form/widget.py
    packages/quixote1/branches/upstream/current/form2/
    packages/quixote1/branches/upstream/current/form2/__init__.py
    packages/quixote1/branches/upstream/current/form2/compatibility.py
    packages/quixote1/branches/upstream/current/form2/css.py
    packages/quixote1/branches/upstream/current/form2/form.py
    packages/quixote1/branches/upstream/current/form2/widget.py
    packages/quixote1/branches/upstream/current/html.py
    packages/quixote1/branches/upstream/current/http_request.py
    packages/quixote1/branches/upstream/current/http_response.py
    packages/quixote1/branches/upstream/current/mod_python_handler.py
    packages/quixote1/branches/upstream/current/ptl_compile.py
    packages/quixote1/branches/upstream/current/ptl_import.py
    packages/quixote1/branches/upstream/current/ptlc_dump.py
    packages/quixote1/branches/upstream/current/publish.py
    packages/quixote1/branches/upstream/current/qx_distutils.py
    packages/quixote1/branches/upstream/current/sendmail.py
    packages/quixote1/branches/upstream/current/server/
    packages/quixote1/branches/upstream/current/server/__init__.py
    packages/quixote1/branches/upstream/current/server/medusa_http.py
    packages/quixote1/branches/upstream/current/server/twisted_http.py
    packages/quixote1/branches/upstream/current/session.py
    packages/quixote1/branches/upstream/current/setup.py
    packages/quixote1/branches/upstream/current/src/
    packages/quixote1/branches/upstream/current/src/Makefile
    packages/quixote1/branches/upstream/current/src/_c_htmltext.c
    packages/quixote1/branches/upstream/current/src/cimport.c
    packages/quixote1/branches/upstream/current/src/setup.py
    packages/quixote1/branches/upstream/current/test/
    packages/quixote1/branches/upstream/current/test/__init__.py
    packages/quixote1/branches/upstream/current/test/ua_test.py
    packages/quixote1/branches/upstream/current/test/utest_html.py   (with props)
    packages/quixote1/branches/upstream/current/upload.py
    packages/quixote1/branches/upstream/current/util.py
    packages/quixote1/tags/

Added: packages/quixote1/branches/upstream/current/ACKS
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/ACKS?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/ACKS (added)
+++ packages/quixote1/branches/upstream/current/ACKS Mon May  8 19:16:16 2006
@@ -1,0 +1,44 @@
+Acknowledgements
+================
+
+The Quixote developer team would like to thank everybody who
+contributed in any way, with code, hints, bug reports, ideas, moral
+support, endorsement, or even complaints.  Listed in alphabetical
+order:
+
+David Ascher
+Anton Benard
+Titus Brown
+Oleg Broytmann
+David M. Cooke
+Jonathan Corbet
+Herman Cuppens
+Toby Dickenson
+Ray Drew
+Jim Dukarm
+Quinn Dunkan
+Robin Dunn (original author of fcgi.py)
+Jon Dyte
+David Edwards
+Graham Fawcett
+Jim Fulton (original author of the *Request and *Response classes)
+David Goodger
+Neal M. Holtz
+Kaweh Kazemi
+Shahms E. King
+A.M. Kuchling    <amk at amk.ca>
+Erno Kuusela
+Nicola Larosa
+Hamish Lawson
+Patrick K. O'Brien
+Brendan T O'Connor
+Ed Overly
+Paul Richardson
+Jeff Rush
+Neil Schemenauer <nascheme at mems-exchange.org>
+Jason Sibre
+Gregory P. Smith
+Mikhail Sobolev
+Johann Visagie
+Greg Ward        <gward at mems-exchange.org>
+The whole gang at the Zope Corporation

Added: packages/quixote1/branches/upstream/current/CHANGES
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/CHANGES?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/CHANGES (added)
+++ packages/quixote1/branches/upstream/current/CHANGES Mon May  8 19:16:16 2006
@@ -1,0 +1,902 @@
+1.2 (2004-10-06) r25277:
+
+  * Fix Medusa server bug introduced in 1.1.  HTTP headers must have
+    "HTTP_" prepended to them.
+
+
+1.1 (2004-10-05) r25259:
+
+  * Fix example Apache directive for SCGI demo.
+
+  * On Windows, put both stdin and stdout in binary mode.
+
+  * Add 'buffered' flag to HTTPResponse (defaulting to True).
+
+  * Support for the Python 2.4 'compiler' package.
+
+  * Add HTTPRequest.get_scheme() method.
+
+  * Use os.urandom() if it is available.  Move randbytes() function
+    to quixote.util module.
+
+  * Convert tests to Sancho's utest framework.
+
+  * Add 'index_filenames' keyword option to StaticDirectory.
+
+  Medusa server:
+
+    * Unquote the PATH_INFO environment variable.
+
+    * Simplify propagation of HTTP headers.
+
+  Form2 library:
+
+    * Don't parse empty POSTs.
+
+    * Fix a subtle bug in the multiple select widget.  If nothing
+      is selected then the value should be None, not [None].
+
+    * Add Widget.set_title().
+
+
+1.0 (2004-07-01) r24581:
+
+  * No changes from 1.0c1.
+
+
+1.0c1 (2004-06-15) r24468:
+
+  * Fix some bugs in form2 library and improve the default look.
+
+  * Add a 'content_type' attribute to Upload instances.
+
+
+1.0b2 (2004-05-20) r24301:
+
+  * Pass along more request headers when using twisted_http.py.
+
+  * Add filter_output() hook (as suggested by Thomas Guettler).
+
+  * Use plain text for the content of redirections. It's extremely
+    unlikely that they will be seen anyhow. Use a modern DOCTYPE
+    declaration for other pages that Quixote itself generates.
+
+  * Fix code in upload.py that handles filename collisions.
+
+  * Use a twisted producer for twisted_server.py (allows Stream
+    responses to be efficient).  Use twisted reactor instead of
+    Application.  Thanks to Jason Sibre.
+
+  Form2 library changes:
+
+    * Change how form2 widgets render themselves.  The new approach
+      makes it easier to build composite widgets and also provides
+      more freedom to control the layout using CSS.  Unfortunately,
+      the forms will not look nice unless some CSS rules are provided.
+
+    * Add recursive .has_error() and .clear_error() to widgets.
+
+    * Ensure that has_errors() completely parse all the widgets.
+
+    * Fix .clear_errors() for form2 Form. Errors can only be reliably
+      cleared after the widget has parsed
+
+    * Fix OptionSelect to always return a value from the options list.
+
+    * Fix some bugs in WidgetList.
+
+    * Check form token validity if tokens are enabled.
+
+
+1.0b1 (2004-04-12) r23961:
+
+  * Remove an underscore from the Session attributes __remote_address,
+    __creation_time, and __access_time.  Note that if you have pickled
+    Session objects you will need to upgrade them somehow.
+
+  * Use a simpler version of ptl_compile.parse() if using Python >= 2.3.
+    Unfortunately, the hacks are still needed for older versions.
+
+  * Change the behavior of session cookies if SESSION_COOKIE_PATH is
+    unset.  The old behavior was to not set the 'path' attribute of the
+    session cookie.  The new behavior is to use SCRIPT_NAME.  Also, set
+    the 'path' attribute when revoking the session cookie.  Some
+    browsers treat it as a separate cookie if it has a different path.
+
+  * Don't try to compress stream responses.
+
+  * Refactor PublishError exception handling.  Requests will now be
+    logged even if they raise an exception.  This also allows some
+    simplification to the Medusa and Twisted server code.
+
+  * Make HTTPRequest.get_server() return HTTP_HOST if it is available
+    rather than using SERVER_NAME and SERVER_PORT.
+
+  * If "Host" header is present, use it to set SERVER_NAME and
+    SERVER_PORT.
+
+  * Make HTTPRequest.get_url() escape unsafe characters in the path part
+    of the URL.
+
+  * Use a simple counter instead of using the 'random' module when
+    trying to generate unique names for uploaded files.  
+
+  * Allow arbitrary mapping objects to be used as the right hand operand
+    when formating htmltext objects (i.e. as an argument to the
+    htmltext.__mod__ method).  The _c_htmltext implemention used to only
+    allow 'dict' instances.  Change _c_htmltext to allow any mapping.
+    Also, instead of accessing every item in the mapping, use
+    PyObject_GetItem() (aka __getitem__) to retrieve the values as they
+    are required.  That matches the behavior of PyString_Format().
+
+  * Don't set DEBUG_LOG in demo.conf.  Extra logs files are confusing
+    when people are starting out.
+
+  * Significant form2 package changes.  Remove FormComponent and related
+    machinery.  Allow widgets to control how they are rendered within
+    forms (by overriding the form_render() method.  Reorganize rendering
+    code in the form module so that it is easier to subclass form and
+    provide a different look.  Change the parsing behavior of widgets.
+    They no longer parse themselves when __init__ is called.  Instead,
+    each widget has a parse() method that calls _parse(), if it hasn't
+    been called already, and returns the widgets value.  Allow
+    Widget._parse() to raise WidgetValueError in order to signal a
+    parsing error.  Add a CompositeWidget class.  Remove
+    Widget.get_value() since parse() seems to be sufficient.
+
+  * Add HTTPS header when running under twisted_http.py.
+
+  * Add cimport as a standard Quixote extension.
+
+  * Disable FIX_TRAILING_SLASH in demo.conf.  It can be confusing when
+    trying to get the demo running.
+
+  * Allow _q_exports to contain mappings of external names to internal
+    names.
+
+  * Twisted does not unquote the path so make twisted_http.py do it.
+
+  * Implement error logging for mod_python handler.
+
+
+0.7a3 (2003-12-03) r23260:
+
+  * Improve init for ListWidget. The value of added_elements_widget
+    needs to be set in the request if the list widget was added to the
+    form after the first form submission.
+
+  * Rename Form.WIDGET_ROW_CLASS to COMPONENT_CLASS, since that's
+    what it really is.  Make it an instance attribute as well as a class
+    attribute, so it can be overridden at form construction time.
+
+  * Remove 'allowed_values' and 'descriptions' from
+    RadiobuttonsWidget.__init__.
+
+  * Make htmltag() more efficient by avoiding repeated string
+    concatenation.
+
+  * Establish a pattern for way to specify html attributes for form2
+    widget constructors.  The attributes may be provided in a dictionary
+    using the 'attr' keyword.  An html attribute 'foo' may also be
+    specified in the widget constructor using using keyword 'foo' or
+    'foo_'.
+
+
+0.7a2 (2003-11-11) r23130:
+
+  * Implement publish_fcgi().  Don't use fcgi module for publish_cgi()
+    since it's not completely portable.  If you want to use FastCGI, you
+    need to call publish_fcgi() instead of publish_cgi().  Don't import
+    fcgi unless it's needed (since it does stuff at import time).
+
+  * Allow StaticFile class to be overridden.  Also, make it easier to
+    subclass StaticDirectory.
+
+  * When loading a PTL module, check if it exists in sys.modules.  If it
+    does, exec the code in that namespace rather than creating a new
+    module.  This is necessary to make reload() work.
+
+  * Move HTTPUploadRequest import out of .create_request().
+
+  * Make HTTPRequest.guess_browser_version() more robust.
+
+  * Remove 'allowed_values' and 'description' parameters from form2
+    SelectWidget.
+
+  * Remove unused 'request' parameter from render() method of
+    form2 RadiobuttonsWidget and OptionSelectWidget.
+
+  * Improve form2 WidgetRow docstring.  Don't use apply().
+
+  * Fix 'required' check in form2 WidgetRow.
+
+  * Set the nb_inplace_add slot of TemplateIO types instead of
+    sq_inplace_concat.  nb_inplace_add takes precedence over __radd__
+    on instance types while sq_inplace_concat does not.
+
+  * Fix typo in repr of _c_htmltext TemplateIO.
+
+  * Use the quixote.sendmail module to send traceback email rather than
+    calling /usr/sbin/sendmail.
+
+  * Allow negative values to be passed to HTTPRequest.get_path().
+
+  * Add WidgetRow to form2.__init__.
+
+  * Implement add_button() and add_reset() methods for form2.Form and
+    rename Form._render_submit_buttons() to _render_button_widgets().
+
+
+0.7a1 (2003-10-09) r22720:
+
+  * By default, don't build _c_htmltext on Win32.
+
+  * In medusa_http.py, propagate 'GATEWAY_INTERFACE', 'SCRIPT_FILENAME',
+    and HTTP headers.
+
+  * Add HTTPRequest.get_method().
+
+  * Add support for keyword attributes on string widget.
+
+  * Add a CollapsibleListWidget.
+
+  * Allow HTTPResponse.body to be a Stream object as well as a string.
+    Stream objects are useful if the body of the response is very large.
+
+  * Change StaticFile to return Stream objects.  Also, fix a minor bug in
+    StaticDirectory (_q_lookup should return a namespace).
+
+  * Improve installation instructions.
+
+  * Add 'Redirector' helper class.
+
+  * In publish.py, call _q_lookup(request, "") if _q_index does not exist
+    and _q_lookup does exist.
+
+  * In medusa_http.py, strip fragments from URLs (some versions of MSIE
+    incorrectly send them at certain times; for example, a redirect
+    includes them).  Also, make REQUEST_URI contain the URI, not just the
+    path.
+
+  * In ptl_import, explicitly close the .ptlc file.  This fixes a bug
+    where a subsequent import may go for this .ptlc file and find it
+    incomplete.
+
+  * Add css_class keyword to htmltag so that class attributes can be
+    specified easily.
+
+  * Implement next generation form framework (currently called 'form2').
+
+
+0.6.1 (2003-07-14) r22004:
+
+  * Make Form.add_widget() return the widget.
+
+  * Allow the "Expires" header to be suppressed by setting the 'cache'
+    attribute of HTTPResponse to None.
+
+  * Use title case for header names added by HTTPResponse.set_headers().
+    Clients are not supposed to care about case but it's better to be
+    conservative.
+
+  * Catch IOError exceptions raised when writing the response and log a
+    message (rather than exiting with a traceback).
+
+  * Fix bug regarding _q_exception_handler.  namespace_stack needs to be
+    updated while the traversal is occuring.  Thanks to Jason Sibre for
+    spotting it and for the fix.
+
+  * Add If-Modified-Since support for StaticFile objects.  Also, don't
+    set the Expires header by default.  Instead, set the Last-Modified
+    header based on the modification time of the file.  The Expires
+    header can be enabled by providing a value for the 'cache_time'
+    argument.
+
+
+0.6 final (2003-04-30) r21480:
+
+  * Add a 'pass' to setup.py to make it easier to comment out the C
+    extension.
+
+  * Simplify 'From:' header in traceback e-mails.
+
+        
+0.6b6 (2003-04-15):
+
+  * Rename _q_getname() to _q_lookup().  The name '_q_getname' is still
+    supported, but will log a warning whenever it's encountered.
+    This change will require users to modify their applications.
+
+  * quixote.form.form has been translated from PTL to Python, meaning 
+    that you can now use the form framework without enabling PTL.
+    (Original suggestion by Jim Dukarm, who also provided a patch 
+    that underwent considerable tweaking.)
+
+  * Fix generation of temporary filenames in upload.py: filename
+    collisions should be impossible now.
+
+  * In medusa_http.py, convert HTTP headers into title case before
+    passing them to Medusa, fixing duplicate Content-length headers.  
+    (Fix by Graham Fawcett.)
+
+  * Added quixote.server.twisted_http, which serves a Quixote application
+    using the Twisted event-driven framework (www.twistedmatrix.com).
+    Contributed by Graham Fawcett.  We don't use this code ourselves,
+    but patches and bug fixes from Twisted users will be gratefully
+    accepted.
+
+
+0.6b5 (2003-03-13):
+
+  * Fix incorrect parameter name for _traverse_url()
+
+
+0.6b4 (2003-03-13):
+
+  * The C version of the htmltext type is now compiled by default;
+    you can edit setup.py manually to disable it.
+
+  * StaticDirectory's list_folder argument renamed to list_directory.
+
+  * StaticFile now supports the HTTP encoding for files.  
+    (Absence of this feature noted by Jim Dukarm.)
+
+  * If Quixote looks for _q_index() in a namespace and doesn't find
+    it, it raises AccessError (resulting in an HTTP 403 Forbidden error)
+    rather than failing with an ImportError.  A minor side effect of
+    this change: Quixote will never attempt to import a module named
+    '_q_index', nor will it pass '_q_index' to any _q_resolve() function.
+    We don't expect this to be a backward compatibility problem .
+
+  * Factored out the traverse_url() and get_component() method 
+    from the Publisher class.  
+
+  * Documented _q_exception_handler().
+
+
+0.6b3 (2003-03-07):
+
+  * Fixed errors in demo_scgi.py. (David M. Cooke)
+
+  * Avoid using True/False and enable nested scopes in _py_htmltext.py 
+    for Python 2.1 compatibility. (Noted by Jeff Bauer)
+
+    Note that this means HTML templates will not work with Python 2.0
+    unless you compile the C extension.
+
+  * Added StaticFile and StaticDirectory classes to quixote.util.
+    Consult doc/static-files.txt for examples.  (Contributed and
+    documented by Hamish Lawson.)
+
+
+0.6b2 (2003-01-27):
+
+  * Added a new hook, _q_resolve(), that can be used to delay 
+    importing modules until they're actually accessed.  Consult
+    doc/programming.txt for an explanation.  (Original
+    suggestion and patch by Jon Corbet.  In the process of adding it,
+    Publisher.get_component() was rearranged to clarify the logic.)
+
+  * Fixed the Medusa HTTP server to work with HTML templates (David M. Cooke)
+    and to call finish_failed_request (pointed out by Graham Fawcett).
+
+  * Added HTTP_USER_AGENT and ACCEPT_ENCODING to Medusa HTTP server. 
+    (Graham Fawcett)
+
+  * Fixed _c_htmltext.c to compile on Windows.  (Graham Fawcett)
+
+  * Fixed two bugs in _c_htmltext.c found by code review,
+    and one bug in _py_htmltext.py found by Nicola Larosa.  
+    (Neil Schemenauer)
+
+  * Added a page to the demo that dumps the contents of the HTTPRequest.
+
+  * Made upload.py write out HTTP upload data in binary mode, so
+    binary content-types work correctly on Windows.  (Graham Fawcett)
+
+  * Added classifiers for use with Python 2.3's "register" command.
+
+
+0.6b1 (2003-01-09):
+
+  * Merged form/form.py and form/form_templates.ptl into
+    form/form.ptl. (This means that you should completely remove (or
+    rename) your old Quixote installation directory *before* installing
+    0.6, or the old form/form.py will shadow the new form.ptl.)
+
+  * A new and preferred syntax for declaring PTL templates has been added.
+    Instead of 'template func(): ...', the new form is 'def func [plain] ()'.
+
+    This uses a notation that's been suggested for adding type
+    information to Python functions.  The Emacs Python mode already handles 
+    this properly, and it may be more compatible with future versions of 
+    Python.
+
+    The 'template' keyword is still supported, but we encourage you 
+    to switch to the new syntax when you get a chance.
+
+  * Quixote now supports a new kind of template that automatically performs
+    HTML escaping.  Here's an example.  (Notice that the '[plain]'
+    annotation is changed to '[html]' to enable this feature.)
+
+        def header [html] (title):
+            "<title>%s</title>" % title
+
+    If the 'title' argument is something like "R&D", it will    
+    automatically be converted to "R&amp;D" following the rules 
+    for escaping HTML special characters.  The aim is to avoid cross-site 
+    scripting attacks by removing the need for the programmer to remember
+    to HTML-escape unsafe text, instead relying on Quixote to 
+    escape text where necessary.
+
+    See doc/PTL.txt for more information about how this works.
+
+    This escaping is implemented using a 'htmltext' class implemented in 
+    Python, and is currently in production use on our web site.
+
+  * An experimental C implementation of the 'htmltext' type is also
+    included; it hasn't been put into production use yet.  Edit 
+    setup.py and uncomment the appropriate line if you want to try the
+    C implementation.
+    
+  * The form framework now uses automatic HTML escaping.  This
+    means that applications using the form framework will have 
+    to either be changed to use automatic HTML escaping themselves, 
+    or to use str() to convert 'htmltext' instances back to Python
+    strings.
+    
+    See doc/upgrading.txt for more information.
+
+  * Make Quixote a bit more friendly to multi-threaded applications
+    by allowing multiple simultaneous requests (patch by Titus Brown).
+
+  * Make util.xmlrpc() return an HTTP 405 Method Not Allowed error
+    if the method isn't a POST.
+
+  * Added demo/run_cgi.py, a script that makes it easy to write one
+    file CGI applications that use Quixote.  See the comments at the 
+    top of the demo/run_cgi.py file for instructions.
+
+
+0.5.1 (2002-10-08):
+
+  * (incompatible change for anyone doing HTTP upload with Quixote)
+    Improved support for HTTP upload requests: any HTTP request with
+    a Content-Type of "multipart/form-data" -- which is generally only
+    used for uploads -- is now represented by HTTPUploadRequest, a
+    subclass of HTTPRequest, and the uploaded files themselves are
+    represented by Upload objects.  See doc/upload.txt for details.
+
+  * (possible incompatible changes for anyone subclassing Publisher,
+    or using it for custom purposes)
+    Various rearrangements and refactoring in the Publisher class.
+    Added create_request() method, which takes responsibility for
+    creating the HTTPRequest (or HTTPUploadRequest) object away
+    from parse_request().  As a consequence, the signature of
+    parse_request() has changed.  Added process_request() method.
+    Changed publish() so it catches exceptions coming from either
+    parse_request() or process_request().  Consult the source code
+    (publish.py) for details.
+
+  * A new subpackage, quixote.server, is intended for code that
+    publishes a Quixote application through HTTP, making it possible
+    to run Quixote applications without having to configure Apache or
+    some other full-blown Web server.  Right now there's only an
+    implementation on top of Medusa; contributions of support for
+    Python's BaseHTTPServer, Twisted, or other frameworks would be
+    welcome.
+
+  * Modified SessionManager.maintain_session() so it explicitly removes
+    a session if that session used to have useful info (ie. exists in
+    the session manager), but no longer does (patch by Jon Corbet).
+
+  * Make the PTL compiler a bit smarter about recognizing "template"
+    lines; PTL code should now be able to use 'template' as an
+    identifier, which is handy when converting existing Python code
+    to PTL.
+
+  * Replaced HTTPRequest.redirect() with a cleaner, more general
+    version supplied by Andreas Kostyrka <andreas at kostyrka.priv.at>.
+    Redirects to fully relative URLs (no leading slash) now work.
+
+  * Added support for putting bits of JavaScript into HTML form
+    documents: added HTTPResponse.add_javascript() to collect snippts
+    of JavaScript code, and Form._render_javascript() to emit them
+    as part of the HTML document.
+
+  * Added global convenience functions get_path() and redirect().
+
+  * Change Publisher so it never modifies SCRIPT_NAME or PATH_INFO.
+
+  * Fixed bug in quixote.sendmail._add_recip_headers(): it crashed
+    if passed an empty list.
+
+  * Factor out get_action_url() method in Form class.
+
+  * Add the HTML version of the documentation to the source release.
+
+
+0.5 (2002-06-10):
+
+  * To fix installation problems on Win98 and Mac OS (pre OS X), 
+    setup.py now uses os.curdir instead of ''.
+
+  * Overhauled handling of PublishError exceptions: Quixote now 
+    looks for the nearest _q_exception_handler() function in your
+    application's namespace; the format_*error() methods of Publisher
+    are gone.
+
+  * Documented and overhauled the session management API.  If you
+    were previously using session management, you will almost certainly
+    need to change your code; see doc/session-mgmt.txt and
+    doc/session-upgrade.txt.  If you've been wanting to use session
+    management in your application but were put off by the lack of
+    documentation, see doc/session-mgmt.txt.
+
+    Specific changes:
+      * removed the global singleton SessionManager object in session.py
+        and several related functions
+      * removed everything having to do with "application state", an
+        unnecessary abstraction caused by premature over-generalization
+      * removed the 'actual_user' attribute from Session -- it is
+        specific to the MEMS Exchange and just confuses matters
+        in Quixote
+      * made most instance attributes of Session private
+      * defined a sensible persistence API that should work with
+        a wide variety of session persistence schemes
+      * COOKIE_* config variables renamed to SESSION_COOKIE_*
+
+  * Fix HTTPResponse so that the cookie domain and path can be None,
+    and they will simply not be set in the cookie sent to the client --
+    that way the browser will simply do the right thing.  Set
+    COOKIE_DOMAIN and COOKIE_PATH in config.py to None, since that's
+    usually fine.  (You may need to set COOKIE_PATH to "/".)
+
+  * Subtle but far-reaching change to the publishing algorithm: objects
+    found by the publisher to handle the terminal component of a URL can
+    now be strings as well as callables; a string simply substitutes for
+    a callable's return value.  The immediate reason for this was to
+    allow _q_lookup() functions to return a string, but a consequence
+    is that you can now put static text in global variables and simply
+    publish them.
+
+  * Add CHECK_SESSION_ADDR config variable to control whether we
+    check that requests in a session all come from the same IP address,
+    as a defence against playback attacks.  (Thanks to Jonathan Corbet.)
+
+  * In error reports, print the traceback first, ahead of form variables,
+    cookies, and the environment.
+
+  * Include the HTTP_USER_AGENT variable in access log lines.
+
+  * Add 'sort' option to SelectWidget class, to force the list of
+    allowed values to be sorted in case-insensitive lexicographic order,
+    with None first.
+
+
+0.4.7 (2002-04-18):
+
+  * Move ACCESS_TIME_RESOLUTION to SessionManager class.  This was another
+    embarrassing bug introduced in 0.4.5.
+
+  * In http_request.py, make the test that prevents stdin from being consumed
+    less restrictive (e.g.  for PUT methods).
+
+  * Add some simple test code.
+
+
+0.4.6 (2002-04-12):
+
+  * a last-minute patch to http_request.py just before release 0.4.5 broke
+    extracting form data from GET requests -- fixed that
+
+
+0.4.5 (2002-04-11):
+
+  * The meaning of the DISPLAY_EXCEPTIONS configuration variable has
+    changed.  It's no longer a Boolean, and instead can take three
+    different values:
+      None (or any false value) [default]
+        an "Internal Server Error" page that exposes no information
+        about the traceback
+      'plain'
+        a plain text page showing the traceback and the request variables
+      'html'
+        a more elaborate HTML display showing the local variables and a
+        few lines of context for each level of the traceback.  (This
+        setting requires the cgitb module that comes with Python 2.2.)
+
+    (Idea and first version of the patch by David Ascher)
+
+  * Fixed SessionManager.expire_session() method so it actually works
+    (spotted by Robin Wohler).
+
+  * Fixed docs so they don't refer to the obsolete URL_PREFIX
+    configuration variable (spotted by Robin Wohler).
+
+  * Various other documentation tweaks and improvements.
+
+  * Fixed sample Apache rewrite rules in demo.txt and web-server.txt
+    (spotted by Joel Shprentz).
+
+  * Generate new form tokens when rendering a form rather then when
+    intializing it.  This prevents an extra token from being created when
+    processing a valid form (suggested by Robin Wohler).
+
+  * Ensure filenames are included in SyntaxError tracebacks from PTL modules.
+
+  * Changed format of session cookies: they're now just random 64-bit
+    numbers in hex.
+
+  * Use HTTP 1.1 cache control headers ("Date" and "Expires") instead
+    of the older "Pragma: no-cache".
+
+  * In the form/widget library: make some effort to generate HTML that
+    is XHTML-compliant.
+
+  * New method: HTTPRequest.get_accepted_types() returns the
+    MIME content types a client will accept as a dictionary mapping
+    MIME type to the quality factor.  (Example: {'text/html':1.0,
+    'text/plain':0.5, ...})
+
+  * Changed escape hatch for XML-RPC handlers; standard input will
+    only be consumed when the HTTP method is POST and the Content-Type 
+    is either application/x-www-form-urlencoded or multipart/form-data.
+
+  * Added quixote.util module to contain various miscellaneous utility
+    functions.  Right now, it contains a single function for 
+    processing requests as XML-RPC invocations.
+
+
+0.4.4 (2002-01-29):
+
+  * Simplify munging of SCRIPT_NAME variable, fixing a bug.
+    Depending on how Quixote was called, the path could have been
+    appended to SCRIPT_NAME without a separating slash.  (Found by
+    Quinn Dunkan.)
+
+  * On Windows, set mode of sys.stdout to binary.  This is important
+    because responses may contain binary data.  Also, EOL translation
+    can throw off content length calculations.  (Found by David Ascher)
+
+  * Added a demonstration of the form framework. (Neil)
+
+  * Added an escape hatch for XML-RPC handlers;
+    http_request.process_inputs() will no longer consume all of standard
+    input when the Content-Type is text/xml.
+
+  * Removed a debug print from form.widget.
+
+
+0.4.3 (2001-12-17):
+
+  * Removed the URL_PREFIX configuration variable; it's not actually
+    needed anywhere, and caused some user confusion.
+
+  * Added FORM_TOKENS configuration variable to enable/disable
+    unique form identifiers.  (These are useful as a measure against
+    cross-site request forgery [CSRF] attacks, but disabled by default
+    because some form of persistent session management is required,
+    which is not currently included with Quixote.)
+
+  * Added demonstration and documentation for the widget classes
+    (the first part of the Quixote Form Library).
+
+  * Added HTTPResponse.set_content_type() method.
+
+  * Fixed some minor bugs in the widget library.
+
+  * Fixed to work with Python 2.2.
+
+  * Greatly reduced the set of symbols imported by
+    "from quixote import *" -- it's useful for interactive sessions.
+
+
+0.4.2 (2001-11-14):
+
+  * Made the quixote.sendmail module a bit more flexible and robust.
+
+  * Fix so it doesn't blow up under Windows if debug logging is disabled
+    (ie. write to NUL, not /dev/null).
+
+  * Clarified some documenation inconsistencies, and added description
+    of logging to doc/programming.txt.
+
+  * Fixed some places that we forgot to update when the PTL-related
+    modules were renamed.
+
+  * Fixed ptl_compile.py so PTL tracebacks include the full path of
+    source file(s).
+
+  * Fixed bug where a missing _q_index() triggered a confusing
+    ImportError; now it triggers a TraversalError, as expected.
+
+  * Various fixes and improvements to the Config class.
+
+  * Miscellaneous fixes to session.py.
+
+  * Miscellaneous fixes to widget classes.
+
+  * Reorganized internal PTL methods of the Form class.
+
+  * Removed the "test" directory from the distribution, since it's not
+    used for anything -- ie., there's no formal test suite yet ;-(
+
+
+0.4.1 (2001-10-10):
+
+  * Made access logging a little more portable (don't depend on Apache's
+    REQUEST_URI environment variable).
+
+  * Work around the broken value of PATH_INFO returned by IIS.
+
+  * Work around IIS weird handling of SERVER_SECURE_PORT (for non-SSL
+    requests, it is set to "0").
+
+  * Reassign sys.stderr so all application output to stderr goes to the
+    Quixote error log.
+
+
+0.4 (2001-10-04):
+
+  * TraversalError now takes a public and a private message, instead of
+    just a single message string.  The private message is shown if
+    SECURE_ERRORS is false; otherwise, the public message is shown.  See
+    the class docstring for TraversalError for more details.
+
+  * Add the Quixote Form Library, a basic form and widget framework
+    for HTML.
+
+  * Allow classes and functions inside PTL modules.
+
+  * Return a string object from templates rather than a TemplateIO
+    instance.
+
+  * Improve the security of session cookies.
+
+  * Don't save empty sessions.
+
+  * Detect expired sessions.
+
+  * Add the quixote.sendmail module, useful for applications that need
+    to send outgoing mail (as many web apps do).
+
+  * Code reorganization -- various modules moved or renamed:
+      quixote.util.fcgi        -> quixote.fcgi
+      quixote.compile_template -> quixote.ptl_compile
+      quixote.imphooks         -> quixote.ptl_import
+      quixote.dumpptlc         -> quixote.ptcl_dump
+
+  * More code reorganization: the quixote.zope package is gone, as are
+    the BaseRequest and BaseResponse modules.  Only HTTPRequest and
+    HTTPResponse survive, in the quixote.http_request and
+    quixote.http_response modules.  All remaining Zope-isms have been
+    removed, so the code now looks much like the rest of Quixote.  Many
+    internal interfaces changed.
+
+  * Added the quixote.mod_python module, contributed by Erno Kuusela
+    <erno at iki.fi>.  Allows Quixote applications to be driven by the
+    Apache module mod_python, so no CGI or or FastCGI driver script is
+    required.
+
+
+0.3 (2001-06-11):
+
+  * Now supports Python 2.1.
+
+  * Names of the form __*__ are reserved for Python, and 2.1 is
+    beginning to enforce this rule.  Accordingly the Quixote special
+    methods have been renamed:
+        __access__     -> _q_access
+        __exports__    -> _q_exports
+        __getname__    -> _q_getname
+        index          -> _q_index
+
+  * Massive changes to quixote.publisher and quixote.config, to make the
+    publishing loop more flexible and more easily changed by
+    applications.  For example, it's now possible to catch the ZODB's
+    ConflictErrors and retry an operation.
+
+  * Added an ACCESS_LOG configuration setting, which allows setting up a
+    file logging every call made to Quixote.
+
+  * The error log now contains the time of each error, and a dump of the
+    user's session object.
+
+  * Added handy functions for getting request, session, user, etc.:
+    quixote.get_publisher(), quixote.get_request(),
+    quixote.get_session(), quixote.get_user().
+
+  * quixote.publish can now gzip-compress its output if the browser
+    claims to support it.  Only the 'gzip' and 'x-gzip' content
+    encodings are supported; 'deflate' isn't because we couldn't get it
+    to work reliably.  Compression can be enabled by setting the
+    'compress_pages' config option to true.
+
+  * Some fixes and minor optimizations to the FCGI code.
+
+  * Added HTTPRequest.get_encoding() method to find the encodings a
+    client accepts.
+
+
+0.2 (2001-01-16):
+
+  * Only pass HTTPRequest object to published functions.  The
+    HTTPResponse object is available as an attribute of the request.
+
+  * Removed more unused Zope code from HTTPRequest.  Add redirect()
+    method to HTTPRequest.
+
+  * Simplify HTTPResponse.  __init__() no longer requires the server
+    name.  redirect() requires a full URL.
+
+  * Fix a bug in the PTL compiler.  PTL modules can now have doc
+    strings.
+
+  * Added a config parser.  Individual Quixote applications can now have
+    their own configuration settings (overriding the Quixote defaults).
+    See the config.py module for details.
+
+  * Re-wrote the exception handling code for exceptions raised inside of
+    published functions.
+
+  * Non-empty PATH_INFO is no longer supported.  __getname__ or query
+    strings are a cleaner solution.
+
+  * Add FIX_TRAILING_SLASH option and make code changes to carefully
+    preserve trailing slashes (ie. an empty component on the end of
+    paths).
+
+  * Session management has been over-hauled.  DummySessionManager can be
+    used for applications that don't require sessions.
+
+  * Set Content-length header correctly in HTTPResponse object
+
+  * Added a demo application.
+
+0.1:
+
+  * Officially given a license (the Python 1.6 license, so it's free
+    software).
+
+  * Added SECURE_ERRORS variable to prevent exception tracebacks from
+    being returned to the Web browser
+
+  * Added a __getname__() function to traversal, which is called if the
+    current name isn't in the current namespace's export list. This
+    allows interpolation of arbitrary user object IDs into the URL,
+    which is why it has to circumvent the __exports__ check: the object
+    IDs won't be known until runtime, so it would be silly to add them
+    to __exports__.  Very useful and powerful feature, but it has
+    security implications, so be careful!
+
+  * compile_template.py should now work for both Python 1.5.2 or 2.0.
+    
+  * Better reporting of syntax errors in PTL
+    
+  * Always assume regular CGI on Windows
+
+0.02 (2000-08-12):
+
+  * Neil Schemenauer has completely rewritten the PTL compiler and
+    changed the syntax to match Python's.  The compiler now relies on
+    Jeremy Hylton's compiler code from the Python 2.0 CVS tree.
+
+  * Added functions to quixote.sessions: get_session(), has_session(),
+    get_app_state()
+
+  * Simplified reload-checking logic 
+
+  * Added .browser_version() method to HTTPRequest
+
+  * Various bugfixes
+
+  * session classes slightly tweaked, so you can subclass them
+
+  * Added .session attribute to request object
+
+  * Added quixote.errors module to hold exceptions
+
+0.01:
+
+  * Initial release.

Added: packages/quixote1/branches/upstream/current/LICENSE
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/LICENSE?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/LICENSE (added)
+++ packages/quixote1/branches/upstream/current/LICENSE Mon May  8 19:16:16 2006
@@ -1,0 +1,60 @@
+CNRI OPEN SOURCE LICENSE AGREEMENT
+
+IMPORTANT: PLEASE READ THE FOLLOWING AGREEMENT CAREFULLY.  BY
+COPYING, INSTALLING OR OTHERWISE USING QUIXOTE-1.2 SOFTWARE, YOU
+ARE DEEMED TO HAVE AGREED TO THE TERMS AND CONDITIONS OF THIS
+LICENSE AGREEMENT.
+
+1. This LICENSE AGREEMENT is between Corporation for National
+   Research Initiatives, having an office at 1895 Preston White
+   Drive, Reston, VA 20191 ("CNRI"), and the Individual or
+   Organization ("Licensee") copying, installing or otherwise using
+   Quixote-1.2 software in source or binary form and its associated
+   documentation ("Quixote-1.2").
+
+2. Subject to the terms and conditions of this License Agreement,
+   CNRI hereby grants Licensee a nonexclusive, royalty-free, world-
+   wide license to reproduce, analyze, test, perform and/or display
+   publicly, prepare derivative works, distribute, and otherwise use
+   Quixote-1.2 alone or in any derivative version, provided,
+   however, that CNRI's License Agreement and CNRI's notice of
+   copyright, i.e., "Copyright (c) 2004 Corporation for National
+   Research Initiatives; All Rights Reserved" are retained in
+   Quixote-1.2 alone or in any derivative version prepared by
+   Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+   or incorporates Quixote-1.2 or any part thereof, and wants to
+   make the derivative work available to others as provided herein,
+   then Licensee hereby agrees to include in any such work a brief
+   summary of the changes made to Quixote-1.2.
+
+4. CNRI is making Quixote-1.2 available to Licensee on an "AS IS"
+   basis.  CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+   IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO
+   AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY
+   OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF QUIXOTE-
+   1.2 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF
+   QUIXOTE-1.2 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES
+   OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE
+   USING QUIXOTE-1.2, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF
+   THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a
+   material breach of its terms and conditions.
+
+7. This License Agreement shall be governed by and interpreted in
+   all respects by the law of the State of Virginia, excluding
+   Virginia's conflict of law provisions.  Nothing in this License
+   Agreement shall be deemed to create any relationship of agency,
+   partnership, or joint venture between CNRI and Licensee.  This
+   License Agreement does not grant permission to use CNRI
+   trademarks or trade name in a trademark sense to endorse or
+   promote products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Quixote-1.2, Licensee
+   agrees to be bound by the terms and conditions of this License
+   Agreement.
+

Added: packages/quixote1/branches/upstream/current/MANIFEST
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/MANIFEST?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/MANIFEST (added)
+++ packages/quixote1/branches/upstream/current/MANIFEST Mon May  8 19:16:16 2006
@@ -1,0 +1,123 @@
+ACKS
+CHANGES
+LICENSE
+MANIFEST
+MANIFEST.in
+README
+TODO
+__init__.py
+_py_htmltext.py
+config.py
+errors.py
+fcgi.py
+html.py
+http_request.py
+http_response.py
+mod_python_handler.py
+ptl_compile.py
+ptl_import.py
+ptlc_dump.py
+publish.py
+qx_distutils.py
+sendmail.py
+session.py
+setup.py
+upload.py
+util.py
+./__init__.py
+./_py_htmltext.py
+./config.py
+./errors.py
+./fcgi.py
+./html.py
+./http_request.py
+./http_response.py
+./mod_python_handler.py
+./ptl_compile.py
+./ptl_import.py
+./ptlc_dump.py
+./publish.py
+./qx_distutils.py
+./sendmail.py
+./session.py
+./upload.py
+./util.py
+./demo/__init__.py
+./demo/demo_scgi.py
+./demo/forms.ptl
+./demo/integer_ui.py
+./demo/pages.ptl
+./demo/run_cgi.py
+./demo/session.ptl
+./demo/widgets.ptl
+./form/__init__.py
+./form/form.py
+./form/widget.py
+./form2/__init__.py
+./form2/compatibility.py
+./form2/css.py
+./form2/form.py
+./form2/widget.py
+./server/__init__.py
+./server/medusa_http.py
+./server/twisted_http.py
+demo/__init__.py
+demo/demo.cgi
+demo/demo.conf
+demo/demo_scgi.py
+demo/demo_scgi.sh
+demo/forms.ptl
+demo/integer_ui.py
+demo/pages.ptl
+demo/run_cgi.py
+demo/session.ptl
+demo/session_demo.cgi
+demo/upload.cgi
+demo/widgets.ptl
+doc/INSTALL.html
+doc/INSTALL.txt
+doc/Makefile
+doc/PTL.html
+doc/PTL.txt
+doc/ZPL.txt
+doc/default.css
+doc/demo.html
+doc/demo.txt
+doc/form2conversion.html
+doc/form2conversion.txt
+doc/multi-threaded.html
+doc/multi-threaded.txt
+doc/programming.html
+doc/programming.txt
+doc/session-mgmt.html
+doc/session-mgmt.txt
+doc/static-files.html
+doc/static-files.txt
+doc/upgrading.html
+doc/upgrading.txt
+doc/upload.html
+doc/upload.txt
+doc/web-server.html
+doc/web-server.txt
+doc/web-services.html
+doc/web-services.txt
+doc/widgets.html
+doc/widgets.txt
+form/__init__.py
+form/form.py
+form/widget.py
+form2/__init__.py
+form2/compatibility.py
+form2/css.py
+form2/form.py
+form2/widget.py
+server/__init__.py
+server/medusa_http.py
+server/twisted_http.py
+src/Makefile
+src/_c_htmltext.c
+src/cimport.c
+src/setup.py
+test/__init__.py
+test/ua_test.py
+test/utest_html.py

Added: packages/quixote1/branches/upstream/current/MANIFEST.in
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/MANIFEST.in?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/MANIFEST.in (added)
+++ packages/quixote1/branches/upstream/current/MANIFEST.in Mon May  8 19:16:16 2006
@@ -1,0 +1,6 @@
+global-include *.py *.ptl
+include README LICENSE MANIFEST.in MANIFEST CHANGES TODO ACKS
+include doc/*.txt doc/*.css doc/Makefile
+recursive-include doc *.html
+include demo/*.cgi demo/*.conf demo/*.sh
+include src/*.c src/Makefile

Added: packages/quixote1/branches/upstream/current/PKG-INFO
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/PKG-INFO?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/PKG-INFO (added)
+++ packages/quixote1/branches/upstream/current/PKG-INFO Mon May  8 19:16:16 2006
@@ -1,0 +1,24 @@
+Metadata-Version: 1.0
+Name: Quixote
+Version: 1.2
+Summary: A highly Pythonic Web application framework
+Home-page: http://www.mems-exchange.org/software/quixote/
+Author: MEMS Exchange
+Author-email: quixote at mems-exchange.org
+License: CNRI Open Source License (see LICENSE.txt)
+Provides: quixote-1.2
+Provides: quixote.demo-1.2
+Provides: quixote.form-1.2
+Provides: quixote.form2-1.2
+Provides: quixote.server-1.2
+Download-URL: http://www.mems-exchange.org/software/files/quixote/Quixote-1.2.tar.gz
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: License :: OSI Approved :: Python License (CNRI Python License)
+Classifier: Intended Audience :: Developers
+Classifier: Operating System :: Unix
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content

Added: packages/quixote1/branches/upstream/current/README
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/README?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/README (added)
+++ packages/quixote1/branches/upstream/current/README Mon May  8 19:16:16 2006
@@ -1,0 +1,166 @@
+Quixote
+=======
+
+Quixote is yet another framework for developing Web applications in
+Python.  The design goals were:
+
+  1) To allow easy development of Web applications where the
+     emphasis is more on complicated programming logic than
+     complicated templating.
+
+  2) To make the templating language as similar to Python as possible,
+     in both syntax and semantics.  The aim is to make as many of the
+     skills and structural techniques used in writing regular Python
+     code applicable to Web applications built using Quixote.
+
+  3) No magic.  When it's not obvious what to do in
+     a certain case, Quixote refuses to guess.
+
+If you view a web site as a program, and web pages as subroutines,
+Quixote just might be the tool for you.  If you view a web site as a
+graphic design showcase, and each web page as an individual work of art,
+Quixote is probably not what you're looking for.
+
+An additional requirement was that the entire system had to be
+implementable in a week or two.  The initial version of Quixote was
+indeed cranked out in about that time -- thank you, Python!
+
+We've tried to reuse as much existing code as possible:
+
+  * The HTTPRequest and HTTPResponse classes are distantly
+    derived from their namesakes in Zope, but we've removed
+    huge amounts of Zope-specific code.
+
+  * The quixote.fcgi module is derived from Robin Dunn's FastCGI module,
+    available at
+      http://alldunn.com/python/#fcgi
+
+Quixote requires Python 2.1 or greater to run.  We only test Quixote
+with Python 2.3, but it should still work with 2.1 and 2.2.
+
+For installation instructions, see the doc/INSTALL.txt file (or
+http://www.mems-exchange.org/software/quixote/doc/INSTALL.html).
+
+If you're switching to a newer version of Quixote from an older
+version, please refer to doc/upgrading.txt for explanations of any
+backward-incompatible changes.  
+
+
+Overview
+========
+
+Quixote works by using a Python package to store all the code and HTML
+for a Web-based application.  There's a simple framework for
+publishing code and objects on the Web, and the publishing loop can be
+customized by subclassing the Publisher class.  You can think of it as
+a toolkit to build your own smaller, simpler version of Zope,
+specialized for your application.
+
+An application using Quixote is a Python package containing .py and
+.ptl files.  
+
+webapp/				# Root of package
+	__init__.py			
+	module1.py
+	module2.py
+	pages1.ptl
+	pages2.ptl
+
+PTL, the Python Template Language, is used to mix HTML with Python code.
+More importantly, Python can be used to drive the generation of HTML.
+An import hook is defined so that PTL files can be imported just like
+Python modules.  The basic syntax of PTL is Python's, with a few small
+changes:
+
+def plain [text] barebones_header(title=None,
+                                  description=None):
+    """
+    <html><head>
+    <title>%s</title>
+    """ % html_quote(str(title))
+    if description:
+        '<meta name="description" content="%s">' % html_quote(description) 
+
+    '</head><body bgcolor="#ffffff">'
+
+See doc/PTL.txt for a detailed explanation of PTL.
+
+
+Quick start
+===========
+
+For instant gratification, see doc/demo.txt.  This explains how to get
+the Quixote demo up and running, so you can play with Quixote without
+actually having to write any code.
+
+
+Documentation
+=============
+
+All the documentation is in the doc/ subdirectory, in both text and
+HTML.  Or you can browse it online from
+  http://www.mems-exchange.org/software/quixote/doc/
+
+Recommended reading:
+
+  demo.txt            getting the Quixote demo up and running, and
+                      how the demo works
+  programming.txt     the components of a Quixote application: how
+                      to write your own Quixote apps
+  PTL.txt             the Python Template Language, used by Quixote
+                      apps to generate web pages
+  web-server.txt      how to configure your web server for Quixote
+
+Optional reading (more advanced or arcane stuff):
+
+  session-mgmt.txt    session management: how to track information
+                      across requests
+  static-files.txt    making static files and CGI scripts available
+  upload.txt          how to handle HTTP uploads with Quixote
+  upgrading.txt       info on backward-incompatible changes that may
+                      affect applications written with earlier versions
+  widgets.txt         reference documentation for the Quixote Widget
+                      classes (which underly the form library)
+  web-services.txt    how to write web services using Quixote and
+                      XML-RPC
+
+
+Authors, copyright, and license
+===============================
+
+Copyright (c) 2000-2003 CNRI.
+
+Quixote was primarily written by Andrew Kuchling, Neil Schemenauer, and
+Greg Ward.
+
+Overall, Quixote is covered by the CNRI Open Source License Agreement;
+see LICENSE for details.
+
+Portions of Quixote are derived from Zope, and are also covered by the
+ZPL (Zope Public License); see ZPL.txt.
+
+Full acknowledgments are in the ACKS file.
+
+
+Availability, home page, and mailing lists
+==========================================
+
+The Quixote home page is:
+    http://www.mems-exchange.org/software/quixote/
+
+You'll find the latest stable release there.  The current development
+code is also available via CVS; for instructions, see
+    http://www.mems-exchange.org/software/quixote/cvs.html
+
+Discussion of Quixote occurs on the quixote-users mailing list:
+    http://mail.mems-exchange.org/mailman/listinfo/quixote-users/
+
+To follow development at the most detailed level by seeing every CVS
+checkin, join the quixote-checkins mailing list:
+    http://mail.mems-exchange.org/mailman/listinfo/quixote-checkins/
+
+
+-- 
+A.M. Kuchling    <akuchlin at mems-exchange.org>
+Neil Schemenauer <nascheme at mems-exchange.org>
+Greg Ward        <gward at mems-exchange.org>

Added: packages/quixote1/branches/upstream/current/TODO
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/TODO?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/TODO (added)
+++ packages/quixote1/branches/upstream/current/TODO Mon May  8 19:16:16 2006
@@ -1,0 +1,27 @@
+
+* Extend HTTPRequest to support single/multiple-valued fields.
+
+* Add way to strip redundant whitespace from output HTML (or pipe it
+  through Tidy, or other transformations).
+
+* Logging doesn't work with CGI scripts (something about our
+  log-opening code depends on how fastcgi.py fiddles stdout).
+
+* Make bare return statements inside of PTL templates work as expected.
+
+* Allow __init__.ptl files to be used as package markers.  It looks like
+  something is wrong with the way ihooks handles __init__ modules.
+
+* Figure out how to unify the various deployment methods (CGI, FastCGI,
+  SCGI, mod_python, ...).  Ideally, a one-line change to the config file
+  and/or driver script (along with corresponding web server config
+  changes) would be enough to change how a Quixote application is
+  deployed.
+
+* For OpenBSD: fcgi.py should catch SIGTERM and, umm, do something.
+  (Terminate the process?)  Otherwise, the FastCGI process can no longer
+  accept() on its socket.  (Reported by Robin Wöhler
+  <rw at robinwoehler.de>, 2002/08/02.)
+
+* For Mac OS X: _startup() in fcgi.py doesn't work for some reason on
+  OS X.  Figure out why and fix it (or kludge around it).

Added: packages/quixote1/branches/upstream/current/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/__init__.py Mon May  8 19:16:16 2006
@@ -1,0 +1,35 @@
+"""quixote
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/__init__.py $
+$Id: __init__.py 25278 2004-10-06 15:49:42Z nascheme $
+
+A highly Pythonic web application framework.
+"""
+
+__revision__ = "$Id: __init__.py 25278 2004-10-06 15:49:42Z nascheme $"
+
+__version__ = "1.2"
+
+__all__ = ['Publisher',
+           'get_publisher', 'get_request', 'get_session', 'get_user',
+           'get_path', 'enable_ptl', 'redirect']
+
+
+# These are frequently needed by Quixote applications, so make them easy
+# to get at.
+from quixote.publish import Publisher, \
+     get_publisher, get_request, get_path, redirect, \
+     get_session, get_session_manager, get_user
+
+# Can't think of anywhere better to put this, so here it is.
+def enable_ptl():
+    """
+    Installs the import hooks needed to import PTL modules.  This must
+    be done explicitly because not all Quixote applications need to use
+    PTL, and import hooks are deep magic that can cause all sorts of
+    mischief and deeply confuse innocent bystanders.  Thus, we avoid
+    invoking them behind the programmer's back.  One known problem is
+    that, if you use ZODB, you must import ZODB before calling this
+    function.
+    """
+    from quixote import ptl_import
+    ptl_import.install()

Added: packages/quixote1/branches/upstream/current/_py_htmltext.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/_py_htmltext.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/_py_htmltext.py (added)
+++ packages/quixote1/branches/upstream/current/_py_htmltext.py Mon May  8 19:16:16 2006
@@ -1,0 +1,231 @@
+"""Python implementation of the htmltext type, the htmlescape function and
+TemplateIO.
+"""
+
+#$HeadURL: svn+ssh://svn/repos/trunk/quixote/_py_htmltext.py $
+#$Id: _py_htmltext.py 25234 2004-09-30 17:36:19Z nascheme $
+
+import sys
+from types import UnicodeType, TupleType, StringType, IntType, FloatType, \
+    LongType
+import re
+
+if sys.hexversion < 0x20200b1:
+    # 2.2 compatibility hacks
+    class object:
+        pass
+
+    def classof(o):
+        if hasattr(o, "__class__"):
+            return o.__class__
+        else:
+            return type(o)
+
+else:
+    classof = type
+
+_format_codes = 'diouxXeEfFgGcrs%'
+_format_re = re.compile(r'%%[^%s]*[%s]' % (_format_codes, _format_codes))
+
+def _escape_string(s):
+    if not isinstance(s, StringType):
+        raise TypeError, 'string required'
+    s = s.replace("&", "&amp;")
+    s = s.replace("<", "&lt;")
+    s = s.replace(">", "&gt;")
+    s = s.replace('"', "&quot;")
+    return s
+
+class htmltext(object):
+    """The htmltext string-like type.  This type serves as a tag
+    signifying that HTML special characters do not need to be escaped
+    using entities.
+    """
+
+    __slots__ = ['s']
+
+    def __init__(self, s):
+        self.s = str(s)
+
+    # XXX make read-only
+    #def __setattr__(self, name, value):
+    #    raise AttributeError, 'immutable object'
+
+    def __getstate__(self):
+        raise ValueError, 'htmltext objects should not be pickled'
+
+    def __repr__(self):
+        return '<htmltext %r>' % self.s
+
+    def __str__(self):
+        return self.s
+
+    def __len__(self):
+        return len(self.s)
+
+    def __cmp__(self, other):
+        return cmp(self.s, other)
+
+    def __hash__(self):
+        return hash(self.s)
+
+    def __mod__(self, args):
+        codes = []
+        usedict = 0
+        for format in _format_re.findall(self.s):
+            if format[-1] != '%':
+                if format[1] == '(':
+                    usedict = 1
+                codes.append(format[-1])
+        if usedict:
+            args = _DictWrapper(args)
+        else:
+            if len(codes) == 1 and not isinstance(args, TupleType):
+                args = (args,)
+            args = tuple([_wraparg(arg) for arg in args])
+        return self.__class__(self.s % args)
+
+    def __add__(self, other):
+        if isinstance(other, StringType):
+            return self.__class__(self.s + _escape_string(other))
+        elif classof(other) is self.__class__:
+            return self.__class__(self.s + other.s)
+        else:
+            return NotImplemented
+
+    def __radd__(self, other):
+        if isinstance(other, StringType):
+            return self.__class__(_escape_string(other) + self.s)
+        else:
+            return NotImplemented
+
+    def __mul__(self, n):
+        return self.__class__(self.s * n)
+
+    def join(self, items):
+        quoted_items = []
+        for item in items:
+            if classof(item) is self.__class__:
+                quoted_items.append(str(item))
+            elif isinstance(item, StringType):
+                quoted_items.append(_escape_string(item))
+            else:
+                raise TypeError(
+                    'join() requires string arguments (got %r)' % item)
+        return self.__class__(self.s.join(quoted_items))
+
+    def startswith(self, s):
+        if isinstance(s, htmltext):
+            s = s.s
+        else:
+            s = _escape_string(s)
+        return self.s.startswith(s)
+
+    def endswith(self, s):
+        if isinstance(s, htmltext):
+            s = s.s
+        else:
+            s = _escape_string(s)
+        return self.s.endswith(s)
+
+    def replace(self, old, new, maxsplit=-1):
+        if isinstance(old, htmltext):
+            old = old.s
+        else:
+            old = _escape_string(old)
+        if isinstance(new, htmltext):
+            new = new.s
+        else:
+            new = _escape_string(new)
+        return self.__class__(self.s.replace(old, new))
+
+    def lower(self):
+        return self.__class__(self.s.lower())
+
+    def upper(self):
+        return self.__class__(self.s.upper())
+
+    def capitalize(self):
+        return self.__class__(self.s.capitalize())
+
+class _QuoteWrapper(object):
+    # helper for htmltext class __mod__
+
+    __slots__ = ['value', 'escape']
+
+    def __init__(self, value, escape):
+        self.value = value
+        self.escape = escape
+
+    def __str__(self):
+        return self.escape(str(self.value))
+
+    def __repr__(self):
+        return self.escape(`self.value`)
+
+class _DictWrapper(object):
+    def __init__(self, value):
+        self.value = value
+
+    def __getitem__(self, key):
+        return _wraparg(self.value[key])
+
+def _wraparg(arg):
+    if (classof(arg) is htmltext or
+        isinstance(arg, IntType) or
+        isinstance(arg, LongType) or
+        isinstance(arg, FloatType)):
+        # ints, longs, floats, and htmltext are okay
+        return arg
+    else:
+        # everything is gets wrapped
+        return _QuoteWrapper(arg, _escape_string)
+
+def htmlescape(s):
+    """htmlescape(s) -> htmltext
+
+    Return an 'htmltext' object using the argument.  If the argument is not
+    already a 'htmltext' object then the HTML markup characters \", <, >,
+    and & are first escaped.
+    """
+    if classof(s) is htmltext:
+        return s
+    elif isinstance(s,  UnicodeType):
+        s = s.encode('iso-8859-1')
+    else:
+        s = str(s)
+    # inline _escape_string for speed
+    s = s.replace("&", "&amp;") # must be done first
+    s = s.replace("<", "&lt;")
+    s = s.replace(">", "&gt;")
+    s = s.replace('"', "&quot;")
+    return htmltext(s)
+
+
+class TemplateIO(object):
+    """Collect output for PTL scripts.
+    """
+
+    __slots__ = ['html', 'data']
+
+    def __init__(self, html=0):
+        self.html = html
+        self.data = []
+
+    def __iadd__(self, other):
+        if other is not None:
+            self.data.append(other)
+        return self
+
+    def __repr__(self):
+        return ("<%s at %x: %d chunks>" %
+                (self.__class__.__name__, id(self), len(self.data)))
+
+    def __str__(self):
+        return str(self.getvalue())
+
+    def getvalue(self):
+        if self.html:
+            return htmltext('').join(map(htmlescape, self.data))
+        else:
+            return ''.join(map(str, self.data))

Added: packages/quixote1/branches/upstream/current/config.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/config.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/config.py (added)
+++ packages/quixote1/branches/upstream/current/config.py Mon May  8 19:16:16 2006
@@ -1,0 +1,300 @@
+"""quixote.config
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/config.py $
+$Id: config.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Quixote configuration information.  This module provides both the
+default configuration values, and some code that Quixote uses for
+dealing with configuration info.  You should not edit the configuration
+values in this file, since your edits will be lost if you upgrade to a
+newer Quixote version in the future.  However, this is the canonical
+source of information about Quixote configuration variables, and editing
+the defaults here is harmless if you're just playing around and don't
+care what happens in the future.
+"""
+
+__revision__ = "$Id: config.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+
+# Note that the default values here are geared towards a production
+# environment, preferring security and performance over verbosity and
+# debug-ability.  If you just want to get a Quixote application
+# up-and-running in a production environment, these settings are mostly
+# right; all you really need to customize are ERROR_EMAIL, and ERROR_LOG.
+# If you need to test/debug/develop a Quixote application, though, you'll
+# probably want to also change DISPLAY_EXCEPTIONS, SECURE_ERRORS, and
+# maybe RUN_ONCE.  Again, you shouldn't edit this file unless you don't
+# care what happens in the future (in particular, an upgrade to Quixote
+# would clobber your edits).
+
+
+# E-mail address to send application errors to; None to send no mail at
+# all.  This should probably be the email address of your web
+# administrator.
+ERROR_EMAIL = None
+#ERROR_EMAIL = 'webmaster at example.com'
+
+# Filename for writing the Quixote access log; None for no access log.
+ACCESS_LOG = None
+#ACCESS_LOG = "/www/log/quixote-access.log"
+
+# Filename for logging error messages; if None, everything will be sent
+# to standard error, so it should wind up in the Web server's error log
+# file.
+ERROR_LOG = None
+
+# Filename for logging debugging output; if None then debugging output
+# goes to the error log. (Anything that application code prints to
+# stdout is debug output.)
+DEBUG_LOG = None
+
+# Controls what's done when uncaught exceptions occur.  If set to
+# 'plain', the traceback will be returned to the browser in addition
+# to being logged, If set to 'html' and the cgitb module is installed,
+# a more elaborate display will be returned to the browser, showing
+# the local variables and a few lines of context for each level of the
+# traceback.  If set to None, a generic error display, containing no
+# information about the traceback, will be used.
+#
+# It is convenient to enable this on development servers.  On publicly
+# accessible servers it should be disabled for security reasons.
+#
+# (For backward compatibility reasons, 0 and 1 are also legal values
+# for this setting.)
+DISPLAY_EXCEPTIONS = None
+
+# If true, then any "resource not found" errors will result in a
+# consistent, terse, mostly-useless message.  If false, then the
+# exact cause of failure will be returned.
+SECURE_ERRORS = 1
+
+# If true, Quixote will service exactly one request at a time, and
+# then exit.  This makes no difference when you're running as a
+# straight CGI script, but it makes it easier to debug while running
+# as a FastCGI script.
+RUN_ONCE = 0
+
+# Automatically redirect paths referencing non-callable objects to a path
+# with a trailing slash.  This is convienent for external users of the
+# site but should be disabled for development.  Internal links on the
+# site should not require redirects.  They are costly, especially on high
+# latency links like dialup lines.
+FIX_TRAILING_SLASH = 1
+
+# Compress large pages using gzip if the client accepts that encoding.
+COMPRESS_PAGES = 0
+
+# If true, then a cryptographically secure token will be inserted into forms
+# as a hidden field.  The token will be checked when the form is submitted.
+# This prevents cross-site request forgeries (CSRF).  It is off by default
+# since it doesn't work if sessions are not persistent across requests.
+FORM_TOKENS = 0
+
+# If true, the remote IP address of requests will be checked against the
+# IP address that created the session; this is a defense against playback
+# attacks.  It will frustrate mobile laptop users, though.
+CHECK_SESSION_ADDR = 0
+
+# Session-related variables
+# =========================
+
+# Name of the cookie that will hold the session ID string.
+SESSION_COOKIE_NAME = "QX_session"
+
+# Domain and path to which the session cookie is restricted.  Leaving
+# these undefined is fine.  Quixote does not have a default "domain"
+# option, meaning the session cookie will only be sent to the
+# originating server.  If you don't set the cookie path, Quixote will
+# use your application's root URL (ie. SCRIPT_NAME in a CGI-like
+# environment), meaning the session cookie will be sent to all URLs
+# controlled by your application, but no other.
+SESSION_COOKIE_DOMAIN = None    # eg. ".example.com"
+SESSION_COOKIE_PATH = None      # eg. "/"
+
+
+# Mail-related variables
+# ======================
+# These are only used by the quixote.sendmail module, which is
+# provided for use by Quixote applications that need to send
+# e-mail.  This is a common task for web apps, but by no means
+# universal.
+#
+# E-mail addresses can be specified either as a lone string
+# containing a bare e-mail address ("addr-spec" in the RFC 822
+# grammar), or as an (address, real_name) tuple.
+
+# MAIL_FROM is used as the default for the "From" header and the SMTP
+# sender for all outgoing e-mail.  If you don't set it, your application
+# will crash the first time it tries to send e-mail without an explicit
+# "From" address.
+MAIL_FROM = None     # eg. "webmaster at example.com"
+                     # or  ("webmaster at example.com", "Example Webmaster")
+
+# E-mail is sent by connecting to an SMTP server on MAIL_SERVER.  This
+# server must be configured to relay outgoing e-mail from the current
+# host (ie., the host where your Quixote application runs, most likely
+# your web server) to anywhere on the Internet.  If you don't know what
+# this means, talk to your system administrator.
+MAIL_SERVER = "localhost"
+
+# If MAIL_DEBUG_ADDR is set, then all e-mail will actually be sent to
+# this address rather than the intended recipients.  This should be a
+# single, bare e-mail address.
+MAIL_DEBUG_ADDR = None   # eg. "developers at example.com"
+
+
+# HTTP file upload variables
+# ==========================
+
+# Any files upload via HTTP will be written to temporary files
+# in UPLOAD_DIR.  If UPLOAD_DIR is not defined, any attempts to
+# upload via HTTP will crash (ie. uncaught exception).
+UPLOAD_DIR = None
+
+# If UPLOAD_DIR does not exist, Quixote will create it with
+# mode UPLOAD_DIR_MODE.  No idea what this should be on Windows.
+UPLOAD_DIR_MODE = 0755
+
+
+# -- End config variables ----------------------------------------------
+# (no user serviceable parts after this point)
+
+# Note that this module is designed to not export any names apart from
+# the above config variables and the following Config class -- hence,
+# all imports are done in local scopes.  This allows application config
+# modules to safely say "from quixote.config import *".
+
+class ConfigError(Exception):
+
+    def __init__(self, msg, source=None, var=None):
+        self.msg = msg
+        self.source = source
+        self.var = var
+
+    def __str__(self):
+        chunks = []
+        if self.source:
+            chunks.append(self.source)
+        if self.var:
+            chunks.append(self.var)
+        chunks.append(self.msg)
+        return ": ".join(chunks)
+
+
+class Config:
+    """Holds all Quixote configuration variables -- see above for
+    documentation of them.  The naming convention is simple:
+    downcase the above variables to get the names of instance
+    attributes of this class.
+    """
+
+    config_vars = [
+        'error_email',
+        'access_log',
+        'debug_log',
+        'display_exceptions',
+        'secure_errors',
+        'error_log',
+        'run_once',
+        'fix_trailing_slash',
+        'compress_pages',
+        'form_tokens',
+        'session_cookie_domain',
+        'session_cookie_name',
+        'session_cookie_path',
+        'check_session_addr',
+        'mail_from',
+        'mail_server',
+        'mail_debug_addr',
+        'upload_dir',
+        'upload_dir_mode',
+        ]
+
+
+    def __init__(self, read_defaults=1):
+        for var in self.config_vars:
+            setattr(self, var, None)
+        if read_defaults:
+            self.read_defaults()
+
+    def __setattr__(self, attr, val):
+        if not attr in self.config_vars:
+            raise AttributeError, "no such configuration variable: %s" % `attr`
+        self.__dict__[attr] = val
+
+
+    def dump(self, file=None):
+        import sys
+        if file is None:
+            file = sys.stdout
+        file.write("<%s.%s instance at %x>:\n" %
+                   (self.__class__.__module__,
+                    self.__class__.__name__,
+                    id(self)))
+        for var in self.config_vars:
+            file.write("  %s = %s\n" % (var, `getattr(self, var)`))
+
+
+    def set_from_dict(self, dict, source=None):
+        import string, re
+        ucstring_re = re.compile(r'^[A-Z_]+$')
+
+        for (var, val) in dict.items():
+            if ucstring_re.match(var):
+                setattr(self, string.lower(var), val)
+
+        self.check_values(source)
+
+    def check_values(self, source):
+        """
+        check_values(source : string)
+
+        Check the configuration variables to ensure that they
+        are all valid.  Raise ConfigError with 'source' as the
+        second argument if any problems are found.
+        """
+        # Check value of DISPLAY_EXCEPTIONS.  Values that are
+        # equivalent to 'false' are set to None; a value of 1
+        # is changed to 'plain'.
+        if not self.display_exceptions:
+            self.display_exceptions = None
+        elif self.display_exceptions == 1:
+            self.display_exceptions = 'plain'
+        if self.display_exceptions not in (None, 'plain', 'html'):
+            raise ConfigError("Must be None,"
+                              " 'plain', or 'html'",
+                              source,
+                              "DISPLAY_EXCEPTIONS")
+
+
+    def read_file(self, filename):
+        """Read configuration from a file.  Any variables already
+        defined in this Config instance, but not in the file, are
+        unchanged, so you can use this to build up a configuration
+        by accumulating data from several config files.
+        """
+        # The config file is Python code -- makes life easy.
+        config_vars = {}
+        try:
+            execfile(filename, config_vars)
+        except IOError, exc:
+            if exc.filename is None:    # arg! execfile() loses filename
+                exc.filename = filename
+            raise exc
+
+        self.set_from_dict(config_vars, source=filename)
+
+    def read_from_module(self, modname):
+        """Read configuration info from a Python module (default
+        is the module where the Config class is defined, ie.
+        quixote.config).  Also accumulates config data, just like
+        'read_file()'.
+        """
+        import sys
+        __import__(modname)
+        module = sys.modules[modname]
+        self.set_from_dict(vars(module), source=module.__file__)
+
+    def read_defaults(self):
+        self.read_from_module("quixote.config")
+
+# class Config

Added: packages/quixote1/branches/upstream/current/demo/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/demo/__init__.py Mon May  8 19:16:16 2006
@@ -1,0 +1,41 @@
+
+_q_exports = ["simple", "error", "publish_error", "widgets",
+              "form_demo", "dumpreq", "srcdir",
+              ("favicon.ico", "q_ico")]
+
+import sys
+from quixote.demo.pages import _q_index, _q_exception_handler, dumpreq
+from quixote.demo.widgets import widgets
+from quixote.demo.integer_ui import IntegerUI
+from quixote.errors import PublishError
+from quixote.util import StaticDirectory, StaticFile
+
+def simple(request):
+    # This function returns a plain text document, not HTML.
+    request.response.set_content_type("text/plain")
+    return "This is the Python function 'quixote.demo.simple'.\n"
+
+def error(request):
+    raise ValueError, "this is a Python exception"
+
+def publish_error(request):
+    raise PublishError(public_msg="Publishing error raised by publish_error")
+
+def _q_lookup(request, component):
+    return IntegerUI(request, component)
+
+def _q_resolve(component):
+    # _q_resolve() is a hook that can be used to import only
+    # when it's actually accessed.  This can be used to make
+    # start-up of your application faster, because it doesn't have
+    # to import every single module when it starts running.
+    if component == 'form_demo':
+        from quixote.demo.forms import form_demo
+        return form_demo
+
+# Get current directory
+import os
+from quixote.demo import forms
+curdir = os.path.dirname(forms.__file__)
+srcdir = StaticDirectory(curdir, list_directory=1)
+q_ico = StaticFile(os.path.join(curdir, 'q.ico'))

Added: packages/quixote1/branches/upstream/current/demo/demo.cgi
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/demo.cgi?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/demo.cgi (added)
+++ packages/quixote1/branches/upstream/current/demo/demo.cgi Mon May  8 19:16:16 2006
@@ -1,0 +1,21 @@
+#!/www/python/bin/python
+
+# Example driver script for the Quixote demo: publishes the contents of
+# the quixote.demo package.
+
+from quixote import enable_ptl, Publisher
+
+# Install the import hook that enables PTL modules.
+enable_ptl()
+
+# Create a Publisher instance 
+app = Publisher('quixote.demo')
+
+# (Optional step) Read a configuration file
+app.read_config("demo.conf")
+
+# Open the configured log files
+app.setup_logs()
+
+# Enter the publishing main loop
+app.publish_cgi()

Propchange: packages/quixote1/branches/upstream/current/demo/demo.cgi
------------------------------------------------------------------------------
    svn:executable = 

Added: packages/quixote1/branches/upstream/current/demo/demo.conf
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/demo.conf?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/demo.conf (added)
+++ packages/quixote1/branches/upstream/current/demo/demo.conf Mon May  8 19:16:16 2006
@@ -1,0 +1,9 @@
+# Config file for the Quixote demo.  This ensures that debug and error
+# messages will be logged, and that you will see full error information
+# in your browser.  (The default settings shipped in Quixote's config.py
+# module are for security rather than ease of testing/development.)
+
+ERROR_LOG = "/tmp/quixote-demo-error.log"
+DISPLAY_EXCEPTIONS = "plain"
+SECURE_ERRORS = 0
+FIX_TRAILING_SLASH = 0

Added: packages/quixote1/branches/upstream/current/demo/demo_scgi.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/demo_scgi.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/demo_scgi.py (added)
+++ packages/quixote1/branches/upstream/current/demo/demo_scgi.py Mon May  8 19:16:16 2006
@@ -1,0 +1,35 @@
+#!/www/python/bin/python
+
+# Example SCGI driver script for the Quixote demo: publishes the contents of
+# the quixote.demo package.  To use this script with mod_scgi and Apache
+# add the following section of your Apache config file:
+#
+# <Location "^/qdemo/">
+#       SCGIServer 127.0.0.1 4000
+#       SCGIHandler On
+# </Location>
+
+
+from scgi.quixote_handler import QuixoteHandler, main
+from quixote import enable_ptl, Publisher
+
+class DemoPublisher(Publisher):
+    def __init__(self, *args, **kwargs):
+        Publisher.__init__(self, *args, **kwargs)
+
+        # (Optional step) Read a configuration file
+        self.read_config("demo.conf")
+
+        # Open the configured log files
+        self.setup_logs()
+
+
+class DemoHandler(QuixoteHandler):
+    publisher_class = DemoPublisher
+    root_namespace = "quixote.demo"
+    prefix = "/qdemo"
+
+
+# Install the import hook that enables PTL modules.
+enable_ptl()
+main(DemoHandler)

Propchange: packages/quixote1/branches/upstream/current/demo/demo_scgi.py
------------------------------------------------------------------------------
    svn:executable = 

Added: packages/quixote1/branches/upstream/current/demo/demo_scgi.sh
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/demo_scgi.sh?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/demo_scgi.sh (added)
+++ packages/quixote1/branches/upstream/current/demo/demo_scgi.sh Mon May  8 19:16:16 2006
@@ -1,0 +1,52 @@
+#!/bin/sh
+#
+# Example init.d script for demo_scgi.py server
+
+PATH=/bin:/usr/bin:/usr/local/bin
+DAEMON=./demo_scgi.py
+PIDFILE=/var/tmp/demo_scgi.pid
+
+NAME=`basename $DAEMON`
+case "$1" in
+  start)
+    if [ -f $PIDFILE ]; then
+      if ps -p `cat $PIDFILE` > /dev/null 2>&1 ; then
+        echo "$NAME appears to be already running ($PIDFILE exists)."
+        exit 1
+      else
+        echo "$PIDFILE exists, but appears to be obsolete; removing it"
+        rm $PIDFILE
+      fi
+    fi 
+
+    echo -n "Starting $NAME: "
+    env -i PATH=$PATH \
+    	$DAEMON -P $PIDFILE -l /var/tmp/quixote-error.log
+    echo "done"
+    ;;
+
+  stop)
+    if [ -f $PIDFILE ]; then
+      echo -n "Stopping $NAME: "
+      kill `cat $PIDFILE`
+      echo "done"
+      if ps -p `cat $PIDFILE` > /dev/null 2>&1 ; then
+      	echo "$NAME is still running, not removing $PIDFILE"
+      else
+        rm -f $PIDFILE
+      fi
+    else
+      echo "$NAME does not appear to be running ($PIDFILE doesn't exist)."
+    fi
+    ;;
+
+  restart)
+    $0 stop
+    $0 start
+    ;;
+
+  *)
+    echo "Usage: $0 {start|stop|restart}"
+    exit 1
+    ;;
+esac

Propchange: packages/quixote1/branches/upstream/current/demo/demo_scgi.sh
------------------------------------------------------------------------------
    svn:executable = 

Added: packages/quixote1/branches/upstream/current/demo/forms.ptl
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/forms.ptl?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/forms.ptl (added)
+++ packages/quixote1/branches/upstream/current/demo/forms.ptl Mon May  8 19:16:16 2006
@@ -1,0 +1,114 @@
+# quixote.demo.forms
+#
+# Demonstrate the Quixote form class.
+
+__revision__ = "$Id: forms.ptl 25234 2004-09-30 17:36:19Z nascheme $"
+
+
+import time
+from quixote.form import Form
+
+class Topping:
+    def __init__(self, name, cost):
+        self.name = name
+        self.cost = cost # in cents
+
+    def __str__(self):
+        return "%s: $%.2f" % (self.name, self.cost/100.)
+
+    def __repr__(self):
+        return "<%s at %08x: %s>" % (self.__class__.__name__,
+                                     id(self), self)
+
+
+TOPPINGS = [Topping('cheese', 50),
+            Topping('pepperoni', 110),
+            Topping('green peppers', 75),
+            Topping('mushrooms', 90),
+            Topping('sausage', 100),
+            Topping('anchovies', 30),
+            Topping('onions', 25)]
+
+class FormDemo(Form):
+    def __init__(self):
+        # build form
+        Form.__init__(self)
+        self.add_widget("string", "name", title="Your Name",
+                        size=20, required=1)
+        self.add_widget("password", "password", title="Password",
+                        size=20, maxlength=20, required=1)
+        self.add_widget("checkbox", "confirm",
+                        title="Are you sure?")
+        self.add_widget("radiobuttons", "color", title="Eye color",
+                        allowed_values=['green', 'blue', 'brown', 'other'])
+        self.add_widget("single_select", "size", title="Size of pizza",
+                        value='medium',
+                        allowed_values=['tiny', 'small', 'medium', 'large',
+                                        'enormous'],
+                        descriptions=['Tiny (4")', 'Small (6")', 'Medium (10")',
+                                      'Large (14")', 'Enormous (18")'],
+                        size=1)
+        # select widgets can use any type of object, no just strings
+        self.add_widget("multiple_select", "toppings", title="Pizza Toppings",
+                        value=TOPPINGS[0],
+                        allowed_values=TOPPINGS,
+                        size=5)
+        self.add_widget('hidden', 'time', value=time.time())
+        self.add_submit_button("go", "Go!")
+
+
+    def render [html] (self, request, action_url):
+        """
+        <html>
+        <head><title>Quixote Form Demo</title></head>
+        <body>
+        <h1>Quixote Form Demo</h1>
+        """
+        Form.render(self, request, action_url)
+        """
+        </body>
+        </html>
+        """
+
+
+    def process(self, request):
+        # check data
+        form_data = Form.process(self, request)
+        if not form_data["name"]:
+            self.error["name"] = "You must provide your name."
+        if not form_data["password"]:
+            self.error["password"] = "You must provide a password."
+        return form_data
+
+
+    def action [html] (self, request, submit_button, form_data):
+        # The data has been submitted and verified.  Do something interesting
+        # with it (save it in DB, send email, etc.).  We'll just display it.
+        """
+        <html>
+        <head><title>Quixote Form Demo</title></head>
+        <body>
+        <h2>Form data:</h2>
+        <table>
+            <tr>
+              <th align=left>Name</th>
+              <th align=left>Type</th>
+              <th align=left>Value</th>
+            </tr>
+        """
+        for name, value in form_data.items():
+            '<tr>'
+            '  <td>%s</td>' % name
+            '  <td>%s</td>' % type(value).__name__
+            if value is None:
+                value = "<i>no value</i>"
+            '  <td>%s</td>' % value
+            '</tr>'
+        """
+        </table>
+        </body>
+        </html>
+        """
+
+def form_demo(request):
+    return FormDemo().handle(request)

Added: packages/quixote1/branches/upstream/current/demo/integer_ui.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/integer_ui.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/integer_ui.py (added)
+++ packages/quixote1/branches/upstream/current/demo/integer_ui.py Mon May  8 19:16:16 2006
@@ -1,0 +1,59 @@
+import sys
+from quixote.errors import TraversalError
+
+def fact(n):
+    f = 1L
+    while n > 1:
+        f *= n
+        n -= 1
+    return f
+
+class IntegerUI:
+
+    _q_exports = ["factorial", "prev", "next"]
+
+    def __init__(self, request, component):
+        try:
+            self.n = int(component)
+        except ValueError, exc:
+            raise TraversalError(str(exc))
+
+    def factorial(self, request):
+        if self.n > 10000:
+            sys.stderr.write("warning: possible denial-of-service attack "
+                             "(request for factorial(%d))\n" % self.n)
+        request.response.set_header("content-type", "text/plain")
+        return "%d! = %d\n" % (self.n, fact(self.n))
+
+    def _q_index(self, request):
+        return """\
+<html>
+<head><title>The Number %d</title></head>
+<body>
+You have selected the integer %d.<p>
+
+You can compute its <a href="factorial">factorial</a> (%d!)<p>
+
+Or, you can visit the web page for the
+<a href="../%d/">previous</a> or
+<a href="../%d/">next</a> integer.<p>
+
+Or, you can use redirects to visit the
+<a href="prev">previous</a> or
+<a href="next">next</a> integer.  This makes
+it a bit easier to generate this HTML code, but
+it's less efficient -- your browser has to go through
+two request/response cycles.  And someone still
+has to generate the URLs for the previous/next
+pages -- only now it's done in the <code>prev()</code>
+and <code>next()</code> methods for this integer.<p>
+
+</body>
+</html>
+""" % (self.n, self.n, self.n, self.n-1, self.n+1)
+
+    def prev(self, request):
+        return request.redirect("../%d/" % (self.n-1))
+
+    def next(self, request):
+        return request.redirect("../%d/" % (self.n+1))

Added: packages/quixote1/branches/upstream/current/demo/pages.ptl
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/pages.ptl?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/pages.ptl (added)
+++ packages/quixote1/branches/upstream/current/demo/pages.ptl Mon May  8 19:16:16 2006
@@ -1,0 +1,87 @@
+# quixote.demo.pages
+#
+# Provides miscellaneous pages for the Quixote demo (currently
+# just the index page).
+
+__revision__ = "$Id: pages.ptl 25234 2004-09-30 17:36:19Z nascheme $"
+
+
+def _q_index [html] (request):
+    print "debug message from the index page"
+    package_name = str('.').join(__name__.split(str('.'))[:-1])
+    module_name = __name__
+    module_file = __file__
+    """
+    <html>
+    <head><title>Quixote Demo</title></head>
+    <body>
+    <h1>Hello, world!</h1>
+
+    <p>(This page is generated by the index function for the
+    <code>%(package_name)s</code> package.  This index function is
+    actually a PTL template, <code>_q_index()</code>, in the
+    <code>%(module_name)s</code> PTL module.  Look in
+    <a href="srcdir/pages.ptl">%(module_file)s</a> to
+    see the source code for this PTL template.)
+    </p>
+
+    <p>To understand what's going on here, be sure to read the
+    <code>doc/demo.txt</code> file included with Quixote.</p>
+
+    <p>
+    Here are some other features of this demo:
+      <ul>
+        <li><a href="simple">simple</a>:
+            A Python function that generates a very simple document.
+        <li><a href="error">error</a>:
+            A Python function that raises an exception.
+        <li><a href="publish_error">publish_error</a>:
+            A Python function that raises
+            a <code>PublishError</code> exception.  This exception
+            will be caught by a <code>_q_exception_handler</code> method.
+        <li><a href="12/">12/</a>:
+            A Python object published through <code>_q_lookup()</code>.
+        <li><a href="12/factorial">12/factorial</a>:
+            A method on a published Python object.
+        <li><a href="dumpreq">dumpreq</a>:
+            Print out the contents of the HTTPRequest object.
+        <li><a href="widgets">widgets</a>:
+            Try out the Quixote widget classes.
+        <li><a href="form_demo">form demo</a>:
+            A Quixote form in action.
+        <li><a href="srcdir/">srcdir</a>:
+            A static directory published through Quixote.
+      </ul>
+    </p>
+    </body>
+    </html>
+    """ % vars()
+
+def _q_exception_handler [html] (request, exc):
+    """
+    <html>
+    <head><title>Quixote Demo</title></head>
+    <body>
+    <h1>Exception Handler</h1>
+    <p>A <code>_q_exception_handler</code> method, if present, is
+    called when a <code>PublishError</code> exception is raised.  It
+    can do whatever it likes to provide a friendly page.
+    </p>
+    <p>Here's the exception that was raised:<br />
+    <code>%s (%s)</code>.</p>
+    </body>
+    </html>
+    """ % (repr(exc), str(exc))
+
+def dumpreq [html] (request):
+    """
+    <html>
+    <head><title>HTTPRequest Object</title></head>
+    <body>
+    <h1>HTTPRequest Object</h1>
+    """
+    htmltext(request.dump_html())
+    """
+    </body>
+    </html>
+    """

Added: packages/quixote1/branches/upstream/current/demo/run_cgi.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/run_cgi.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/run_cgi.py (added)
+++ packages/quixote1/branches/upstream/current/demo/run_cgi.py Mon May  8 19:16:16 2006
@@ -1,0 +1,29 @@
+# This is a simple script that makes it easy to write one file CGI
+# applications that use Quixote.  To use, add the following line to the top
+# of your CGI script:
+#
+#  #!/usr/local/bin/python <some_path>/run_cgi.py
+#
+# Your CGI script becomes the root namespace and you may use PTL syntax
+# inside the script.  Errors will go to stderr and should end up in the server
+# error log.
+#
+# Note that this will be quite slow since the script will be recompiled on
+# every hit.  If you are using Apache with mod_fastcgi installed you should be
+# able to use .fcgi as an extension instead of .cgi and get much better
+# performance.  Maybe someday I will write code that caches the compiled
+# script on the filesystem. :-)
+
+import sys
+import new
+from quixote import enable_ptl, ptl_compile, Publisher
+
+enable_ptl()
+filename = sys.argv[1]
+root_code = ptl_compile.compile_template(open(filename), filename)
+root = new.module("root")
+root.__file__ = filename
+root.__name__ = "root"
+exec root_code in root.__dict__
+p = Publisher(root)
+p.publish_cgi()

Added: packages/quixote1/branches/upstream/current/demo/session.ptl
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/session.ptl?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/session.ptl (added)
+++ packages/quixote1/branches/upstream/current/demo/session.ptl Mon May  8 19:16:16 2006
@@ -1,0 +1,151 @@
+# quixote.demo.session
+#
+# Application code for the Quixote session management demo.
+# Driver script is session_demo.cgi.
+
+__revision__ = "$Id: session.ptl 23532 2004-02-20 22:38:57Z dbinger $"
+
+from quixote import get_session_manager
+from quixote.errors import QueryError
+
+_q_exports = ['login', 'logout']
+
+
+# Typical stuff for any Quixote app.
+
+def page_header [html] (title):
+    '''\
+<html>
+<head><title>%s</title></head>
+<body>
+<h1>%s</h1>
+''' % (title, title)
+
+def page_footer [html] ():
+    '''\
+</body>
+</html>
+'''
+
+
+# We include the login form on two separate pages, so it's been factored
+# out to a separate template.
+
+def login_form [html] ():
+    '''
+<form method="POST" action="login">
+  <input name="name" width=30>
+  <input type="submit">
+</form>
+'''
+
+
+def _q_index [html] (request):
+    page_header("Quixote Session Management Demo")
+
+    session = request.session
+
+    # All Quixote sessions have the ability to track the user's identity
+    # in session.user.  In this simple application, session.user is just
+    # a string which the user enters directly into this form.  In the
+    # real world, you would of course use a more sophisticated form of
+    # authentication (eg. enter a password over an SSL connection), and
+    # session.user might be an object with information about the user
+    # (their email address, password hash, preferences, etc.).
+
+    if session.user is None:
+        '''
+<p>You haven\'t introduced yourself yet.<br>
+Please tell me your name:
+'''
+        login_form()
+    else:
+        '<p>Hello, %s.  Good to see you again.</p>\n' % session.user
+
+    '''
+You can now:
+<ul>
+'''
+    if session.user:
+        '  <li><a href="login">become someone else</a> (login again)\n'
+    '  <li><a href="logout">leave this session</a> (logout)\n'
+    '</ul>\n'
+
+    # The other piece of information we track here is the number of
+    # requests made in each session; report that information for the
+    # current session here.
+    """\
+<p>Your session is <code>%s</code><br>
+You have made %d request(s) (including this one) in this session.</p>
+""" % (repr(session), session.num_requests)
+
+    # The session manager is the collection of all sessions managed by
+    # the current publisher, ie. in this process.  Poking around in the
+    # session manager is not something you do often, but it's really
+    # handy for debugging/site administration.
+    mgr = get_session_manager()
+    session_ids = mgr.keys()
+    '''
+<p>The current session manager is <code>%s</code><br>
+It has %d session(s) in it right now:</p>
+<table border=1>
+  <tr><th>session id</th><th>user</th><th>num requests</th></tr>
+''' % (repr(mgr), len(session_ids))
+    for sess_id in session_ids:
+        sess = mgr[sess_id]
+        ('  <tr><td>%s</td><td>%s</td><td>%d</td>\n'
+         % (sess.id,
+            sess.user and sess.user or "<i>none</i>",
+            sess.num_requests))
+    '<table>\n'
+
+    page_footer()
+
+
+# The login() template has two purposes: to display a page with just a
+# login form, and to process the login form submitted either from the
+# index page or from login() itself.  This is a fairly common idiom in
+# Quixote (just as it's a fairly common idiom with CGI scripts -- it's
+# just cleaner with Quixote).
+
+def login [html] (request):
+    page_header("Quixote Session Demo: Login")
+    session = request.session
+
+    # We seem to be processing the login form.
+    if request.form:
+        user = request.form.get("name")
+        if not user:
+            raise QueryError("no user name supplied")
+
+        session.user = user
+
+        '<p>Welcome, %s!  Thank you for logging in.</p>\n' % user
+        '<a href="./">back to start</a>\n'
+
+    # No form data to process, so generate the login form instead.  When
+    # the user submits it, we'll return to this template and take the
+    # above branch.
+    else:
+        '<p>Please enter your name here:</p>\n'
+        login_form()
+
+    page_footer()
+
+
+# logout() just expires the current session, ie. removes it from the
+# session manager and instructs the client to forget about the session
+# cookie.  The only code necessary is the call to
+# SessionManager.expire_session() -- the rest is just user interface.
+
+def logout [html] (request):
+    page_header("Quixote Session Demo: Logout")
+    session = request.session
+    if session.user:
+        '<p>Goodbye, %s.  See you around.</p>\n' % session.user
+
+    get_session_manager().expire_session(request)
+
+    '<p>Your session has been expired.</p>\n'
+    '<p><a href="./">start over</a></p>\n'
+    page_footer()

Added: packages/quixote1/branches/upstream/current/demo/session_demo.cgi
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/session_demo.cgi?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/session_demo.cgi (added)
+++ packages/quixote1/branches/upstream/current/demo/session_demo.cgi Mon May  8 19:16:16 2006
@@ -1,0 +1,173 @@
+#!/www/python/bin/python
+
+# Demonstrate Quixote session management, along with the application
+# code in session.ptl (aka quixote.demo.session).
+
+__revision__ = "$Id: session_demo.cgi 21182 2003-03-17 21:46:52Z gward $"
+
+import os
+from stat import ST_MTIME
+from time import time
+from cPickle import load, dump
+from quixote import enable_ptl
+from quixote.session import Session, SessionManager
+from quixote.publish import SessionPublisher
+
+class DemoSession (Session):
+    """
+    Session class that tracks the number of requests made within a
+    session.
+    """
+
+    def __init__ (self, request, id):
+        Session.__init__(self, request, id)
+        self.num_requests = 0
+
+    def start_request (self, request):
+
+        # This is called from the main object publishing loop whenever
+        # we start processing a new request.  Obviously, this is a good
+        # place to track the number of requests made.  (If we were
+        # interested in the number of *successful* requests made, then
+        # we could override finish_request(), which is called by
+        # the publisher at the end of each successful request.)
+
+        Session.start_request(self, request)
+        self.num_requests += 1
+
+    def has_info (self):
+
+        # Overriding has_info() is essential but non-obvious.  The
+        # session manager uses has_info() to know if it should hang on
+        # to a session object or not: if a session is "dirty", then it
+        # must be saved.  This prevents saving sessions that don't need
+        # to be saved, which is especially important as a defensive
+        # measure against clients that don't handle cookies: without it,
+        # we might create and store a new session object for every
+        # request made by such clients.  With has_info(), we create the
+        # new session object every time, but throw it away unsaved as
+        # soon as the request is complete.
+        # 
+        # (Of course, if you write your session class such that
+        # has_info() always returns true after a request has been
+        # processed, you're back to the original problem -- and in fact,
+        # this class *has* been written that way, because num_requests
+        # is incremented on every request, which makes has_info() return
+        # true, which makes SessionManager always store the session
+        # object.  In a real application, think carefully before putting
+        # data in a session object that causes has_info() to return
+        # true.)
+
+        return (self.num_requests > 0) or Session.has_info(self)
+
+    is_dirty = has_info
+
+
+class DirMapping:
+    """A mapping object that stores values as individual pickle
+    files all in one directory.  You wouldn't want to use this in
+    production unless you're using a filesystem optimized for
+    handling large numbers of small files, like ReiserFS.  However,
+    it's pretty easy to implement and understand, it doesn't require
+    any external libraries, and it's really easy to browse the
+    "database".
+    """
+
+    def __init__ (self, save_dir=None):
+        self.set_save_dir(save_dir)
+        self.cache = {}
+        self.cache_time = {}
+
+    def set_save_dir (self, save_dir):
+        self.save_dir = save_dir
+        if save_dir and not os.path.isdir(save_dir):
+            os.mkdir(save_dir, 0700)
+    
+    def keys (self):
+        return os.listdir(self.save_dir)
+
+    def values (self):
+        # This is pretty expensive!
+        return [self[id] for id in self.keys()]
+
+    def items (self):
+        return [(id, self[id]) for id in self.keys()]
+
+    def _gen_filename (self, session_id):
+        return os.path.join(self.save_dir, session_id)
+
+    def __getitem__ (self, session_id):
+
+        filename = self._gen_filename(session_id)
+        if (self.cache.has_key(session_id) and
+            os.stat(filename)[ST_MTIME] <= self.cache_time[session_id]):
+            return self.cache[session_id]
+
+        if os.path.exists(filename):
+            try:
+                file = open(filename, "rb")
+                try:
+                    print "loading session from %r" % file
+                    session = load(file)
+                    self.cache[session_id] = session
+                    self.cache_time[session_id] = time()
+                    return session
+                finally:
+                    file.close()
+            except IOError, err:
+                raise KeyError(session_id,
+                               "error reading session from %s: %s"
+                               % (filename, err))
+        else:
+            raise KeyError(session_id,
+                           "no such file %s" % filename)
+
+    def get (self, session_id, default=None):
+        try:
+            return self[session_id]
+        except KeyError:
+            return default
+
+    def has_key (self, session_id):
+        return os.path.exists(self._gen_filename(session_id))
+
+    def __setitem__ (self, session_id, session):
+        filename = self._gen_filename(session.id)
+        file = open(filename, "wb")
+        print "saving session to %s" % file
+        dump(session, file, 1)
+        file.close()
+
+        self.cache[session_id] = session
+        self.cache_time[session_id] = time()
+
+    def __delitem__ (self, session_id):
+        filename = self._gen_filename(session_id)
+        if os.path.exists(filename):
+            os.remove(filename)
+            if self.cache.has_key(session_id):
+                del self.cache[session_id]
+                del self.cache_time[session_id]
+        else:
+            raise KeyError(session_id, "no such file: %s" % filename)
+
+
+# This is mostly the same as the standard boilerplate for any Quixote
+# driver script.  The main difference is that we have to instantiate a
+# session manager, and use SessionPublisher instead of the normal
+# Publisher class.  Just like demo.cgi, we use demo.conf to setup log
+# files and ensure that error messages are more informative than secure.
+
+# You can use the 'shelve' module to create an alternative persistent
+# mapping to the DirMapping class above.
+#import shelve
+#sessions = shelve.open("/tmp/quixote-sessions")
+
+enable_ptl()
+sessions = DirMapping(save_dir="/tmp/quixote-session-demo")
+session_mgr = SessionManager(session_class=DemoSession,
+                             session_mapping=sessions)
+app = SessionPublisher('quixote.demo.session', session_mgr=session_mgr)
+app.read_config("demo.conf")
+app.setup_logs()
+app.publish_cgi()

Propchange: packages/quixote1/branches/upstream/current/demo/session_demo.cgi
------------------------------------------------------------------------------
    svn:executable = 

Added: packages/quixote1/branches/upstream/current/demo/upload.cgi
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/upload.cgi?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/upload.cgi (added)
+++ packages/quixote1/branches/upstream/current/demo/upload.cgi Mon May  8 19:16:16 2006
@@ -1,0 +1,64 @@
+#!/www/python/bin/python
+
+# Simple demo of HTTP upload with Quixote.  Also serves as an example
+# of how to put a (simple) Quixote application into a single file.
+
+__revision__ = "$Id: upload.cgi 21182 2003-03-17 21:46:52Z gward $"
+
+import os
+import stat
+from quixote import Publisher
+from quixote.html import html_quote
+
+_q_exports = ['receive']
+
+def header (title):
+    return '''\
+      <html><head><title>%s</title></head>
+      <body>
+      ''' % title
+
+def footer ():
+    return '</body></html>\n'
+
+def _q_index (request):
+    return header("Quixote Upload Demo") + '''\
+      <form enctype="multipart/form-data"
+            method="POST" 
+            action="receive">
+        Your name:<br>
+        <input type="text" name="name"><br>
+        File to upload:<br>
+        <input type="file" name="upload"><br>
+        <input type="submit" value="Upload">
+      </form>
+      ''' + footer()
+
+def receive (request):
+    result = []
+    name = request.form.get("name")
+    if name:
+        result.append("<p>Thanks, %s!</p>" % html_quote(name))
+
+    upload = request.form.get("upload")
+    size = os.stat(upload.tmp_filename)[stat.ST_SIZE]
+    if not upload.base_filename or size == 0:
+        title = "Empty Upload"
+        result.append("<p>You appear not to have uploaded anything.</p>")
+    else:
+        title = "Upload Received"
+        result.append("<p>You just uploaded <code>%s</code> (%d bytes)<br>"
+                      % (html_quote(upload.base_filename), size))
+        result.append("which is temporarily stored in <code>%s</code>.</p>"
+                      % html_quote(upload.tmp_filename))
+
+    return header(title) + "\n".join(result) + footer()
+
+def main ():
+    pub = Publisher('__main__')
+    pub.read_config("demo.conf")
+    pub.configure(UPLOAD_DIR="/tmp/quixote-upload-demo")
+    pub.setup_logs()
+    pub.publish_cgi()
+
+main()

Propchange: packages/quixote1/branches/upstream/current/demo/upload.cgi
------------------------------------------------------------------------------
    svn:executable = 

Added: packages/quixote1/branches/upstream/current/demo/widgets.ptl
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/widgets.ptl?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/widgets.ptl (added)
+++ packages/quixote1/branches/upstream/current/demo/widgets.ptl Mon May  8 19:16:16 2006
@@ -1,0 +1,129 @@
+# quixote.demo.widgets
+#
+# Demonstrate the Quixote widget classes.
+
+__revision__ = "$Id: widgets.ptl 23532 2004-02-20 22:38:57Z dbinger $"
+
+
+import time
+from quixote.form import widget
+
+
+def widgets(request):
+
+    # Whether we are generating or processing the form with these
+    # widgets, we need all the widget objects -- so create them now.
+    widgets = {}
+    widgets['name'] = widget.StringWidget('name', size=20)
+    widgets['password'] = widget.PasswordWidget(
+        'password', size=20, maxlength=20)
+    widgets['confirm'] = widget.CheckboxWidget('confirm')
+    widgets['colour'] = widget.RadiobuttonsWidget(
+        'colour', allowed_values=['green', 'blue', 'brown', 'other'])
+    widgets['size'] = widget.SingleSelectWidget(
+        'size', value='medium',
+        allowed_values=['tiny', 'small', 'medium', 'large', 'enormous'],
+        descriptions=['Tiny (4")', 'Small (6")', 'Medium (10")',
+                      'Large (14")', 'Enormous (18")'])
+    widgets['toppings'] = widget.MultipleSelectWidget(
+        'toppings', value=['cheese'],
+        allowed_values=['cheese', 'pepperoni', 'green peppers', 'mushrooms',
+                        'sausage', 'anchovies', 'onions'],
+        size=5)
+    widgets['time'] = widget.HiddenWidget('time', value=time.time())
+
+    if request.form:
+        # If we have some form data, then we're being invoked to process
+        # the form; call process_widgets() to do the real work.  We only
+        # handle it in this page to conserve urls: the "widget" url both
+        # generates the form and processes it, and behaves very
+        # differently depending on whether there are form variables
+        # present when it is invoked.
+        return process_widgets(request, widgets)
+    else:
+        # No form data, so generate the form from scratch.  When the
+        # user submits it, we'll come back to this page, but
+        # request.form won't be empty that time -- so we'll call
+        # process_widgets() instead.
+        return render_widgets(request, widgets)
+
+
+def render_widgets [html] (request, widgets):
+    """\
+<html>
+<head><title>Quixote Widget Demo</title></head>
+<body>
+<h1>Quixote Widget Demo</h1>
+"""
+
+    """\
+<form method="POST" action="widgets">
+<table>
+"""
+    row_fmt = '''\
+  <tr>
+    <th align="left">%s</th>
+    <td colspan=2>%s</td>
+  </tr>
+'''
+    row_fmt % ("Your name", widgets['name'].render(request))
+    row_fmt % ("Password", widgets['password'].render(request))
+    row_fmt % ("Are you sure?", widgets['confirm'].render(request))
+    row_fmt % ("Eye colour", widgets['colour'].render(request))
+
+    '''\
+  <tr>
+    <th align="left" valign="top">Select a<br>size of pizza</th>
+    <td valign="top">%s</td>
+    <th align="left" valign="top">And some<br>pizza toppings</th>
+    <td valign="top">%s</td>
+  </tr>
+''' % (widgets['size'].render(request),
+       widgets['toppings'].render(request))
+
+    widgets['time'].render(request)
+
+    '</table>\n'
+    widget.SubmitButtonWidget(value="Submit").render(request)
+    '''\
+</form>
+</body>
+</html>
+'''
+
+def process_widgets [html] (request, widgets):
+    """\
+<html>
+<head><title>Quixote Widget Demo</title></head>
+<body>
+<h2>You entered the following values:</h2>
+<table>
+"""
+
+    row_fmt = '  <tr><th align="left">%s</th><td>%s</td></tr>\n'
+    fallback = '<i>nothing</i>'
+    row_fmt % ("name",
+               widgets['name'].parse(request) or fallback)
+    row_fmt % ("password",
+               widgets['password'].parse(request) or fallback)
+    row_fmt % ("confirmation",
+               widgets['confirm'].parse(request))
+    row_fmt % ("eye colour",
+               widgets['colour'].parse(request) or fallback)
+    row_fmt % ("pizza size",
+               widgets['size'].parse(request) or fallback)
+    toppings = widgets['toppings'].parse(request)
+    row_fmt % ("pizza toppings",
+               toppings and (", ".join(toppings)) or fallback)
+
+    '</table>\n'
+
+    form_time = float(widgets['time'].parse(request))
+    now = time.time()
+    ("<p>It took you %.1f sec to fill out and submit the form</p>\n"
+     % (now - form_time))
+
+    """\
+</body>
+</html>
+"""

Added: packages/quixote1/branches/upstream/current/doc/INSTALL.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/INSTALL.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/INSTALL.html (added)
+++ packages/quixote1/branches/upstream/current/doc/INSTALL.html Mon May  8 19:16:16 2006
@@ -1,0 +1,55 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Installing Quixote</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="installing-quixote">
+<h1 class="title">Installing Quixote</h1>
+<div class="section" id="best-case-scenario">
+<h1><a name="best-case-scenario">Best-case scenario</a></h1>
+<p>If you are using Python 2.2 or later, and have never installed Quixote
+with your current version of Python, you're in luck.  Just run</p>
+<blockquote>
+python setup.py install</blockquote>
+<p>and you're done.  Proceed to the demo documentation to learn how to get
+Quixote working.</p>
+<p>If you're using an older Python, or if you're upgrading from an older
+Quixote version, read on.</p>
+</div>
+<div class="section" id="upgrading-from-an-older-quixote-version">
+<h1><a name="upgrading-from-an-older-quixote-version">Upgrading from an older Quixote version</a></h1>
+<p>We strongly recommend that you remove any old Quixote version before
+installing a new one.  First, find out where your old Quixote
+installation is:</p>
+<blockquote>
+python -c &quot;import os, quixote; print os.path.dirname(quixote.__file__)&quot;</blockquote>
+<p>and then remove away the reported directory.  (If the import fails, then
+you don't have an existing Quixote installation.)</p>
+<p>Then proceed as above:</p>
+<blockquote>
+python setup.py install</blockquote>
+</div>
+<div class="section" id="using-quixote-with-python-2-0-or-2-1">
+<h1><a name="using-quixote-with-python-2-0-or-2-1">Using Quixote with Python 2.0 or 2.1</a></h1>
+<p>If you are using Python 2.0 or 2.1 then you need to install the
+<tt class="literal"><span class="pre">compiler</span></tt> package from the Python source distribution.  The
+<tt class="literal"><span class="pre">compiler</span></tt> package is for parsing Python source code and generating
+Python bytecode, and the PTL compiler is built on top of it.  With
+Python 2.0 and 2.1, this package was included in Python's source
+distribution, but not installed as part of the standard library.</p>
+<p>Assuming your Python source distribution is in <tt class="literal"><span class="pre">/tmp/Python-2.1.2</span></tt>:</p>
+<pre class="literal-block">
+cd /tmp/Python-2.1.2/Tools/compiler
+python setup.py install
+</pre>
+<p>(Obviously, you'll have to adjust this to reflect your Python version
+and where you kept the source distribution after installing Python.)</p>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/INSTALL.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/INSTALL.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/INSTALL.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/INSTALL.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,53 @@
+Installing Quixote
+==================
+
+Best-case scenario
+------------------
+
+If you are using Python 2.2 or later, and have never installed Quixote
+with your current version of Python, you're in luck.  Just run
+
+  python setup.py install
+
+and you're done.  Proceed to the demo documentation to learn how to get
+Quixote working.
+
+If you're using an older Python, or if you're upgrading from an older
+Quixote version, read on.
+
+
+Upgrading from an older Quixote version
+---------------------------------------
+
+We strongly recommend that you remove any old Quixote version before
+installing a new one.  First, find out where your old Quixote
+installation is:
+
+  python -c "import os, quixote; print os.path.dirname(quixote.__file__)"
+
+and then remove away the reported directory.  (If the import fails, then
+you don't have an existing Quixote installation.)
+
+Then proceed as above:
+
+  python setup.py install
+
+
+Using Quixote with Python 2.0 or 2.1
+------------------------------------
+
+If you are using Python 2.0 or 2.1 then you need to install the
+``compiler`` package from the Python source distribution.  The
+``compiler`` package is for parsing Python source code and generating
+Python bytecode, and the PTL compiler is built on top of it.  With
+Python 2.0 and 2.1, this package was included in Python's source
+distribution, but not installed as part of the standard library.
+
+Assuming your Python source distribution is in ``/tmp/Python-2.1.2``::
+
+    cd /tmp/Python-2.1.2/Tools/compiler
+    python setup.py install
+
+(Obviously, you'll have to adjust this to reflect your Python version
+and where you kept the source distribution after installing Python.)
+

Added: packages/quixote1/branches/upstream/current/doc/Makefile
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/Makefile?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/Makefile (added)
+++ packages/quixote1/branches/upstream/current/doc/Makefile Mon May  8 19:16:16 2006
@@ -1,0 +1,31 @@
+#
+# Makefile to convert Quixote docs to HTML
+#
+# $Id: Makefile 20217 2003-01-16 20:51:53Z akuchlin $
+#
+
+TXT_FILES = $(wildcard *.txt)
+HTML_FILES = $(filter-out ZPL%,$(TXT_FILES:%.txt=%.html))
+
+RST2HTML = /www/python/bin/rst2html
+RST2HTML_OPTS = -o us-ascii
+
+DEST_HOST = staging.mems-exchange.org
+DEST_DIR = /www/www-docroot/software/quixote/doc
+
+SS = default.css
+
+%.html: %.txt
+	$(RST2HTML) $(RST2HTML_OPTS) $< $@
+
+all: $(HTML_FILES)
+
+clean:
+	rm -f $(HTML_FILES)
+
+install:
+	rsync -vptgo *.html $(SS) $(DEST_HOST):$(DEST_DIR)
+
+local-install:
+	dir=`pwd` ; \
+	cd $(DEST_DIR) && ln -sf $$dir/*.html $$dir/$(SS) .

Added: packages/quixote1/branches/upstream/current/doc/PTL.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/PTL.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/PTL.html (added)
+++ packages/quixote1/branches/upstream/current/doc/PTL.html Mon May  8 19:16:16 2006
@@ -1,0 +1,257 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>PTL: Python Template Language</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="ptl-python-template-language">
+<h1 class="title">PTL: Python Template Language</h1>
+<div class="section" id="introduction">
+<h1><a name="introduction">Introduction</a></h1>
+<p>PTL is the templating language used by Quixote.  Most web templating
+languages embed a real programming language in HTML, but PTL inverts
+this model by merely tweaking Python to make it easier to generate
+HTML pages (or other forms of text).  In other words, PTL is basically
+Python with a novel way to specify function return values.</p>
+<p>Specifically, a PTL template is designated by inserting a <tt class="literal"><span class="pre">[plain]</span></tt>
+or <tt class="literal"><span class="pre">[html]</span></tt> modifier after the function name.  The value of
+expressions inside templates are kept, not discarded.  If the type is
+<tt class="literal"><span class="pre">[html]</span></tt> then non-literal strings are passed through a function that
+escapes HTML special characters.</p>
+</div>
+<div class="section" id="plain-text-templates">
+<h1><a name="plain-text-templates">Plain text templates</a></h1>
+<p>Here's a sample plain text template:</p>
+<pre class="literal-block">
+def foo [plain] (x, y = 5):
+    &quot;This is a chunk of static text.&quot;
+    greeting = &quot;hello world&quot; # statement, no PTL output
+    print 'Input values:', x, y
+    z = x + y
+    &quot;&quot;&quot;You can plug in variables like x (%s)
+in a variety of ways.&quot;&quot;&quot; % x
+
+    &quot;\n\n&quot;
+    &quot;Whitespace is important in generated text.\n&quot;
+    &quot;z = &quot;; z
+    &quot;, but y is &quot;
+    y
+    &quot;.&quot;
+</pre>
+<p>Obviously, templates can't have docstrings, but otherwise they follow
+Python's syntactic rules: indentation indicates scoping, single-quoted
+and triple-quoted strings can be used, the same rules for continuing
+lines apply, and so forth.  PTL also follows all the expected semantics
+of normal Python code: so templates can have parameters, and the
+parameters can have default values, be treated as keyword arguments,
+etc.</p>
+<p>The difference between a template and a regular Python function is that
+inside a template the result of expressions are saved as the return
+value of that template.  Look at the first part of the example again:</p>
+<pre class="literal-block">
+def foo [plain] (x, y = 5):
+    &quot;This is a chunk of static text.&quot;
+    greeting = &quot;hello world&quot; # statement, no PTL output
+    print 'Input values:', x, y
+    z = x + y
+    &quot;&quot;&quot;You can plug in variables like x (%s)
+in a variety of ways.&quot;&quot;&quot; % x
+</pre>
+<p>Calling this template with <tt class="literal"><span class="pre">foo(1,</span> <span class="pre">2)</span></tt> results in the following
+string:</p>
+<pre class="literal-block">
+This is a chunk of static text.You can plug in variables like x (1)
+in a variety of ways.
+</pre>
+<p>Normally when Python evaluates expressions inside functions, it just
+discards their values, but in a <tt class="literal"><span class="pre">[plain]</span></tt> PTL template the value is
+converted to a string using <tt class="literal"><span class="pre">str()</span></tt> and appended to the template's
+return value.  There's a single exception to this rule: <tt class="literal"><span class="pre">None</span></tt> is the
+only value that's ever ignored, adding nothing to the output.  (If this
+weren't the case, calling methods or functions that return <tt class="literal"><span class="pre">None</span></tt>
+would require assigning their value to a variable.  You'd have to write
+<tt class="literal"><span class="pre">dummy</span> <span class="pre">=</span> <span class="pre">list.sort()</span></tt> in PTL code, which would be strange and
+confusing.)</p>
+<p>The initial string in a template isn't treated as a docstring, but is
+just incorporated in the generated output; therefore, templates can't
+have docstrings.  No whitespace is ever automatically added to the
+output, resulting in <tt class="literal"><span class="pre">...text.You</span> <span class="pre">can</span> <span class="pre">...</span></tt> from the example.  You'd
+have to add an extra space to one of the string literals to correct
+this.</p>
+<p>The assignment to the <tt class="literal"><span class="pre">greeting</span></tt> local variable is a statement, not an
+expression, so it doesn't return a value and produces no output.  The
+output from the <tt class="literal"><span class="pre">print</span></tt> statement will be printed as usual, but won't
+go into the string generated by the template.  Quixote directs standard
+output into Quixote's debugging log; if you're using PTL on its own, you
+should consider doing something similar.  <tt class="literal"><span class="pre">print</span></tt> should never be used
+to generate output returned to the browser, only for adding debugging
+traces to a template.</p>
+<p>Inside templates, you can use all of Python's control-flow statements:</p>
+<pre class="literal-block">
+def numbers [plain] (n):
+    for i in range(n):
+        i
+        &quot; &quot; # PTL does not add any whitespace
+</pre>
+<p>Calling <tt class="literal"><span class="pre">numbers(5)</span></tt> will return the string <tt class="literal"><span class="pre">&quot;1</span> <span class="pre">2</span> <span class="pre">3</span> <span class="pre">4</span> <span class="pre">5</span> <span class="pre">&quot;</span></tt>.  You can
+also have conditional logic or exception blocks:</p>
+<pre class="literal-block">
+def international_hello [plain] (language):
+    if language == &quot;english&quot;:
+        &quot;hello&quot;
+    elif language == &quot;french&quot;:
+        &quot;bonjour&quot;
+    else:
+        raise ValueError, &quot;I don't speak %s&quot; % language
+</pre>
+</div>
+<div class="section" id="html-templates">
+<h1><a name="html-templates">HTML templates</a></h1>
+<p>Since PTL is usually used to generate HTML documents, an <tt class="literal"><span class="pre">[html]</span></tt>
+template type has been provided to make generating HTML easier.</p>
+<p>A common error when generating HTML is to grab data from the browser
+or from a database and incorporate the contents without escaping
+special characters such as '&lt;' and '&amp;'.  This leads to a class of
+security bugs called &quot;cross-site scripting&quot; bugs, where a hostile user
+can insert arbitrary HTML in your site's output that can link to other
+sites or contain JavaScript code that does something nasty (say,
+popping up 10,000 browser windows).</p>
+<p>Such bugs occur because it's easy to forget to HTML-escape a string,
+and forgetting it in just one location is enough to open a hole.  PTL
+offers a solution to this problem by being able to escape strings
+automatically when generating HTML output, at the cost of slightly
+diminished performance (a few percent).</p>
+<p>Here's how this feature works.  PTL defines a class called
+<tt class="literal"><span class="pre">htmltext</span></tt> that represents a string that's already been HTML-escaped
+and can be safely sent to the client.  The function <tt class="literal"><span class="pre">htmlescape(string)</span></tt>
+is used to escape data, and it always returns an <tt class="literal"><span class="pre">htmltext</span></tt>
+instance.  It does nothing if the argument is already <tt class="literal"><span class="pre">htmltext</span></tt>.
+Both <tt class="literal"><span class="pre">htmltext</span></tt> and <tt class="literal"><span class="pre">htmlescape</span></tt> are available from the global
+namespace in PTL modules.</p>
+<p>If a template function is declared <tt class="literal"><span class="pre">[html]</span></tt> instead of <tt class="literal"><span class="pre">[text]</span></tt>
+then two things happen.  First, all literal strings in the function
+become instances of <tt class="literal"><span class="pre">htmltext</span></tt> instead of Python's <tt class="literal"><span class="pre">str</span></tt>.  Second,
+the values of expressions are passed through <tt class="literal"><span class="pre">htmlescape()</span></tt> instead
+of <tt class="literal"><span class="pre">str()</span></tt>.</p>
+<p><tt class="literal"><span class="pre">htmltext</span></tt> type is like the <tt class="literal"><span class="pre">str</span></tt> type except that operations
+combining strings and <tt class="literal"><span class="pre">htmltext</span></tt> instances will result in the string
+being passed through <tt class="literal"><span class="pre">htmlescape()</span></tt>.  For example:</p>
+<pre class="literal-block">
+&gt;&gt;&gt; from quixote.html import htmltext
+&gt;&gt;&gt; htmltext('a') + 'b'
+&lt;htmltext 'ab'&gt;
+&gt;&gt;&gt; 'a' + htmltext('b')
+&lt;htmltext 'ab'&gt;
+&gt;&gt;&gt; htmltext('a%s') % 'b'
+&lt;htmltext 'ab'&gt;
+&gt;&gt;&gt; response = 'green eggs &amp; ham'
+&gt;&gt;&gt; htmltext('The response was: %s') % response
+&lt;htmltext 'The response was: green eggs &amp;amp; ham'&gt;
+</pre>
+<p>Note that calling <tt class="literal"><span class="pre">str()</span></tt> strips the <tt class="literal"><span class="pre">htmltext</span></tt> type and should be
+avoided since it usually results in characters being escaped more than
+once.  While <tt class="literal"><span class="pre">htmltext</span></tt> behaves much like a regular string, it is
+sometimes necessary to insert a <tt class="literal"><span class="pre">str()</span></tt> inside a template in order
+to obtain a genuine string.  For example, the <tt class="literal"><span class="pre">re</span></tt> module requires
+genuine strings.  We have found that explicit calls to <tt class="literal"><span class="pre">str()</span></tt> can
+often be avoided by splitting some code out of the template into a
+helper function written in regular Python.</p>
+<p>It is also recommended that the <tt class="literal"><span class="pre">htmltext</span></tt> constructor be used as
+sparingly as possible.  The reason is that when using the htmltext
+feature of PTL, explicit calls to <tt class="literal"><span class="pre">htmltext</span></tt> become the most likely
+source of cross-site scripting holes.  Calling <tt class="literal"><span class="pre">htmltext</span></tt> is like
+saying &quot;I am absolutely sure this piece of data cannot contain malicious
+HTML code injected by a user.  Don't escape HTML special characters
+because I want them.&quot;</p>
+<p>Note that literal strings in template functions declared with
+<tt class="literal"><span class="pre">[html]</span></tt> are htmltext instances, and therefore won't be escaped.
+You'll only need to use <tt class="literal"><span class="pre">htmltext</span></tt> when HTML markup comes from
+outside the template.  For example, if you want to include a file
+containing HTML:</p>
+<pre class="literal-block">
+def output_file [html] ():
+    '&lt;html&gt;&lt;body&gt;' # does not get escaped
+    htmltext(open(&quot;myfile.html&quot;).read())
+    '&lt;/body&gt;&lt;/html&gt;'
+</pre>
+<p>In the common case, templates won't be dealing with HTML markup from
+external sources, so you can write straightforward code.  Consider
+this function to generate the contents of the <tt class="literal"><span class="pre">HEAD</span></tt> element:</p>
+<pre class="literal-block">
+def meta_tags [html] (title, description):
+    '&lt;title&gt;%s&lt;/title&gt;' % title
+    '&lt;meta name=&quot;description&quot; content=&quot;%s&quot;&gt;\n' % description
+</pre>
+<p>There are no calls to <tt class="literal"><span class="pre">htmlescape()</span></tt> at all, but string literals
+such as <tt class="literal"><span class="pre">&lt;title&gt;%s&lt;/title&gt;</span></tt> have all be turned into <tt class="literal"><span class="pre">htmltext</span></tt>
+instances, so the string variables will be automatically escaped:</p>
+<pre class="literal-block">
+&gt;&gt;&gt; t.meta_tags('Catalog', 'A catalog of our cool products')
+&lt;htmltext '&lt;title&gt;Catalog&lt;/title&gt;
+  &lt;meta name=&quot;description&quot; content=&quot;A catalog of our cool products&quot;&gt;\n'&gt;
+&gt;&gt;&gt; t.meta_tags('Dissertation on &lt;HEAD&gt;', 
+...             'Discusses the &quot;LINK&quot; and &quot;META&quot; tags')
+&lt;htmltext '&lt;title&gt;Dissertation on &amp;lt;HEAD&amp;gt;&lt;/title&gt;
+  &lt;meta name=&quot;description&quot; 
+   content=&quot;Discusses the &amp;quot;LINK&amp;quot; and &amp;quot;META&amp;quot; tags&quot;&gt;\n'&gt;
+&gt;&gt;&gt;
+</pre>
+<p>Note how the title and description have had HTML-escaping applied to them.
+(The output has been manually pretty-printed to be more readable.)</p>
+<p>Once you start using <tt class="literal"><span class="pre">htmltext</span></tt> in one of your templates, mixing
+plain and HTML templates is tricky because of <tt class="literal"><span class="pre">htmltext</span></tt>'s automatic
+escaping; plain templates that generate HTML tags will be
+double-escaped.  One approach is to just use HTML templates throughout
+your application.  Alternatively you can use <tt class="literal"><span class="pre">str()</span></tt> to convert
+<tt class="literal"><span class="pre">htmltext</span></tt> instances to regular Python strings; just be sure the
+resulting string isn't HTML-escaped again.</p>
+<p>Two implementations of <tt class="literal"><span class="pre">htmltext</span></tt> are provided, one written in pure
+Python and a second one implemented as a C extension.  Both versions
+have seen production use.</p>
+</div>
+<div class="section" id="ptl-modules">
+<h1><a name="ptl-modules">PTL modules</a></h1>
+<p>PTL templates are kept in files with the extension .ptl.  Like Python
+files, they are byte-compiled on import, and the byte-code is written to
+a compiled file with the extension <tt class="literal"><span class="pre">.ptlc</span></tt>.  Since vanilla Python
+doesn't know anything about PTL, Quixote provides an import hook to let
+you import PTL files just like regular Python modules.  The standard way
+to install this import hook is by calling the <tt class="literal"><span class="pre">enable_ptl()</span></tt> function:</p>
+<pre class="literal-block">
+from quixote import enable_ptl
+enable_ptl()
+</pre>
+<p>(Note: if you're using ZODB, always import ZODB <em>before</em> installing the
+PTL import hook.  There's some interaction which causes importing the
+TimeStamp module to fail when the PTL import hook is installed; we
+haven't debugged the problem.)</p>
+<p>Once the import hook is installed, PTL files can be imported as if they
+were Python modules.  If all the example templates shown here were put
+into a file named <tt class="literal"><span class="pre">foo.ptl</span></tt>, you could then write Python code that did
+this:</p>
+<pre class="literal-block">
+from foo import numbers
+def f():
+    return numbers(10)
+</pre>
+<p>You may want to keep this little function in your <tt class="literal"><span class="pre">PYTHONSTARTUP</span></tt>
+file:</p>
+<pre class="literal-block">
+def ptl():
+    try:
+        import ZODB
+    except ImportError:
+        pass
+    from quixote import enable_ptl
+    enable_ptl()
+</pre>
+<p>This is useful if you want to interactively play with a PTL module.</p>
+<p>$Id: PTL.txt 25237 2004-09-30 18:28:31Z nascheme $</p>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/PTL.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/PTL.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/PTL.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/PTL.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,267 @@
+PTL: Python Template Language
+=============================
+
+Introduction
+------------
+
+PTL is the templating language used by Quixote.  Most web templating
+languages embed a real programming language in HTML, but PTL inverts
+this model by merely tweaking Python to make it easier to generate
+HTML pages (or other forms of text).  In other words, PTL is basically
+Python with a novel way to specify function return values.
+
+Specifically, a PTL template is designated by inserting a ``[plain]``
+or ``[html]`` modifier after the function name.  The value of
+expressions inside templates are kept, not discarded.  If the type is
+``[html]`` then non-literal strings are passed through a function that
+escapes HTML special characters.
+
+
+Plain text templates
+--------------------
+
+Here's a sample plain text template::
+
+    def foo [plain] (x, y = 5):
+        "This is a chunk of static text."
+        greeting = "hello world" # statement, no PTL output
+        print 'Input values:', x, y
+        z = x + y
+        """You can plug in variables like x (%s)
+    in a variety of ways.""" % x
+
+        "\n\n"
+        "Whitespace is important in generated text.\n"
+        "z = "; z
+        ", but y is "
+        y
+        "."
+
+Obviously, templates can't have docstrings, but otherwise they follow
+Python's syntactic rules: indentation indicates scoping, single-quoted
+and triple-quoted strings can be used, the same rules for continuing
+lines apply, and so forth.  PTL also follows all the expected semantics
+of normal Python code: so templates can have parameters, and the
+parameters can have default values, be treated as keyword arguments,
+etc.
+
+The difference between a template and a regular Python function is that
+inside a template the result of expressions are saved as the return
+value of that template.  Look at the first part of the example again::
+
+    def foo [plain] (x, y = 5):
+        "This is a chunk of static text."
+        greeting = "hello world" # statement, no PTL output
+        print 'Input values:', x, y
+        z = x + y
+        """You can plug in variables like x (%s)
+    in a variety of ways.""" % x
+
+Calling this template with ``foo(1, 2)`` results in the following
+string::
+
+    This is a chunk of static text.You can plug in variables like x (1)
+    in a variety of ways.
+
+Normally when Python evaluates expressions inside functions, it just
+discards their values, but in a ``[plain]`` PTL template the value is
+converted to a string using ``str()`` and appended to the template's
+return value.  There's a single exception to this rule: ``None`` is the
+only value that's ever ignored, adding nothing to the output.  (If this
+weren't the case, calling methods or functions that return ``None``
+would require assigning their value to a variable.  You'd have to write
+``dummy = list.sort()`` in PTL code, which would be strange and
+confusing.)
+
+The initial string in a template isn't treated as a docstring, but is
+just incorporated in the generated output; therefore, templates can't
+have docstrings.  No whitespace is ever automatically added to the
+output, resulting in ``...text.You can ...`` from the example.  You'd
+have to add an extra space to one of the string literals to correct
+this.
+
+The assignment to the ``greeting`` local variable is a statement, not an
+expression, so it doesn't return a value and produces no output.  The
+output from the ``print`` statement will be printed as usual, but won't
+go into the string generated by the template.  Quixote directs standard
+output into Quixote's debugging log; if you're using PTL on its own, you
+should consider doing something similar.  ``print`` should never be used
+to generate output returned to the browser, only for adding debugging
+traces to a template.
+
+Inside templates, you can use all of Python's control-flow statements::
+
+    def numbers [plain] (n):
+        for i in range(n):
+            i
+            " " # PTL does not add any whitespace
+
+Calling ``numbers(5)`` will return the string ``"1 2 3 4 5 "``.  You can
+also have conditional logic or exception blocks::
+
+    def international_hello [plain] (language):
+        if language == "english":
+            "hello"
+        elif language == "french":
+            "bonjour"
+        else:
+            raise ValueError, "I don't speak %s" % language
+
+
+HTML templates
+--------------
+
+Since PTL is usually used to generate HTML documents, an ``[html]``
+template type has been provided to make generating HTML easier.  
+
+A common error when generating HTML is to grab data from the browser
+or from a database and incorporate the contents without escaping
+special characters such as '<' and '&'.  This leads to a class of
+security bugs called "cross-site scripting" bugs, where a hostile user
+can insert arbitrary HTML in your site's output that can link to other
+sites or contain JavaScript code that does something nasty (say,
+popping up 10,000 browser windows).
+
+Such bugs occur because it's easy to forget to HTML-escape a string,
+and forgetting it in just one location is enough to open a hole.  PTL
+offers a solution to this problem by being able to escape strings
+automatically when generating HTML output, at the cost of slightly
+diminished performance (a few percent).
+
+Here's how this feature works.  PTL defines a class called
+``htmltext`` that represents a string that's already been HTML-escaped
+and can be safely sent to the client.  The function ``htmlescape(string)``
+is used to escape data, and it always returns an ``htmltext``
+instance.  It does nothing if the argument is already ``htmltext``.
+Both ``htmltext`` and ``htmlescape`` are available from the global
+namespace in PTL modules.
+
+If a template function is declared ``[html]`` instead of ``[text]``
+then two things happen.  First, all literal strings in the function
+become instances of ``htmltext`` instead of Python's ``str``.  Second,
+the values of expressions are passed through ``htmlescape()`` instead
+of ``str()``.
+
+``htmltext`` type is like the ``str`` type except that operations
+combining strings and ``htmltext`` instances will result in the string
+being passed through ``htmlescape()``.  For example::
+
+    >>> from quixote.html import htmltext
+    >>> htmltext('a') + 'b'
+    <htmltext 'ab'>
+    >>> 'a' + htmltext('b')
+    <htmltext 'ab'>
+    >>> htmltext('a%s') % 'b'
+    <htmltext 'ab'>
+    >>> response = 'green eggs & ham'
+    >>> htmltext('The response was: %s') % response
+    <htmltext 'The response was: green eggs &amp; ham'>
+
+Note that calling ``str()`` strips the ``htmltext`` type and should be
+avoided since it usually results in characters being escaped more than
+once.  While ``htmltext`` behaves much like a regular string, it is
+sometimes necessary to insert a ``str()`` inside a template in order
+to obtain a genuine string.  For example, the ``re`` module requires
+genuine strings.  We have found that explicit calls to ``str()`` can
+often be avoided by splitting some code out of the template into a
+helper function written in regular Python.
+
+It is also recommended that the ``htmltext`` constructor be used as
+sparingly as possible.  The reason is that when using the htmltext
+feature of PTL, explicit calls to ``htmltext`` become the most likely
+source of cross-site scripting holes.  Calling ``htmltext`` is like
+saying "I am absolutely sure this piece of data cannot contain malicious
+HTML code injected by a user.  Don't escape HTML special characters
+because I want them."
+
+Note that literal strings in template functions declared with
+``[html]`` are htmltext instances, and therefore won't be escaped.
+You'll only need to use ``htmltext`` when HTML markup comes from
+outside the template.  For example, if you want to include a file
+containing HTML::
+
+    def output_file [html] ():
+        '<html><body>' # does not get escaped
+        htmltext(open("myfile.html").read())
+        '</body></html>'
+
+In the common case, templates won't be dealing with HTML markup from
+external sources, so you can write straightforward code.  Consider
+this function to generate the contents of the ``HEAD`` element::
+
+    def meta_tags [html] (title, description):
+        '<title>%s</title>' % title
+        '<meta name="description" content="%s">\n' % description
+
+There are no calls to ``htmlescape()`` at all, but string literals
+such as ``<title>%s</title>`` have all be turned into ``htmltext``
+instances, so the string variables will be automatically escaped::
+
+    >>> t.meta_tags('Catalog', 'A catalog of our cool products')
+    <htmltext '<title>Catalog</title>
+      <meta name="description" content="A catalog of our cool products">\n'>
+    >>> t.meta_tags('Dissertation on <HEAD>', 
+    ...             'Discusses the "LINK" and "META" tags')
+    <htmltext '<title>Dissertation on &lt;HEAD&gt;</title>
+      <meta name="description" 
+       content="Discusses the &quot;LINK&quot; and &quot;META&quot; tags">\n'>
+    >>>
+
+Note how the title and description have had HTML-escaping applied to them.
+(The output has been manually pretty-printed to be more readable.)
+
+Once you start using ``htmltext`` in one of your templates, mixing
+plain and HTML templates is tricky because of ``htmltext``'s automatic
+escaping; plain templates that generate HTML tags will be
+double-escaped.  One approach is to just use HTML templates throughout
+your application.  Alternatively you can use ``str()`` to convert
+``htmltext`` instances to regular Python strings; just be sure the
+resulting string isn't HTML-escaped again.
+
+Two implementations of ``htmltext`` are provided, one written in pure
+Python and a second one implemented as a C extension.  Both versions
+have seen production use.  
+
+
+PTL modules
+-----------
+
+PTL templates are kept in files with the extension .ptl.  Like Python
+files, they are byte-compiled on import, and the byte-code is written to
+a compiled file with the extension ``.ptlc``.  Since vanilla Python
+doesn't know anything about PTL, Quixote provides an import hook to let
+you import PTL files just like regular Python modules.  The standard way
+to install this import hook is by calling the ``enable_ptl()`` function::
+
+    from quixote import enable_ptl
+    enable_ptl()
+
+(Note: if you're using ZODB, always import ZODB *before* installing the
+PTL import hook.  There's some interaction which causes importing the
+TimeStamp module to fail when the PTL import hook is installed; we
+haven't debugged the problem.)
+
+Once the import hook is installed, PTL files can be imported as if they
+were Python modules.  If all the example templates shown here were put
+into a file named ``foo.ptl``, you could then write Python code that did
+this::
+
+    from foo import numbers
+    def f():
+        return numbers(10)
+
+You may want to keep this little function in your ``PYTHONSTARTUP``
+file::
+
+    def ptl():
+        try:
+            import ZODB
+        except ImportError:
+            pass
+        from quixote import enable_ptl
+        enable_ptl()
+
+This is useful if you want to interactively play with a PTL module.
+
+
+$Id: PTL.txt 25237 2004-09-30 18:28:31Z nascheme $

Added: packages/quixote1/branches/upstream/current/doc/ZPL.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/ZPL.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/ZPL.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/ZPL.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,59 @@
+Zope Public License (ZPL) Version 2.0
+-----------------------------------------------
+
+This software is Copyright (c) Zope Corporation (tm) and
+Contributors. All rights reserved.
+
+This license has been certified as open source. It has also
+been designated as GPL compatible by the Free Software
+Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions in source code must retain the above
+   copyright notice, this list of conditions, and the following
+   disclaimer.
+
+2. Redistributions in binary form must reproduce the above
+   copyright notice, this list of conditions, and the following
+   disclaimer in the documentation and/or other materials
+   provided with the distribution.
+
+3. The name Zope Corporation (tm) must not be used to
+   endorse or promote products derived from this software
+   without prior written permission from Zope Corporation.
+
+4. The right to distribute this software or to use it for
+   any purpose does not give you the right to use Servicemarks
+   (sm) or Trademarks (tm) of Zope Corporation. Use of them is
+   covered in a separate agreement (see
+   http://www.zope.com/Marks).
+
+5. If any files are modified, you must cause the modified
+   files to carry prominent notices stating that you changed
+   the files and the date of any change.
+
+Disclaimer
+
+  THIS SOFTWARE IS PROVIDED BY ZOPE CORPORATION ``AS IS''
+  AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+  NO EVENT SHALL ZOPE CORPORATION OR ITS CONTRIBUTORS BE
+  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+  DAMAGE.
+
+
+This software consists of contributions made by Zope
+Corporation and many individuals on behalf of Zope
+Corporation.  Specific attributions are listed in the
+accompanying credits file.

Added: packages/quixote1/branches/upstream/current/doc/default.css
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/default.css?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/default.css (added)
+++ packages/quixote1/branches/upstream/current/doc/default.css Mon May  8 19:16:16 2006
@@ -1,0 +1,16 @@
+/*
+Cascading style sheet for the Quixote documentation.
+Just overrides what I don't like about the standard docutils
+stylesheet.
+
+$Id: default.css 20217 2003-01-16 20:51:53Z akuchlin $
+*/
+
+ at import url(/misc/docutils.css);
+
+pre.literal-block, pre.doctest-block {
+  margin-left: 1em ;
+  margin-right: 1em ;
+  background-color: #f4f4f4 }
+
+tt { background-color: transparent }

Added: packages/quixote1/branches/upstream/current/doc/demo.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/demo.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/demo.html (added)
+++ packages/quixote1/branches/upstream/current/doc/demo.html Mon May  8 19:16:16 2006
@@ -1,0 +1,587 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Running the Quixote Demo</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="running-the-quixote-demo">
+<h1 class="title">Running the Quixote Demo</h1>
+<p>Quixote comes with a tiny demonstration application that you can install
+and run on your web server.  In a few dozen lines of Python and PTL
+code, it demonstrates most of Quixote's basic capabilities.  It's also
+an easy way to make sure that your Python installation and web server
+configuration are cooperating so that Quixote applications can work.</p>
+<div class="section" id="installation">
+<h1><a name="installation">Installation</a></h1>
+<p>The demo is included in the quixote.demo package, which is installed
+along with the rest of Quixote when you run <tt class="literal"><span class="pre">python</span> <span class="pre">setup.py</span> <span class="pre">install</span></tt>.
+The driver script (demo.cgi) and associated configuration file
+(demo.conf) are <em>not</em> installed automatically -- you'll have to copy
+them from the demo/ subdirectory to your web server's CGI directory.
+Eg., if you happen to use the same web server tree as we do:</p>
+<pre class="literal-block">
+cp -p demo/demo.cgi demo/demo.conf /www/cgi-bin
+</pre>
+<p>You'll almost certainly need to edit the <tt class="literal"><span class="pre">#!</span></tt> line of demo.cgi to ensure
+that it points to the correct Python interpreter -- it should be the
+same interpreter that you used to run <tt class="literal"><span class="pre">setup.py</span> <span class="pre">install</span></tt>.</p>
+</div>
+<div class="section" id="verifying-the-installation">
+<h1><a name="verifying-the-installation">Verifying the installation</a></h1>
+<p>Before we try to access the demo via your web server, let's make sure
+that the quixote and quixote.demo packages are installed on your system:</p>
+<pre class="literal-block">
+$ python
+Python 2.1.1 (#2, Jul 30 2001, 12:04:51) 
+[GCC 2.95.2 20000220 (Debian GNU/Linux)] on linux2
+Type &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
+&gt;&gt;&gt; import quixote
+&gt;&gt;&gt; quixote.enable_ptl()
+&gt;&gt;&gt; import quixote.demo
+</pre>
+<p>(Quixote requires Python 2.0 or greater; you might have to name an
+explicit Python interpreter, eg. <tt class="literal"><span class="pre">/usr/local/bin/python2.1</span></tt>.  Make
+sure that the Python interpreter you use here is the same as you put in
+the <tt class="literal"><span class="pre">#!</span></tt> line of demo.cgi, and the same that you used to install
+Quixote.)</p>
+<p>If this runs without errors, then Quixote (and its demo) are installed
+such that you can import them.  It remains to be seen if the user that
+will run the driver script -- usually <tt class="literal"><span class="pre">nobody</span></tt> -- can import them.</p>
+</div>
+<div class="section" id="running-the-demo-directly">
+<h1><a name="running-the-demo-directly">Running the demo directly</a></h1>
+<p>Assuming that</p>
+<ul class="simple">
+<li>your web server is running on the current host</li>
+<li>your web server is configured to handle requests to
+/cgi-bin/demo.cgi by running the demo.cgi script that you just
+installed (eg. to /www/cgi-bin/demo.cgi)</li>
+</ul>
+<p>then you should now be able to run the Quixote demo by directly
+referring to the demo.cgi script.</p>
+<p>Another option, if you have the Medusa package installed, is to run:</p>
+<pre class="literal-block">
+python server/medusa_http.py
+</pre>
+<p>This will start a small pure-Python web server running on port 8080.
+(Medusa is available from <a class="reference" href="http://www.amk.ca/python/code/medusa.html">http://www.amk.ca/python/code/medusa.html</a> .)</p>
+<p>Start a web browser and load</p>
+<pre class="literal-block">
+http://localhost/cgi-bin/demo.cgi/
+</pre>
+<p>or</p>
+<pre class="literal-block">
+http://localhost:8080/
+</pre>
+<p>if you're using Medusa.</p>
+<p>You should see a page titled &quot;Quixote Demo&quot; with the headline &quot;Hello,
+world!&quot;.  Feel free to poke around; you can't break anything through the
+demo.  (That's not to say you can't break things with Quixote in
+general; since Quixote gives you the full power of Python for your web
+applications, you have the power to create stupid security holes.)</p>
+<p>If you don't get the &quot;Quixote Demo&quot; page, go look in your web server's
+error log.  Some things that might go wrong:</p>
+<ul>
+<li><p class="first">your web server is not configured to run CGI scripts, or it
+might use a different base URL for them.  If you're running
+Apache, look for something like</p>
+<pre class="literal-block">
+ScriptAlias /cgi-bin/ /www/cgi-bin/
+</pre>
+<p>in your httpd.conf (for some value of &quot;/www/cgi-bin&quot;).</p>
+<p>(This is not a problem with Quixote or the Quixote demo; this is a
+problem with your web server's configuration.)</p>
+</li>
+<li><p class="first">your web server was unable to execute the script.  Make sure
+its permissions are correct:</p>
+<pre class="literal-block">
+chmod 755 /www/cgi-bin/demo.cgi
+</pre>
+<p>(This shouldn't happen if you installed demo.cgi with <tt class="literal"><span class="pre">cp</span> <span class="pre">-p</span></tt> as
+illustrated above.)</p>
+</li>
+<li><p class="first">demo.cgi started, but was unable to import the Quixote modules.
+In this case, there should be a short Python traceback in your web
+server's error log ending with a message like</p>
+<pre class="literal-block">
+ImportError: No module named quixote
+</pre>
+<p>Remember, just because you can &quot;import quixote&quot; in a Python
+interpreter doesn't mean the user that runs CGI scripts (usually
+&quot;nobody&quot;) can.  You might have installed Quixote in a non-standard
+location, in which case you should either install it in the standard
+location (your Python interpreter's &quot;site-packages&quot; directory) or
+instruct your web server to set the PYTHONPATH environment variable.
+Or you might be using the wrong Python interpreter -- check the <tt class="literal"><span class="pre">#!</span></tt>
+line of demo.cgi.</p>
+</li>
+<li><p class="first">demo.cgi started and imported Quixote, but was unable to read its
+config file.  There should be a short Python traceback in your web
+server's error log ending with a message like</p>
+<pre class="literal-block">
+IOError: [Errno 2] No such file or directory: 'demo.conf'
+</pre>
+<p>in this case.</p>
+<p>Make sure you copied demo.conf to the same directory as demo.cgi,
+and make sure it is readable:</p>
+<pre class="literal-block">
+chmod 644 /www/cgi-bin/demo.conf
+</pre>
+<p>(This shouldn't happen if you install demo.conf with <tt class="literal"><span class="pre">cp</span> <span class="pre">-p</span></tt> as
+illustrated above.)</p>
+</li>
+</ul>
+</div>
+<div class="section" id="running-the-demo-indirectly">
+<h1><a name="running-the-demo-indirectly">Running the demo indirectly</a></h1>
+<p>One of the main tenets of Quixote's design is that, in a web
+application, the URL is part of the user interface.  We consider it
+undesirable to expose implementation details -- such as
+&quot;/cgi-bin/demo.cgi&quot; -- to users.  That sort of thing should be tucked
+away out of sight.  Depending on your web server, this should be easy to
+do with a simple tweak to its configuration.</p>
+<p>For example, say you want the &quot;/qdemo&quot; URL to be the location of the
+Quixote demo.  If you're using Apache with the rewrite engine loaded and
+enabled, all you need to do is add this to your httpd.conf:</p>
+<pre class="literal-block">
+RewriteRule ^/qdemo(/.*) /www/cgi-bin/demo.cgi$1 [last]
+</pre>
+<p>With this rule in effect (don't forget to restart your server!),
+accesses to &quot;/qdemo/&quot; are the same as accesses to &quot;/cgi-bin/demo.cgi/&quot; --
+except they're a lot easier for the user to understand and don't expose
+implementation details of your application.</p>
+<p>Try it out.  In your web browser, visit <tt class="literal"><span class="pre">http://localhost/qdemo/</span></tt>.</p>
+<p>You should get exactly the same page as you got visiting
+&quot;/cgi-bin/demo.cgi/&quot; earlier, and all the links should work exactly the
+same.</p>
+<p>You can use any URL prefix you like -- there's nothing special about
+&quot;/qdemo&quot;.</p>
+<p>One small but important detail here is &quot;/qdemo&quot; versus &quot;/qdemo/&quot;.  In
+the above configuration, requests for &quot;/qdemo&quot; will fail, and requests
+for &quot;/qdemo/&quot; will succeed.  See the &quot;URL rewriting&quot; section of
+web-server.txt for details and how to fix this.</p>
+</div>
+<div class="section" id="understanding-the-demo">
+<h1><a name="understanding-the-demo">Understanding the demo</a></h1>
+<p>Now that you've gotten the demo to run successfully, let's look under
+the hood and see how it works.  Before we start following links in the
+demo (don't worry if you already have, you can't hurt anything), make
+sure you're watching all the relevant log files.  As with any web
+application, log files are essential for debugging Quixote applications.</p>
+<p>Assuming that your web server's error log is in /www/log/error_log, and
+that you haven't changed the DEBUG_LOG and ERROR_LOG settings in
+demo.conf:</p>
+<pre class="literal-block">
+$ tail -f /www/log/error_log &amp; \
+  tail -f /tmp/quixote-demo-debug.log &amp; \
+  tail -f /tmp/quixote-demo-error.log 
+</pre>
+<p>(Note that recent versions of GNU tail let you tail multiple files with
+the same command.  Cool!)</p>
+</div>
+<div class="section" id="lesson-1-the-top-page">
+<h1><a name="lesson-1-the-top-page">Lesson 1: the top page</a></h1>
+<p>Reload the top of the demo, presumably <tt class="literal"><span class="pre">http://localhost/qdemo/</span></tt>.  You
+should see &quot;debug message from the index page&quot; in the debug log file.</p>
+<p>Where is this message coming from?  To find out, we need to delve into
+the source code for the demo.  Load up demo/__init__.py and let's take a
+look.  In the process, we'll learn how to explore a Quixote application
+and find the source code that corresponds to a given URL.</p>
+<p>First, why are we loading demo/__init__.py?  Because that's where some
+of the names in the &quot;quixote.demo&quot; namespace are defined, and it's where
+the list of names that may be &quot;exported&quot; by Quixote from this namespace
+to the web is given.  Recall that under Quixote, every URL boils down to
+a callable Python object -- usually a function or method.  The root of
+this application is a Python package (&quot;quixote.demo&quot;), which is just a
+special kind of module.  But modules aren't callable -- so what does the
+&quot;/qdemo/&quot; URL boil down to?  That's what <tt class="literal"><span class="pre">_q_index()</span></tt> is for -- you
+can define a special function that is called by default when Quixote
+resolves a URL to a namespace rather than a callable.  That is,
+&quot;/qdemo/&quot; resolves to the &quot;quixote.demo&quot; package; a package is a
+namespace, so it can't be called; therefore Quixote looks for a function
+called <tt class="literal"><span class="pre">_q_index()</span></tt> in that namespace and calls it.</p>
+<p>In this case, <tt class="literal"><span class="pre">_q_index()</span></tt> is not defined in demo/__init__.py -- but
+it is imported there from the quixote.demo.pages module.  This is
+actually a PTL module -- demo/pages.py does not exist, but
+demo/pages.ptl does.  So load it up and take a look:</p>
+<pre class="literal-block">
+def _q_index [plain] (request):
+    print &quot;debug message from the index page&quot;
+    &quot;&quot;&quot;
+    &lt;html&gt;
+    &lt;head&gt;&lt;title&gt;Quixote Demo&lt;/title&gt;&lt;/head&gt;
+    &lt;body&gt;
+    &lt;h1&gt;Hello, world!&lt;/h1&gt;
+    [...]
+    &lt;/body&gt;
+    &lt;/html&gt;
+    &quot;&quot;&quot;
+</pre>
+<p>A-ha!  There's the PTL code that generates the &quot;Quixote Demo&quot; page.
+This <tt class="literal"><span class="pre">_q_index()</span></tt> template is quite simple PTL -- it's mostly an HTML
+document with a single debug print thrown in to demonstrate Quixote's
+debug logging facility.</p>
+<p>Outcome of lesson 1:</p>
+<ul class="simple">
+<li>a URL maps to either a namespace (package, module, class instance) 
+or a callable (function, method, PTL template)</li>
+<li>if a URL maps to a namespace, Quixote looks for a callable 
+<tt class="literal"><span class="pre">_q_index()</span></tt> in that namespace and calls it</li>
+<li><tt class="literal"><span class="pre">_q_index()</span></tt> doesn't have to be explicitly exported by your 
+namespace; if it exists, it will be used</li>
+<li>anything your application prints to standard output goes to
+Quixote's debug log.  (If you didn't specify a debug log in
+your config file, debug messages are discarded.)</li>
+</ul>
+</div>
+<div class="section" id="lesson-2-a-link-to-a-simple-document">
+<h1><a name="lesson-2-a-link-to-a-simple-document">Lesson 2: a link to a simple document</a></h1>
+<p>The first two links in the &quot;Quixote Demo&quot; page are quite simple.  Each
+one is handled by a Python function defined in the &quot;quixote.demo&quot;
+namespace, i.e. in demo/__init__.py.  For example, following the
+&quot;simple&quot; link is equivalent to calling the <tt class="literal"><span class="pre">simple()</span></tt> function in
+&quot;quixote.demo&quot;.  Let's take a look at that function:</p>
+<pre class="literal-block">
+def simple (request):
+    request.response.set_content_type(&quot;text/plain&quot;)
+    return &quot;This is the Python function 'quixote.demo.simple'.\n&quot;
+</pre>
+<p>Note that this could equivalently be coded in PTL:</p>
+<pre class="literal-block">
+def simple [plain] (request):
+    request.response.set_content_type(&quot;text/plain&quot;)
+    &quot;This is the Python function 'quixote.demo.simple'.\n&quot;
+</pre>
+<p>...but for such a simple document, why bother?</p>
+<p>Since this function doesn't generate an HTML document, it would be
+misleading for the HTTP response that Quixote generates to claim a
+&quot;Content-type&quot; of &quot;text/html&quot;.  That is the default for Quixote's HTTP
+responses, however, since most HTTP responses are indeed HTML documents.
+Therefore, if the content you're returning is anything other than an
+HTML document, you should set the &quot;Content-type&quot; header on the HTTP
+response.</p>
+<p>This brings up a larger issue: request and response objects.  Quixote
+includes two classes, HTTPRequest and HTTPResponse, to encapsulate every
+HTTP request and its accompanying response.  Whenever Quixote resolves a
+URL to a callable and calls it, it passes precisely one argument: an
+HTTPRequest object.</p>
+<p>The HTTPRequest object includes (almost) everything you might want to
+know about the HTTP request that caused Quixote to be invoked and to
+call a particular function, method, or PTL template.  You have access to
+CGI environment variables, HTML form variables (parsed and
+ready-to-use), and HTTP cookies.  Finally, the HTTPRequest object also
+includes an HTTPResponse object -- after all, every request implies a
+response.  You can set the response status, set response headers, set
+cookies, or force a redirect using the HTTPResponse object.</p>
+<p>Note that it's not enough that the <tt class="literal"><span class="pre">simple()</span></tt> function merely exists.
+If that were the case, then overly-curious users or attackers could
+craft URLs that point to any Python function in any module under your
+application's root namespace, potentially causing all sorts of havoc.
+You need to explicitly declare which names are exported from your
+application to the web, using the <tt class="literal"><span class="pre">_q_exports</span></tt> variable.  For example,
+demo/__init__.py has this export list:</p>
+<pre class="literal-block">
+_q_exports = [&quot;simple&quot;, &quot;error&quot;]
+</pre>
+<p>This means that only these two names are explicitly exported by the
+Quixote demo.  (The empty string is implicitly exported from a namespace
+if a <tt class="literal"><span class="pre">_q_index()</span></tt> callable exists there -- thus &quot;/qdemo/&quot; is handled
+by <tt class="literal"><span class="pre">_q_index()</span></tt> in the &quot;quixote.demo&quot; namespace.  Arbitrary names may
+be implicitly exported using a <tt class="literal"><span class="pre">_q_lookup()</span></tt> function; see Lesson 4
+below.)</p>
+</div>
+<div class="section" id="lesson-3-error-handling">
+<h1><a name="lesson-3-error-handling">Lesson 3: error-handling</a></h1>
+<p>The next link in the &quot;Quixote Demo&quot; page is to the &quot;error&quot; document,
+which is handled by the <tt class="literal"><span class="pre">error()</span></tt> function in demo/__init__.py.  All
+this function does is raise an exception:</p>
+<pre class="literal-block">
+def error (request):
+    raise ValueError, &quot;this is a Python exception&quot;
+</pre>
+<p>Follow the link, and you should see a Python traceback followed by a
+dump of the CGI environment for this request (along with other request
+data, such as a list of cookies).</p>
+<p>This is extremely useful when developing, testing, and debugging.  In a
+production environment, though, it reveals way too much about your
+implementation to hapless users who should happen to hit an error, and
+it also reveals internal details to attackers who might use it to crack
+your site.  (It's just as easy to write an insecure web application with
+Quixote as with any other tool.)</p>
+<p>Thus, Quixote offers the <tt class="literal"><span class="pre">DISPLAY_EXCEPTIONS</span></tt> config variable.  This
+is false by default, but the demo.conf file enables it.  To see what
+happens with <tt class="literal"><span class="pre">DISPLAY_EXCEPTIONS</span></tt> off, edit demo.conf and reload the
+&quot;error&quot; page.  You should see a bland, generic error message that
+reveals very little about your implementation.  (This error page is
+deliberately very similar, but not identical, to Apache's &quot;Internal
+Server Error&quot; page.)</p>
+<p>Unhandled exceptions raised by application code (aka &quot;application bugs&quot;)
+are only one kind of error you're likely to encounter when developing a
+Quixote application.  The other ones are:</p>
+<ul class="simple">
+<li>driver script crashes or doesn't run (eg. can't import quixote
+modules, can't load config file).  This is covered under
+&quot;Running the demo directly&quot; above</li>
+<li>publishing errors, such as a request for &quot;/simpel&quot; that should
+have been &quot;/simple&quot;, or a request for a resource that exists but is
+denied to the current user.  Quixote has a family of exception
+classes for dealing with these; such exceptions may be raised by
+Quixote itself or by your application.  They are usually handled by
+Quixote and turned into HTTP error responses (4xx status code),
+but it's possible for your application to define a special handler 
+for such exceptions.</li>
+<li>bugs in Quixote itself; hopefully this won't happen, but you
+never know.  These usually look a lot like problems in the driver
+script: the script crashes and prints a traceback to stderr, which
+most likely winds up in your web server's error log.  The length of
+the traceback is generally a clue as to whether there's a problem
+with your driver script or a bug in Quixote.</li>
+</ul>
+<p>Publishing errors result in a 4xx HTTP response code, and can be
+entirely handled by Quixote -- that is, your web server just returns
+the HTTP response that Quixote prepares.  It's also possible to write
+a <tt class="literal"><span class="pre">_q_exception_handler()</span></tt> method that will be called on triggering
+a publishing error.  This method can then provide a friendlier
+response; for example, the page might provide a link to the site map,
+or the method might look at the problematic path and try to correct
+misspellings.  The demo defines a <tt class="literal"><span class="pre">_q_exception_handler()</span></tt> in
+demo/pages.ptl.</p>
+<p>Application bugs result in a 5xx HTTP response code, and are 
+entirely handled by Quixote.  Don't get confused by the fact that
+Quixote's and Apache's &quot;Internal Server Error&quot; pages are quite similar!</p>
+<p>Driver script crashes and Quixote bugs (which are essentially the same
+thing; the main difference is who to blame) are handled by your web
+server.  (In the first case, Quixote doesn't even enter into it; in the
+second case, Quixote dies horribly and is no longer in control.)  Under
+Apache, the Python traceback resulting from the crash is written to
+Apache's error log, and a 5xx response is returned to the client with
+Apache's &quot;Internal Server Error&quot; error page.</p>
+</div>
+<div class="section" id="lesson-4-object-publishing">
+<h1><a name="lesson-4-object-publishing">Lesson 4: object publishing</a></h1>
+<p>Publishing Python callables on the web -- i.e., translating URLs to
+Python functions/methods/PTL templates and calling them to determine the
+HTTP response -- is a very powerful way of writing web applications.
+However, Quixote has one more trick up its sleeve: object publishing.
+You can translate arbitrary names to arbitrary objects which are then
+published on the web, and you can create URLs that call methods on those
+objects.</p>
+<p>This is all accomplished with the <tt class="literal"><span class="pre">_q_lookup()</span></tt> function.  Every
+namespace that Quixote encounters may have a <tt class="literal"><span class="pre">_q_lookup()</span></tt>, just like
+it may have a <tt class="literal"><span class="pre">_q_index()</span></tt>.  <tt class="literal"><span class="pre">_q_index()</span></tt> is used to handle requests for
+the empty name -- as we saw in Lesson 1, a request for &quot;/qdemo/&quot;, maps to
+the &quot;quixote.demo&quot; namespace; the empty string after the last slash
+means that Quixote will call <tt class="literal"><span class="pre">_q_index()</span></tt> in this namespace to handle
+the request.</p>
+<p><tt class="literal"><span class="pre">_q_lookup()</span></tt> is for requests that aren't handled by a Python callable
+in the namespace.  As seen in Lessons 2 and 3, requests for
+&quot;/qdemo/simple&quot; and &quot;/qdemo/error&quot; are handled by the <tt class="literal"><span class="pre">simple()</span></tt> and
+<tt class="literal"><span class="pre">error()</span></tt> functions in the &quot;quixote.demo&quot; namespace.  What if someone
+requests &quot;/qdemo/foo&quot;?  There's no function <tt class="literal"><span class="pre">foo()</span></tt> in the
+&quot;quixote.demo&quot; namespace, so normally this would be an error.
+(Specifically, it would be a publishing error: Quixote would raise
+TraversalError, which is the error used for non-existent or non-exported
+names.  Another part of Quixote then turns this into an HTTP 404
+response.)</p>
+<p>However, this particular namespace also defines a <tt class="literal"><span class="pre">_q_lookup()</span></tt>
+function.  That means that the application wants a chance to handle
+unknown names before Quixote gives up entirely.  Let's take a look at
+the implementation of <tt class="literal"><span class="pre">_q_lookup()</span></tt>:</p>
+<pre class="literal-block">
+from quixote.demo.integer_ui import IntegerUI
+[...]
+def _q_lookup(request, component):
+    return IntegerUI(request, component)
+</pre>
+<p>Pretty simple: we just construct an IntegerUI object and return it.  So
+what is IntegerUI?  Take a look in the demo/integer_ui.py file to see;
+it's just a web interface to integers.  (Normally, you would write a
+wrapper class that provides a web interface to something more
+interesting than integers.  This just demonstrates how simple an object
+published by Quixote can be.)</p>
+<p>So, what is an IntegerUI object?  From Quixote's point of view, it's
+just another namespace to publish: like modules and packages, class
+instances have attributes, some of which (methods) are callable.  In the
+case of IntegerUI, two of those attributes are <tt class="literal"><span class="pre">_q_exports</span></tt> and
+<tt class="literal"><span class="pre">_q_index</span></tt> -- every namespace published by Quixote must have an export
+list, and an index function is almost always advisable.</p>
+<p>What this means is that any name that the IntegerUI constructor accepts
+is a valid name to tack onto the &quot;/qdemo/&quot; URL.  Take a look at the
+IntegerUI constructor; you'll see that it works fine when passed
+something that can be converted to an integer (eg. &quot;12&quot; or 1.0), and
+raises Quixote's TraversalError if not.  As it happens, Quixote always
+passes in a string -- URLs are just strings, after all -- so we only
+have to worry about things like &quot;12&quot; or &quot;foo&quot;.</p>
+<p>The error case is actually easier to understand, so try to access
+<tt class="literal"><span class="pre">http://localhost/qdemo/foo/</span></tt>.  You should get an error page that
+complains about an &quot;invalid literal for int()&quot;.</p>
+<p>Now let's build a real IntegerUI object and see the results.  Follow the
+third link in the &quot;Quixote Demo&quot; page, or just go to
+<tt class="literal"><span class="pre">http://localhost/qdemo/12/</span></tt>.  You should see a web page titled &quot;The
+Number 12&quot;.</p>
+<p>This web page is generated by the <tt class="literal"><span class="pre">_q_index()</span></tt> method of IntegerUI:
+after all, you've selected a namespace (the IntegerUI object
+corresponding to the number 12) with no explicit callable, so Quixote
+falls back on the <tt class="literal"><span class="pre">_q_index()</span></tt> attribute of that namespace.</p>
+<p>IntegerUI only exports one interesting method, <tt class="literal"><span class="pre">factorial()</span></tt>.  You can
+call this method by following the &quot;factorial&quot; link, or just by accessing
+<tt class="literal"><span class="pre">http://localhost/qdemo/12/factorial</span></tt>.</p>
+<p>Remember how I said the URL is part of the user interface?  Here's a
+great example: edit the current URL to point to a different integer.  A
+fun one to try is 2147483646.  If you follow the &quot;next&quot; link, you'll get
+an OverflowError traceback (unless you're using a 64-bit Python!),
+because the web page for 2147483647 attempts to generate its own &quot;next&quot;
+link to the web page for 2147483648 -- but that fails because current
+versions of Python on 32-bit platforms can't handle regular integers
+larger than 2147483647.</p>
+<p>Now go back to the page for 2147483646 and hit the &quot;factorial&quot; link.
+Run &quot;top&quot; on the web server.  Get yourself a coffee.  Await the heat
+death of the universe.  (Actually, your browser will probably timeout
+first.)  This doesn't overflow, because the factorial() function uses
+Python long integers, which can handle any integer -- they just take a
+while to get there.  However, it illustrates another interesting
+vulnerability: an attacker could use this to launch a denial-of-service
+attack on the server running the Quixote demo.  (Hey, it's just a demo!)</p>
+<p>Rather than fix the DoS vulnerability, I decided to use it to illustrate
+another Quixote feature: if you write to stderr, the message winds up in
+the Quixote error log for this application (/tmp/quixote-demo-error.log
+by default).  The IntegerUI.factorial() method uses this to log a
+warning of an apparent denial-of-service attack:</p>
+<pre class="literal-block">
+def factorial (self, request):
+    if self.n &gt; 10000:
+        sys.stderr.write(&quot;warning: possible denial-of-service attack &quot;
+                         &quot;(request for factorial(%d))\n&quot; % self.n)
+    return &quot;%d! = %d&quot; % (self.n, fact(self.n))
+</pre>
+<p>Since the Quixote error log is where application tracebacks are
+recorded, you should be watching this log file regularly, so you would
+presumably notice these messages.</p>
+<p>In real life, you'd probably just deny such a ludicrous request.  You
+could do this by raising a Quixote publishing error.  For example:</p>
+<pre class="literal-block">
+def factorial (self, request):
+    from quixote.errors import AccessError
+    if self.n &gt; 10000:
+        raise AccessError(&quot;ridiculous request denied&quot;)
+    return &quot;%d! = %d&quot; % (self.n, fact(self.n))
+</pre>
+</div>
+<div class="section" id="lesson-5-widgets">
+<h1><a name="lesson-5-widgets">Lesson 5: widgets</a></h1>
+<p>You can't get very far writing web applications without writing forms,
+and the building blocks of web forms are generally called &quot;form
+elements&quot;: string input, checkboxes, radiobuttons, select lists, and so
+forth.  Quixote provides an abstraction for all of these form elements:
+the Widget class hierarchy.  The widget classes are explained in detail
+in widget.txt; I'm going to give a brief description of the &quot;Quixote
+Widget Demo&quot; page and the code behind it here.</p>
+<p>If you follow the &quot;widgets&quot; link from the main page, you'll see a fairly
+ordinary-looking web form -- the sort of thing you might have to fill
+out to order a pizza on-line, with the oddity that this pizza shop is
+asking for your eye colour.  (Hey, I had to demonstrate radiobuttons
+somehow!)  This form demonstrates most of HTML's basic form
+capabilities: a simple string, a password, a checkbox, a set of
+radiobuttons, a single-select list, and a multiple-select list.</p>
+<p>Whenever you implement a web form, there are two things you have to
+worry about: generating the form elements and processing the
+client-submitted form data.  There are as many ways of dividing up this
+work as there are web programmers.  (Possibly more: every time I tackle
+this problem, I seem to come up with a different way of solving it.)
+The form in the Quixote widget demo is implemented in three parts, all
+of them in demo/widgets.ptl:</p>
+<ul class="simple">
+<li><tt class="literal"><span class="pre">widgets()</span></tt> is the callable that handles the &quot;/qdemo/widgets&quot; URL.
+This template creates all the widget objects needed for the form and
+then calls either <tt class="literal"><span class="pre">render_widgets()</span></tt> or <tt class="literal"><span class="pre">process_widgets()</span></tt> as
+appropriate.</li>
+<li><tt class="literal"><span class="pre">render_widgets()</span></tt> is called by <tt class="literal"><span class="pre">widgets()</span></tt> when there is no form
+data to process, eg. on the first access to the &quot;/qdemo/widgets&quot; URL.
+It generates the form elements and returns an HTML document consisting
+of a table that lays them out in an attractive form.</li>
+<li><tt class="literal"><span class="pre">process_widgets()</span></tt> is called by <tt class="literal"><span class="pre">widgets()</span></tt> when there is form data
+to process, ie. when the form generated by <tt class="literal"><span class="pre">render_widgets()</span></tt> is
+submitted by the user.  It processes the form data, ie. it looks up
+the user-submitted form values and returns an HTML document listing
+those values.</li>
+</ul>
+<p>This division of labour works well with Quixote's widget classes, since
+you need a collection of Widget objects whether you are generating the
+form elements or processing form data.  For generating the form, we (1)
+create all the widget objects, and (2) generate an HTML document that
+includes the output of each widget object's <tt class="literal"><span class="pre">render()</span></tt> method.  (Laying
+out the form is the responsibility of render_widgets(), which is why
+it's littered with table tags.)  For processing the form, we (1) create
+all the widget objects, and (2) generate an HTML document incorporating
+the user-submitted form values.  In both cases, step (1) is handled by
+the widgets() template, which then calls either render_widgets() or
+process_widgets() for step (2).</p>
+<p>Thus, there are three things you have to understand about widget
+objects: how to create them, how to render them, and how to use them to
+parse form values.  Widget creation is the only step that's very
+interesting, since each widget class has different constructor
+arguments.  For example, here's how we create the &quot;name&quot; widget in the
+pizza shop form:</p>
+<pre class="literal-block">
+widgets['name'] = widget.StringWidget('name', size=20)
+</pre>
+<p>When rendered, this widget will produce the following HTML:</p>
+<pre class="literal-block">
+&lt;input size=&quot;20&quot; name=&quot;name&quot; type=&quot;text&quot;&gt;
+</pre>
+<p>A more complex example is the &quot;pizza size&quot; widget:</p>
+<pre class="literal-block">
+widgets['size'] = widget.SingleSelectWidget(
+    'size', value='medium',
+    allowed_values=['tiny', 'small', 'medium', 'large', 'enormous'],
+    descriptions=['Tiny (4&quot;)', 'Small (6&quot;)', 'Medium (10&quot;)',
+                  'Large (14&quot;)', 'Enormous (18&quot;)'],
+    size=5)
+</pre>
+<p>which will generate the following HTML when rendered:</p>
+<pre class="literal-block">
+&lt;select size=&quot;5&quot; name=&quot;size&quot;&gt;
+  &lt;option value=&quot;0&quot;&gt;Tiny (4&quot;)
+  &lt;option value=&quot;1&quot;&gt;Small (6&quot;)
+  &lt;option selected value=&quot;2&quot;&gt;Medium (10&quot;)
+  &lt;option value=&quot;3&quot;&gt;Large (14&quot;)
+  &lt;option value=&quot;4&quot;&gt;Enormous (18&quot;)
+&lt;/select&gt;
+</pre>
+<p>Some things you might need to know about widget creation:</p>
+<ul class="simple">
+<li>the standard widget classes are in the quixote.form.widget module;
+see widget.txt and/or the source code for the complete list</li>
+<li>every widget class constructor has exactly one required argument:
+the widget name.  This is used as the form element name
+in the generated HTML.  (Things are a bit different for compound
+widgets, but I'm not covering them in this document.)</li>
+<li>every widget class supports a number of keyword arguments that
+generally correspond to attributes of some HTML tag.  The one
+argument common to all widget classes is <tt class="literal"><span class="pre">value</span></tt>, the current
+value for this widget.</li>
+</ul>
+<p>Rendering widgets is easy: just call the render() method, passing in the
+current HTTPRequest object.  (It's currently not used by the standard
+widget classes, but could be used by derived or compound widget classes
+for context-sensitive widget rendering.)</p>
+<p>Parsing form values is just as easy: call the parse() method, again
+passing in the current HTTPRequest object.  The return value depends on
+the nature of the widget, eg.:</p>
+<ul class="simple">
+<li>StringWidget and PasswordWidget return a string</li>
+<li>CheckboxWidget returns a boolean</li>
+<li>RadiobuttonsWidget, SingleSelectWidget, and MultipleSelectWidget
+return one of the values supplied in <tt class="literal"><span class="pre">allowed_values</span></tt> -- in the
+demo these are all strings, but they can be any Python value.
+(If the client submits bogus data, the widget will return None.)</li>
+</ul>
+<p>$Id: demo.txt 21292 2003-04-08 16:48:47Z akuchlin $</p>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/demo.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/demo.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/demo.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/demo.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,643 @@
+Running the Quixote Demo
+========================
+
+Quixote comes with a tiny demonstration application that you can install
+and run on your web server.  In a few dozen lines of Python and PTL
+code, it demonstrates most of Quixote's basic capabilities.  It's also
+an easy way to make sure that your Python installation and web server
+configuration are cooperating so that Quixote applications can work.
+
+
+Installation
+------------
+
+The demo is included in the quixote.demo package, which is installed
+along with the rest of Quixote when you run ``python setup.py install``.
+The driver script (demo.cgi) and associated configuration file
+(demo.conf) are *not* installed automatically -- you'll have to copy
+them from the demo/ subdirectory to your web server's CGI directory.
+Eg., if you happen to use the same web server tree as we do::
+
+    cp -p demo/demo.cgi demo/demo.conf /www/cgi-bin
+
+You'll almost certainly need to edit the ``#!`` line of demo.cgi to ensure
+that it points to the correct Python interpreter -- it should be the
+same interpreter that you used to run ``setup.py install``.
+
+
+Verifying the installation
+--------------------------
+
+Before we try to access the demo via your web server, let's make sure
+that the quixote and quixote.demo packages are installed on your system::
+
+    $ python
+    Python 2.1.1 (#2, Jul 30 2001, 12:04:51) 
+    [GCC 2.95.2 20000220 (Debian GNU/Linux)] on linux2
+    Type "copyright", "credits" or "license" for more information.
+    >>> import quixote
+    >>> quixote.enable_ptl()
+    >>> import quixote.demo
+
+(Quixote requires Python 2.0 or greater; you might have to name an
+explicit Python interpreter, eg. ``/usr/local/bin/python2.1``.  Make
+sure that the Python interpreter you use here is the same as you put in
+the ``#!`` line of demo.cgi, and the same that you used to install
+Quixote.)
+
+If this runs without errors, then Quixote (and its demo) are installed
+such that you can import them.  It remains to be seen if the user that
+will run the driver script -- usually ``nobody`` -- can import them.
+
+
+Running the demo directly
+-------------------------
+
+Assuming that
+
+* your web server is running on the current host
+* your web server is configured to handle requests to
+  /cgi-bin/demo.cgi by running the demo.cgi script that you just
+  installed (eg. to /www/cgi-bin/demo.cgi)
+
+then you should now be able to run the Quixote demo by directly
+referring to the demo.cgi script.
+
+Another option, if you have the Medusa package installed, is to run::
+
+    python server/medusa_http.py
+
+This will start a small pure-Python web server running on port 8080.
+(Medusa is available from http://www.amk.ca/python/code/medusa.html .)
+
+Start a web browser and load ::
+
+    http://localhost/cgi-bin/demo.cgi/
+
+or ::
+
+    http://localhost:8080/
+
+if you're using Medusa. 
+
+You should see a page titled "Quixote Demo" with the headline "Hello,
+world!".  Feel free to poke around; you can't break anything through the
+demo.  (That's not to say you can't break things with Quixote in
+general; since Quixote gives you the full power of Python for your web
+applications, you have the power to create stupid security holes.)
+
+If you don't get the "Quixote Demo" page, go look in your web server's
+error log.  Some things that might go wrong:
+    
+* your web server is not configured to run CGI scripts, or it
+  might use a different base URL for them.  If you're running
+  Apache, look for something like ::
+
+    ScriptAlias /cgi-bin/ /www/cgi-bin/
+
+  in your httpd.conf (for some value of "/www/cgi-bin").
+
+  (This is not a problem with Quixote or the Quixote demo; this is a
+  problem with your web server's configuration.)
+
+* your web server was unable to execute the script.  Make sure
+  its permissions are correct::
+
+    chmod 755 /www/cgi-bin/demo.cgi
+
+  (This shouldn't happen if you installed demo.cgi with ``cp -p`` as
+  illustrated above.)
+
+* demo.cgi started, but was unable to import the Quixote modules.
+  In this case, there should be a short Python traceback in your web
+  server's error log ending with a message like ::
+
+    ImportError: No module named quixote
+
+  Remember, just because you can "import quixote" in a Python
+  interpreter doesn't mean the user that runs CGI scripts (usually
+  "nobody") can.  You might have installed Quixote in a non-standard
+  location, in which case you should either install it in the standard
+  location (your Python interpreter's "site-packages" directory) or
+  instruct your web server to set the PYTHONPATH environment variable.
+  Or you might be using the wrong Python interpreter -- check the ``#!``
+  line of demo.cgi.
+
+* demo.cgi started and imported Quixote, but was unable to read its
+  config file.  There should be a short Python traceback in your web
+  server's error log ending with a message like ::
+
+    IOError: [Errno 2] No such file or directory: 'demo.conf'
+
+  in this case.
+
+  Make sure you copied demo.conf to the same directory as demo.cgi,
+  and make sure it is readable::
+
+    chmod 644 /www/cgi-bin/demo.conf
+
+  (This shouldn't happen if you install demo.conf with ``cp -p`` as
+  illustrated above.)
+
+
+Running the demo indirectly
+---------------------------
+
+One of the main tenets of Quixote's design is that, in a web
+application, the URL is part of the user interface.  We consider it
+undesirable to expose implementation details -- such as
+"/cgi-bin/demo.cgi" -- to users.  That sort of thing should be tucked
+away out of sight.  Depending on your web server, this should be easy to
+do with a simple tweak to its configuration.
+
+For example, say you want the "/qdemo" URL to be the location of the
+Quixote demo.  If you're using Apache with the rewrite engine loaded and
+enabled, all you need to do is add this to your httpd.conf::
+
+    RewriteRule ^/qdemo(/.*) /www/cgi-bin/demo.cgi$1 [last]
+
+With this rule in effect (don't forget to restart your server!),
+accesses to "/qdemo/" are the same as accesses to "/cgi-bin/demo.cgi/" --
+except they're a lot easier for the user to understand and don't expose
+implementation details of your application.
+
+Try it out.  In your web browser, visit ``http://localhost/qdemo/``.
+
+You should get exactly the same page as you got visiting
+"/cgi-bin/demo.cgi/" earlier, and all the links should work exactly the
+same.
+
+You can use any URL prefix you like -- there's nothing special about
+"/qdemo".
+
+One small but important detail here is "/qdemo" versus "/qdemo/".  In
+the above configuration, requests for "/qdemo" will fail, and requests
+for "/qdemo/" will succeed.  See the "URL rewriting" section of
+web-server.txt for details and how to fix this.
+
+
+Understanding the demo
+----------------------
+
+Now that you've gotten the demo to run successfully, let's look under
+the hood and see how it works.  Before we start following links in the
+demo (don't worry if you already have, you can't hurt anything), make
+sure you're watching all the relevant log files.  As with any web
+application, log files are essential for debugging Quixote applications.
+
+Assuming that your web server's error log is in /www/log/error_log, and
+that you haven't changed the DEBUG_LOG and ERROR_LOG settings in
+demo.conf::
+
+    $ tail -f /www/log/error_log & \
+      tail -f /tmp/quixote-demo-debug.log & \
+      tail -f /tmp/quixote-demo-error.log 
+
+(Note that recent versions of GNU tail let you tail multiple files with
+the same command.  Cool!)
+
+Lesson 1: the top page
+----------------------
+
+Reload the top of the demo, presumably ``http://localhost/qdemo/``.  You
+should see "debug message from the index page" in the debug log file.
+
+Where is this message coming from?  To find out, we need to delve into
+the source code for the demo.  Load up demo/__init__.py and let's take a
+look.  In the process, we'll learn how to explore a Quixote application
+and find the source code that corresponds to a given URL.
+
+First, why are we loading demo/__init__.py?  Because that's where some
+of the names in the "quixote.demo" namespace are defined, and it's where
+the list of names that may be "exported" by Quixote from this namespace
+to the web is given.  Recall that under Quixote, every URL boils down to
+a callable Python object -- usually a function or method.  The root of
+this application is a Python package ("quixote.demo"), which is just a
+special kind of module.  But modules aren't callable -- so what does the
+"/qdemo/" URL boil down to?  That's what ``_q_index()`` is for -- you
+can define a special function that is called by default when Quixote
+resolves a URL to a namespace rather than a callable.  That is,
+"/qdemo/" resolves to the "quixote.demo" package; a package is a
+namespace, so it can't be called; therefore Quixote looks for a function
+called ``_q_index()`` in that namespace and calls it.
+
+In this case, ``_q_index()`` is not defined in demo/__init__.py -- but
+it is imported there from the quixote.demo.pages module.  This is
+actually a PTL module -- demo/pages.py does not exist, but
+demo/pages.ptl does.  So load it up and take a look::
+
+    def _q_index [plain] (request):
+        print "debug message from the index page"
+        """
+        <html>
+        <head><title>Quixote Demo</title></head>
+        <body>
+        <h1>Hello, world!</h1>
+        [...]
+        </body>
+        </html>
+        """
+
+A-ha!  There's the PTL code that generates the "Quixote Demo" page.
+This ``_q_index()`` template is quite simple PTL -- it's mostly an HTML
+document with a single debug print thrown in to demonstrate Quixote's
+debug logging facility.
+
+Outcome of lesson 1:
+
+* a URL maps to either a namespace (package, module, class instance) 
+  or a callable (function, method, PTL template)
+
+* if a URL maps to a namespace, Quixote looks for a callable 
+  ``_q_index()`` in that namespace and calls it
+
+* ``_q_index()`` doesn't have to be explicitly exported by your 
+  namespace; if it exists, it will be used
+
+* anything your application prints to standard output goes to
+  Quixote's debug log.  (If you didn't specify a debug log in
+  your config file, debug messages are discarded.)
+
+
+Lesson 2: a link to a simple document
+-------------------------------------
+
+The first two links in the "Quixote Demo" page are quite simple.  Each
+one is handled by a Python function defined in the "quixote.demo"
+namespace, i.e. in demo/__init__.py.  For example, following the
+"simple" link is equivalent to calling the ``simple()`` function in
+"quixote.demo".  Let's take a look at that function::
+
+    def simple (request):
+        request.response.set_content_type("text/plain")
+        return "This is the Python function 'quixote.demo.simple'.\n"
+
+Note that this could equivalently be coded in PTL::
+
+    def simple [plain] (request):
+        request.response.set_content_type("text/plain")
+        "This is the Python function 'quixote.demo.simple'.\n"
+
+...but for such a simple document, why bother?
+
+Since this function doesn't generate an HTML document, it would be
+misleading for the HTTP response that Quixote generates to claim a
+"Content-type" of "text/html".  That is the default for Quixote's HTTP
+responses, however, since most HTTP responses are indeed HTML documents.
+Therefore, if the content you're returning is anything other than an
+HTML document, you should set the "Content-type" header on the HTTP
+response.
+
+This brings up a larger issue: request and response objects.  Quixote
+includes two classes, HTTPRequest and HTTPResponse, to encapsulate every
+HTTP request and its accompanying response.  Whenever Quixote resolves a
+URL to a callable and calls it, it passes precisely one argument: an
+HTTPRequest object.
+
+The HTTPRequest object includes (almost) everything you might want to
+know about the HTTP request that caused Quixote to be invoked and to
+call a particular function, method, or PTL template.  You have access to
+CGI environment variables, HTML form variables (parsed and
+ready-to-use), and HTTP cookies.  Finally, the HTTPRequest object also
+includes an HTTPResponse object -- after all, every request implies a
+response.  You can set the response status, set response headers, set
+cookies, or force a redirect using the HTTPResponse object.
+
+Note that it's not enough that the ``simple()`` function merely exists.
+If that were the case, then overly-curious users or attackers could
+craft URLs that point to any Python function in any module under your
+application's root namespace, potentially causing all sorts of havoc.
+You need to explicitly declare which names are exported from your
+application to the web, using the ``_q_exports`` variable.  For example,
+demo/__init__.py has this export list::
+
+    _q_exports = ["simple", "error"]
+
+This means that only these two names are explicitly exported by the
+Quixote demo.  (The empty string is implicitly exported from a namespace
+if a ``_q_index()`` callable exists there -- thus "/qdemo/" is handled
+by ``_q_index()`` in the "quixote.demo" namespace.  Arbitrary names may
+be implicitly exported using a ``_q_lookup()`` function; see Lesson 4
+below.)
+
+
+Lesson 3: error-handling
+------------------------
+
+The next link in the "Quixote Demo" page is to the "error" document,
+which is handled by the ``error()`` function in demo/__init__.py.  All
+this function does is raise an exception::
+
+    def error (request):
+        raise ValueError, "this is a Python exception"
+
+Follow the link, and you should see a Python traceback followed by a
+dump of the CGI environment for this request (along with other request
+data, such as a list of cookies).
+
+This is extremely useful when developing, testing, and debugging.  In a
+production environment, though, it reveals way too much about your
+implementation to hapless users who should happen to hit an error, and
+it also reveals internal details to attackers who might use it to crack
+your site.  (It's just as easy to write an insecure web application with
+Quixote as with any other tool.)
+
+Thus, Quixote offers the ``DISPLAY_EXCEPTIONS`` config variable.  This
+is false by default, but the demo.conf file enables it.  To see what
+happens with ``DISPLAY_EXCEPTIONS`` off, edit demo.conf and reload the
+"error" page.  You should see a bland, generic error message that
+reveals very little about your implementation.  (This error page is
+deliberately very similar, but not identical, to Apache's "Internal
+Server Error" page.)
+
+Unhandled exceptions raised by application code (aka "application bugs")
+are only one kind of error you're likely to encounter when developing a
+Quixote application.  The other ones are:
+
+* driver script crashes or doesn't run (eg. can't import quixote
+  modules, can't load config file).  This is covered under
+  "Running the demo directly" above
+
+* publishing errors, such as a request for "/simpel" that should
+  have been "/simple", or a request for a resource that exists but is
+  denied to the current user.  Quixote has a family of exception
+  classes for dealing with these; such exceptions may be raised by
+  Quixote itself or by your application.  They are usually handled by
+  Quixote and turned into HTTP error responses (4xx status code),
+  but it's possible for your application to define a special handler 
+  for such exceptions.
+
+* bugs in Quixote itself; hopefully this won't happen, but you
+  never know.  These usually look a lot like problems in the driver
+  script: the script crashes and prints a traceback to stderr, which
+  most likely winds up in your web server's error log.  The length of
+  the traceback is generally a clue as to whether there's a problem
+  with your driver script or a bug in Quixote.
+
+Publishing errors result in a 4xx HTTP response code, and can be
+entirely handled by Quixote -- that is, your web server just returns
+the HTTP response that Quixote prepares.  It's also possible to write
+a ``_q_exception_handler()`` method that will be called on triggering
+a publishing error.  This method can then provide a friendlier
+response; for example, the page might provide a link to the site map,
+or the method might look at the problematic path and try to correct
+misspellings.  The demo defines a ``_q_exception_handler()`` in
+demo/pages.ptl.
+
+Application bugs result in a 5xx HTTP response code, and are 
+entirely handled by Quixote.  Don't get confused by the fact that
+Quixote's and Apache's "Internal Server Error" pages are quite similar!
+
+Driver script crashes and Quixote bugs (which are essentially the same
+thing; the main difference is who to blame) are handled by your web
+server.  (In the first case, Quixote doesn't even enter into it; in the
+second case, Quixote dies horribly and is no longer in control.)  Under
+Apache, the Python traceback resulting from the crash is written to
+Apache's error log, and a 5xx response is returned to the client with
+Apache's "Internal Server Error" error page.
+
+
+Lesson 4: object publishing
+---------------------------
+
+Publishing Python callables on the web -- i.e., translating URLs to
+Python functions/methods/PTL templates and calling them to determine the
+HTTP response -- is a very powerful way of writing web applications.
+However, Quixote has one more trick up its sleeve: object publishing.
+You can translate arbitrary names to arbitrary objects which are then
+published on the web, and you can create URLs that call methods on those
+objects.
+
+This is all accomplished with the ``_q_lookup()`` function.  Every
+namespace that Quixote encounters may have a ``_q_lookup()``, just like
+it may have a ``_q_index()``.  ``_q_index()`` is used to handle requests for
+the empty name -- as we saw in Lesson 1, a request for "/qdemo/", maps to
+the "quixote.demo" namespace; the empty string after the last slash
+means that Quixote will call ``_q_index()`` in this namespace to handle
+the request.
+
+``_q_lookup()`` is for requests that aren't handled by a Python callable
+in the namespace.  As seen in Lessons 2 and 3, requests for
+"/qdemo/simple" and "/qdemo/error" are handled by the ``simple()`` and
+``error()`` functions in the "quixote.demo" namespace.  What if someone
+requests "/qdemo/foo"?  There's no function ``foo()`` in the
+"quixote.demo" namespace, so normally this would be an error.
+(Specifically, it would be a publishing error: Quixote would raise
+TraversalError, which is the error used for non-existent or non-exported
+names.  Another part of Quixote then turns this into an HTTP 404
+response.)
+
+However, this particular namespace also defines a ``_q_lookup()``
+function.  That means that the application wants a chance to handle
+unknown names before Quixote gives up entirely.  Let's take a look at
+the implementation of ``_q_lookup()``::
+
+    from quixote.demo.integer_ui import IntegerUI
+    [...]
+    def _q_lookup(request, component):
+        return IntegerUI(request, component)
+
+Pretty simple: we just construct an IntegerUI object and return it.  So
+what is IntegerUI?  Take a look in the demo/integer_ui.py file to see;
+it's just a web interface to integers.  (Normally, you would write a
+wrapper class that provides a web interface to something more
+interesting than integers.  This just demonstrates how simple an object
+published by Quixote can be.)
+
+So, what is an IntegerUI object?  From Quixote's point of view, it's
+just another namespace to publish: like modules and packages, class
+instances have attributes, some of which (methods) are callable.  In the
+case of IntegerUI, two of those attributes are ``_q_exports`` and
+``_q_index`` -- every namespace published by Quixote must have an export
+list, and an index function is almost always advisable.
+
+What this means is that any name that the IntegerUI constructor accepts
+is a valid name to tack onto the "/qdemo/" URL.  Take a look at the
+IntegerUI constructor; you'll see that it works fine when passed
+something that can be converted to an integer (eg. "12" or 1.0), and
+raises Quixote's TraversalError if not.  As it happens, Quixote always
+passes in a string -- URLs are just strings, after all -- so we only
+have to worry about things like "12" or "foo".
+
+The error case is actually easier to understand, so try to access
+``http://localhost/qdemo/foo/``.  You should get an error page that
+complains about an "invalid literal for int()".
+
+Now let's build a real IntegerUI object and see the results.  Follow the
+third link in the "Quixote Demo" page, or just go to
+``http://localhost/qdemo/12/``.  You should see a web page titled "The
+Number 12".
+
+This web page is generated by the ``_q_index()`` method of IntegerUI:
+after all, you've selected a namespace (the IntegerUI object
+corresponding to the number 12) with no explicit callable, so Quixote
+falls back on the ``_q_index()`` attribute of that namespace.
+
+IntegerUI only exports one interesting method, ``factorial()``.  You can
+call this method by following the "factorial" link, or just by accessing
+``http://localhost/qdemo/12/factorial``.
+
+Remember how I said the URL is part of the user interface?  Here's a
+great example: edit the current URL to point to a different integer.  A
+fun one to try is 2147483646.  If you follow the "next" link, you'll get
+an OverflowError traceback (unless you're using a 64-bit Python!),
+because the web page for 2147483647 attempts to generate its own "next"
+link to the web page for 2147483648 -- but that fails because current
+versions of Python on 32-bit platforms can't handle regular integers
+larger than 2147483647.
+
+Now go back to the page for 2147483646 and hit the "factorial" link.
+Run "top" on the web server.  Get yourself a coffee.  Await the heat
+death of the universe.  (Actually, your browser will probably timeout
+first.)  This doesn't overflow, because the factorial() function uses
+Python long integers, which can handle any integer -- they just take a
+while to get there.  However, it illustrates another interesting
+vulnerability: an attacker could use this to launch a denial-of-service
+attack on the server running the Quixote demo.  (Hey, it's just a demo!)
+
+Rather than fix the DoS vulnerability, I decided to use it to illustrate
+another Quixote feature: if you write to stderr, the message winds up in
+the Quixote error log for this application (/tmp/quixote-demo-error.log
+by default).  The IntegerUI.factorial() method uses this to log a
+warning of an apparent denial-of-service attack::
+
+    def factorial (self, request):
+        if self.n > 10000:
+            sys.stderr.write("warning: possible denial-of-service attack "
+                             "(request for factorial(%d))\n" % self.n)
+        return "%d! = %d" % (self.n, fact(self.n))
+
+Since the Quixote error log is where application tracebacks are
+recorded, you should be watching this log file regularly, so you would
+presumably notice these messages.
+
+In real life, you'd probably just deny such a ludicrous request.  You
+could do this by raising a Quixote publishing error.  For example::
+
+    def factorial (self, request):
+        from quixote.errors import AccessError
+        if self.n > 10000:
+            raise AccessError("ridiculous request denied")
+        return "%d! = %d" % (self.n, fact(self.n))
+
+
+Lesson 5: widgets
+-----------------
+
+You can't get very far writing web applications without writing forms,
+and the building blocks of web forms are generally called "form
+elements": string input, checkboxes, radiobuttons, select lists, and so
+forth.  Quixote provides an abstraction for all of these form elements:
+the Widget class hierarchy.  The widget classes are explained in detail
+in widget.txt; I'm going to give a brief description of the "Quixote
+Widget Demo" page and the code behind it here.
+
+If you follow the "widgets" link from the main page, you'll see a fairly
+ordinary-looking web form -- the sort of thing you might have to fill
+out to order a pizza on-line, with the oddity that this pizza shop is
+asking for your eye colour.  (Hey, I had to demonstrate radiobuttons
+somehow!)  This form demonstrates most of HTML's basic form
+capabilities: a simple string, a password, a checkbox, a set of
+radiobuttons, a single-select list, and a multiple-select list.
+
+Whenever you implement a web form, there are two things you have to
+worry about: generating the form elements and processing the
+client-submitted form data.  There are as many ways of dividing up this
+work as there are web programmers.  (Possibly more: every time I tackle
+this problem, I seem to come up with a different way of solving it.)
+The form in the Quixote widget demo is implemented in three parts, all
+of them in demo/widgets.ptl:
+
+* ``widgets()`` is the callable that handles the "/qdemo/widgets" URL.
+  This template creates all the widget objects needed for the form and
+  then calls either ``render_widgets()`` or ``process_widgets()`` as
+  appropriate.
+
+* ``render_widgets()`` is called by ``widgets()`` when there is no form
+  data to process, eg. on the first access to the "/qdemo/widgets" URL.
+  It generates the form elements and returns an HTML document consisting
+  of a table that lays them out in an attractive form.
+
+* ``process_widgets()`` is called by ``widgets()`` when there is form data
+  to process, ie. when the form generated by ``render_widgets()`` is
+  submitted by the user.  It processes the form data, ie. it looks up
+  the user-submitted form values and returns an HTML document listing
+  those values.
+
+This division of labour works well with Quixote's widget classes, since
+you need a collection of Widget objects whether you are generating the
+form elements or processing form data.  For generating the form, we (1)
+create all the widget objects, and (2) generate an HTML document that
+includes the output of each widget object's ``render()`` method.  (Laying
+out the form is the responsibility of render_widgets(), which is why
+it's littered with table tags.)  For processing the form, we (1) create
+all the widget objects, and (2) generate an HTML document incorporating
+the user-submitted form values.  In both cases, step (1) is handled by
+the widgets() template, which then calls either render_widgets() or
+process_widgets() for step (2).
+
+Thus, there are three things you have to understand about widget
+objects: how to create them, how to render them, and how to use them to
+parse form values.  Widget creation is the only step that's very
+interesting, since each widget class has different constructor
+arguments.  For example, here's how we create the "name" widget in the
+pizza shop form::
+
+    widgets['name'] = widget.StringWidget('name', size=20)
+
+When rendered, this widget will produce the following HTML::
+
+    <input size="20" name="name" type="text">
+
+A more complex example is the "pizza size" widget::
+
+    widgets['size'] = widget.SingleSelectWidget(
+        'size', value='medium',
+        allowed_values=['tiny', 'small', 'medium', 'large', 'enormous'],
+        descriptions=['Tiny (4")', 'Small (6")', 'Medium (10")',
+                      'Large (14")', 'Enormous (18")'],
+        size=5)
+
+which will generate the following HTML when rendered::
+
+    <select size="5" name="size">
+      <option value="0">Tiny (4")
+      <option value="1">Small (6")
+      <option selected value="2">Medium (10")
+      <option value="3">Large (14")
+      <option value="4">Enormous (18")
+    </select>
+
+Some things you might need to know about widget creation:
+
+* the standard widget classes are in the quixote.form.widget module;
+  see widget.txt and/or the source code for the complete list
+
+* every widget class constructor has exactly one required argument:
+  the widget name.  This is used as the form element name
+  in the generated HTML.  (Things are a bit different for compound
+  widgets, but I'm not covering them in this document.)
+
+* every widget class supports a number of keyword arguments that
+  generally correspond to attributes of some HTML tag.  The one
+  argument common to all widget classes is ``value``, the current
+  value for this widget.
+
+Rendering widgets is easy: just call the render() method, passing in the
+current HTTPRequest object.  (It's currently not used by the standard
+widget classes, but could be used by derived or compound widget classes
+for context-sensitive widget rendering.)
+
+Parsing form values is just as easy: call the parse() method, again
+passing in the current HTTPRequest object.  The return value depends on
+the nature of the widget, eg.:
+
+* StringWidget and PasswordWidget return a string
+* CheckboxWidget returns a boolean
+* RadiobuttonsWidget, SingleSelectWidget, and MultipleSelectWidget
+  return one of the values supplied in ``allowed_values`` -- in the
+  demo these are all strings, but they can be any Python value.
+  (If the client submits bogus data, the widget will return None.)
+
+
+$Id: demo.txt 21292 2003-04-08 16:48:47Z akuchlin $

Added: packages/quixote1/branches/upstream/current/doc/form2conversion.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/form2conversion.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/form2conversion.html (added)
+++ packages/quixote1/branches/upstream/current/doc/form2conversion.html Mon May  8 19:16:16 2006
@@ -1,0 +1,369 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Converting form1 forms to use the form2 library</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="converting-form1-forms-to-use-the-form2-library">
+<h1 class="title">Converting form1 forms to use the form2 library</h1>
+<div class="section" id="introduction">
+<h1><a name="introduction">Introduction</a></h1>
+<p>These are some notes and examples for converting Quixote form1 forms,
+that is forms derived from <tt class="literal"><span class="pre">quixote.form.Form</span></tt>, to the newer form2
+forms.</p>
+<p>Form2 forms are more flexible than their form1 counterparts in that they
+do not require you to use the <tt class="literal"><span class="pre">Form</span></tt> class as a base to get form
+functionality as form1 forms did.  Form2 forms can be instantiated
+directly and then manipulated as instances.  You may also continue to
+use inheritance for your form2 classes to get form functionality,
+particularly if the structured separation of <tt class="literal"><span class="pre">process</span></tt>, <tt class="literal"><span class="pre">render</span></tt>,
+and <tt class="literal"><span class="pre">action</span></tt> is desirable.</p>
+<p>There are many ways to get from form1 code ported to form2.  At one
+end of the spectrum is to rewrite the form class using a functional
+programing style.  This method is arguably best since the functional
+style makes the flow of control clearer.</p>
+<p>The other end of the spectrum and normally the easiest way to port 
+form1 forms to form2 is to use the <tt class="literal"><span class="pre">compatibility</span></tt> module provided
+in the form2 package.  The compatibility module's Form class provides
+much of the same highly structured machinery (via a <tt class="literal"><span class="pre">handle</span></tt> master
+method) that the form1 framework uses.</p>
+</div>
+<div class="section" id="converting-form1-forms-using-using-the-compatibility-module">
+<h1><a name="converting-form1-forms-using-using-the-compatibility-module">Converting form1 forms using using the compatibility module</a></h1>
+<p>Here's the short list of things to do to convert form1 forms to 
+form2 using compatibility.</p>
+<blockquote>
+<ol class="arabic">
+<li><p class="first">Import the Form base class from <tt class="literal"><span class="pre">quixote.form2.compatibility</span></tt>
+rather than from quixote.form.</p>
+</li>
+<li><p class="first">Getting and setting errors is slightly different.  In your form's 
+process method, where errors are typically set, form2
+has a new interface for marking a widget as having an error.</p>
+<blockquote>
+<p>Form1 API:</p>
+<pre class="literal-block">
+self.error['widget_name'] = 'the error message'
+</pre>
+<p>Form2 API:</p>
+<pre class="literal-block">
+self.set_error('widget_name', 'the error message') 
+</pre>
+</blockquote>
+<p>If you want to find out if the form already has errors, change
+the form1 style of direct references to the <tt class="literal"><span class="pre">self.errors</span></tt>
+dictionary to a call to the <tt class="literal"><span class="pre">has_errors</span></tt> method.</p>
+<blockquote>
+<p>Form1 API:</p>
+<pre class="literal-block">
+if not self.error:
+   do some more error checking...
+</pre>
+<p>Form2 API:</p>
+<pre class="literal-block">
+if not self.has_errors():
+   do some more error checking...
+</pre>
+</blockquote>
+</li>
+<li><p class="first">Form2 select widgets no longer take <tt class="literal"><span class="pre">allowed_values</span></tt> or
+<tt class="literal"><span class="pre">descriptions</span></tt> arguments.  If you are adding type of form2 select
+widget, you must provide the <tt class="literal"><span class="pre">options</span></tt> argument instead.  Options
+are the way you define the list of things that are selectable and
+what is returned when they are selected.  the options list can be
+specified in in one of three ways:</p>
+<pre class="literal-block">
+    options: [objects:any]
+or
+    options: [(object:any, description:any)]
+or
+    options: [(object:any, description:any, key:any)]
+</pre>
+<p>An easy way to construct options if you already have
+allowed_values and descriptions is to use the built-in function
+<tt class="literal"><span class="pre">zip</span></tt> to define options:</p>
+<pre class="literal-block">
+options=zip(allowed_values, descriptions)
+</pre>
+</li>
+</ol>
+<blockquote>
+Note, however, that often it is simpler to to construct the
+<tt class="literal"><span class="pre">options</span></tt> list directly.</blockquote>
+<ol class="arabic simple" start="4">
+<li>You almost certainly want to include some kind of cascading style
+sheet (since form2 forms render with minimal markup).  There is a
+basic set of CSS rules in <tt class="literal"><span class="pre">quixote.form2.css</span></tt>.</li>
+</ol>
+</blockquote>
+<p>Here's the longer list of things you may need to tweak in order for
+form2 compatibility forms to work with your form1 code.</p>
+<blockquote>
+<ul>
+<li><p class="first"><tt class="literal"><span class="pre">widget_type</span></tt> widget class attribute is gone.  This means when
+adding widgets other than widgets defined in <tt class="literal"><span class="pre">quixote.form2.widget</span></tt>,
+you must import the widget class into your module and pass the 
+widget class as the first argument to the <tt class="literal"><span class="pre">add_widget</span></tt> method
+rather than using the <tt class="literal"><span class="pre">widget_type</span></tt> string.</p>
+</li>
+<li><p class="first">The <tt class="literal"><span class="pre">action_url</span></tt> argument to the form's render method is now
+a keyword argument.</p>
+</li>
+<li><p class="first">If you use <tt class="literal"><span class="pre">OptionSelectWidget</span></tt>, there is no longer a 
+<tt class="literal"><span class="pre">get_current_option</span></tt> method.  You can get the current value
+in the normal way.</p>
+</li>
+<li><p class="first"><tt class="literal"><span class="pre">ListWidget</span></tt> has been renamed to <tt class="literal"><span class="pre">WidgetList</span></tt>.</p>
+</li>
+<li><p class="first">There is no longer a <tt class="literal"><span class="pre">CollapsibleListWidget</span></tt> class.  If you need
+this functionality, consider writing a 'deletable composite widget'
+to wrap your <tt class="literal"><span class="pre">WidgetList</span></tt> widgets in it:</p>
+<pre class="literal-block">
+class DeletableWidget(CompositeWidget):
+
+  def __init__(self, name, value=None,
+               element_type=StringWidget,
+               element_kwargs={}, **kwargs):
+     CompositeWidget.__init__(self, name, value=value, **kwargs)
+     self.add(HiddenWidget, 'deleted', value='0')
+       if self.get('deleted') != '1':
+          self.add(element_type, 'element', value=value,
+                   **element_kwargs)
+          self.add(SubmitWidget, 'delete', value='Delete')
+          if self.get('delete'):
+             self.get_widget('deleted').set_value('1')
+
+  def _parse(self, request):
+     if self.get('deleted') == '1':
+        self.value = None
+     else:
+        self.value = self.get('element')
+
+  def render(self):
+     if self.get('deleted') == '1':
+        return self.get_widget('deleted').render()
+     else:
+        return CompositeWidget.render(self)
+</pre>
+</li>
+</ul>
+</blockquote>
+<p>Congratulations, now that you've gotten your form1 forms working in form2,
+you may wish to simplify this code using some of the new features available
+in form2 forms.  Here's a list of things you may wish to consider:</p>
+<blockquote>
+<ul>
+<li><p class="first">In your process method, you don't really need to get a <tt class="literal"><span class="pre">form_data</span></tt>
+dictionary by calling <tt class="literal"><span class="pre">Form.process</span></tt> to ensure your widgets are
+parsed.  Instead, the parsed value of any widget is easy to obtain
+using the widget's <tt class="literal"><span class="pre">get_value</span></tt> method or the form's
+<tt class="literal"><span class="pre">__getitem__</span></tt> method.  So, instead of:</p>
+<pre class="literal-block">
+form_data = Form.process(self, request)
+val = form_data['my_widget']
+</pre>
+<p>You can use:</p>
+<pre class="literal-block">
+val = self['my_widget']
+</pre>
+<p>If the widget may or may not be in the form, you can use <tt class="literal"><span class="pre">get</span></tt>:</p>
+<pre class="literal-block">
+val = self.get('my_widget')
+</pre>
+</li>
+<li><p class="first">It's normally not necessary to provide the <tt class="literal"><span class="pre">action_url</span></tt> argument
+to the form's <tt class="literal"><span class="pre">render</span></tt> method.</p>
+</li>
+<li><p class="first">You don't need to save references to your widgets in your form
+class.  You may have a particular reason for wanting to do that,
+but any widget added to the form using <tt class="literal"><span class="pre">add</span></tt> (or <tt class="literal"><span class="pre">add_widget</span></tt> in
+the compatibility module) can be retrieved using the form's 
+<tt class="literal"><span class="pre">get_widget</span></tt> method.</p>
+</li>
+</ul>
+</blockquote>
+</div>
+<div class="section" id="converting-form1-forms-to-form2-by-functional-rewrite">
+<h1><a name="converting-form1-forms-to-form2-by-functional-rewrite">Converting form1 forms to form2 by functional rewrite</a></h1>
+<p>The best way to get started on a functional version of a form2 rewrite
+is to look at a trivial example form first written using the form1 
+inheritance model followed by it's form2 functional equivalent.</p>
+<p>First the form1 form:</p>
+<pre class="literal-block">
+class MyForm1Form(Form):
+    def __init__(self, request, obj):
+        Form.__init__(self)
+
+        if obj is None:
+            self.obj = Obj()
+            self.add_submit_button('add', 'Add')
+        else:
+            self.obj = obj
+            self.add_submit_button('update', 'Update')
+
+        self.add_cancel_button('Cancel', request.get_path(1) + '/')
+
+        self.add_widget('single_select', 'obj_type',
+                        title='Object Type',
+                        value=self.obj.get_type(),
+                        allowed_values=list(obj.VALID_TYPES),
+                        descriptions=['type1', 'type2', 'type3'])
+        self.add_widget('float', 'cost',
+                        title='Cost',
+                        value=obj.get_cost())
+
+    def render [html] (self, request, action_url):
+        title = 'Obj %s: Edit Object' % self.obj
+        header(title)
+        Form.render(self, request, action_url)
+        footer(title)
+
+    def process(self, request):
+        form_data = Form.process(self, request)
+
+        if not self.error:
+            if form_data['cost'] is None:
+                self.error['cost'] = 'A cost is required.'
+            elif form_data['cost'] &lt; 0:
+                self.error['cost'] = 'The amount must be positive'
+        return form_data
+
+    def action(self, request, submit, form_data):
+        self.obj.set_type(form_data['obj_type'])
+        self.obj.set_cost(form_data['cost'])
+        if submit == 'add':
+            db = get_database()
+            db.add(self.obj)
+        else:
+            assert submit == 'update'
+        return request.redirect(request.get_path(1) + '/')
+</pre>
+<p>Here's the same form using form2 where the function operates on a Form
+instance it keeps a reference to it as a local variable:</p>
+<pre class="literal-block">
+def obj_form(request, obj):
+    form = Form() # quixote.form2.Form
+    if obj is None:
+        obj = Obj()
+        form.add_submit('add', 'Add')
+    else:
+        form.add_submit('update', 'Update')
+    form.add_submit('cancel', 'Cancel')
+
+    form.add_single_select('obj_type',
+                           title='Object Type',
+                           value=obj.get_type(),
+                           options=zip(obj.VALID_TYPES, 
+                                       ['type1', 'type2', 'type3']))
+    form.add_float('cost',
+                   title='Cost',
+                   value=obj.get_cost(),
+                   required=1)
+
+    def render [html] ():
+        title = 'Obj %s: Edit Object' % obj
+        header(title)
+        form.render()
+        footer(title)
+
+    def process():
+       if form['cost'] &lt; 0:
+          self.set_error('cost', 'The amount must be positive')
+
+    def action(submit):
+      obj.set_type(form['obj_type'])
+      obj.set_cost(form['cost'])
+      if submit == 'add':
+          db = get_database()
+          db.add(self.obj)
+      else:
+          assert submit == 'update'
+
+    exit_path = request.get_path(1) + '/'
+    submit = form.get_submit()
+    if submit == 'cancel':
+        return request.redirect(exit_path)
+
+    if not form.is_submitted() or form.has_errors():
+        return render()
+    process()
+    if form.has_errors():
+        return render()
+   
+    action(submit)
+    return request.redirect(exit_path)
+</pre>
+<p>As you can see in the example, the function still has all of the same
+parts of it's form1 equivalent.</p>
+<blockquote>
+<ol class="arabic simple">
+<li>It determines if it's to create a new object or edit an existing one</li>
+<li>It adds submit buttons and widgets</li>
+<li>It has a function that knows how to render the form</li>
+<li>It has a function that knows how to do error processing on the form</li>
+<li>It has a function that knows how to register permanent changes to
+objects when the form is submitted successfully.</li>
+</ol>
+</blockquote>
+<p>In the form2 example, we have used inner functions to separate out these
+parts.  This, of course, is optional, but it does help readability once
+the form gets more complicated and has the additional advantage of
+mapping directly with it's form1 counterparts.</p>
+<p>Form2 functional forms do not have the <tt class="literal"><span class="pre">handle</span></tt> master-method that
+is called after the form is initialized.  Instead, we deal with this
+functionality manually.  Here are some things that the <tt class="literal"><span class="pre">handle</span></tt>
+portion of your form might need to implement illustrated in the 
+order that often makes sense.</p>
+<blockquote>
+<ol class="arabic simple">
+<li>Get the value of any submit buttons using <tt class="literal"><span class="pre">form.get_submit</span></tt></li>
+<li>If the form has not been submitted yet, return <tt class="literal"><span class="pre">render()</span></tt>.</li>
+<li>See if the cancel button was pressed, if so return a redirect.</li>
+<li>Call your <tt class="literal"><span class="pre">process</span></tt> inner function to do any widget-level error
+checks.  The form may already have set some errors, so you
+may wish to check for that before trying additional error checks.</li>
+<li>See if the form was submitted by an unknown submit button.
+This will be the case if the form was submitted via a JavaScript
+action, which is the case when an option select widget is selected.
+The value of <tt class="literal"><span class="pre">get_submit</span></tt> is <tt class="literal"><span class="pre">True</span></tt> in this case and if it is,
+you want to clear any errors and re-render the form.</li>
+<li>If the form has not been submitted or if the form has errors,
+you simply want to render the form.</li>
+<li>Check for your named submit buttons which you expect for
+successful form posting e.g. <tt class="literal"><span class="pre">add</span></tt> or <tt class="literal"><span class="pre">update</span></tt>.  If one of 
+these is pressed, call you action inner function.</li>
+<li>Finally, return a redirect to the expected page following a
+form submission.</li>
+</ol>
+</blockquote>
+<p>These steps are illustrated by the following snippet of code and to a
+large degree in the above functional form2 code example.  Often this
+<tt class="literal"><span class="pre">handle</span></tt> block of code can be simplified.  For example, if you do not
+expect form submissions from unregistered submit buttons, you can
+eliminate the test for that.  Similarly, if your form does not do any
+widget-specific error checking, there's no reason to have an error
+checking <tt class="literal"><span class="pre">process</span></tt> function or the call to it:</p>
+<pre class="literal-block">
+exit_path = request.get_path(1) + '/'
+submit = form.get_submit()
+if not submit:
+    return render()
+if submit == 'cancel':
+    return request.redirect(exit_path)
+if submit == True:
+    form.clear_errors()
+    return render()
+process()
+if form.has_errors():
+    return render()
+action(submit)
+return request.redirect(exit_path)
+</pre>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/form2conversion.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/form2conversion.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/form2conversion.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/form2conversion.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,347 @@
+Converting form1 forms to use the form2 library
+===============================================
+
+Introduction
+------------
+
+These are some notes and examples for converting Quixote form1 forms,
+that is forms derived from ``quixote.form.Form``, to the newer form2
+forms.
+
+Form2 forms are more flexible than their form1 counterparts in that they
+do not require you to use the ``Form`` class as a base to get form
+functionality as form1 forms did.  Form2 forms can be instantiated
+directly and then manipulated as instances.  You may also continue to
+use inheritance for your form2 classes to get form functionality,
+particularly if the structured separation of ``process``, ``render``,
+and ``action`` is desirable.  
+
+There are many ways to get from form1 code ported to form2.  At one
+end of the spectrum is to rewrite the form class using a functional
+programing style.  This method is arguably best since the functional
+style makes the flow of control clearer.
+
+The other end of the spectrum and normally the easiest way to port 
+form1 forms to form2 is to use the ``compatibility`` module provided
+in the form2 package.  The compatibility module's Form class provides
+much of the same highly structured machinery (via a ``handle`` master
+method) that the form1 framework uses.  
+
+Converting form1 forms using using the compatibility module
+-----------------------------------------------------------
+
+Here's the short list of things to do to convert form1 forms to 
+form2 using compatibility.
+
+  1. Import the Form base class from ``quixote.form2.compatibility``
+     rather than from quixote.form.
+
+  2. Getting and setting errors is slightly different.  In your form's 
+     process method, where errors are typically set, form2
+     has a new interface for marking a widget as having an error.
+
+        Form1 API::
+     
+          self.error['widget_name'] = 'the error message'
+
+        Form2 API::
+
+          self.set_error('widget_name', 'the error message') 
+
+     If you want to find out if the form already has errors, change
+     the form1 style of direct references to the ``self.errors``
+     dictionary to a call to the ``has_errors`` method.
+
+        Form1 API::
+
+           if not self.error:
+              do some more error checking...
+ 
+        Form2 API::
+
+           if not self.has_errors():
+              do some more error checking...
+
+  3. Form2 select widgets no longer take ``allowed_values`` or
+     ``descriptions`` arguments.  If you are adding type of form2 select
+     widget, you must provide the ``options`` argument instead.  Options
+     are the way you define the list of things that are selectable and
+     what is returned when they are selected.  the options list can be
+     specified in in one of three ways::
+
+             options: [objects:any]
+         or
+             options: [(object:any, description:any)]
+         or
+             options: [(object:any, description:any, key:any)]
+
+     An easy way to construct options if you already have
+     allowed_values and descriptions is to use the built-in function
+     ``zip`` to define options::
+
+             options=zip(allowed_values, descriptions)
+
+    Note, however, that often it is simpler to to construct the
+    ``options`` list directly.
+
+  4. You almost certainly want to include some kind of cascading style
+     sheet (since form2 forms render with minimal markup).  There is a
+     basic set of CSS rules in ``quixote.form2.css``.
+
+
+Here's the longer list of things you may need to tweak in order for
+form2 compatibility forms to work with your form1 code.
+ 
+  *  ``widget_type`` widget class attribute is gone.  This means when
+     adding widgets other than widgets defined in ``quixote.form2.widget``,
+     you must import the widget class into your module and pass the 
+     widget class as the first argument to the ``add_widget`` method
+     rather than using the ``widget_type`` string.
+
+  *  The ``action_url`` argument to the form's render method is now
+     a keyword argument.
+
+  *  If you use ``OptionSelectWidget``, there is no longer a 
+     ``get_current_option`` method.  You can get the current value
+     in the normal way.
+
+  *  ``ListWidget`` has been renamed to ``WidgetList``.
+
+  *  There is no longer a ``CollapsibleListWidget`` class.  If you need
+     this functionality, consider writing a 'deletable composite widget'
+     to wrap your ``WidgetList`` widgets in it::
+
+       class DeletableWidget(CompositeWidget):
+
+         def __init__(self, name, value=None,
+                      element_type=StringWidget,
+                      element_kwargs={}, **kwargs):
+            CompositeWidget.__init__(self, name, value=value, **kwargs)
+            self.add(HiddenWidget, 'deleted', value='0')
+              if self.get('deleted') != '1':
+                 self.add(element_type, 'element', value=value,
+                          **element_kwargs)
+                 self.add(SubmitWidget, 'delete', value='Delete')
+                 if self.get('delete'):
+                    self.get_widget('deleted').set_value('1')
+
+         def _parse(self, request):
+            if self.get('deleted') == '1':
+               self.value = None
+            else:
+               self.value = self.get('element')
+
+         def render(self):
+            if self.get('deleted') == '1':
+               return self.get_widget('deleted').render()
+            else:
+               return CompositeWidget.render(self)
+
+
+Congratulations, now that you've gotten your form1 forms working in form2,
+you may wish to simplify this code using some of the new features available
+in form2 forms.  Here's a list of things you may wish to consider:
+
+  *  In your process method, you don't really need to get a ``form_data``
+     dictionary by calling ``Form.process`` to ensure your widgets are
+     parsed.  Instead, the parsed value of any widget is easy to obtain
+     using the widget's ``get_value`` method or the form's
+     ``__getitem__`` method.  So, instead of::
+
+       form_data = Form.process(self, request)
+       val = form_data['my_widget']
+
+     You can use::
+
+       val = self['my_widget']
+
+     If the widget may or may not be in the form, you can use ``get``::
+
+       val = self.get('my_widget')
+
+
+  *  It's normally not necessary to provide the ``action_url`` argument
+     to the form's ``render`` method.
+
+  *  You don't need to save references to your widgets in your form
+     class.  You may have a particular reason for wanting to do that,
+     but any widget added to the form using ``add`` (or ``add_widget`` in
+     the compatibility module) can be retrieved using the form's 
+     ``get_widget`` method.
+
+
+Converting form1 forms to form2 by functional rewrite
+-----------------------------------------------------
+
+The best way to get started on a functional version of a form2 rewrite
+is to look at a trivial example form first written using the form1 
+inheritance model followed by it's form2 functional equivalent.
+
+First the form1 form::
+
+        class MyForm1Form(Form):
+            def __init__(self, request, obj):
+                Form.__init__(self)
+        
+                if obj is None:
+                    self.obj = Obj()
+                    self.add_submit_button('add', 'Add')
+                else:
+                    self.obj = obj
+                    self.add_submit_button('update', 'Update')
+        
+                self.add_cancel_button('Cancel', request.get_path(1) + '/')
+        
+                self.add_widget('single_select', 'obj_type',
+                                title='Object Type',
+                                value=self.obj.get_type(),
+                                allowed_values=list(obj.VALID_TYPES),
+                                descriptions=['type1', 'type2', 'type3'])
+                self.add_widget('float', 'cost',
+                                title='Cost',
+                                value=obj.get_cost())
+        
+            def render [html] (self, request, action_url):
+                title = 'Obj %s: Edit Object' % self.obj
+                header(title)
+                Form.render(self, request, action_url)
+                footer(title)
+        
+            def process(self, request):
+                form_data = Form.process(self, request)
+        
+                if not self.error:
+                    if form_data['cost'] is None:
+                        self.error['cost'] = 'A cost is required.'
+                    elif form_data['cost'] < 0:
+                        self.error['cost'] = 'The amount must be positive'
+                return form_data
+        
+            def action(self, request, submit, form_data):
+                self.obj.set_type(form_data['obj_type'])
+                self.obj.set_cost(form_data['cost'])
+                if submit == 'add':
+                    db = get_database()
+                    db.add(self.obj)
+                else:
+                    assert submit == 'update'
+                return request.redirect(request.get_path(1) + '/')
+        
+Here's the same form using form2 where the function operates on a Form
+instance it keeps a reference to it as a local variable::
+
+        def obj_form(request, obj):
+            form = Form() # quixote.form2.Form
+            if obj is None:
+                obj = Obj()
+                form.add_submit('add', 'Add')
+            else:
+                form.add_submit('update', 'Update')
+            form.add_submit('cancel', 'Cancel')
+        
+            form.add_single_select('obj_type',
+                                   title='Object Type',
+                                   value=obj.get_type(),
+                                   options=zip(obj.VALID_TYPES, 
+                                               ['type1', 'type2', 'type3']))
+            form.add_float('cost',
+                           title='Cost',
+                           value=obj.get_cost(),
+                           required=1)
+        
+            def render [html] ():
+                title = 'Obj %s: Edit Object' % obj
+                header(title)
+                form.render()
+                footer(title)
+
+            def process():
+               if form['cost'] < 0:
+                  self.set_error('cost', 'The amount must be positive')
+
+            def action(submit):
+              obj.set_type(form['obj_type'])
+              obj.set_cost(form['cost'])
+              if submit == 'add':
+                  db = get_database()
+                  db.add(self.obj)
+              else:
+                  assert submit == 'update'
+
+            exit_path = request.get_path(1) + '/'
+            submit = form.get_submit()
+            if submit == 'cancel':
+                return request.redirect(exit_path)
+        
+            if not form.is_submitted() or form.has_errors():
+                return render()
+            process()
+            if form.has_errors():
+                return render()
+           
+            action(submit)
+            return request.redirect(exit_path)
+
+
+As you can see in the example, the function still has all of the same
+parts of it's form1 equivalent.
+
+   1. It determines if it's to create a new object or edit an existing one
+   2. It adds submit buttons and widgets
+   3. It has a function that knows how to render the form
+   4. It has a function that knows how to do error processing on the form
+   5. It has a function that knows how to register permanent changes to
+      objects when the form is submitted successfully.
+
+In the form2 example, we have used inner functions to separate out these
+parts.  This, of course, is optional, but it does help readability once
+the form gets more complicated and has the additional advantage of
+mapping directly with it's form1 counterparts.
+
+Form2 functional forms do not have the ``handle`` master-method that
+is called after the form is initialized.  Instead, we deal with this
+functionality manually.  Here are some things that the ``handle``
+portion of your form might need to implement illustrated in the 
+order that often makes sense.
+
+  1. Get the value of any submit buttons using ``form.get_submit``
+  2. If the form has not been submitted yet, return ``render()``.
+  3. See if the cancel button was pressed, if so return a redirect.
+  4. Call your ``process`` inner function to do any widget-level error
+     checks.  The form may already have set some errors, so you
+     may wish to check for that before trying additional error checks.
+  5. See if the form was submitted by an unknown submit button.
+     This will be the case if the form was submitted via a JavaScript
+     action, which is the case when an option select widget is selected.
+     The value of ``get_submit`` is ``True`` in this case and if it is,
+     you want to clear any errors and re-render the form.
+  6. If the form has not been submitted or if the form has errors,
+     you simply want to render the form.
+  7. Check for your named submit buttons which you expect for
+     successful form posting e.g. ``add`` or ``update``.  If one of 
+     these is pressed, call you action inner function.
+  8. Finally, return a redirect to the expected page following a
+     form submission.
+
+These steps are illustrated by the following snippet of code and to a
+large degree in the above functional form2 code example.  Often this
+``handle`` block of code can be simplified.  For example, if you do not
+expect form submissions from unregistered submit buttons, you can
+eliminate the test for that.  Similarly, if your form does not do any
+widget-specific error checking, there's no reason to have an error
+checking ``process`` function or the call to it::
+
+      exit_path = request.get_path(1) + '/'
+      submit = form.get_submit()
+      if not submit:
+          return render()
+      if submit == 'cancel':
+          return request.redirect(exit_path)
+      if submit == True:
+          form.clear_errors()
+          return render()
+      process()
+      if form.has_errors():
+          return render()
+      action(submit)
+      return request.redirect(exit_path)

Added: packages/quixote1/branches/upstream/current/doc/multi-threaded.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/multi-threaded.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/multi-threaded.html (added)
+++ packages/quixote1/branches/upstream/current/doc/multi-threaded.html Mon May  8 19:16:16 2006
@@ -1,0 +1,48 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Multi-Threaded Quixote Applications</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="multi-threaded-quixote-applications">
+<h1 class="title">Multi-Threaded Quixote Applications</h1>
+<p>Starting with Quixote 0.6, it's possible to write multi-threaded Quixote
+applications.  In previous versions, Quixote stored the current
+HTTPRequest object in a global variable, meaning that processing
+multiple requests in the same process simultaneously was impossible.</p>
+<p>However, the Publisher class as shipped still can't handle multiple
+simultaneous requests; you'll need to subclass Publisher to make it
+re-entrant.  Here's a starting point:</p>
+<pre class="literal-block">
+import thread
+from quixote.publish import Publisher
+
+[...]
+
+class ThreadedPublisher (Publisher):
+    def __init__ (self, root_namespace, config=None):
+        Publisher.__init__(self, root_namespace, config)
+        self._request_dict = {}
+
+    def _set_request(self, request):
+        self._request_dict[thread.get_ident()] = request
+
+    def _clear_request(self):
+        try:
+            del self._request_dict[thread.get_ident()]
+        except KeyError:
+            pass
+
+    def get_request(self):
+        return self._request_dict.get(thread.get_ident())
+</pre>
+<p>Using ThreadedPublisher, you now have one current request per thread,
+rather than one for the entire process.</p>
+<p>$Id: multi-threaded.txt 20217 2003-01-16 20:51:53Z akuchlin $</p>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/multi-threaded.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/multi-threaded.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/multi-threaded.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/multi-threaded.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,39 @@
+Multi-Threaded Quixote Applications
+===================================
+
+Starting with Quixote 0.6, it's possible to write multi-threaded Quixote
+applications.  In previous versions, Quixote stored the current
+HTTPRequest object in a global variable, meaning that processing
+multiple requests in the same process simultaneously was impossible.
+
+However, the Publisher class as shipped still can't handle multiple
+simultaneous requests; you'll need to subclass Publisher to make it
+re-entrant.  Here's a starting point::
+
+  import thread
+  from quixote.publish import Publisher
+
+  [...]
+
+  class ThreadedPublisher (Publisher):
+      def __init__ (self, root_namespace, config=None):
+          Publisher.__init__(self, root_namespace, config)
+          self._request_dict = {}
+
+      def _set_request(self, request):
+          self._request_dict[thread.get_ident()] = request
+
+      def _clear_request(self):
+          try:
+              del self._request_dict[thread.get_ident()]
+          except KeyError:
+              pass
+
+      def get_request(self):
+          return self._request_dict.get(thread.get_ident())
+
+Using ThreadedPublisher, you now have one current request per thread,
+rather than one for the entire process.
+
+
+$Id: multi-threaded.txt 20217 2003-01-16 20:51:53Z akuchlin $

Added: packages/quixote1/branches/upstream/current/doc/programming.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/programming.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/programming.html (added)
+++ packages/quixote1/branches/upstream/current/doc/programming.html Mon May  8 19:16:16 2006
@@ -1,0 +1,445 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Quixote Programming Overview</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="quixote-programming-overview">
+<h1 class="title">Quixote Programming Overview</h1>
+<p>This document explains how a Quixote application is structured.  Be sure
+you have read the &quot;Understanding the demo&quot; section of demo.txt first --
+this explains a lot of Quixote fundamentals.</p>
+<p>There are three components to a Quixote application:</p>
+<ol class="arabic">
+<li><p class="first">A driver script, usually a CGI or FastCGI script.  This is
+the interface between your web server (eg., Apache) and the bulk of
+your application code.</p>
+<p>The driver script is responsible for creating a Quixote publisher
+customized for your application and invoking its publishing loop.</p>
+</li>
+<li><p class="first">A configuration file.  This file specifies various features of the
+Publisher class, such as how errors are handled, the paths of
+various log files, and various other things.  Read through
+quixote/config.py for the full list of configuration settings.</p>
+<p>The most important configuration parameters are:</p>
+<blockquote>
+<dl>
+<dt><tt class="literal"><span class="pre">ERROR_EMAIL</span></tt></dt>
+<dd><p class="first last">e-mail address to which errors will be mailed</p>
+</dd>
+<dt><tt class="literal"><span class="pre">ERROR_LOG</span></tt></dt>
+<dd><p class="first last">file to which errors will be logged</p>
+</dd>
+</dl>
+</blockquote>
+<p>For development/debugging, you should also set <tt class="literal"><span class="pre">DISPLAY_EXCEPTIONS</span></tt>
+true and <tt class="literal"><span class="pre">SECURE_ERRORS</span></tt> false; the defaults are the reverse, to
+favour security over convenience.</p>
+</li>
+<li><p class="first">Finally, the bulk of the code will be in a Python package or
+module, called the root namespace.  The Quixote publisher will be set
+up to start traversing at the root namespace.</p>
+</li>
+</ol>
+<div class="section" id="driver-script">
+<h1><a name="driver-script">Driver script</a></h1>
+<p>The driver script is the interface between your web server and Quixote's
+&quot;publishing loop&quot;, which in turn is the gateway to your application
+code.  Thus, there are two things that your Quixote driver script must
+do:</p>
+<ul class="simple">
+<li>create a Quixote publisher -- that is, an instance of the Publisher
+class provided by the quixote.publish module -- and customize it for
+your application</li>
+<li>invoke the Quixote publishing loop by calling the 'publish_cgi()'
+method of the publisher</li>
+</ul>
+<p>The publisher is responsible for translating URLs to Python objects and
+calling the appropriate function, method, or PTL template to retrieve
+the information and/or carry out the action requested by the URL.</p>
+<p>The most important application-specific customization done by the driver
+script is to set the root namespace of your application.  Broadly
+speaking, a namespace is any Python object with attributes.  The most
+common namespaces are modules, packages, and class instances.  The root
+namespace of a Quixote application is usually a Python package, although
+for a small application it could be a regular module.</p>
+<p>The driver script can be very simple; for example, here is a
+trimmed-down version of demo.cgi, the driver script for the Quixote
+demo:</p>
+<pre class="literal-block">
+from quixote import enable_ptl, Publisher
+enable_ptl()
+app = Publisher(&quot;quixote.demo&quot;)
+app.setup_logs()
+app.publish_cgi()
+</pre>
+<p>(Whether you install this as <tt class="literal"><span class="pre">demo.cgi</span></tt>, <tt class="literal"><span class="pre">demo.fcgi</span></tt>, <tt class="literal"><span class="pre">demo.py</span></tt>,
+or whatever is up to you and your web server.)</p>
+<p>That's almost the simplest possible case -- there's no
+application-specific configuration info apart from the root namespace.
+(The only way to make this simpler would be to remove the
+<tt class="literal"><span class="pre">enable_ptl()</span></tt> and <tt class="literal"><span class="pre">setup_logs()</span></tt> calls.  The former would remove
+the ability to import PTL modules, which is at least half the fun with
+Quixote; the latter would disable Quixote's debug and error logging,
+which is very useful.)</p>
+<p>Here's a slightly more elaborate example, for a hypothetical database of
+books:</p>
+<pre class="literal-block">
+from quixote import enable_ptl, Publisher
+from quixote.config import Config
+
+# Install the PTL import hook, so we can use PTL modules in this app
+enable_ptl()
+
+# Create a Publisher instance with the default configuration.
+pub = Publisher('books')
+
+# Read a config file to override some default values.
+pub.read_config('/www/conf/books.conf')
+
+# Setup error and debug logging (do this after read_config(), so
+# the settings in /www/conf/books.conf have an effect!).
+pub.setup_logs()
+
+# Enter the publishing main loop
+pub.publish_cgi()
+</pre>
+<p>The application code is kept in a package named simply 'books' in this
+example, so its name is provided as the root namespace when creating the
+Publisher instance.</p>
+<p>The SessionPublisher class in quixote.publish can also be used; it
+provides session tracking.  The changes required to use
+SessionPublisher would be:</p>
+<pre class="literal-block">
+...
+from quixote.publish import SessionPublisher
+...
+pub = SessionPublisher(PACKAGE_NAME)
+...
+</pre>
+<p>For details on session management, see session-mgmt.txt.</p>
+<p>Getting the driver script to actually run is between you and your web
+server.  See the web-server.txt document for help, especially with
+Apache (which is the only web server we currently know anything about).</p>
+</div>
+<div class="section" id="configuration-file">
+<h1><a name="configuration-file">Configuration file</a></h1>
+<p>In the <tt class="literal"><span class="pre">books.cgi</span></tt> driver script, configuration information is read
+from a file by this line:</p>
+<pre class="literal-block">
+pub.read_config('/www/conf/books.conf')
+</pre>
+<p>You should never edit the default values in quixote/config.py, because
+your edits will be lost if you upgrade to a newer Quixote version.  You
+should certainly read it, though, to understand what all the
+configuration variables are.</p>
+<p>The configuration file contains Python code, which is then evaluated
+using Python's built-in function <tt class="literal"><span class="pre">execfile()</span></tt>.  Since it's Python code,
+it's easy to set config variables:</p>
+<pre class="literal-block">
+ACCESS_LOG = &quot;/www/log/access/books.log&quot; 
+DEBUG_LOG = &quot;/www/log/books-debug.log&quot;
+ERROR_LOG = &quot;/www/log/books-error.log&quot;
+</pre>
+<p>You can also execute arbitrary Python code to figure out what the
+variables should be.  The following example changes some settings to
+be more convenient for a developer when the <tt class="literal"><span class="pre">WEB_MODE</span></tt> environment
+variable is the string <tt class="literal"><span class="pre">DEVEL</span></tt>:</p>
+<pre class="literal-block">
+web_mode = os.environ[&quot;WEB_MODE&quot;]
+if web_mode == &quot;DEVEL&quot;:
+    DISPLAY_EXCEPTIONS = 1
+    SECURE_ERRORS = 0
+    RUN_ONCE = 1
+elif web_mode in (&quot;STAGING&quot;, &quot;LIVE&quot;):
+    DISPLAY_EXCEPTIONS = 0
+    SECURE_ERRORS = 1
+    RUN_ONCE = 0
+else:
+    raise RuntimeError, &quot;unknown server mode: %s&quot; % web_mode
+</pre>
+<p>At the MEMS Exchange, we use this flexibility to display tracebacks in
+<tt class="literal"><span class="pre">DEVEL</span></tt> mode, to redirect generated e-mails to a staging address in
+<tt class="literal"><span class="pre">STAGING</span></tt> mode, and to enable all features in <tt class="literal"><span class="pre">LIVE</span></tt> mode.</p>
+</div>
+<div class="section" id="logging">
+<h1><a name="logging">Logging</a></h1>
+<p>Every Quixote application can have up to three log files, each of
+which is selected by a different configuration variable:</p>
+<ul class="simple">
+<li>access log (<tt class="literal"><span class="pre">ACCESS_LOG</span></tt>)</li>
+<li>error log (<tt class="literal"><span class="pre">ERROR_LOG</span></tt>)</li>
+<li>debug log (<tt class="literal"><span class="pre">DEBUG_LOG</span></tt>)</li>
+</ul>
+<p>If you want logging to work, you must call <tt class="literal"><span class="pre">setup_logs()</span></tt> on your
+Publisher object after creating it and reading any application-specific
+config file.  (This only applies for CGI/FastCGI driver scripts, where
+you are responsible for creating the Publisher object.  With mod_python
+under Apache, it's taken care of for you.)</p>
+<p>Quixote writes one (rather long) line to the access log for each request
+it handles; we have split that line up here to make it easier to read:</p>
+<pre class="literal-block">
+127.0.0.1 - 2001-10-15 09:48:43
+  2504 &quot;GET /catalog/ HTTP/1.1&quot;
+  200 'Opera/6.0 (Linux; U)' 0.10sec
+</pre>
+<p>This line consists of:</p>
+<ul class="simple">
+<li>client IP address</li>
+<li>current user (according to Quixote session management mechanism,
+so this will be &quot;-&quot; unless you're using a session manager that
+does authentication)</li>
+<li>date and time of request in local timezone, as YYYY-MM-DD hh:mm:ss</li>
+<li>process ID of the process serving the request (eg. your CGI/FastCGI
+driver script)</li>
+<li>the HTTP request line (request method, URI, and protocol)</li>
+<li>response status code</li>
+<li>HTTP user agent string (specifically, this is
+<tt class="literal"><span class="pre">repr(os.environ.get('HTTP_USER_AGENT',</span> <span class="pre">''))</span></tt>)</li>
+<li>time to complete the request</li>
+</ul>
+<p>If no access log is configured (ie., <tt class="literal"><span class="pre">ACCESS_LOG</span></tt> is <tt class="literal"><span class="pre">None</span></tt>), then
+Quixote will not do any access logging.</p>
+<p>The error log is used for two purposes:</p>
+<ul class="simple">
+<li>all application output to standard error (<tt class="literal"><span class="pre">sys.stderr</span></tt>) goes to
+Quixote's error log</li>
+<li>all application tracebacks will be written to Quixote's error log</li>
+</ul>
+<p>If no error log is configured (with <tt class="literal"><span class="pre">ERROR_LOG</span></tt>), then both types of
+messages will be written to the stderr supplied to Quixote for this
+request by your web server.  At least for CGI/FastCGI scripts under
+Apache, this winds up in Apache's error log.</p>
+<p>The debug log is where any application output to stdout goes.  Thus, you
+can just sprinkle <tt class="literal"><span class="pre">print</span></tt> statements into your application for
+debugging; if you have configured a debug log, those print statements
+will wind up there.  If you don't configure a debug log, they go to the
+bit bucket (<tt class="literal"><span class="pre">/dev/null</span></tt> on Unix, <tt class="literal"><span class="pre">NUL</span></tt> on Windows).</p>
+</div>
+<div class="section" id="application-code">
+<h1><a name="application-code">Application code</a></h1>
+<p>Finally, we reach the most complicated part of a Quixote application.
+However, thanks to Quixote's design, everything you've ever learned
+about designing and writing Python code is applicable, so there are no
+new hoops to jump through.  The only new language to learn is PTL, which
+is simply Python with a novel way of generating function return values
+-- see PTL.txt for details.</p>
+<p>An application's code lives in a Python package that contains both .py
+and .ptl files.  Complicated logic should be in .py files, while .ptl
+files, ideally, should contain only the logic needed to render your Web
+interface and basic objects as HTML.  As long as your driver script
+calls <tt class="literal"><span class="pre">enable_ptl()</span></tt>, you can import PTL modules (.ptl files) just as
+if they were Python modules.</p>
+<p>Quixote's publisher will start at the root of this package, and will
+treat the rest of the URL as a path into the package's contents.  Here
+are some examples, assuming that the <tt class="literal"><span class="pre">URL_PREFIX</span></tt> is <tt class="literal"><span class="pre">&quot;/q&quot;</span></tt>, your
+web server is setup to rewrite <tt class="literal"><span class="pre">/q</span></tt> requests as calls to (eg.)
+<tt class="literal"><span class="pre">/www/cgi-bin/books.cgi</span></tt>, and the root package for your application is
+'books':</p>
+<pre class="literal-block">
+http://.../q/         call         books._q_index()
+http://.../q/other    call         books.other(), if books.other
+                                   is callable (eg. a function or
+                                   method)
+http://.../q/other    redirect to  /q/other/, if books.other is a
+                                   namespace (eg. a module or sub-package)
+http://.../q/other/   call         books.other._q_index(), if books.other
+                                   is a namespace
+</pre>
+<p>One of Quixote's design principles is &quot;Be explicit.&quot;  Therefore there's
+no complicated rule for remembering which functions in a module are
+public; you just have to list them all in the _q_exports variable, which
+should be a list of strings naming the public functions.  You don't need
+to list the <tt class="literal"><span class="pre">_q_index()</span></tt> function as being public; that's assumed.
+Eg. if <tt class="literal"><span class="pre">foo()</span></tt> is a function to be exported (via Quixote to the web)
+from your application's namespace, you should have this somewhere in
+that namespace (ie. at module level in a module or __init__.py file):</p>
+<pre class="literal-block">
+_q_exports = ['foo']
+</pre>
+<p>At times it is desirable for URLs to contain path components that are
+not valid Python identifiers.  In these cases you can provide an
+explicit external to internal name mapping.  For example:</p>
+<pre class="literal-block">
+_q_exports = ['foo', ('stylesheet.css', 'stylesheet_css')]
+</pre>
+<p>When a function is callable from the web, it must expect a single
+parameter, which will be an instance of the HTTPRequest class.  This
+object contains everything Quixote could discover about the current HTTP
+request -- CGI environment variables, form data, cookies, etc.  When
+using SessionPublisher, request.session is a Session object for the user
+agent making the request.</p>
+<p>The function should return a string; all PTL templates return a string
+automatically.  <tt class="literal"><span class="pre">request.response</span></tt> is an HTTPResponse instance, which
+has methods for setting the content-type of the function's output,
+generating an HTTP redirect, specifying arbitrary HTTP response headers,
+and other common tasks.  (Actually, the request object also has a method
+for generating a redirect.  It's usually better to use this -- ie. code
+<tt class="literal"><span class="pre">request.redirect(...)</span></tt> because generating a redirect correctly
+requires knowledge of the request, and only the request object has that
+knowledge.  <tt class="literal"><span class="pre">request.response.redirect(...)</span></tt> only works if you supply
+an absolute URL, eg. <tt class="literal"><span class="pre">&quot;http://www.example.com/foo/bar&quot;</span></tt>.)</p>
+<p>Use</p>
+<pre class="literal-block">
+pydoc quixote.http_request
+pydoc quixote.http_response
+</pre>
+<p>to view the documentation for the HTTPRequest and HTTPResponse classes,
+or consult the source code for all the gory details.</p>
+<p>There are a few special functions that affect Quixote's 
+traversal of a URL to determine how to handle it: <tt class="literal"><span class="pre">_q_access()</span></tt>,
+<tt class="literal"><span class="pre">_q_lookup()</span></tt>, and  <tt class="literal"><span class="pre">_q_resolve()</span></tt>.</p>
+</div>
+<div class="section" id="q-access-request">
+<h1><a name="q-access-request"><tt class="literal"><span class="pre">_q_access(request)</span></tt></a></h1>
+<p>If this function is present in a module, it will be called before
+attempting to traverse any further.  It can look at the contents of
+request and decide if the traversal can continue; if not, it should
+raise quixote.errors.AccessError (or a subclass), and Quixote will
+return a 403 (&quot;forbidden&quot;) HTTP status code.  The return value is
+ignored if <tt class="literal"><span class="pre">_q_access()</span></tt> doesn't raise an exception.</p>
+<p>For example, in the MEMS Exchange code, we have some sets of pages that
+are only accessible to signed-in users of a certain type.  The
+<tt class="literal"><span class="pre">_q_access()</span></tt> function looks like this:</p>
+<pre class="literal-block">
+def _q_access (request):
+    if request.session.user is None:
+        raise NotLoggedInError(&quot;You must be signed in.&quot;)
+    if not (request.session.user.is_admin() or
+            request.session.user.is_fab()):
+        raise AccessError(&quot;You don't have access to the reports page.&quot;)
+</pre>
+<p>This is less error-prone than having to remember to add checks to 
+every single public function.</p>
+</div>
+<div class="section" id="q-lookup-request-name">
+<h1><a name="q-lookup-request-name"><tt class="literal"><span class="pre">_q_lookup(request,</span> <span class="pre">name)</span></tt></a></h1>
+<p>This function translates an arbitrary string into an object that we
+continue traversing.  This is very handy; it lets you put user-space
+objects into your URL-space, eliminating the need for digging ID
+strings out of a query, or checking <tt class="literal"><span class="pre">PATH_INFO</span></tt> after Quixote's done
+with it.  But it is a compromise with security: it opens up the
+traversal algorithm to arbitrary names not listed in <tt class="literal"><span class="pre">_q_exports</span></tt>.
+(<tt class="literal"><span class="pre">_q_lookup()</span></tt> is never called for names listed in <tt class="literal"><span class="pre">_q_exports</span></tt>.)
+You should therefore be extremely paranoid about checking the value of
+<tt class="literal"><span class="pre">name</span></tt>.</p>
+<p><tt class="literal"><span class="pre">request</span></tt> is the request object, as it is everywhere else; <tt class="literal"><span class="pre">name</span></tt> is
+a string containing the next component of the path.  <tt class="literal"><span class="pre">_q_lookup()</span></tt> should
+return either a string (a complete document that will be returned to the
+client) or some object that can be traversed further.  Returning a
+string is useful in simple cases, eg. if you want the <tt class="literal"><span class="pre">/user/joe</span></tt> URI
+to show everything about user &quot;joe&quot; in your database, you would define a
+<tt class="literal"><span class="pre">_q_lookup()</span></tt> in the namespace that handles <tt class="literal"><span class="pre">/user/</span></tt> requests:</p>
+<pre class="literal-block">
+def _q_lookup [plain] (request, name):
+    if not request.session.user.is_admin():
+        raise AccessError(&quot;permission denied&quot;)
+    user = get_database().get_user(name)
+    if user is None:
+        raise TraversalError(&quot;no such user: %r&quot; % name)
+    else:
+        &quot;&lt;h1&gt;User %s&lt;/h1&gt;\n&quot; % html_quote(name)
+        &quot;&lt;table&gt;\n&quot;
+        &quot; &lt;tr&gt;&lt;th&gt;real name&lt;/th&gt;&lt;td&gt;%s&lt;/td&gt;\n&quot; % user.real_name
+        # ...
+</pre>
+<p>(This assumes that the namespace in question is a PTL module, not a
+Python module.)</p>
+<p>To publish more complex objects, you'll want to use <tt class="literal"><span class="pre">_q_lookup()</span></tt>'s
+ability to return a new namespace that Quixote continues traversing.
+The usual way to do this is to return an instance of a class that
+implements the web front-end to your object.  That class must have a
+<tt class="literal"><span class="pre">_q_exports</span></tt> attribute, and it will almost certainly have a
+<tt class="literal"><span class="pre">_q_index()</span></tt> method.  It might also have <tt class="literal"><span class="pre">_q_access()</span></tt> and
+<tt class="literal"><span class="pre">_q_lookup()</span></tt> (yes, <tt class="literal"><span class="pre">_q_lookup()</span></tt> calls can nest arbitrarily
+deeply).</p>
+<p>For example, you might want <tt class="literal"><span class="pre">/user/joe/</span></tt> to show a summary,
+<tt class="literal"><span class="pre">/user/joe/history</span></tt> to show a login history, <tt class="literal"><span class="pre">/user/joe/prefs</span></tt> to be
+a page where joe can edit his personal preferences, etc.  The
+<tt class="literal"><span class="pre">_q_lookup()</span></tt> function would then be</p>
+<pre class="literal-block">
+def _q_lookup (request, name):
+    return UserUI(request, name)
+</pre>
+<p>and the UserUI class, which implements the web interface to user
+objects, might look like</p>
+<pre class="literal-block">
+class UserUI:
+    _q_exports = ['history', 'prefs']
+
+    def __init__ (self, request, name):
+        if not request.session.user.is_admin():
+            raise AccessError(&quot;permission denied&quot;)
+        self.user = get_database().get_user(name)
+        if self.user is None:
+            raise TraversalError(&quot;no such user: %r&quot; % name)
+
+    def _q_index (self, request):
+        # ... generate summary page ...
+
+    def history (self, request):
+        # ... generate history page ...
+
+    def prefs (self, request):
+        # ... generate prefs-editing page ...
+</pre>
+</div>
+<div class="section" id="q-resolve-name">
+<h1><a name="q-resolve-name"><tt class="literal"><span class="pre">_q_resolve(name)</span></tt></a></h1>
+<p><tt class="literal"><span class="pre">_q_resolve()</span></tt> looks a bit like <tt class="literal"><span class="pre">_q_lookup()</span></tt>, but is intended for
+a different purpose.  Quixote applications can be slow to start up 
+because they have to import a large number of Python and PTL modules.
+<tt class="literal"><span class="pre">_q_resolve()</span></tt> is a hook that lets time-consuming imports 
+be postponed until the code is actually needed</p>
+<p><tt class="literal"><span class="pre">name</span></tt> is a string containing the next component of the path.
+<tt class="literal"><span class="pre">_q_resolve()</span></tt> should do whatever imports are necessary and return a
+module that will be traversed further.  (Nothing enforces that this
+function return a module, so you could also return other types, such
+as a class instance, a callable object, or even a string) if the last
+component of the path is being resolved.  Given <tt class="literal"><span class="pre">_q_resolve()</span></tt>'s
+memoization feature, though, returning a module is the most useful
+thing to do.)</p>
+<p><tt class="literal"><span class="pre">_q_resolve()</span></tt> is only ever called for names that are in
+<tt class="literal"><span class="pre">_q_exports</span></tt> and that don't already exist in the containing
+namespace.  It is not passed the request object, so its return value
+can't depend on the client in any way.  Calls are also memoized; after
+being called the object returned will be added to the containing
+namespace, so <tt class="literal"><span class="pre">_q_resolve()</span></tt> will be called at most once for a given
+name.</p>
+<p>Most commonly, <tt class="literal"><span class="pre">_q_resolve()</span></tt> will look something like this:</p>
+<pre class="literal-block">
+_q_exports = [..., 'expensive', ...]
+
+def _q_resolve(name):
+    if name == 'expensive':
+        from otherpackage import expensive
+        return expensive
+</pre>
+<p>Let's say this function is in <tt class="literal"><span class="pre">app.ui</span></tt>.  The first time
+<tt class="literal"><span class="pre">/expensive</span></tt> is accessed, <tt class="literal"><span class="pre">_q_resolve('expensive')</span></tt> is called, the
+<tt class="literal"><span class="pre">otherpackage.expensive</span></tt> module is returned and traversal continues.
+The imported module is also saved as <tt class="literal"><span class="pre">app.ui.expensive</span></tt>, so future
+references to <tt class="literal"><span class="pre">/expensive</span></tt> won't need to invoke the <tt class="literal"><span class="pre">_q_resolve()</span></tt>
+hook.</p>
+</div>
+<div class="section" id="q-exception-handler-request-exception">
+<h1><a name="q-exception-handler-request-exception"><tt class="literal"><span class="pre">_q_exception_handler(request,</span> <span class="pre">exception)</span></tt></a></h1>
+<p>Quixote will display a default error page when a <tt class="literal"><span class="pre">PublishError</span></tt>
+exception is raised.  Before displaying the default page, Quixote will
+search back through the list of namespaces traversed looking for an
+object with a <tt class="literal"><span class="pre">_q_exception_handler</span></tt> attribute.  That attribute is
+expected to be a function and is called with the request and exception
+instance as arguments and should return the error page (e.g. a
+string).  If the handler doesn't want to handle a particular error it
+can re-raise it and the next nearest handler will be found.  If no
+<tt class="literal"><span class="pre">_q_exception_handler</span></tt> is found, the default Quixote handler is
+used.</p>
+<p>$Id: programming.txt 23893 2004-04-06 19:31:20Z nascheme $</p>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/programming.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/programming.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/programming.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/programming.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,468 @@
+Quixote Programming Overview
+============================
+
+This document explains how a Quixote application is structured.  Be sure
+you have read the "Understanding the demo" section of demo.txt first --
+this explains a lot of Quixote fundamentals.
+
+There are three components to a Quixote application:
+
+1) A driver script, usually a CGI or FastCGI script.  This is
+   the interface between your web server (eg., Apache) and the bulk of
+   your application code.
+
+   The driver script is responsible for creating a Quixote publisher
+   customized for your application and invoking its publishing loop.
+
+2) A configuration file.  This file specifies various features of the
+   Publisher class, such as how errors are handled, the paths of
+   various log files, and various other things.  Read through
+   quixote/config.py for the full list of configuration settings.
+   
+   The most important configuration parameters are:
+
+      ``ERROR_EMAIL``
+        e-mail address to which errors will be mailed
+      ``ERROR_LOG``
+        file to which errors will be logged
+
+   For development/debugging, you should also set ``DISPLAY_EXCEPTIONS``
+   true and ``SECURE_ERRORS`` false; the defaults are the reverse, to
+   favour security over convenience.
+
+3) Finally, the bulk of the code will be in a Python package or
+   module, called the root namespace.  The Quixote publisher will be set
+   up to start traversing at the root namespace.
+
+
+Driver script
+-------------
+
+The driver script is the interface between your web server and Quixote's
+"publishing loop", which in turn is the gateway to your application
+code.  Thus, there are two things that your Quixote driver script must
+do:
+
+* create a Quixote publisher -- that is, an instance of the Publisher
+  class provided by the quixote.publish module -- and customize it for
+  your application
+
+* invoke the Quixote publishing loop by calling the 'publish_cgi()'
+  method of the publisher
+
+The publisher is responsible for translating URLs to Python objects and
+calling the appropriate function, method, or PTL template to retrieve
+the information and/or carry out the action requested by the URL.
+
+The most important application-specific customization done by the driver
+script is to set the root namespace of your application.  Broadly
+speaking, a namespace is any Python object with attributes.  The most
+common namespaces are modules, packages, and class instances.  The root
+namespace of a Quixote application is usually a Python package, although
+for a small application it could be a regular module.
+
+The driver script can be very simple; for example, here is a
+trimmed-down version of demo.cgi, the driver script for the Quixote
+demo::
+
+    from quixote import enable_ptl, Publisher
+    enable_ptl()
+    app = Publisher("quixote.demo")
+    app.setup_logs()
+    app.publish_cgi()
+
+(Whether you install this as ``demo.cgi``, ``demo.fcgi``, ``demo.py``,
+or whatever is up to you and your web server.)
+
+That's almost the simplest possible case -- there's no
+application-specific configuration info apart from the root namespace.
+(The only way to make this simpler would be to remove the
+``enable_ptl()`` and ``setup_logs()`` calls.  The former would remove
+the ability to import PTL modules, which is at least half the fun with
+Quixote; the latter would disable Quixote's debug and error logging,
+which is very useful.)
+
+Here's a slightly more elaborate example, for a hypothetical database of
+books::
+
+    from quixote import enable_ptl, Publisher
+    from quixote.config import Config
+
+    # Install the PTL import hook, so we can use PTL modules in this app
+    enable_ptl()
+
+    # Create a Publisher instance with the default configuration.
+    pub = Publisher('books')
+
+    # Read a config file to override some default values.
+    pub.read_config('/www/conf/books.conf')
+
+    # Setup error and debug logging (do this after read_config(), so
+    # the settings in /www/conf/books.conf have an effect!).
+    pub.setup_logs()
+
+    # Enter the publishing main loop
+    pub.publish_cgi()
+
+The application code is kept in a package named simply 'books' in this
+example, so its name is provided as the root namespace when creating the
+Publisher instance.
+
+The SessionPublisher class in quixote.publish can also be used; it
+provides session tracking.  The changes required to use
+SessionPublisher would be::
+
+    ...
+    from quixote.publish import SessionPublisher
+    ...
+    pub = SessionPublisher(PACKAGE_NAME)
+    ...
+
+For details on session management, see session-mgmt.txt.
+
+Getting the driver script to actually run is between you and your web
+server.  See the web-server.txt document for help, especially with
+Apache (which is the only web server we currently know anything about).
+
+
+Configuration file
+------------------
+
+In the ``books.cgi`` driver script, configuration information is read
+from a file by this line::
+
+    pub.read_config('/www/conf/books.conf')
+
+You should never edit the default values in quixote/config.py, because
+your edits will be lost if you upgrade to a newer Quixote version.  You
+should certainly read it, though, to understand what all the
+configuration variables are.
+
+The configuration file contains Python code, which is then evaluated
+using Python's built-in function ``execfile()``.  Since it's Python code,
+it's easy to set config variables::
+
+    ACCESS_LOG = "/www/log/access/books.log" 
+    DEBUG_LOG = "/www/log/books-debug.log"
+    ERROR_LOG = "/www/log/books-error.log"
+
+You can also execute arbitrary Python code to figure out what the
+variables should be.  The following example changes some settings to
+be more convenient for a developer when the ``WEB_MODE`` environment
+variable is the string ``DEVEL``::
+
+    web_mode = os.environ["WEB_MODE"]
+    if web_mode == "DEVEL":
+        DISPLAY_EXCEPTIONS = 1
+        SECURE_ERRORS = 0
+        RUN_ONCE = 1
+    elif web_mode in ("STAGING", "LIVE"):
+        DISPLAY_EXCEPTIONS = 0
+        SECURE_ERRORS = 1
+        RUN_ONCE = 0
+    else:
+        raise RuntimeError, "unknown server mode: %s" % web_mode
+
+At the MEMS Exchange, we use this flexibility to display tracebacks in
+``DEVEL`` mode, to redirect generated e-mails to a staging address in
+``STAGING`` mode, and to enable all features in ``LIVE`` mode.
+
+
+Logging
+-------
+
+Every Quixote application can have up to three log files, each of
+which is selected by a different configuration variable:
+
+* access log (``ACCESS_LOG``)
+* error log (``ERROR_LOG``)
+* debug log (``DEBUG_LOG``)
+
+If you want logging to work, you must call ``setup_logs()`` on your
+Publisher object after creating it and reading any application-specific
+config file.  (This only applies for CGI/FastCGI driver scripts, where
+you are responsible for creating the Publisher object.  With mod_python
+under Apache, it's taken care of for you.)
+
+Quixote writes one (rather long) line to the access log for each request
+it handles; we have split that line up here to make it easier to read::
+
+    127.0.0.1 - 2001-10-15 09:48:43
+      2504 "GET /catalog/ HTTP/1.1"
+      200 'Opera/6.0 (Linux; U)' 0.10sec
+
+This line consists of:
+
+* client IP address
+* current user (according to Quixote session management mechanism,
+  so this will be "-" unless you're using a session manager that
+  does authentication)
+* date and time of request in local timezone, as YYYY-MM-DD hh:mm:ss
+* process ID of the process serving the request (eg. your CGI/FastCGI
+  driver script)
+* the HTTP request line (request method, URI, and protocol)
+* response status code
+* HTTP user agent string (specifically, this is
+  ``repr(os.environ.get('HTTP_USER_AGENT', ''))``)
+* time to complete the request
+
+If no access log is configured (ie., ``ACCESS_LOG`` is ``None``), then
+Quixote will not do any access logging.
+
+The error log is used for two purposes:
+
+* all application output to standard error (``sys.stderr``) goes to
+  Quixote's error log
+* all application tracebacks will be written to Quixote's error log
+
+If no error log is configured (with ``ERROR_LOG``), then both types of
+messages will be written to the stderr supplied to Quixote for this
+request by your web server.  At least for CGI/FastCGI scripts under
+Apache, this winds up in Apache's error log.
+
+The debug log is where any application output to stdout goes.  Thus, you
+can just sprinkle ``print`` statements into your application for
+debugging; if you have configured a debug log, those print statements
+will wind up there.  If you don't configure a debug log, they go to the
+bit bucket (``/dev/null`` on Unix, ``NUL`` on Windows).
+
+
+Application code
+----------------
+
+Finally, we reach the most complicated part of a Quixote application.
+However, thanks to Quixote's design, everything you've ever learned
+about designing and writing Python code is applicable, so there are no
+new hoops to jump through.  The only new language to learn is PTL, which
+is simply Python with a novel way of generating function return values
+-- see PTL.txt for details.
+
+An application's code lives in a Python package that contains both .py
+and .ptl files.  Complicated logic should be in .py files, while .ptl
+files, ideally, should contain only the logic needed to render your Web
+interface and basic objects as HTML.  As long as your driver script
+calls ``enable_ptl()``, you can import PTL modules (.ptl files) just as
+if they were Python modules.
+
+Quixote's publisher will start at the root of this package, and will
+treat the rest of the URL as a path into the package's contents.  Here
+are some examples, assuming that the ``URL_PREFIX`` is ``"/q"``, your
+web server is setup to rewrite ``/q`` requests as calls to (eg.)
+``/www/cgi-bin/books.cgi``, and the root package for your application is
+'books'::
+
+  http://.../q/         call         books._q_index()
+  http://.../q/other    call         books.other(), if books.other
+                                     is callable (eg. a function or
+                                     method)
+  http://.../q/other    redirect to  /q/other/, if books.other is a
+                                     namespace (eg. a module or sub-package)
+  http://.../q/other/   call         books.other._q_index(), if books.other
+                                     is a namespace
+
+One of Quixote's design principles is "Be explicit."  Therefore there's
+no complicated rule for remembering which functions in a module are
+public; you just have to list them all in the _q_exports variable, which
+should be a list of strings naming the public functions.  You don't need
+to list the ``_q_index()`` function as being public; that's assumed.
+Eg. if ``foo()`` is a function to be exported (via Quixote to the web)
+from your application's namespace, you should have this somewhere in
+that namespace (ie. at module level in a module or __init__.py file)::
+
+    _q_exports = ['foo']
+
+At times it is desirable for URLs to contain path components that are
+not valid Python identifiers.  In these cases you can provide an
+explicit external to internal name mapping.  For example::
+
+    _q_exports = ['foo', ('stylesheet.css', 'stylesheet_css')]
+
+When a function is callable from the web, it must expect a single
+parameter, which will be an instance of the HTTPRequest class.  This
+object contains everything Quixote could discover about the current HTTP
+request -- CGI environment variables, form data, cookies, etc.  When
+using SessionPublisher, request.session is a Session object for the user
+agent making the request.
+
+The function should return a string; all PTL templates return a string
+automatically.  ``request.response`` is an HTTPResponse instance, which
+has methods for setting the content-type of the function's output,
+generating an HTTP redirect, specifying arbitrary HTTP response headers,
+and other common tasks.  (Actually, the request object also has a method
+for generating a redirect.  It's usually better to use this -- ie. code
+``request.redirect(...)`` because generating a redirect correctly
+requires knowledge of the request, and only the request object has that
+knowledge.  ``request.response.redirect(...)`` only works if you supply
+an absolute URL, eg. ``"http://www.example.com/foo/bar"``.)
+
+Use ::
+
+    pydoc quixote.http_request
+    pydoc quixote.http_response
+
+to view the documentation for the HTTPRequest and HTTPResponse classes,
+or consult the source code for all the gory details.
+
+There are a few special functions that affect Quixote's 
+traversal of a URL to determine how to handle it: ``_q_access()``,
+``_q_lookup()``, and  ``_q_resolve()``.
+
+
+``_q_access(request)``
+----------------------
+
+If this function is present in a module, it will be called before
+attempting to traverse any further.  It can look at the contents of
+request and decide if the traversal can continue; if not, it should
+raise quixote.errors.AccessError (or a subclass), and Quixote will
+return a 403 ("forbidden") HTTP status code.  The return value is
+ignored if ``_q_access()`` doesn't raise an exception.
+
+For example, in the MEMS Exchange code, we have some sets of pages that
+are only accessible to signed-in users of a certain type.  The
+``_q_access()`` function looks like this::
+
+    def _q_access (request):
+        if request.session.user is None:
+            raise NotLoggedInError("You must be signed in.")
+        if not (request.session.user.is_admin() or
+                request.session.user.is_fab()):
+            raise AccessError("You don't have access to the reports page.")
+
+This is less error-prone than having to remember to add checks to 
+every single public function.
+
+
+``_q_lookup(request, name)``
+-----------------------------
+
+This function translates an arbitrary string into an object that we
+continue traversing.  This is very handy; it lets you put user-space
+objects into your URL-space, eliminating the need for digging ID
+strings out of a query, or checking ``PATH_INFO`` after Quixote's done
+with it.  But it is a compromise with security: it opens up the
+traversal algorithm to arbitrary names not listed in ``_q_exports``.
+(``_q_lookup()`` is never called for names listed in ``_q_exports``.)
+You should therefore be extremely paranoid about checking the value of
+``name``.
+
+``request`` is the request object, as it is everywhere else; ``name`` is
+a string containing the next component of the path.  ``_q_lookup()`` should
+return either a string (a complete document that will be returned to the
+client) or some object that can be traversed further.  Returning a
+string is useful in simple cases, eg. if you want the ``/user/joe`` URI
+to show everything about user "joe" in your database, you would define a
+``_q_lookup()`` in the namespace that handles ``/user/`` requests::
+
+    def _q_lookup [plain] (request, name):
+        if not request.session.user.is_admin():
+            raise AccessError("permission denied")
+        user = get_database().get_user(name)
+        if user is None:
+            raise TraversalError("no such user: %r" % name)
+        else:
+            "<h1>User %s</h1>\n" % html_quote(name)
+            "<table>\n"
+            " <tr><th>real name</th><td>%s</td>\n" % user.real_name
+            # ...
+
+(This assumes that the namespace in question is a PTL module, not a
+Python module.)
+
+To publish more complex objects, you'll want to use ``_q_lookup()``'s
+ability to return a new namespace that Quixote continues traversing.
+The usual way to do this is to return an instance of a class that
+implements the web front-end to your object.  That class must have a
+``_q_exports`` attribute, and it will almost certainly have a
+``_q_index()`` method.  It might also have ``_q_access()`` and
+``_q_lookup()`` (yes, ``_q_lookup()`` calls can nest arbitrarily
+deeply).
+
+For example, you might want ``/user/joe/`` to show a summary,
+``/user/joe/history`` to show a login history, ``/user/joe/prefs`` to be
+a page where joe can edit his personal preferences, etc.  The
+``_q_lookup()`` function would then be ::
+
+     def _q_lookup (request, name):
+         return UserUI(request, name)
+
+and the UserUI class, which implements the web interface to user
+objects, might look like ::
+
+    class UserUI:
+        _q_exports = ['history', 'prefs']
+
+        def __init__ (self, request, name):
+            if not request.session.user.is_admin():
+                raise AccessError("permission denied")
+            self.user = get_database().get_user(name)
+            if self.user is None:
+                raise TraversalError("no such user: %r" % name)
+
+        def _q_index (self, request):
+            # ... generate summary page ...
+
+        def history (self, request):
+            # ... generate history page ...
+
+        def prefs (self, request):
+            # ... generate prefs-editing page ...
+
+``_q_resolve(name)``
+--------------------
+
+``_q_resolve()`` looks a bit like ``_q_lookup()``, but is intended for
+a different purpose.  Quixote applications can be slow to start up 
+because they have to import a large number of Python and PTL modules.
+``_q_resolve()`` is a hook that lets time-consuming imports 
+be postponed until the code is actually needed
+
+``name`` is a string containing the next component of the path.
+``_q_resolve()`` should do whatever imports are necessary and return a
+module that will be traversed further.  (Nothing enforces that this
+function return a module, so you could also return other types, such
+as a class instance, a callable object, or even a string) if the last
+component of the path is being resolved.  Given ``_q_resolve()``'s
+memoization feature, though, returning a module is the most useful
+thing to do.)
+
+``_q_resolve()`` is only ever called for names that are in
+``_q_exports`` and that don't already exist in the containing
+namespace.  It is not passed the request object, so its return value
+can't depend on the client in any way.  Calls are also memoized; after
+being called the object returned will be added to the containing
+namespace, so ``_q_resolve()`` will be called at most once for a given
+name.
+
+Most commonly, ``_q_resolve()`` will look something like this::
+
+    _q_exports = [..., 'expensive', ...]
+
+    def _q_resolve(name):
+        if name == 'expensive':
+            from otherpackage import expensive
+            return expensive
+
+Let's say this function is in ``app.ui``.  The first time
+``/expensive`` is accessed, ``_q_resolve('expensive')`` is called, the
+``otherpackage.expensive`` module is returned and traversal continues.
+The imported module is also saved as ``app.ui.expensive``, so future
+references to ``/expensive`` won't need to invoke the ``_q_resolve()``
+hook.
+
+``_q_exception_handler(request, exception)``
+--------------------------------------------
+
+Quixote will display a default error page when a ``PublishError``
+exception is raised.  Before displaying the default page, Quixote will
+search back through the list of namespaces traversed looking for an
+object with a ``_q_exception_handler`` attribute.  That attribute is
+expected to be a function and is called with the request and exception
+instance as arguments and should return the error page (e.g. a
+string).  If the handler doesn't want to handle a particular error it
+can re-raise it and the next nearest handler will be found.  If no
+``_q_exception_handler`` is found, the default Quixote handler is
+used.
+
+
+$Id: programming.txt 23893 2004-04-06 19:31:20Z nascheme $

Added: packages/quixote1/branches/upstream/current/doc/session-mgmt.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/session-mgmt.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/session-mgmt.html (added)
+++ packages/quixote1/branches/upstream/current/doc/session-mgmt.html Mon May  8 19:16:16 2006
@@ -1,0 +1,335 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Quixote Session Management</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="quixote-session-management">
+<h1 class="title">Quixote Session Management</h1>
+<p>HTTP was originally designed as a stateless protocol, meaning that every
+request for a document or image was conducted in a separate TCP
+connection, and that there was no way for a web server to tell if two
+separate requests actually come from the same user.  It's no longer
+necessarily true that every request is conducted in a separate TCP
+connection, but HTTP is still fundamentally stateless.  However, there
+are many applications where it is desirable or even essential to
+establish a &quot;session&quot; for each user, ie. where all requests performed by
+that user are somehow tied together on the server.</p>
+<p>HTTP cookies were invented to address this requirement, and they are
+still the best solution for establishing sessions on top of HTTP.  Thus,
+Quixote's session management mechanism is cookie-based.  (The most
+common alternative is to generate long, complicated URLs with an
+embedded session identifier.  Since Quixote views the URL as a
+fundamental part of the web user interface, a URL-based session
+management scheme would be un-Quixotic.)</p>
+<p>For further reading: the standard for cookies that is approximately
+implemented by most current browsers is RFC 2109; the latest version of
+the standard is RFC 2965.  Those RFCs can be found here:</p>
+<blockquote>
+<p><a class="reference" href="ftp://ftp.isi.edu/in-notes/rfc2109.txt">ftp://ftp.isi.edu/in-notes/rfc2109.txt</a></p>
+<p><a class="reference" href="ftp://ftp.isi.edu/in-notes/rfc2965.txt">ftp://ftp.isi.edu/in-notes/rfc2965.txt</a></p>
+</blockquote>
+<p>In a nutshell, session management with Quixote works like this:</p>
+<ul>
+<li><p class="first">when a user-agent first requests a page from a Quixote application
+that implements session management, Quixote creates a Session object
+and generates a session ID (a random 64-bit number).  The Session
+object is attached to the current HTTPRequest object, so that
+application code involved in processing this request has access to
+the Session object.</p>
+</li>
+<li><p class="first">if, at the end of processing that request, the application code has
+stored any information in the Session object, Quixote saves the
+session in its SessionManager object for use by future requests and
+sends a session cookie, called <tt class="literal"><span class="pre">QX_session</span></tt> by default, to the user.
+The session cookie contains the session ID encoded as a hexadecimal
+string, and is included in the response headers, eg.</p>
+<pre class="literal-block">
+Set-Cookie: QX_session=&quot;928F82A9B8FA92FD&quot;
+</pre>
+<p>(You can instruct Quixote to specify the domain and path for
+URLs to which this cookie should be sent.)</p>
+</li>
+<li><p class="first">the user agent stores this cookie for future requests</p>
+</li>
+<li><p class="first">the next time the user agent requests a resource that matches the
+cookie's domain and path, it includes the <tt class="literal"><span class="pre">QX_session</span></tt> cookie
+previously generated by Quixote in the request headers, eg.:</p>
+<pre class="literal-block">
+Cookie: QX_session=&quot;928F82A9B8FA92FD&quot;
+</pre>
+</li>
+<li><p class="first">while processing the request, Quixote decodes the session ID and
+looks up the corresponding Session object in its SessionManager.  If
+there is no such session, the session cookie is bogus or
+out-of-date, so Quixote raises SessionError; ultimately the user
+gets an error page.  Otherwise, the Session object is attached to
+the HTTPRequest object that is available to all application code
+used to process the request.</p>
+</li>
+</ul>
+<p>There are two caveats to keep in mind before proceeding, one major and
+one minor:</p>
+<ul class="simple">
+<li>Quixote's standard Session and SessionManager class do not
+implement any sort of persistence, meaning that all sessions
+disappear when the process handling web requests terminates.
+Thus, session management is completely useless with a plain
+CGI driver script unless you add some persistence to the mix;
+see &quot;Session persistence&quot; below for information.</li>
+<li>Quixote never expires sessions; if you want user sessions to
+be cleaned up after a period of inactivity, you will have to
+write code to do it yourself.</li>
+</ul>
+<div class="section" id="session-management-demo">
+<h1><a name="session-management-demo">Session management demo</a></h1>
+<p>There's a simple demo of Quixote's session management in
+<tt class="literal"><span class="pre">demo/session_demo.cgi</span></tt> and <tt class="literal"><span class="pre">demo/session.ptl</span></tt>.  The demo implements
+a simple session persistence scheme (each session is written to a
+separate pickle file in <tt class="literal"><span class="pre">/tmp/quixote-session-demo</span></tt>), so running it
+through CGI is just fine.</p>
+<p>I'll assume that you've added a rewrite rule so that requests for
+<tt class="literal"><span class="pre">/qsdemo/</span></tt> are handled by <tt class="literal"><span class="pre">session_demo.cgi</span></tt>, similar to the
+rewriting for <tt class="literal"><span class="pre">/qdemo/</span></tt> described in web-server.txt.  Once that's
+done, point your browser at</p>
+<pre class="literal-block">
+http://&lt;hostname&gt;/qsdemo/
+</pre>
+<p>and play around.</p>
+<p>This particular application uses sessions to keep track of just two
+things: the user's identity and the number of requests made in this
+session.  The first is addressed by Quixote's standard Session class --
+every Session object has a <tt class="literal"><span class="pre">user</span></tt> attribute, which you can use for
+anything you like.  In the session demo, we simply store a string, the
+user's name, which is entered by the user.</p>
+<p>Tracking the number of requests is a bit more interesting: from the
+DemoSession class in session_demo.cgi:</p>
+<pre class="literal-block">
+def __init__ (self, request, id):
+    Session.__init__(self, request, id)
+    self.num_requests = 0
+
+def start_request (self, request):
+    Session.start_request(self, request)
+    self.num_requests += 1
+</pre>
+<p>When the session is created, we initialize the request counter; and when
+we start processing each request, we increment it.</p>
+<p>Using the session information in the application code is simple.  For
+example, here's the PTL code that checks if the user has logged in
+(identified herself) yet, and generates a login form if not:</p>
+<pre class="literal-block">
+session = request.session
+if session.user is None:
+    '''
+    &lt;p&gt;You haven\'t introduced yourself yet.&lt;br&gt;
+    Please tell me your name:
+    '''
+    login_form()
+</pre>
+<p>(The <tt class="literal"><span class="pre">login_form()</span></tt> template just emits a simple HTML form -- see
+<tt class="literal"><span class="pre">demo/session.ptl</span></tt> for full source.)</p>
+<p>If the user has already identified herself, then she doesn't need to do
+so again -- so the other branch of that <tt class="literal"><span class="pre">if</span></tt> statement simply prints a
+friendly greeting:</p>
+<pre class="literal-block">
+else:
+    ('&lt;p&gt;Hello, %s.  Good to see you again.&lt;/p&gt;\n' 
+     % html_quote(session.user))
+</pre>
+<p>Note that we must quote the user's name, because they are free to enter
+anything they please, including special HTML characters like <tt class="literal"><span class="pre">&amp;</span></tt> or
+<tt class="literal"><span class="pre">&lt;</span></tt>.</p>
+<p>Of course, <tt class="literal"><span class="pre">session.user</span></tt> will never be set if we don't set it
+ourselves.  The code that processes the login form is just this (from
+<tt class="literal"><span class="pre">login()</span></tt> in <tt class="literal"><span class="pre">demo/session.ptl</span></tt>):</p>
+<pre class="literal-block">
+if request.form:
+    user = request.form.get(&quot;name&quot;)
+    if not user:
+        raise QueryError(&quot;no user name supplied&quot;)
+
+    session.user = user
+</pre>
+<p>This is obviously a very simple application -- we're not doing any
+verification of the user's input.  We have no user database, no
+passwords, and no limitations on what constitutes a &quot;user name&quot;.  A real
+application would have all of these, as well as a way for users to add
+themselves to the user database -- ie. register with your web site.</p>
+</div>
+<div class="section" id="configuring-the-session-cookie">
+<h1><a name="configuring-the-session-cookie">Configuring the session cookie</a></h1>
+<p>Quixote allows you to configure several aspects of the session cookie
+that it exchanges with clients.  First, you can set the name of the
+cookie; this is important if you have multiple independent Quixote
+applications running on the same server.  For example, the config file
+for the first application might have</p>
+<pre class="literal-block">
+SESSION_COOKIE_NAME = &quot;foo_session&quot;
+</pre>
+<p>and the second application might have</p>
+<pre class="literal-block">
+SESSION_COOKIE_NAME = &quot;bar_session&quot;
+</pre>
+<p>Next, you can use <tt class="literal"><span class="pre">SESSION_COOKIE_DOMAIN</span></tt> and <tt class="literal"><span class="pre">SESSION_COOKIE_PATH</span></tt>
+to set the cookie attributes that control which requests the cookie is
+included with.  By default, these are both <tt class="literal"><span class="pre">None</span></tt>, which instructs
+Quixote to send the cookie without <tt class="literal"><span class="pre">Domain</span></tt> or <tt class="literal"><span class="pre">Path</span></tt> qualifiers.
+For example, if the client requests <tt class="literal"><span class="pre">/foo/bar/</span></tt> from
+www.example.com, and Quixote decides that it must set the session
+cookie in the response to that request, then the server would send</p>
+<pre class="literal-block">
+Set-Cookie: QX_session=&quot;928F82A9B8FA92FD&quot;
+</pre>
+<p>in the response headers.  Since no domain or path were specified with
+that cookie, the browser will only include the cookie with requests to
+www.example.com for URIs that start with <tt class="literal"><span class="pre">/foo/bar/</span></tt>.</p>
+<p>If you want to ensure that your session cookie is included with all
+requests to www.example.com, you should set <tt class="literal"><span class="pre">SESSION_COOKIE_PATH</span></tt> in your
+config file:</p>
+<pre class="literal-block">
+SESSION_COOKIE_PATH = &quot;/&quot;
+</pre>
+<p>which will cause Quixote to set the cookie like this:</p>
+<pre class="literal-block">
+Set-Cookie: QX_session=&quot;928F82A9B8FA92FD&quot;; Path=&quot;/&quot;
+</pre>
+<p>which will instruct the browser to include that cookie with <em>all</em>
+requests to www.example.com.</p>
+<p>However, think carefully about what you set <tt class="literal"><span class="pre">SESSION_COOKIE_PATH</span></tt> to
+-- eg. if you set it to &quot;/&quot;, but all of your Quixote code is under &quot;/q/&quot;
+in your server's URL-space, then your user's session cookies could be
+unnecessarily exposed.  On shared servers where you don't control all of
+the code, this is especially dangerous; be sure to use (eg.)</p>
+<pre class="literal-block">
+SESSION_COOKIE_PATH = &quot;/q/&quot;
+</pre>
+<p>on such servers.  The trailing slash is important; without it, your
+session cookies will be sent to URIs like <tt class="literal"><span class="pre">/qux</span></tt> and <tt class="literal"><span class="pre">/qix</span></tt>, even if
+you don't control those URIs.</p>
+<p>If you want to share the cookie across servers in your domain,
+eg. www1.example.com and www2.example.com, you'll also need to set
+<tt class="literal"><span class="pre">SESSION_COOKIE_DOMAIN</span></tt>:</p>
+<blockquote>
+SESSION_COOKIE_DOMAIN = &quot;.example.com&quot;</blockquote>
+<p>Finally, note that the <tt class="literal"><span class="pre">SESSION_COOKIE_*</span></tt> configuration variables
+<em>only</em> affect Quixote's session cookie; if you set your own cookies
+using the <tt class="literal"><span class="pre">HTTPResponse.set_cookie()</span></tt> method, then the cookie sent to
+the client is completely determined by that <tt class="literal"><span class="pre">set_cookie()</span></tt> call.</p>
+<p>See RFCs 2109 and 2965 for more information on the rules browsers are
+supposed to follow for including cookies with HTTP requests.</p>
+</div>
+<div class="section" id="writing-the-session-class">
+<h1><a name="writing-the-session-class">Writing the session class</a></h1>
+<p>You will almost certainly have to write a custom session class for your
+application by subclassing Quixote's standard Session class.  Every
+custom session class has two essential responsibilities:</p>
+<ul class="simple">
+<li>initialize the attributes that will be used by your application</li>
+<li>override the <tt class="literal"><span class="pre">has_info()</span></tt> method, so the session manager knows when
+it must save your session object</li>
+</ul>
+<p>The first one is fairly obvious and just good practice.  The second is
+essential, and not at all obvious.  The has_info() method exists because
+SessionManager does not automatically hang on to all session objects;
+this is a defence against clients that ignore cookies, making your
+session manager create lots of session objects that are just used once.
+As long as those session objects are not saved, the burden imposed by
+these clients is not too bad -- at least they aren't sucking up your
+memory, or bogging down the database that you save session data to.
+Thus, the session manager uses has_info() to know if it should hang on
+to a session object or not: if a session has information that must be
+saved, the session manager saves it and sends a session cookie to the
+client.</p>
+<p>For development/testing work, it's fine to say that your session objects
+should always be saved:</p>
+<pre class="literal-block">
+def has_info (self):
+    return 1
+</pre>
+<p>The opposite extreme is to forget to override <tt class="literal"><span class="pre">has_info()</span></tt> altogether,
+in which case session management most likely won't work: unless you
+tickle the Session object such that the base <tt class="literal"><span class="pre">has_info()</span></tt> method
+returns true, the session manager won't save the sessions that it
+creates, and Quixote will never drop a session cookie on the client.</p>
+<p>In a real application, you need to think carefully about what data to
+store in your sessions, and how <tt class="literal"><span class="pre">has_info()</span></tt> should react to the
+presence of that data.  If you try and track something about every
+single visitor to your site, sooner or later one of those a
+broken/malicious client that ignores cookies and <tt class="literal"><span class="pre">robots.txt</span></tt> will
+come along and crawl your entire site, wreaking havoc on your Quixote
+application (or the database underlying it).</p>
+</div>
+<div class="section" id="session-persistence">
+<h1><a name="session-persistence">Session persistence</a></h1>
+<p>Keeping session data across requests is all very nice, but in the real
+world you want that data to survive across process termination.  With
+CGI, this is essential, since each process serves exactly one request
+and then terminates.  With other execution mechanisms, though, it's
+still important -- you don't want to lose all your session data just
+because your long-lived server process was restarted, or your server
+machine was rebooted.</p>
+<p>However, every application is different, so Quixote doesn't provide any
+built-in mechanism for session persistence.  Instead, it provides a
+number of hooks, most in the SessionManager class, that let you plug in
+your preferred persistence mechanism.</p>
+<p>The first and most important hook is in the SessionManager constructor:
+you can provide an alternate mapping object that SessionManager will use
+to store session objects in.  By default, SessionManager uses an
+ordinary dictionary; if you provide a mapping object that implements
+persistence, then your session data will automatically persist across
+processes.  For example, you might use the standard 'shelve' module,
+which provides a mapping object on top of a DBM or Berkeley DB file:</p>
+<pre class="literal-block">
+import shelve
+sessions = shelve.open(&quot;/tmp/quixote-sessions&quot;)
+session_mgr = SessionManager(session_mapping=sessions)
+</pre>
+<p>For a persistent mapping implementation that doesn't require any
+external libraries, see the DirMapping class in
+<tt class="literal"><span class="pre">demo/session_demo.cgi</span></tt>.</p>
+<p>If you use one of these relatively simple persistent mapping types,
+you'll also need to override <tt class="literal"><span class="pre">is_dirty()</span></tt> in your Session class.
+That's in addition to overriding <tt class="literal"><span class="pre">has_info()</span></tt>, which determines if a
+session object is <em>ever</em> saved; <tt class="literal"><span class="pre">is_dirty()</span></tt> is only called on
+sessions that have already been added to the session mapping, to see if
+they need to be &quot;re-added&quot;.  The default implementation always returns
+false, because once an object has been added to a normal dictionary,
+there's no need to add it again.  However, with simple persistent
+mapping types like shelve and DirMapping, you need to store the object
+again each time it changes.  Thus, <tt class="literal"><span class="pre">is_dirty()</span></tt> should return true if
+the session object needs to be re-written.  For a simple, naive, but
+inefficient implementation, making is_dirty an alias for <tt class="literal"><span class="pre">has_info()</span></tt>
+will work -- that just means that once the session has been written
+once, it will be re-written on every request.  (This is what DemoSession
+in <tt class="literal"><span class="pre">demo/session_demo.cgi</span></tt> does.)</p>
+<p>The third and final part of the persistence interface only applies if
+you are using a transactional persistence mechanism, such as ZODB or an
+industrial-strength relational database.  In that case, you need a place
+to commit or abort the transaction that contains pending changes to the
+current session.  SessionManager provides two methods for you to
+override: <tt class="literal"><span class="pre">abort_changes()</span></tt> and <tt class="literal"><span class="pre">commit_changes()</span></tt>.
+<tt class="literal"><span class="pre">abort_changes()</span></tt> is called by SessionPublisher whenever a request
+crashes, ie. whenever your application raises an exception other than
+PublishError.  <tt class="literal"><span class="pre">commit_changes()</span></tt> is called for requests that complete
+successfully, or that raise a PublishError exception.  They are defined
+as follows:</p>
+<pre class="literal-block">
+def abort_changes (self, session):
+    &quot;&quot;&quot;abort_changes(session : Session)&quot;&quot;&quot;
+
+def commit_changes (self, session):
+    &quot;&quot;&quot;commit_changes(session : Session)&quot;&quot;&quot;
+</pre>
+<p>Obviously, you'll have to write your own SessionManager subclass if you
+need to take advantage of these hooks for transactional session
+persistence.</p>
+<p>$Id: session-mgmt.txt 20217 2003-01-16 20:51:53Z akuchlin $</p>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/session-mgmt.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/session-mgmt.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/session-mgmt.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/session-mgmt.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,352 @@
+Quixote Session Management
+==========================
+
+HTTP was originally designed as a stateless protocol, meaning that every
+request for a document or image was conducted in a separate TCP
+connection, and that there was no way for a web server to tell if two
+separate requests actually come from the same user.  It's no longer
+necessarily true that every request is conducted in a separate TCP
+connection, but HTTP is still fundamentally stateless.  However, there
+are many applications where it is desirable or even essential to
+establish a "session" for each user, ie. where all requests performed by
+that user are somehow tied together on the server.
+
+HTTP cookies were invented to address this requirement, and they are
+still the best solution for establishing sessions on top of HTTP.  Thus,
+Quixote's session management mechanism is cookie-based.  (The most
+common alternative is to generate long, complicated URLs with an
+embedded session identifier.  Since Quixote views the URL as a
+fundamental part of the web user interface, a URL-based session
+management scheme would be un-Quixotic.)
+
+For further reading: the standard for cookies that is approximately
+implemented by most current browsers is RFC 2109; the latest version of
+the standard is RFC 2965.  Those RFCs can be found here:
+
+  ftp://ftp.isi.edu/in-notes/rfc2109.txt
+
+  ftp://ftp.isi.edu/in-notes/rfc2965.txt
+
+In a nutshell, session management with Quixote works like this:
+
+* when a user-agent first requests a page from a Quixote application
+  that implements session management, Quixote creates a Session object
+  and generates a session ID (a random 64-bit number).  The Session
+  object is attached to the current HTTPRequest object, so that
+  application code involved in processing this request has access to
+  the Session object.
+
+* if, at the end of processing that request, the application code has
+  stored any information in the Session object, Quixote saves the
+  session in its SessionManager object for use by future requests and
+  sends a session cookie, called ``QX_session`` by default, to the user.
+  The session cookie contains the session ID encoded as a hexadecimal
+  string, and is included in the response headers, eg. ::
+
+    Set-Cookie: QX_session="928F82A9B8FA92FD"
+
+  (You can instruct Quixote to specify the domain and path for
+  URLs to which this cookie should be sent.)
+
+* the user agent stores this cookie for future requests
+
+* the next time the user agent requests a resource that matches the
+  cookie's domain and path, it includes the ``QX_session`` cookie
+  previously generated by Quixote in the request headers, eg.::
+
+    Cookie: QX_session="928F82A9B8FA92FD"
+
+* while processing the request, Quixote decodes the session ID and
+  looks up the corresponding Session object in its SessionManager.  If
+  there is no such session, the session cookie is bogus or
+  out-of-date, so Quixote raises SessionError; ultimately the user
+  gets an error page.  Otherwise, the Session object is attached to
+  the HTTPRequest object that is available to all application code
+  used to process the request.
+
+There are two caveats to keep in mind before proceeding, one major and
+one minor:
+
+* Quixote's standard Session and SessionManager class do not
+  implement any sort of persistence, meaning that all sessions
+  disappear when the process handling web requests terminates.
+  Thus, session management is completely useless with a plain
+  CGI driver script unless you add some persistence to the mix;
+  see "Session persistence" below for information.
+
+* Quixote never expires sessions; if you want user sessions to
+  be cleaned up after a period of inactivity, you will have to
+  write code to do it yourself.
+
+
+Session management demo
+-----------------------
+
+There's a simple demo of Quixote's session management in
+``demo/session_demo.cgi`` and ``demo/session.ptl``.  The demo implements
+a simple session persistence scheme (each session is written to a
+separate pickle file in ``/tmp/quixote-session-demo``), so running it
+through CGI is just fine.
+
+I'll assume that you've added a rewrite rule so that requests for
+``/qsdemo/`` are handled by ``session_demo.cgi``, similar to the
+rewriting for ``/qdemo/`` described in web-server.txt.  Once that's
+done, point your browser at ::
+
+  http://<hostname>/qsdemo/
+
+and play around.
+
+This particular application uses sessions to keep track of just two
+things: the user's identity and the number of requests made in this
+session.  The first is addressed by Quixote's standard Session class --
+every Session object has a ``user`` attribute, which you can use for
+anything you like.  In the session demo, we simply store a string, the
+user's name, which is entered by the user.
+
+Tracking the number of requests is a bit more interesting: from the
+DemoSession class in session_demo.cgi::
+
+    def __init__ (self, request, id):
+        Session.__init__(self, request, id)
+        self.num_requests = 0
+
+    def start_request (self, request):
+        Session.start_request(self, request)
+        self.num_requests += 1
+
+When the session is created, we initialize the request counter; and when
+we start processing each request, we increment it.
+
+Using the session information in the application code is simple.  For
+example, here's the PTL code that checks if the user has logged in
+(identified herself) yet, and generates a login form if not::
+
+    session = request.session
+    if session.user is None:
+        '''
+        <p>You haven\'t introduced yourself yet.<br>
+        Please tell me your name:
+        '''
+        login_form()
+
+(The ``login_form()`` template just emits a simple HTML form -- see
+``demo/session.ptl`` for full source.)
+
+If the user has already identified herself, then she doesn't need to do
+so again -- so the other branch of that ``if`` statement simply prints a
+friendly greeting::
+
+    else:
+        ('<p>Hello, %s.  Good to see you again.</p>\n' 
+         % html_quote(session.user))
+
+Note that we must quote the user's name, because they are free to enter
+anything they please, including special HTML characters like ``&`` or
+``<``.
+
+Of course, ``session.user`` will never be set if we don't set it
+ourselves.  The code that processes the login form is just this (from
+``login()`` in ``demo/session.ptl``)::
+
+    if request.form:
+        user = request.form.get("name")
+        if not user:
+            raise QueryError("no user name supplied")
+
+        session.user = user
+
+This is obviously a very simple application -- we're not doing any
+verification of the user's input.  We have no user database, no
+passwords, and no limitations on what constitutes a "user name".  A real
+application would have all of these, as well as a way for users to add
+themselves to the user database -- ie. register with your web site.
+
+
+Configuring the session cookie
+------------------------------
+
+Quixote allows you to configure several aspects of the session cookie
+that it exchanges with clients.  First, you can set the name of the
+cookie; this is important if you have multiple independent Quixote
+applications running on the same server.  For example, the config file
+for the first application might have ::
+
+  SESSION_COOKIE_NAME = "foo_session"
+
+and the second application might have ::
+
+  SESSION_COOKIE_NAME = "bar_session"
+
+Next, you can use ``SESSION_COOKIE_DOMAIN`` and ``SESSION_COOKIE_PATH``
+to set the cookie attributes that control which requests the cookie is
+included with.  By default, these are both ``None``, which instructs
+Quixote to send the cookie without ``Domain`` or ``Path`` qualifiers.
+For example, if the client requests ``/foo/bar/`` from
+www.example.com, and Quixote decides that it must set the session
+cookie in the response to that request, then the server would send ::
+
+  Set-Cookie: QX_session="928F82A9B8FA92FD"
+
+in the response headers.  Since no domain or path were specified with
+that cookie, the browser will only include the cookie with requests to
+www.example.com for URIs that start with ``/foo/bar/``.
+
+If you want to ensure that your session cookie is included with all
+requests to www.example.com, you should set ``SESSION_COOKIE_PATH`` in your
+config file::
+
+  SESSION_COOKIE_PATH = "/"
+
+which will cause Quixote to set the cookie like this::
+
+  Set-Cookie: QX_session="928F82A9B8FA92FD"; Path="/"
+
+which will instruct the browser to include that cookie with *all*
+requests to www.example.com.
+
+However, think carefully about what you set ``SESSION_COOKIE_PATH`` to
+-- eg. if you set it to "/", but all of your Quixote code is under "/q/"
+in your server's URL-space, then your user's session cookies could be
+unnecessarily exposed.  On shared servers where you don't control all of
+the code, this is especially dangerous; be sure to use (eg.) ::
+
+  SESSION_COOKIE_PATH = "/q/"
+
+on such servers.  The trailing slash is important; without it, your
+session cookies will be sent to URIs like ``/qux`` and ``/qix``, even if
+you don't control those URIs.
+
+If you want to share the cookie across servers in your domain,
+eg. www1.example.com and www2.example.com, you'll also need to set
+``SESSION_COOKIE_DOMAIN``:
+
+  SESSION_COOKIE_DOMAIN = ".example.com"
+
+Finally, note that the ``SESSION_COOKIE_*`` configuration variables
+*only* affect Quixote's session cookie; if you set your own cookies
+using the ``HTTPResponse.set_cookie()`` method, then the cookie sent to
+the client is completely determined by that ``set_cookie()`` call.
+
+See RFCs 2109 and 2965 for more information on the rules browsers are
+supposed to follow for including cookies with HTTP requests.
+
+
+Writing the session class
+-------------------------
+
+You will almost certainly have to write a custom session class for your
+application by subclassing Quixote's standard Session class.  Every
+custom session class has two essential responsibilities:
+
+* initialize the attributes that will be used by your application
+
+* override the ``has_info()`` method, so the session manager knows when
+  it must save your session object
+
+The first one is fairly obvious and just good practice.  The second is
+essential, and not at all obvious.  The has_info() method exists because
+SessionManager does not automatically hang on to all session objects;
+this is a defence against clients that ignore cookies, making your
+session manager create lots of session objects that are just used once.
+As long as those session objects are not saved, the burden imposed by
+these clients is not too bad -- at least they aren't sucking up your
+memory, or bogging down the database that you save session data to.
+Thus, the session manager uses has_info() to know if it should hang on
+to a session object or not: if a session has information that must be
+saved, the session manager saves it and sends a session cookie to the
+client.
+
+For development/testing work, it's fine to say that your session objects
+should always be saved::
+
+  def has_info (self):
+      return 1
+
+The opposite extreme is to forget to override ``has_info()`` altogether,
+in which case session management most likely won't work: unless you
+tickle the Session object such that the base ``has_info()`` method
+returns true, the session manager won't save the sessions that it
+creates, and Quixote will never drop a session cookie on the client.
+
+In a real application, you need to think carefully about what data to
+store in your sessions, and how ``has_info()`` should react to the
+presence of that data.  If you try and track something about every
+single visitor to your site, sooner or later one of those a
+broken/malicious client that ignores cookies and ``robots.txt`` will
+come along and crawl your entire site, wreaking havoc on your Quixote
+application (or the database underlying it).
+
+
+Session persistence
+-------------------
+
+Keeping session data across requests is all very nice, but in the real
+world you want that data to survive across process termination.  With
+CGI, this is essential, since each process serves exactly one request
+and then terminates.  With other execution mechanisms, though, it's
+still important -- you don't want to lose all your session data just
+because your long-lived server process was restarted, or your server
+machine was rebooted.
+
+However, every application is different, so Quixote doesn't provide any
+built-in mechanism for session persistence.  Instead, it provides a
+number of hooks, most in the SessionManager class, that let you plug in
+your preferred persistence mechanism.
+
+The first and most important hook is in the SessionManager constructor:
+you can provide an alternate mapping object that SessionManager will use
+to store session objects in.  By default, SessionManager uses an
+ordinary dictionary; if you provide a mapping object that implements
+persistence, then your session data will automatically persist across
+processes.  For example, you might use the standard 'shelve' module,
+which provides a mapping object on top of a DBM or Berkeley DB file::
+
+  import shelve
+  sessions = shelve.open("/tmp/quixote-sessions")
+  session_mgr = SessionManager(session_mapping=sessions)
+
+For a persistent mapping implementation that doesn't require any
+external libraries, see the DirMapping class in
+``demo/session_demo.cgi``.
+
+If you use one of these relatively simple persistent mapping types,
+you'll also need to override ``is_dirty()`` in your Session class.
+That's in addition to overriding ``has_info()``, which determines if a
+session object is *ever* saved; ``is_dirty()`` is only called on
+sessions that have already been added to the session mapping, to see if
+they need to be "re-added".  The default implementation always returns
+false, because once an object has been added to a normal dictionary,
+there's no need to add it again.  However, with simple persistent
+mapping types like shelve and DirMapping, you need to store the object
+again each time it changes.  Thus, ``is_dirty()`` should return true if
+the session object needs to be re-written.  For a simple, naive, but
+inefficient implementation, making is_dirty an alias for ``has_info()``
+will work -- that just means that once the session has been written
+once, it will be re-written on every request.  (This is what DemoSession
+in ``demo/session_demo.cgi`` does.)
+
+The third and final part of the persistence interface only applies if
+you are using a transactional persistence mechanism, such as ZODB or an
+industrial-strength relational database.  In that case, you need a place
+to commit or abort the transaction that contains pending changes to the
+current session.  SessionManager provides two methods for you to
+override: ``abort_changes()`` and ``commit_changes()``.
+``abort_changes()`` is called by SessionPublisher whenever a request
+crashes, ie. whenever your application raises an exception other than
+PublishError.  ``commit_changes()`` is called for requests that complete
+successfully, or that raise a PublishError exception.  They are defined
+as follows::
+
+    def abort_changes (self, session):
+        """abort_changes(session : Session)"""
+
+    def commit_changes (self, session):
+        """commit_changes(session : Session)"""
+
+Obviously, you'll have to write your own SessionManager subclass if you
+need to take advantage of these hooks for transactional session
+persistence.
+
+
+$Id: session-mgmt.txt 20217 2003-01-16 20:51:53Z akuchlin $

Added: packages/quixote1/branches/upstream/current/doc/static-files.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/static-files.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/static-files.html (added)
+++ packages/quixote1/branches/upstream/current/doc/static-files.html Mon May  8 19:16:16 2006
@@ -1,0 +1,55 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Examples of serving static files</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="examples-of-serving-static-files">
+<h1 class="title">Examples of serving static files</h1>
+<p>The <tt class="literal"><span class="pre">quixote.util</span></tt> module includes classes for making files and
+directories available as Quixote resources.  Here are some examples.</p>
+<div class="section" id="publishing-a-single-file">
+<h1><a name="publishing-a-single-file">Publishing a Single File</a></h1>
+<p>The <tt class="literal"><span class="pre">StaticFile</span></tt> class makes an individual filesystem file (possibly
+a symbolic link) available.  You can also specify the MIME type and
+encoding of the file; if you don't specify this, the MIME type will be
+guessed using the standard Python <tt class="literal"><span class="pre">mimetypes.guess_type()</span></tt> function.
+The default action is to not follow symbolic links, but this behaviour
+can be changed using the <tt class="literal"><span class="pre">follow_symlinks</span></tt> parameter.</p>
+<p>The following example publishes a file with the URL <tt class="literal"><span class="pre">.../stylesheet_css</span></tt>:</p>
+<pre class="literal-block">
+# 'stylesheet_css' must be in the _q_exports list
+_q_exports = [ ..., 'stylesheet_css', ...]
+
+stylesheet_css = StaticFile(
+        &quot;/htdocs/legacy_app/stylesheet.css&quot;,
+        follow_symlinks=1, mime_type=&quot;text/css&quot;)
+</pre>
+<p>If you want the URL of the file to have a <tt class="literal"><span class="pre">.css</span></tt> extension, you use
+the external to internal name mapping feature of <tt class="literal"><span class="pre">_q_exports</span></tt>.  For
+example:</p>
+<pre class="literal-block">
+_q_exports = [ ..., ('stylesheet.css', 'stylesheet_css'), ...]
+</pre>
+</div>
+<div class="section" id="publishing-a-directory">
+<h1><a name="publishing-a-directory">Publishing a Directory</a></h1>
+<p>Publishing a directory is similar.  The <tt class="literal"><span class="pre">StaticDirectory</span></tt> class
+makes a complete filesystem directory available.  Again, the default
+behaviour is to not follow symlinks.  You can also request that the
+<tt class="literal"><span class="pre">StaticDirectory</span></tt> object cache information about the files in
+memory so that it doesn't try to guess the MIME type on every hit.</p>
+<p>This example publishes the <tt class="literal"><span class="pre">notes/</span></tt> directory:</p>
+<pre class="literal-block">
+_q_exports = [ ..., 'notes', ...]
+
+notes = StaticDirectory(&quot;/htdocs/legacy_app/notes&quot;)
+</pre>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/static-files.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/static-files.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/static-files.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/static-files.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,51 @@
+Examples of serving static files
+================================
+
+The ``quixote.util`` module includes classes for making files and
+directories available as Quixote resources.  Here are some examples.
+
+
+Publishing a Single File
+------------------------
+
+The ``StaticFile`` class makes an individual filesystem file (possibly
+a symbolic link) available.  You can also specify the MIME type and
+encoding of the file; if you don't specify this, the MIME type will be
+guessed using the standard Python ``mimetypes.guess_type()`` function.
+The default action is to not follow symbolic links, but this behaviour
+can be changed using the ``follow_symlinks`` parameter.
+
+The following example publishes a file with the URL ``.../stylesheet_css``::
+
+    # 'stylesheet_css' must be in the _q_exports list
+    _q_exports = [ ..., 'stylesheet_css', ...]
+
+    stylesheet_css = StaticFile(
+            "/htdocs/legacy_app/stylesheet.css",
+            follow_symlinks=1, mime_type="text/css")
+    
+
+If you want the URL of the file to have a ``.css`` extension, you use
+the external to internal name mapping feature of ``_q_exports``.  For
+example::
+
+    _q_exports = [ ..., ('stylesheet.css', 'stylesheet_css'), ...]
+
+
+
+Publishing a Directory
+----------------------
+
+Publishing a directory is similar.  The ``StaticDirectory`` class
+makes a complete filesystem directory available.  Again, the default
+behaviour is to not follow symlinks.  You can also request that the
+``StaticDirectory`` object cache information about the files in
+memory so that it doesn't try to guess the MIME type on every hit.
+
+This example publishes the ``notes/`` directory::
+
+    _q_exports = [ ..., 'notes', ...]
+
+    notes = StaticDirectory("/htdocs/legacy_app/notes")
+
+

Added: packages/quixote1/branches/upstream/current/doc/upgrading.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/upgrading.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/upgrading.html (added)
+++ packages/quixote1/branches/upstream/current/doc/upgrading.html Mon May  8 19:16:16 2006
@@ -1,0 +1,239 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Upgrading code from older versions of Quixote</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="upgrading-code-from-older-versions-of-quixote">
+<h1 class="title">Upgrading code from older versions of Quixote</h1>
+<p>This document lists backward-incompatible changes in Quixote, and
+explains how to update application code to work with the newer
+version.</p>
+<div class="section" id="changes-from-0-6-1-to-1-0">
+<h1><a name="changes-from-0-6-1-to-1-0">Changes from 0.6.1 to 1.0</a></h1>
+<div class="section" id="sessions">
+<h2><a name="sessions">Sessions</a></h2>
+<p>A leading underscore was removed from the <tt class="literal"><span class="pre">Session</span></tt> attributes
+<tt class="literal"><span class="pre">__remote_address</span></tt>, <tt class="literal"><span class="pre">__creation_time</span></tt>, and <tt class="literal"><span class="pre">__access_time</span></tt>.  If
+you have pickled <tt class="literal"><span class="pre">Session</span></tt> objects you will need to upgrade them
+somehow.  Our preferred method is to write a script that unpickles each
+object, renames the attributes and then re-pickles it.</p>
+</div>
+</div>
+<div class="section" id="changes-from-0-6-to-0-6-1">
+<h1><a name="changes-from-0-6-to-0-6-1">Changes from 0.6 to 0.6.1</a></h1>
+<div class="section" id="q-exception-handler-now-called-if-exception-while-traversing">
+<h2><a name="q-exception-handler-now-called-if-exception-while-traversing"><tt class="literal"><span class="pre">_q_exception_handler</span></tt> now called if exception while traversing</a></h2>
+<p><tt class="literal"><span class="pre">_q_exception_handler</span></tt> hooks will now be called if an exception is
+raised during the traversal process.  Quixote 0.6 had a bug that caused
+<tt class="literal"><span class="pre">_q_exception_handler</span></tt> hooks to only be called if an exception was
+raised after the traversal completed.</p>
+</div>
+</div>
+<div class="section" id="changes-from-0-5-to-0-6">
+<h1><a name="changes-from-0-5-to-0-6">Changes from 0.5 to 0.6</a></h1>
+<div class="section" id="q-getname-renamed-to-q-lookup">
+<h2><a name="q-getname-renamed-to-q-lookup"><tt class="literal"><span class="pre">_q_getname</span></tt> renamed to <tt class="literal"><span class="pre">_q_lookup</span></tt></a></h2>
+<p>The <tt class="literal"><span class="pre">_q_getname</span></tt> special function was renamed to <tt class="literal"><span class="pre">_q_lookup</span></tt>,
+because that name gives a clearer impression of the function's
+purpose.  In 0.6, <tt class="literal"><span class="pre">_q_getname</span></tt> still works but will trigger a
+warning.</p>
+</div>
+<div class="section" id="form-framework-changes">
+<h2><a name="form-framework-changes">Form Framework Changes</a></h2>
+<p>The <tt class="literal"><span class="pre">quixote.form.form</span></tt> module was changed from a .ptl file to a .py
+file.  You should delete or move the existing <tt class="literal"><span class="pre">quixote/</span></tt> directory
+in <tt class="literal"><span class="pre">site-packages</span></tt> before running <tt class="literal"><span class="pre">setup.py</span></tt>, or at least delete
+the old <tt class="literal"><span class="pre">form.ptl</span></tt> and <tt class="literal"><span class="pre">form.ptlc</span></tt> files.</p>
+<p>The widget and form classes in the <tt class="literal"><span class="pre">quixote.form</span></tt> package now return
+<tt class="literal"><span class="pre">htmltext</span></tt> instances.  Applications that use forms and widgets will
+likely have to be changed to use the <tt class="literal"><span class="pre">[html]</span></tt> template type to avoid
+over-escaping of HTML special characters.</p>
+<p>Also, the constructor arguments to <tt class="literal"><span class="pre">SelectWidget</span></tt> and its subclasses have
+changed.  This only affects applications that use the form framework
+located in the <tt class="literal"><span class="pre">quixote.form</span></tt> package.</p>
+<p>In Quixote 0.5, the <tt class="literal"><span class="pre">SelectWidget</span></tt> constructor had this signature:</p>
+<pre class="literal-block">
+def __init__ (self, name, value=None,
+              allowed_values=None,
+              descriptions=None,
+              size=None,
+              sort=0):
+</pre>
+<p><tt class="literal"><span class="pre">allowed_values</span></tt> was the list of objects that the user could choose,
+and <tt class="literal"><span class="pre">descriptions</span></tt> was a list of strings that would actually be
+shown to the user in the generated HTML.</p>
+<p>In Quixote 0.6, the signature has changed slightly:</p>
+<pre class="literal-block">
+def __init__ (self, name, value=None,
+              allowed_values=None,
+              descriptions=None,
+              options=None,
+              size=None,
+              sort=0):
+</pre>
+<p>The <tt class="literal"><span class="pre">quote</span></tt> argument is gone, and the <tt class="literal"><span class="pre">options</span></tt> argument has been 
+added.  If an <tt class="literal"><span class="pre">options</span></tt> argument is provided, <tt class="literal"><span class="pre">allowed_values</span></tt> 
+and <tt class="literal"><span class="pre">descriptions</span></tt> must not be supplied.</p>
+<p>The <tt class="literal"><span class="pre">options</span></tt> argument, if present, must be a list of tuples with
+1,2, or 3 elements, of the form <tt class="literal"><span class="pre">(value:any,</span> <span class="pre">description:any,</span>
+<span class="pre">key:string)</span></tt>.</p>
+<blockquote>
+<ul class="simple">
+<li><tt class="literal"><span class="pre">value</span></tt> is the object that will be returned if the user chooses 
+this item, and must always be supplied.</li>
+<li><tt class="literal"><span class="pre">description</span></tt> is a string or htmltext instance which will be
+shown to the user in the generated HTML.  It will be passed
+through the htmlescape() functions, so for an ordinary string
+special characters such as '&amp;' will be converted to '&amp;amp;'.
+htmltext instances will be left as they are.</li>
+<li>If supplied, <tt class="literal"><span class="pre">key</span></tt> will be used in the value attribute 
+of the option element (<tt class="literal"><span class="pre">&lt;option</span> <span class="pre">value=&quot;...&quot;&gt;</span></tt>).  
+If not supplied, keys will be generated; <tt class="literal"><span class="pre">value</span></tt> is checked for a 
+<tt class="literal"><span class="pre">_p_oid</span></tt> attribute and if present, that string is used; 
+otherwise the description is used.</li>
+</ul>
+</blockquote>
+<p>In the common case, most applications won't have to change anything,
+though the ordering of selection items may change due to the
+difference in how keys are generated.</p>
+</div>
+<div class="section" id="file-upload-changes">
+<h2><a name="file-upload-changes">File Upload Changes</a></h2>
+<p>Quixote 0.6 introduces new support for HTTP upload requests.  Any HTTP
+request with a Content-Type of &quot;multipart/form-data&quot; -- which is
+generally only used for uploads -- is now represented by
+HTTPUploadRequest, a subclass of HTTPRequest, and the uploaded files
+themselves are represented by Upload objects.</p>
+<p>Whenever an HTTP request has a Content-Type of &quot;multipart/form-data&quot;,
+an instance of HTTPUploadRequest is created instead of HTTPRequest.
+Some of the fields in the request are presumably uploaded files and
+might be quite large, so HTTPUploadRequest will read all of the fields
+supplied in the request body and write them out to temporary files;
+the temporary files are written in the directory specified by the
+UPLOAD_DIR configuration variable.</p>
+<p>Once the temporary files have been written, the HTTPUploadRequest
+object is passed to a function or PTL template, just like an ordinary
+request.  The difference between HTTPRequest and HTTPUploadRequest 
+is that all of the form variables are represented as Upload objects.
+Upload objects have three attributes:</p>
+<dl>
+<dt><tt class="literal"><span class="pre">orig_filename</span></tt></dt>
+<dd>the filename supplied by the browser.</dd>
+<dt><tt class="literal"><span class="pre">base_filename</span></tt></dt>
+<dd>a stripped-down version of orig_filename with unsafe characters removed.
+This could be used when writing uploaded data to a permanent location.</dd>
+<dt><tt class="literal"><span class="pre">tmp_filename</span></tt></dt>
+<dd>the path of the temporary file containing the uploaded data for this field.</dd>
+</dl>
+<p>Consult upload.txt for more information about handling file uploads.</p>
+</div>
+<div class="section" id="refactored-publisher-class">
+<h2><a name="refactored-publisher-class">Refactored <cite>Publisher</cite> Class</a></h2>
+<p>Various methods in the <cite>Publisher</cite> class were rearranged.  If your
+application subclasses Publisher, you may need to change your code
+accordingly.</p>
+<blockquote>
+<ul>
+<li><p class="first"><tt class="literal"><span class="pre">parse_request()</span></tt> no longer creates the HTTPRequest object; 
+instead a new method, <tt class="literal"><span class="pre">create_request()</span></tt>,  handles this, 
+and can be overridden as required.</p>
+<p>As a result, the method signature has changed from 
+<tt class="literal"><span class="pre">parse_request(stdin,</span> <span class="pre">env)</span></tt> to <tt class="literal"><span class="pre">parse_request(request)</span></tt>.</p>
+</li>
+<li><p class="first">The <tt class="literal"><span class="pre">Publisher.publish()</span></tt> method now catches exceptions raised 
+by <tt class="literal"><span class="pre">parse_request()</span></tt>.</p>
+</li>
+</ul>
+</blockquote>
+</div>
+</div>
+<div class="section" id="changes-from-0-4-to-0-5">
+<h1><a name="changes-from-0-4-to-0-5">Changes from 0.4 to 0.5</a></h1>
+<div class="section" id="session-management-changes">
+<h2><a name="session-management-changes">Session Management Changes</a></h2>
+<p>The Quixote session management interface underwent lots of change and
+cleanup with Quixote 0.5.  It was previously undocumented (apart from
+docstrings in the code), so we thought that this was a good opportunity
+to clean up the interface.  Nevertheless, those brave souls who got
+session management working just by reading the code are in for a bit of
+suffering; this brief note should help clarify things.  The definitive
+documentation for session management is session-mgmt.txt -- you should
+start there.</p>
+<div class="section" id="attribute-renamings-and-pickled-objects">
+<h3><a name="attribute-renamings-and-pickled-objects">Attribute renamings and pickled objects</a></h3>
+<p>Most attributes of the standard Session class were made private in order
+to reduce collisions with subclasses.  The downside is that pickled
+Session objects will break.  You might want to (temporarily) modify
+session.py and add this method to Session:</p>
+<pre class="literal-block">
+def __setstate__ (self, dict):
+    # Update for attribute renamings made in rev. 1.51.2.3
+    # (between Quixote 0.4.7 and 0.5).
+    self.__dict__.update(dict)
+    if hasattr(self, 'remote_address'):
+        self.__remote_address = self.remote_address
+        del self.remote_address
+    if hasattr(self, 'creation_time'):
+        self.__creation_time = self.creation_time
+        del self.creation_time
+    if hasattr(self, 'access_time'):
+        self.__access_time = self.access_time
+        del self.access_time
+    if hasattr(self, 'form_tokens'):
+        self._form_tokens = self.form_tokens
+        del self.form_tokens
+</pre>
+<p>However, if your sessions were pickled via ZODB, this may not work.  (It
+didn't work for us.)  In that case, you'll have to add something like
+this to your class that inherits from both ZODB's Persistent and
+Quixote's Session:</p>
+<pre class="literal-block">
+def __setstate__ (self, dict):
+    # Blechhh!  This doesn't work if I put it in Quixote's
+    # session.py, so I have to second-guess how Python
+    # treats &quot;__&quot; attribute names.
+    self.__dict__.update(dict)
+    if hasattr(self, 'remote_address'):
+        self._Session__remote_address = self.remote_address
+        del self.remote_address
+    if hasattr(self, 'creation_time'):
+        self._Session__creation_time = self.creation_time
+        del self.creation_time
+    if hasattr(self, 'access_time'):
+        self._Session__access_time = self.access_time
+        del self.access_time
+    if hasattr(self, 'form_tokens'):
+        self._form_tokens = self.form_tokens
+        del self.form_tokens
+</pre>
+<p>It's not pretty, but it worked for us.</p>
+</div>
+<div class="section" id="cookie-domains-and-paths">
+<h3><a name="cookie-domains-and-paths">Cookie domains and paths</a></h3>
+<p>The session cookie config variables -- <tt class="literal"><span class="pre">COOKIE_NAME</span></tt>,
+<tt class="literal"><span class="pre">COOKIE_DOMAIN</span></tt>, and <tt class="literal"><span class="pre">COOKIE_PATH</span></tt> -- have been renamed to
+<tt class="literal"><span class="pre">SESSION_COOKIE_*</span></tt> for clarity.</p>
+<p>If you previously set the config variable <tt class="literal"><span class="pre">COOKIE_DOMAIN</span></tt> to the name
+of your server, this is most likely no longer necessary -- it's now fine
+to leave <tt class="literal"><span class="pre">SESSION_COOKIE_DOMAIN</span></tt> unset (ie. <tt class="literal"><span class="pre">None</span></tt>), which
+ultimately means browsers will only include the session cookie in
+requests to the same server that sent it to them in the first place.</p>
+<p>If you previously set <tt class="literal"><span class="pre">COOKIE_PATH</span></tt>, then you should probably preserve
+your setting as <tt class="literal"><span class="pre">SESSION_COOKIE_PATH</span></tt>.  The default of <tt class="literal"><span class="pre">None</span></tt> means
+that browsers will only send session cookies with requests for URIs
+under the URI that originally resulted in the session cookie being sent.
+See session-mgmt.txt and RFCs 2109 and 2965.</p>
+<p>If you previously set <tt class="literal"><span class="pre">COOKIE_NAME</span></tt>, change it to
+<tt class="literal"><span class="pre">SESSION_COOKIE_NAME</span></tt>.</p>
+<p>$Id: upgrading.txt 23961 2004-04-12 15:51:38Z nascheme $</p>
+</div>
+</div>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/upgrading.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/upgrading.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/upgrading.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/upgrading.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,256 @@
+Upgrading code from older versions of Quixote
+=============================================
+
+This document lists backward-incompatible changes in Quixote, and
+explains how to update application code to work with the newer
+version.
+
+Changes from 0.6.1 to 1.0
+-------------------------
+
+Sessions
+********
+
+A leading underscore was removed from the ``Session`` attributes
+``__remote_address``, ``__creation_time``, and ``__access_time``.  If
+you have pickled ``Session`` objects you will need to upgrade them
+somehow.  Our preferred method is to write a script that unpickles each
+object, renames the attributes and then re-pickles it.
+
+
+
+Changes from 0.6 to 0.6.1
+-------------------------
+
+``_q_exception_handler`` now called if exception while traversing
+*****************************************************************
+
+``_q_exception_handler`` hooks will now be called if an exception is
+raised during the traversal process.  Quixote 0.6 had a bug that caused
+``_q_exception_handler`` hooks to only be called if an exception was
+raised after the traversal completed.
+
+
+
+Changes from 0.5 to 0.6
+-----------------------
+
+``_q_getname`` renamed to ``_q_lookup``
+***************************************
+
+The ``_q_getname`` special function was renamed to ``_q_lookup``,
+because that name gives a clearer impression of the function's
+purpose.  In 0.6, ``_q_getname`` still works but will trigger a
+warning.
+
+
+Form Framework Changes
+**********************
+
+The ``quixote.form.form`` module was changed from a .ptl file to a .py
+file.  You should delete or move the existing ``quixote/`` directory
+in ``site-packages`` before running ``setup.py``, or at least delete
+the old ``form.ptl`` and ``form.ptlc`` files.
+
+The widget and form classes in the ``quixote.form`` package now return
+``htmltext`` instances.  Applications that use forms and widgets will
+likely have to be changed to use the ``[html]`` template type to avoid
+over-escaping of HTML special characters.
+
+Also, the constructor arguments to ``SelectWidget`` and its subclasses have
+changed.  This only affects applications that use the form framework
+located in the ``quixote.form`` package.
+
+In Quixote 0.5, the ``SelectWidget`` constructor had this signature::
+
+     def __init__ (self, name, value=None,
+                   allowed_values=None,
+                   descriptions=None,
+                   size=None,
+                   sort=0):
+
+``allowed_values`` was the list of objects that the user could choose,
+and ``descriptions`` was a list of strings that would actually be
+shown to the user in the generated HTML.
+
+In Quixote 0.6, the signature has changed slightly::
+
+     def __init__ (self, name, value=None,
+                   allowed_values=None,
+                   descriptions=None,
+                   options=None,
+                   size=None,
+                   sort=0):
+
+The ``quote`` argument is gone, and the ``options`` argument has been 
+added.  If an ``options`` argument is provided, ``allowed_values`` 
+and ``descriptions`` must not be supplied.
+
+The ``options`` argument, if present, must be a list of tuples with
+1,2, or 3 elements, of the form ``(value:any, description:any,
+key:string)``.
+
+    * ``value`` is the object that will be returned if the user chooses 
+      this item, and must always be supplied.
+  
+    * ``description`` is a string or htmltext instance which will be
+      shown to the user in the generated HTML.  It will be passed
+      through the htmlescape() functions, so for an ordinary string
+      special characters such as '&' will be converted to '&amp;'.
+      htmltext instances will be left as they are.
+
+    * If supplied, ``key`` will be used in the value attribute 
+      of the option element (``<option value="...">``).  
+      If not supplied, keys will be generated; ``value`` is checked for a 
+      ``_p_oid`` attribute and if present, that string is used; 
+      otherwise the description is used.
+
+In the common case, most applications won't have to change anything,
+though the ordering of selection items may change due to the
+difference in how keys are generated.
+
+
+File Upload Changes
+*******************
+
+Quixote 0.6 introduces new support for HTTP upload requests.  Any HTTP
+request with a Content-Type of "multipart/form-data" -- which is
+generally only used for uploads -- is now represented by
+HTTPUploadRequest, a subclass of HTTPRequest, and the uploaded files
+themselves are represented by Upload objects.  
+
+Whenever an HTTP request has a Content-Type of "multipart/form-data",
+an instance of HTTPUploadRequest is created instead of HTTPRequest.
+Some of the fields in the request are presumably uploaded files and
+might be quite large, so HTTPUploadRequest will read all of the fields
+supplied in the request body and write them out to temporary files;
+the temporary files are written in the directory specified by the
+UPLOAD_DIR configuration variable.
+
+Once the temporary files have been written, the HTTPUploadRequest
+object is passed to a function or PTL template, just like an ordinary
+request.  The difference between HTTPRequest and HTTPUploadRequest 
+is that all of the form variables are represented as Upload objects.
+Upload objects have three attributes:
+
+``orig_filename``
+  the filename supplied by the browser.
+``base_filename``
+  a stripped-down version of orig_filename with unsafe characters removed.
+  This could be used when writing uploaded data to a permanent location.
+``tmp_filename``
+  the path of the temporary file containing the uploaded data for this field.
+
+Consult upload.txt for more information about handling file uploads.
+ 
+
+Refactored `Publisher` Class
+****************************
+
+Various methods in the `Publisher` class were rearranged.  If your
+application subclasses Publisher, you may need to change your code
+accordingly.
+
+  * ``parse_request()`` no longer creates the HTTPRequest object; 
+    instead a new method, ``create_request()``,  handles this, 
+    and can be overridden as required.
+
+    As a result, the method signature has changed from 
+    ``parse_request(stdin, env)`` to ``parse_request(request)``.
+
+  * The ``Publisher.publish()`` method now catches exceptions raised 
+    by ``parse_request()``.
+
+
+Changes from 0.4 to 0.5
+-----------------------
+
+Session Management Changes
+**************************
+
+The Quixote session management interface underwent lots of change and
+cleanup with Quixote 0.5.  It was previously undocumented (apart from
+docstrings in the code), so we thought that this was a good opportunity
+to clean up the interface.  Nevertheless, those brave souls who got
+session management working just by reading the code are in for a bit of
+suffering; this brief note should help clarify things.  The definitive
+documentation for session management is session-mgmt.txt -- you should
+start there.
+
+
+Attribute renamings and pickled objects
++++++++++++++++++++++++++++++++++++++++
+
+Most attributes of the standard Session class were made private in order
+to reduce collisions with subclasses.  The downside is that pickled
+Session objects will break.  You might want to (temporarily) modify
+session.py and add this method to Session::
+
+    def __setstate__ (self, dict):
+        # Update for attribute renamings made in rev. 1.51.2.3
+        # (between Quixote 0.4.7 and 0.5).
+        self.__dict__.update(dict)
+        if hasattr(self, 'remote_address'):
+            self.__remote_address = self.remote_address
+            del self.remote_address
+        if hasattr(self, 'creation_time'):
+            self.__creation_time = self.creation_time
+            del self.creation_time
+        if hasattr(self, 'access_time'):
+            self.__access_time = self.access_time
+            del self.access_time
+        if hasattr(self, 'form_tokens'):
+            self._form_tokens = self.form_tokens
+            del self.form_tokens
+
+However, if your sessions were pickled via ZODB, this may not work.  (It
+didn't work for us.)  In that case, you'll have to add something like
+this to your class that inherits from both ZODB's Persistent and
+Quixote's Session::
+
+    def __setstate__ (self, dict):
+        # Blechhh!  This doesn't work if I put it in Quixote's
+        # session.py, so I have to second-guess how Python
+        # treats "__" attribute names.
+        self.__dict__.update(dict)
+        if hasattr(self, 'remote_address'):
+            self._Session__remote_address = self.remote_address
+            del self.remote_address
+        if hasattr(self, 'creation_time'):
+            self._Session__creation_time = self.creation_time
+            del self.creation_time
+        if hasattr(self, 'access_time'):
+            self._Session__access_time = self.access_time
+            del self.access_time
+        if hasattr(self, 'form_tokens'):
+            self._form_tokens = self.form_tokens
+            del self.form_tokens
+
+It's not pretty, but it worked for us.
+
+
+Cookie domains and paths
+++++++++++++++++++++++++
+
+The session cookie config variables -- ``COOKIE_NAME``,
+``COOKIE_DOMAIN``, and ``COOKIE_PATH`` -- have been renamed to
+``SESSION_COOKIE_*`` for clarity.
+
+If you previously set the config variable ``COOKIE_DOMAIN`` to the name
+of your server, this is most likely no longer necessary -- it's now fine
+to leave ``SESSION_COOKIE_DOMAIN`` unset (ie. ``None``), which
+ultimately means browsers will only include the session cookie in
+requests to the same server that sent it to them in the first place.
+
+If you previously set ``COOKIE_PATH``, then you should probably preserve
+your setting as ``SESSION_COOKIE_PATH``.  The default of ``None`` means
+that browsers will only send session cookies with requests for URIs
+under the URI that originally resulted in the session cookie being sent.
+See session-mgmt.txt and RFCs 2109 and 2965.
+
+If you previously set ``COOKIE_NAME``, change it to
+``SESSION_COOKIE_NAME``.
+
+
+$Id: upgrading.txt 23961 2004-04-12 15:51:38Z nascheme $
+

Added: packages/quixote1/branches/upstream/current/doc/upload.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/upload.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/upload.html (added)
+++ packages/quixote1/branches/upstream/current/doc/upload.html Mon May  8 19:16:16 2006
@@ -1,0 +1,163 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>HTTP Upload with Quixote</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="http-upload-with-quixote">
+<h1 class="title">HTTP Upload with Quixote</h1>
+<p>Starting with Quixote 0.5.1, Quixote has a new mechanism for handling
+HTTP upload requests.  The bad news is that Quixote applications that
+already handle file uploads will have to change; the good news is that
+the new way is much simpler, saner, and more efficient.</p>
+<p>As (vaguely) specified by RFC 1867, HTTP upload requests are implemented
+by transmitting requests with a Content-Type header of
+<tt class="literal"><span class="pre">multipart/form-data</span></tt>.  (Normal HTTP form-processing requests have a
+Content-Type of <tt class="literal"><span class="pre">application/x-www-form-urlencoded</span></tt>.)  Since this type
+of request is generally only used for file uploads, Quixote 0.5.1
+introduced a new class for dealing with it: HTTPUploadRequest, a
+subclass of HTTPRequest.</p>
+<div class="section" id="upload-form">
+<h1><a name="upload-form">Upload Form</a></h1>
+<p>Here's how it works: first, you create a form that will be encoded
+according to RFC 1867, ie. with <tt class="literal"><span class="pre">multipart/form-data</span></tt>.  You can put
+any ordinary form elements there, but for a file upload to take place,
+you need to supply at least one <tt class="literal"><span class="pre">file</span></tt> form element.  Here's an
+example:</p>
+<pre class="literal-block">
+def upload_form [html] (request):
+    '''
+    &lt;form enctype=&quot;multipart/form-data&quot;
+          method=&quot;POST&quot; 
+          action=&quot;receive&quot;&gt;
+      Your name:&lt;br&gt;
+      &lt;input type=&quot;text&quot; name=&quot;name&quot;&gt;&lt;br&gt;
+      File to upload:&lt;br&gt;
+      &lt;input type=&quot;file&quot; name=&quot;upload&quot;&gt;&lt;br&gt;
+      &lt;input type=&quot;submit&quot; value=&quot;Upload&quot;&gt;
+    &lt;/form&gt;
+    '''
+</pre>
+<p>(You can use Quixote's widget classes to construct the non-<tt class="literal"><span class="pre">file</span></tt> form
+elements, but the Form class currently doesn't know about the
+<tt class="literal"><span class="pre">enctype</span></tt> attribute, so it's not much use here.  Also, you can supply
+multiple <tt class="literal"><span class="pre">file</span></tt> widgets to upload multiple files simultaneously.)</p>
+<p>The user fills out this form as usual; most browsers let the user either
+enter a filename or select a file from a dialog box.  But when the form
+is submitted, the browser creates an HTTP request that is different from
+other HTTP requests in two ways:</p>
+<ul class="simple">
+<li>it's encoded according to RFC 1867, i.e. as a MIME message where each
+sub-part is one form variable (this is irrelevant to you -- Quixote's
+HTTPUploadRequest takes care of the details)</li>
+<li>it's arbitrarily large -- even for very large and complicated HTML
+forms, the HTTP request is usually no more than a few hundred bytes.
+With file upload, the uploaded file is included right in the request,
+so the HTTP request is as large as the upload, plus a bit of overhead.</li>
+</ul>
+</div>
+<div class="section" id="how-quixote-handles-the-upload-request">
+<h1><a name="how-quixote-handles-the-upload-request">How Quixote Handles the Upload Request</a></h1>
+<p>When Quixote sees an HTTP request with a Content-Type of
+<tt class="literal"><span class="pre">multipart/form-data</span></tt>, it creates an HTTPUploadRequest object instead
+of the usual HTTPRequest.  (This happens even if there's not an uploaded
+file in the request -- Quixote doesn't know this when the request object
+is created, and <tt class="literal"><span class="pre">multipart/form-data</span></tt> requests are oddballs that are
+better handled by a completely separate class, whether they actually
+include an upload or not.)  This is the <tt class="literal"><span class="pre">request</span></tt> object that will be
+passed to your form-handling function or template, eg.</p>
+<pre class="literal-block">
+def receive [html] (request):
+    print request
+</pre>
+<p>should print an HTTPUploadRequest object to the debug log, assuming that
+<tt class="literal"><span class="pre">receive()</span></tt> is being invoked as a result of the above form.</p>
+<p>However, since upload requests can be arbitrarily large, it might be
+some time before Quixote actually calls <tt class="literal"><span class="pre">receive()</span></tt>.  And Quixote has
+to interact with the real world in a number of ways in order to parse
+the request, so there are a number of opportunities for things to go
+wrong.  In particular, whenever Quixote sees a file upload variable in
+the request, it:</p>
+<ul class="simple">
+<li>checks that the <tt class="literal"><span class="pre">UPLOAD_DIR</span></tt> configuration variable was defined.
+If not, it raises ConfigError.</li>
+<li>ensures that <tt class="literal"><span class="pre">UPLOAD_DIR</span></tt> exists, and creates it if not. (It's
+created with the mode specified by <tt class="literal"><span class="pre">UPLOAD_DIR_MODE</span></tt>, which defaults
+to <tt class="literal"><span class="pre">0755</span></tt>.  I have no idea what this should be on Windows.) If this
+fails, your application will presumably crash with an OSError.</li>
+<li>opens a temporary file in <tt class="literal"><span class="pre">UPLOAD_DIR</span></tt> and write the contents
+of the uploaded file to it.  Either opening or writing could fail
+with IOError.</li>
+</ul>
+<p>Furthermore, if there are any problems parsing the request body -- which
+could be the result of either a broken/malicious client or of a bug in
+HTTPUploadRequest -- then Quixote raises RequestError.</p>
+<p>These errors are treated the same as any other exception Quixote
+encounters: RequestError (which is a subclass of PublishError) is
+transformed into a &quot;400 Invalid request&quot; HTTP response, and the others
+become some form of &quot;internal server error&quot; response, with traceback
+optionally shown to the user, emailed to you, etc.</p>
+</div>
+<div class="section" id="processing-the-upload-request">
+<h1><a name="processing-the-upload-request">Processing the Upload Request</a></h1>
+<p>If Quixote successfully parses the upload request, then it passes a
+<tt class="literal"><span class="pre">request</span></tt> object to some function or PTL template that you supply, as
+usual.  Of course, that <tt class="literal"><span class="pre">request</span></tt> object will be an instance of
+HTTPUploadRequest rather than HTTPRequest, but that doesn't make much
+difference to you.  You can access form variables, cookies, etc. just as
+you usually do.  The only difference is that form variables associated
+with uploaded files are represented as Upload objects.  Here's an
+example that goes with the above upload form:</p>
+<pre class="literal-block">
+def receive [html] (request):
+    name = request.form.get(&quot;name&quot;)
+    if name:
+        &quot;&lt;p&gt;Thanks, %s!&lt;/p&gt;\n&quot; % name
+
+    upload = request.form.get(&quot;upload&quot;)
+    size = os.stat(upload.tmp_filename)[stat.ST_SIZE]
+    if not upload.base_filename or size == 0:
+        &quot;&lt;p&gt;You appear not to have uploaded anything.&lt;/p&gt;\n&quot;
+    else:
+        '''\
+        &lt;p&gt;You just uploaded &lt;code&gt;%s&lt;/code&gt; (%d bytes)&lt;br&gt;
+        which is temporarily stored in &lt;code&gt;%s&lt;/code&gt;.&lt;/p&gt;
+        ''' % (upload.base_filename, size, upload.tmp_filename)
+</pre>
+<p>Upload objects provide three attributes of interest:</p>
+<dl>
+<dt><tt class="literal"><span class="pre">orig_filename</span></tt></dt>
+<dd>the complete filename supplied by the user-agent in the request that
+uploaded this file.  Depending on the browser, this might have the
+complete path of the original file on the client system, in the client
+system's syntax -- eg.  <tt class="literal"><span class="pre">C:\foo\bar\upload_this</span></tt> or
+<tt class="literal"><span class="pre">/foo/bar/upload_this</span></tt> or <tt class="literal"><span class="pre">foo:bar:upload_this</span></tt>.</dd>
+<dt><tt class="literal"><span class="pre">base_filename</span></tt></dt>
+<dd>the base component of orig_filename, shorn of MS-DOS, Mac OS, and Unix
+path components and with &quot;unsafe&quot; characters replaced with
+underscores.  (The &quot;safe&quot; characters are <tt class="literal"><span class="pre">A-Z</span></tt>, <tt class="literal"><span class="pre">a-z</span></tt>, <tt class="literal"><span class="pre">0-9</span></tt>,
+<tt class="literal"><span class="pre">-</span> <span class="pre">&#64;</span> <span class="pre">&amp;</span> <span class="pre">+</span> <span class="pre">=</span> <span class="pre">_</span> <span class="pre">.</span></tt>, and space.  Thus, this is &quot;safe&quot; in the sense that
+it's OK to create a filename with any of those characters on Unix, Mac
+OS, and Windows, <em>not</em> in the sense that you can use the filename in
+an HTML document without quoting it!)</dd>
+<dt><tt class="literal"><span class="pre">tmp_filename</span></tt></dt>
+<dd>where you'll actually find the file on the current system</dd>
+</dl>
+<p>Thus, you could open the file directly using <tt class="literal"><span class="pre">tmp_filename</span></tt>, or move
+it to a permanent location using <tt class="literal"><span class="pre">tmp_filename</span></tt> and <tt class="literal"><span class="pre">base_filename</span></tt>
+-- whatever.</p>
+</div>
+<div class="section" id="upload-demo">
+<h1><a name="upload-demo">Upload Demo</a></h1>
+<p>The above upload form and form-processor are available, in a slightly
+different form, in <tt class="literal"><span class="pre">demo/upload.cgi</span></tt>.  Install that file to your usual
+<tt class="literal"><span class="pre">cgi-bin</span></tt> directory and play around.</p>
+<p>$Id: upload.txt 20217 2003-01-16 20:51:53Z akuchlin $</p>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/upload.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/upload.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/upload.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/upload.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,168 @@
+HTTP Upload with Quixote
+========================
+
+Starting with Quixote 0.5.1, Quixote has a new mechanism for handling
+HTTP upload requests.  The bad news is that Quixote applications that
+already handle file uploads will have to change; the good news is that
+the new way is much simpler, saner, and more efficient.
+
+As (vaguely) specified by RFC 1867, HTTP upload requests are implemented
+by transmitting requests with a Content-Type header of
+``multipart/form-data``.  (Normal HTTP form-processing requests have a
+Content-Type of ``application/x-www-form-urlencoded``.)  Since this type
+of request is generally only used for file uploads, Quixote 0.5.1
+introduced a new class for dealing with it: HTTPUploadRequest, a
+subclass of HTTPRequest.
+
+
+Upload Form
+-----------
+
+Here's how it works: first, you create a form that will be encoded
+according to RFC 1867, ie. with ``multipart/form-data``.  You can put
+any ordinary form elements there, but for a file upload to take place,
+you need to supply at least one ``file`` form element.  Here's an
+example::
+
+    def upload_form [html] (request):
+        '''
+        <form enctype="multipart/form-data"
+              method="POST" 
+              action="receive">
+          Your name:<br>
+          <input type="text" name="name"><br>
+          File to upload:<br>
+          <input type="file" name="upload"><br>
+          <input type="submit" value="Upload">
+        </form>
+        '''
+
+(You can use Quixote's widget classes to construct the non-``file`` form
+elements, but the Form class currently doesn't know about the
+``enctype`` attribute, so it's not much use here.  Also, you can supply
+multiple ``file`` widgets to upload multiple files simultaneously.)
+
+The user fills out this form as usual; most browsers let the user either
+enter a filename or select a file from a dialog box.  But when the form
+is submitted, the browser creates an HTTP request that is different from
+other HTTP requests in two ways:
+
+* it's encoded according to RFC 1867, i.e. as a MIME message where each
+  sub-part is one form variable (this is irrelevant to you -- Quixote's
+  HTTPUploadRequest takes care of the details)
+
+* it's arbitrarily large -- even for very large and complicated HTML
+  forms, the HTTP request is usually no more than a few hundred bytes.
+  With file upload, the uploaded file is included right in the request,
+  so the HTTP request is as large as the upload, plus a bit of overhead.
+
+
+How Quixote Handles the Upload Request
+--------------------------------------
+
+When Quixote sees an HTTP request with a Content-Type of
+``multipart/form-data``, it creates an HTTPUploadRequest object instead
+of the usual HTTPRequest.  (This happens even if there's not an uploaded
+file in the request -- Quixote doesn't know this when the request object
+is created, and ``multipart/form-data`` requests are oddballs that are
+better handled by a completely separate class, whether they actually
+include an upload or not.)  This is the ``request`` object that will be
+passed to your form-handling function or template, eg. ::
+
+    def receive [html] (request):
+        print request
+
+should print an HTTPUploadRequest object to the debug log, assuming that
+``receive()`` is being invoked as a result of the above form.
+
+However, since upload requests can be arbitrarily large, it might be
+some time before Quixote actually calls ``receive()``.  And Quixote has
+to interact with the real world in a number of ways in order to parse
+the request, so there are a number of opportunities for things to go
+wrong.  In particular, whenever Quixote sees a file upload variable in
+the request, it:
+
+* checks that the ``UPLOAD_DIR`` configuration variable was defined.
+  If not, it raises ConfigError.
+
+* ensures that ``UPLOAD_DIR`` exists, and creates it if not. (It's
+  created with the mode specified by ``UPLOAD_DIR_MODE``, which defaults
+  to ``0755``.  I have no idea what this should be on Windows.) If this
+  fails, your application will presumably crash with an OSError.
+
+* opens a temporary file in ``UPLOAD_DIR`` and write the contents
+  of the uploaded file to it.  Either opening or writing could fail
+  with IOError.
+
+Furthermore, if there are any problems parsing the request body -- which
+could be the result of either a broken/malicious client or of a bug in
+HTTPUploadRequest -- then Quixote raises RequestError.
+
+These errors are treated the same as any other exception Quixote
+encounters: RequestError (which is a subclass of PublishError) is
+transformed into a "400 Invalid request" HTTP response, and the others
+become some form of "internal server error" response, with traceback
+optionally shown to the user, emailed to you, etc.
+
+
+Processing the Upload Request
+-----------------------------
+
+If Quixote successfully parses the upload request, then it passes a
+``request`` object to some function or PTL template that you supply, as
+usual.  Of course, that ``request`` object will be an instance of
+HTTPUploadRequest rather than HTTPRequest, but that doesn't make much
+difference to you.  You can access form variables, cookies, etc. just as
+you usually do.  The only difference is that form variables associated
+with uploaded files are represented as Upload objects.  Here's an
+example that goes with the above upload form::
+
+    def receive [html] (request):
+        name = request.form.get("name")
+        if name:
+            "<p>Thanks, %s!</p>\n" % name
+
+        upload = request.form.get("upload")
+        size = os.stat(upload.tmp_filename)[stat.ST_SIZE]
+        if not upload.base_filename or size == 0:
+            "<p>You appear not to have uploaded anything.</p>\n"
+        else:
+            '''\
+            <p>You just uploaded <code>%s</code> (%d bytes)<br>
+            which is temporarily stored in <code>%s</code>.</p>
+            ''' % (upload.base_filename, size, upload.tmp_filename)
+
+Upload objects provide three attributes of interest:
+
+``orig_filename``
+  the complete filename supplied by the user-agent in the request that
+  uploaded this file.  Depending on the browser, this might have the
+  complete path of the original file on the client system, in the client
+  system's syntax -- eg.  ``C:\foo\bar\upload_this`` or
+  ``/foo/bar/upload_this`` or ``foo:bar:upload_this``.
+
+``base_filename``
+  the base component of orig_filename, shorn of MS-DOS, Mac OS, and Unix
+  path components and with "unsafe" characters replaced with
+  underscores.  (The "safe" characters are ``A-Z``, ``a-z``, ``0-9``,
+  ``- @ & + = _ .``, and space.  Thus, this is "safe" in the sense that
+  it's OK to create a filename with any of those characters on Unix, Mac
+  OS, and Windows, *not* in the sense that you can use the filename in
+  an HTML document without quoting it!)
+  
+``tmp_filename``
+  where you'll actually find the file on the current system
+
+Thus, you could open the file directly using ``tmp_filename``, or move
+it to a permanent location using ``tmp_filename`` and ``base_filename``
+-- whatever.
+
+
+Upload Demo
+-----------
+
+The above upload form and form-processor are available, in a slightly
+different form, in ``demo/upload.cgi``.  Install that file to your usual
+``cgi-bin`` directory and play around.
+
+$Id: upload.txt 20217 2003-01-16 20:51:53Z akuchlin $

Added: packages/quixote1/branches/upstream/current/doc/web-server.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/web-server.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/web-server.html (added)
+++ packages/quixote1/branches/upstream/current/doc/web-server.html Mon May  8 19:16:16 2006
@@ -1,0 +1,240 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Web Server Configuration for Quixote</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="web-server-configuration-for-quixote">
+<h1 class="title">Web Server Configuration for Quixote</h1>
+<p>For a simple Quixote installation, there are two things you have to get
+right:</p>
+<ul class="simple">
+<li>installation of the Quixote modules to Python's library (the
+trick here is that the <tt class="literal"><span class="pre">quixote</span></tt> package must be visible to the user
+that CGI scripts run as, not necessarily to you as an interactive
+command-line user)</li>
+<li>configuration of your web server to run Quixote driver scripts
+(such as demo.cgi)</li>
+</ul>
+<p>This document is concerned with the second of these.</p>
+<div class="section" id="which-web-servers">
+<h1><a name="which-web-servers">Which web servers?</a></h1>
+<p>We are only familiar with Apache, and we develop Quixote for use under
+Apache.  However, Quixote doesn't rely on any Apache-specific tricks;
+if you can execute CGI scripts, then you can run Quixote applications
+(although they'll run a lot faster with mod_scgi or FastCGI).  If you
+can redirect arbitrary URLs to a CGI script and preserve parts of the
+URL as an add-on to the script name (with <tt class="literal"><span class="pre">PATH_INFO</span></tt>), then you can
+run Quixote applications in the ideal manner, ie. with superfluous
+implementation details hidden from the user.</p>
+</div>
+<div class="section" id="which-operating-systems">
+<h1><a name="which-operating-systems">Which operating systems?</a></h1>
+<p>We are mainly familiar with Unix, and develop and deploy Quixote under
+Linux.  However, we've had several reports of people using Quixote under
+Windows, more-or-less successfully.  There are still a few Unix-isms in
+the code, but they are being rooted out in favour of portability.</p>
+<p>Remember that your system is only as secure as its weakest link.
+Quixote can't help you write secure web applications on an inherently
+insecure operating system.</p>
+</div>
+<div class="section" id="basic-cgi-configuration">
+<h1><a name="basic-cgi-configuration">Basic CGI configuration</a></h1>
+<p>Throughout this document, I'm going to assume that:</p>
+<ul class="simple">
+<li>CGI scripts live in the <tt class="literal"><span class="pre">/www/cgi-bin</span></tt> directory of your web server,
+and have the extension <tt class="literal"><span class="pre">.cgi</span></tt></li>
+<li>HTTP requests for <tt class="literal"><span class="pre">/cgi-bin/foo.cgi</span></tt> will result in the execution
+of <tt class="literal"><span class="pre">/www/cgi-bin/foo.cgi</span></tt> (for various values of <tt class="literal"><span class="pre">foo</span></tt>)</li>
+<li>if the web server is instructed to serve an executable file
+<tt class="literal"><span class="pre">bar.cgi</span></tt>, the file is treated as a CGI script</li>
+</ul>
+<p>With Apache, these configuration directives will do the trick:</p>
+<pre class="literal-block">
+AddHandler cgi-script .cgi
+ScriptAlias /cgi-bin/ /www/cgi-bin/
+</pre>
+<p>Consult the Apache documentation for other ways of configuring CGI
+script execution.</p>
+<p>For other web servers, consult your server's documentation.</p>
+</div>
+<div class="section" id="installing-driver-scripts">
+<h1><a name="installing-driver-scripts">Installing driver scripts</a></h1>
+<p>Given the above configuration, installing a Quixote driver script is the
+same as installing any other CGI script: copy it to <tt class="literal"><span class="pre">/www/cgi-bin</span></tt> (or
+whatever).  To install the Quixote demo's driver script:</p>
+<pre class="literal-block">
+cp -p demo/demo.cgi /www/cgi-bin
+</pre>
+<p>(The <tt class="literal"><span class="pre">-p</span></tt> option ensures that <tt class="literal"><span class="pre">cp</span></tt> preserves the file mode, so that
+it remains executable.)</p>
+</div>
+<div class="section" id="url-rewriting">
+<h1><a name="url-rewriting">URL rewriting</a></h1>
+<p>With the above configuration, users need to use URLs like</p>
+<pre class="literal-block">
+http://www.example.com/cgi-bin/demo.cgi
+</pre>
+<p>to access the Quixote demo (or other Quixote applications installed in
+the same way).  This works, but it's ugly and unnecessarily exposes
+implementation details.</p>
+<p>In our view, it's preferable to give each Quixote application its own
+chunk of URL-space -- a &quot;virtual directory&quot; if you like.  For example,
+you might want</p>
+<pre class="literal-block">
+http://www.example.com/qdemo
+</pre>
+<p>to handle the Quixote demo.</p>
+<p>With Apache, this is quite easy, as long as mod_rewrite is compiled,
+loaded, and enabled.  (Building and loading Apache modules is beyond the
+scope of this document; consult the Apache documentation.)</p>
+<p>To enable the rewrite engine, use the</p>
+<pre class="literal-block">
+RewriteEngine on
+</pre>
+<p>directive.  If you have virtual hosts, make sure to repeat this for each
+<tt class="literal"><span class="pre">&lt;VirtualHost&gt;</span></tt> section of your config file.</p>
+<p>The rewrite rule to use in this case is</p>
+<pre class="literal-block">
+RewriteRule ^/qdemo(/.*) /www/cgi-bin/demo.cgi$1 [last]
+</pre>
+<p>This is <em>not</em> a redirect; this is all handled with one HTTP
+request/response cycle, and the user never sees <tt class="literal"><span class="pre">/cgi-bin/demo.cgi</span></tt> in
+a URL.</p>
+<p>Note that requests for <tt class="literal"><span class="pre">/qdemo/</span></tt> and <tt class="literal"><span class="pre">/qdemo</span></tt> are <em>not</em> the same; in
+particular, with the above rewrite rule, the former will succeed and the
+latter will not.  (Look at the regex again if you don't believe me:
+<tt class="literal"><span class="pre">/qdemo</span></tt> doesn't match the regex, so <tt class="literal"><span class="pre">demo.cgi</span></tt> is never invoked.)</p>
+<p>The solution for <tt class="literal"><span class="pre">/qdemo</span></tt> is the same as if it corresponded to a
+directory in your document tree: redirect it to <tt class="literal"><span class="pre">/qdemo/</span></tt>.  Apache
+(and, presumably, other web servers) does this automatically for &quot;real&quot;
+directories; however, <tt class="literal"><span class="pre">/qdemo/</span></tt> is just a directory-like chunk of
+URL-space, so either you or Quixote have to take care of the redirect.</p>
+<p>It's almost certainly faster for you to take care of it in the web
+server's configuration.  With Apache, simply insert this directive
+<em>before</em> the above rewrite rule:</p>
+<pre class="literal-block">
+RewriteRule ^/qdemo$ /qdemo/ [redirect=permanent]
+</pre>
+<p>If, for some reason, you are unwilling or unable to instruct your web
+server to perform this redirection, Quixote will do it for you.
+However, you have to make sure that the <tt class="literal"><span class="pre">/qdemo</span></tt> URL is handled by
+Quixote.  Change the rewrite rule to:</p>
+<pre class="literal-block">
+RewriteRule ^/qdemo(/.*)?$ /www/cgi-bin/demo.cgi$1 [last]
+</pre>
+<p>Now a request for <tt class="literal"><span class="pre">/qdemo</span></tt> will be handled by Quixote, and it will
+generate a redirect to <tt class="literal"><span class="pre">/qdemo/</span></tt>.  If you're using a CGI driver
+script, this will be painfully slow, but it will work.</p>
+<p>For redirecting and rewriting URLs with other web servers, consult your
+server's documentation.</p>
+</div>
+<div class="section" id="long-running-processes">
+<h1><a name="long-running-processes">Long-running processes</a></h1>
+<p>For serious web applications, CGI is unacceptably slow.  For a CGI-based
+Quixote application, you have to start a Python interpreter, load the
+Quixote modules, and load your application's modules before you can
+start working.  For sophisticated, database-backed applications, you'll
+probably have to open a new database connection as well for every hit.</p>
+<p>Small wonder so many high-performance alternatives to CGI exist.  (The
+main advantages of CGI are that it is widely supported and easy to
+develop with.  Even for large Quixote applications, running in CGI mode
+is nice in development because you don't have to kill a long-running
+driver script every time the code changes.)  Currently, Quixote supports
+three such alternatives: mod_scgi, FastCGI, and mod_python.</p>
+</div>
+<div class="section" id="mod-scgi-configuration">
+<h1><a name="mod-scgi-configuration">mod_scgi configuration</a></h1>
+<p>SCGI is a CGI replacement written by Neil Schemenauer, one of
+Quixote's developers, and is similar to FastCGI but is designed to be
+easier to implement.  mod_scgi simply forwards requests to an
+already-running SCGI server on a different TCP port, and doesn't try
+to start or stop processes, leaving that up to the SCGI server.</p>
+<p>The SCGI code is available from <a class="reference" href="http://www.mems-exchange.org/software/scgi/">http://www.mems-exchange.org/software/scgi/</a> .  
+It contains a Python module, scgi.quixote_handler, that will publish a
+Quixote-based application via SCGI.  Here's an example script to publish
+an application:</p>
+<pre class="literal-block">
+#!/usr/bin/python
+from scgi.quixote_handler import QuixoteHandler, main
+from quixote.publisher import SessionPublisher
+
+class MyAppHandler(QuixoteHandler):
+    publisher_class = SessionPublisher
+    root_namespace = &quot;myapp.ui&quot;
+    prefix = &quot;&quot;
+
+if __name__ == '__main__':
+    main(MyAppHandler)
+</pre>
+<p>When run, this script will take various command-line arguments.  <tt class="literal"><span class="pre">-p</span>
+<span class="pre">&lt;port&gt;</span></tt> specifies the TCP port that the SCGI server will listen to.
+The following Apache directive will direct requests to an SCGI server
+running on port 3001:</p>
+<pre class="literal-block">
+&lt;Location /&gt;
+  SCGIServer 127.0.0.1 3001
+  SCGIHandler On
+&lt;/Location&gt;
+</pre>
+</div>
+<div class="section" id="fastcgi-configuration">
+<h1><a name="fastcgi-configuration">FastCGI configuration</a></h1>
+<p>If your web server supports FastCGI, you can significantly speed up your
+Quixote applications with a simple change to your configuration.  You
+don't have to change your code at all (unless it makes assumptions about
+how many requests are handled by each process).  (See
+<a class="reference" href="http://www.fastcgi.com/">http://www.fastcgi.com/</a> for more information on FastCGI.)</p>
+<p>To use FastCGI with Apache, you'll need to download mod_fastcgi from
+<a class="reference" href="http://www.fastcgi.com/">http://www.fastcgi.com/</a> and add it to your Apache installation.</p>
+<p>Configuring a FastCGI driver script is best done after reading the fine
+documentation for mod_fastcgi at
+<a class="reference" href="http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html">http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html</a></p>
+<p>However, if you just want to try it with the Quixote demo to see if it
+works, add this directive to your Apache configuration:</p>
+<pre class="literal-block">
+AddHandler fastcgi-script .fcgi
+</pre>
+<p>and rename demo.cgi to demo.fcgi.  If you're using a URL rewrite to map
+requests for (eg.) <tt class="literal"><span class="pre">/qdemo</span></tt> to <tt class="literal"><span class="pre">/www/cgi-bin/demo.cgi</span></tt>, be sure to
+change the rewrite -- it should now point to <tt class="literal"><span class="pre">/www/cgi-bin/demo.fcgi</span></tt>.</p>
+<p>After the first access to <tt class="literal"><span class="pre">demo.fcgi</span></tt> (or <tt class="literal"><span class="pre">/qdemo/</span></tt> with the
+modified rewrite rule), the demo should be noticeably faster.  You
+should also see a <tt class="literal"><span class="pre">demo.fcgi</span></tt> process running if you do <tt class="literal"><span class="pre">ps</span> <span class="pre">-le</span></tt>
+(<tt class="literal"><span class="pre">ps</span> <span class="pre">-aux</span></tt> on BSD-ish systems, or maybe <tt class="literal"><span class="pre">ps</span> <span class="pre">aux</span></tt>).  (On my 800 MHz
+Athlon machine, there are slight but perceptible delays navigating the
+Quixote demo in CGI mode.  In FastCGI mode, the delay between pages is
+no longer perceptible -- navigation is instantaneous.)  The larger your
+application is, the more code it loads, and the more work it does at
+startup, the bigger a win FastCGI will be for you.</p>
+</div>
+<div class="section" id="mod-python-configuration">
+<h1><a name="mod-python-configuration">mod_python configuration</a></h1>
+<p>mod_python is an Apache module for embedding a Python interpreter into
+the Apache server.  To use mod_python as the interface layer between
+Apache and Quixote, add something like this to your httpd.conf:</p>
+<pre class="literal-block">
+LoadModule python_module /usr/lib/apache/1.3/mod_python.so
+&lt;LocationMatch &quot;^/qdemo(/|$)&quot;&gt;
+    SetHandler python-program
+    PythonHandler quixote.mod_python_handler
+    PythonOption quixote-root-namespace quixote.demo
+    PythonInterpreter quixote.demo
+    PythonDebug On
+&lt;/LocationMatch&gt;
+</pre>
+<p>This will attach URLs starting with <tt class="literal"><span class="pre">/qdemo</span></tt> to the Quixote demo.
+When you use mod_python, there's no need for rewrite rules (because of
+the pattern in the <tt class="literal"><span class="pre">LocationMatch</span></tt> directive), and no need for a
+driver script.</p>
+<p>mod_python support was contributed to Quixote by Erno Kuusela
+&lt;<a class="reference" href="mailto:erno&#64;iki.fi">erno&#64;iki.fi</a>&gt;.</p>
+<p>$Id: web-server.txt 21999 2003-07-14 15:24:42Z nascheme $</p>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/web-server.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/web-server.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/web-server.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/web-server.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,267 @@
+Web Server Configuration for Quixote
+====================================
+
+For a simple Quixote installation, there are two things you have to get
+right:
+
+* installation of the Quixote modules to Python's library (the
+  trick here is that the ``quixote`` package must be visible to the user
+  that CGI scripts run as, not necessarily to you as an interactive
+  command-line user)
+
+* configuration of your web server to run Quixote driver scripts
+  (such as demo.cgi)
+
+This document is concerned with the second of these.
+
+
+Which web servers?
+------------------
+
+We are only familiar with Apache, and we develop Quixote for use under
+Apache.  However, Quixote doesn't rely on any Apache-specific tricks;
+if you can execute CGI scripts, then you can run Quixote applications
+(although they'll run a lot faster with mod_scgi or FastCGI).  If you
+can redirect arbitrary URLs to a CGI script and preserve parts of the
+URL as an add-on to the script name (with ``PATH_INFO``), then you can
+run Quixote applications in the ideal manner, ie. with superfluous
+implementation details hidden from the user.
+
+
+Which operating systems?
+------------------------
+
+We are mainly familiar with Unix, and develop and deploy Quixote under
+Linux.  However, we've had several reports of people using Quixote under
+Windows, more-or-less successfully.  There are still a few Unix-isms in
+the code, but they are being rooted out in favour of portability.
+
+Remember that your system is only as secure as its weakest link.
+Quixote can't help you write secure web applications on an inherently
+insecure operating system.
+
+
+Basic CGI configuration
+-----------------------
+
+Throughout this document, I'm going to assume that:
+  
+* CGI scripts live in the ``/www/cgi-bin`` directory of your web server,
+  and have the extension ``.cgi``
+
+* HTTP requests for ``/cgi-bin/foo.cgi`` will result in the execution
+  of ``/www/cgi-bin/foo.cgi`` (for various values of ``foo``)
+
+* if the web server is instructed to serve an executable file
+  ``bar.cgi``, the file is treated as a CGI script
+
+With Apache, these configuration directives will do the trick::
+
+    AddHandler cgi-script .cgi
+    ScriptAlias /cgi-bin/ /www/cgi-bin/
+
+Consult the Apache documentation for other ways of configuring CGI
+script execution.
+
+For other web servers, consult your server's documentation.
+
+
+Installing driver scripts
+-------------------------
+
+Given the above configuration, installing a Quixote driver script is the
+same as installing any other CGI script: copy it to ``/www/cgi-bin`` (or
+whatever).  To install the Quixote demo's driver script::
+
+    cp -p demo/demo.cgi /www/cgi-bin
+
+(The ``-p`` option ensures that ``cp`` preserves the file mode, so that
+it remains executable.)
+
+
+URL rewriting
+-------------
+
+With the above configuration, users need to use URLs like ::
+
+    http://www.example.com/cgi-bin/demo.cgi
+
+to access the Quixote demo (or other Quixote applications installed in
+the same way).  This works, but it's ugly and unnecessarily exposes
+implementation details.
+
+In our view, it's preferable to give each Quixote application its own
+chunk of URL-space -- a "virtual directory" if you like.  For example,
+you might want ::
+
+    http://www.example.com/qdemo
+
+to handle the Quixote demo.
+
+With Apache, this is quite easy, as long as mod_rewrite is compiled,
+loaded, and enabled.  (Building and loading Apache modules is beyond the
+scope of this document; consult the Apache documentation.)
+
+To enable the rewrite engine, use the ::
+
+    RewriteEngine on
+
+directive.  If you have virtual hosts, make sure to repeat this for each
+``<VirtualHost>`` section of your config file.
+
+The rewrite rule to use in this case is ::
+
+    RewriteRule ^/qdemo(/.*) /www/cgi-bin/demo.cgi$1 [last]
+
+This is *not* a redirect; this is all handled with one HTTP
+request/response cycle, and the user never sees ``/cgi-bin/demo.cgi`` in
+a URL.
+
+Note that requests for ``/qdemo/`` and ``/qdemo`` are *not* the same; in
+particular, with the above rewrite rule, the former will succeed and the
+latter will not.  (Look at the regex again if you don't believe me:
+``/qdemo`` doesn't match the regex, so ``demo.cgi`` is never invoked.)
+
+The solution for ``/qdemo`` is the same as if it corresponded to a
+directory in your document tree: redirect it to ``/qdemo/``.  Apache
+(and, presumably, other web servers) does this automatically for "real"
+directories; however, ``/qdemo/`` is just a directory-like chunk of
+URL-space, so either you or Quixote have to take care of the redirect.
+
+It's almost certainly faster for you to take care of it in the web
+server's configuration.  With Apache, simply insert this directive
+*before* the above rewrite rule::
+
+    RewriteRule ^/qdemo$ /qdemo/ [redirect=permanent]
+
+If, for some reason, you are unwilling or unable to instruct your web
+server to perform this redirection, Quixote will do it for you.
+However, you have to make sure that the ``/qdemo`` URL is handled by
+Quixote.  Change the rewrite rule to::
+
+    RewriteRule ^/qdemo(/.*)?$ /www/cgi-bin/demo.cgi$1 [last]
+
+Now a request for ``/qdemo`` will be handled by Quixote, and it will
+generate a redirect to ``/qdemo/``.  If you're using a CGI driver
+script, this will be painfully slow, but it will work.
+
+For redirecting and rewriting URLs with other web servers, consult your
+server's documentation.
+
+
+Long-running processes
+----------------------
+
+For serious web applications, CGI is unacceptably slow.  For a CGI-based
+Quixote application, you have to start a Python interpreter, load the
+Quixote modules, and load your application's modules before you can
+start working.  For sophisticated, database-backed applications, you'll
+probably have to open a new database connection as well for every hit.
+
+Small wonder so many high-performance alternatives to CGI exist.  (The
+main advantages of CGI are that it is widely supported and easy to
+develop with.  Even for large Quixote applications, running in CGI mode
+is nice in development because you don't have to kill a long-running
+driver script every time the code changes.)  Currently, Quixote supports
+three such alternatives: mod_scgi, FastCGI, and mod_python.
+
+
+mod_scgi configuration
+----------------------
+
+SCGI is a CGI replacement written by Neil Schemenauer, one of
+Quixote's developers, and is similar to FastCGI but is designed to be
+easier to implement.  mod_scgi simply forwards requests to an
+already-running SCGI server on a different TCP port, and doesn't try
+to start or stop processes, leaving that up to the SCGI server.
+
+The SCGI code is available from http://www.mems-exchange.org/software/scgi/ .  
+It contains a Python module, scgi.quixote_handler, that will publish a
+Quixote-based application via SCGI.  Here's an example script to publish
+an application::
+
+    #!/usr/bin/python
+    from scgi.quixote_handler import QuixoteHandler, main
+    from quixote.publisher import SessionPublisher
+
+    class MyAppHandler(QuixoteHandler):
+        publisher_class = SessionPublisher
+        root_namespace = "myapp.ui"
+        prefix = ""
+
+    if __name__ == '__main__':
+        main(MyAppHandler)
+
+When run, this script will take various command-line arguments.  ``-p
+<port>`` specifies the TCP port that the SCGI server will listen to.
+The following Apache directive will direct requests to an SCGI server
+running on port 3001::
+
+    <Location />
+      SCGIServer 127.0.0.1 3001
+      SCGIHandler On
+    </Location>
+
+
+FastCGI configuration
+---------------------
+
+If your web server supports FastCGI, you can significantly speed up your
+Quixote applications with a simple change to your configuration.  You
+don't have to change your code at all (unless it makes assumptions about
+how many requests are handled by each process).  (See
+http://www.fastcgi.com/ for more information on FastCGI.)
+
+To use FastCGI with Apache, you'll need to download mod_fastcgi from
+http://www.fastcgi.com/ and add it to your Apache installation.
+
+Configuring a FastCGI driver script is best done after reading the fine
+documentation for mod_fastcgi at
+http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html
+
+However, if you just want to try it with the Quixote demo to see if it
+works, add this directive to your Apache configuration::
+
+    AddHandler fastcgi-script .fcgi
+
+and rename demo.cgi to demo.fcgi.  If you're using a URL rewrite to map
+requests for (eg.) ``/qdemo`` to ``/www/cgi-bin/demo.cgi``, be sure to
+change the rewrite -- it should now point to ``/www/cgi-bin/demo.fcgi``.
+
+After the first access to ``demo.fcgi`` (or ``/qdemo/`` with the
+modified rewrite rule), the demo should be noticeably faster.  You
+should also see a ``demo.fcgi`` process running if you do ``ps -le``
+(``ps -aux`` on BSD-ish systems, or maybe ``ps aux``).  (On my 800 MHz
+Athlon machine, there are slight but perceptible delays navigating the
+Quixote demo in CGI mode.  In FastCGI mode, the delay between pages is
+no longer perceptible -- navigation is instantaneous.)  The larger your
+application is, the more code it loads, and the more work it does at
+startup, the bigger a win FastCGI will be for you.
+
+
+mod_python configuration
+------------------------
+
+mod_python is an Apache module for embedding a Python interpreter into
+the Apache server.  To use mod_python as the interface layer between
+Apache and Quixote, add something like this to your httpd.conf::
+
+    LoadModule python_module /usr/lib/apache/1.3/mod_python.so
+    <LocationMatch "^/qdemo(/|$)">
+        SetHandler python-program
+        PythonHandler quixote.mod_python_handler
+        PythonOption quixote-root-namespace quixote.demo
+        PythonInterpreter quixote.demo
+        PythonDebug On
+    </LocationMatch>
+
+This will attach URLs starting with ``/qdemo`` to the Quixote demo.
+When you use mod_python, there's no need for rewrite rules (because of
+the pattern in the ``LocationMatch`` directive), and no need for a
+driver script.
+
+mod_python support was contributed to Quixote by Erno Kuusela
+<erno at iki.fi>.
+
+
+$Id: web-server.txt 21999 2003-07-14 15:24:42Z nascheme $

Added: packages/quixote1/branches/upstream/current/doc/web-services.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/web-services.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/web-services.html (added)
+++ packages/quixote1/branches/upstream/current/doc/web-services.html Mon May  8 19:16:16 2006
@@ -1,0 +1,187 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Implementing Web Services with Quixote</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="implementing-web-services-with-quixote">
+<h1 class="title">Implementing Web Services with Quixote</h1>
+<p>This document will show you how to implement Web services using
+Quixote.</p>
+<div class="section" id="an-xml-rpc-service">
+<h1><a name="an-xml-rpc-service">An XML-RPC Service</a></h1>
+<p>XML-RPC is the simplest protocol commonly used to expose a Web
+service.  In XML-RPC, there are a few basic data types such as
+integers, floats, strings, and dates, and a few aggregate types such
+as arrays and structs.  The xmlrpclib module, part of the Python 2.2
+standard library and available separately from
+<a class="reference" href="http://www.pythonware.com/products/xmlrpc/">http://www.pythonware.com/products/xmlrpc/</a>, converts between Python's
+standard data types and the XML-RPC data types.</p>
+<table class="table" frame="border" rules="all">
+<colgroup>
+<col width="40%" />
+<col width="60%" />
+</colgroup>
+<tbody valign="top">
+<tr><td>XML-RPC Type</td>
+<td>Python Type or Class</td>
+</tr>
+<tr><td>&lt;int&gt;</td>
+<td>int</td>
+</tr>
+<tr><td>&lt;double&gt;</td>
+<td>float</td>
+</tr>
+<tr><td>&lt;string&gt;</td>
+<td>string</td>
+</tr>
+<tr><td>&lt;array&gt;</td>
+<td>list</td>
+</tr>
+<tr><td>&lt;struct&gt;</td>
+<td>dict</td>
+</tr>
+<tr><td>&lt;boolean&gt;</td>
+<td>xmlrpclib.Boolean</td>
+</tr>
+<tr><td>&lt;base64&gt;</td>
+<td>xmlrpclib.Binary</td>
+</tr>
+<tr><td>&lt;dateTime&gt;</td>
+<td>xmlrpclib.DateTime</td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="section" id="making-xml-rpc-calls">
+<h1><a name="making-xml-rpc-calls">Making XML-RPC Calls</a></h1>
+<p>Making an XML-RPC call using xmlrpclib is easy.  An XML-RPC server
+lives at a particular URL, so the first step is to create an
+xmlrpclib.ServerProxy object pointing at that URL.</p>
+<pre class="literal-block">
+&gt;&gt;&gt; import xmlrpclib
+&gt;&gt;&gt; s = xmlrpclib.ServerProxy(
+             'http://www.stuffeddog.com/speller/speller-rpc.cgi')
+</pre>
+<p>Now you can simply make a call to the spell-checking service offered
+by this server:</p>
+<pre class="literal-block">
+&gt;&gt;&gt; s.speller.spellCheck('my speling isnt gud', {})
+[{'word': 'speling', 'suggestions': ['apeling', 'spelding',
+  'spelling', 'sperling', 'spewing', 'spiling'], 'location': 4},
+{'word': 'isnt', 'suggestions': [``isn't'', 'ist'], 'location': 12}]
+&gt;&gt;&gt; 
+</pre>
+<p>This call results in the following XML being sent:</p>
+<pre class="literal-block">
+&lt;?xml version='1.0'?&gt;
+&lt;methodCall&gt;
+     &lt;methodName&gt;speller.spellCheck&lt;/methodName&gt;
+     &lt;params&gt;
+         &lt;param&gt;
+                &lt;value&gt;&lt;string&gt;my speling isnt gud&lt;/string&gt;&lt;/value&gt;
+         &lt;/param&gt;
+         &lt;param&gt;
+                 &lt;value&gt;&lt;struct&gt;&lt;/struct&gt;&lt;/value&gt;
+         &lt;/param&gt;
+     &lt;/params&gt;
+&lt;/methodCall&gt;
+</pre>
+</div>
+<div class="section" id="writing-a-quixote-service">
+<h1><a name="writing-a-quixote-service">Writing a Quixote Service</a></h1>
+<p>In the quixote.util module, Quixote provides a function,
+<tt class="literal"><span class="pre">xmlrpc(request,</span> <span class="pre">func)</span></tt>, that processes the body of an XML-RPC
+request.  <tt class="literal"><span class="pre">request</span></tt> is the HTTPRequest object that Quixote passes to
+every function it invokes.  <tt class="literal"><span class="pre">func</span></tt> is a user-supplied function that
+receives the name of the XML-RPC method being called and a tuple
+containing the method's parameters.  If there's a bug in the function
+you supply and it raises an exception, the <tt class="literal"><span class="pre">xmlrpc()</span></tt> function will
+catch the exception and return a <tt class="literal"><span class="pre">Fault</span></tt> to the remote caller.</p>
+<p>Here's an example of implementing a simple XML-RPC handler with a
+single method, <tt class="literal"><span class="pre">get_time()</span></tt>, that simply returns the current
+time.  The first task is to expose a URL for accessing the service.</p>
+<pre class="literal-block">
+from quixote.util import xmlrpc
+
+_q_exports = ['rpc']
+
+def rpc (request):
+    return xmlrpc(request, rpc_process)
+
+def rpc_process (meth, params):
+    ...
+</pre>
+<p>When the above code is placed in the __init__.py file for the Python
+package corresponding to your Quixote application, it exposes the URL
+<tt class="literal"><span class="pre">http://&lt;hostname&gt;/rpc</span></tt> as the access point for the XML-RPC service.</p>
+<p>Next, we need to fill in the contents of the <tt class="literal"><span class="pre">rpc_process()</span></tt>
+function:</p>
+<pre class="literal-block">
+import time
+
+def rpc_process (meth, params):
+    if meth == 'get_time':
+        # params is ignored
+        now = time.gmtime(time.time())
+        return xmlrpclib.DateTime(now)
+    else:
+        raise RuntimeError, &quot;Unknown XML-RPC method: %r&quot; % meth
+</pre>
+<p><tt class="literal"><span class="pre">rpc_process()</span></tt> receives the method name and the parameters, and its
+job is to run the right code for the method, returning a result that
+will be marshalled into XML-RPC.  The body of <tt class="literal"><span class="pre">rpc_process()</span></tt> will
+therefore usually be an <tt class="literal"><span class="pre">if</span></tt> statement that checks the name of the
+method, and calls another function to do the actual work.  In this case,
+<tt class="literal"><span class="pre">get_time()</span></tt> is very simple so the two lines of code it requires are
+simply included in the body of <tt class="literal"><span class="pre">rpc_process()</span></tt>.</p>
+<p>If the method name doesn't belong to a supported method, execution
+will fall through to the <tt class="literal"><span class="pre">else</span></tt> clause, which will raise a
+RuntimeError exception.  Quixote's <tt class="literal"><span class="pre">xmlrpc()</span></tt> will catch this
+exception and report it to the caller as an XML-RPC fault, with the
+error code set to 1.</p>
+<p>As you add additional XML-RPC services, the <tt class="literal"><span class="pre">if</span></tt> statement in
+<tt class="literal"><span class="pre">rpc_process()</span></tt> will grow more branches.  You might be tempted to pass
+the method name to <tt class="literal"><span class="pre">getattr()</span></tt> to select a method from a module or
+class.  That would work, too, and avoids having a continually growing
+set of branches, but you should be careful with this and be sure that
+there are no private methods that a remote caller could access.  I
+generally prefer to have the <tt class="literal"><span class="pre">if...</span> <span class="pre">elif...</span> <span class="pre">elif...</span> <span class="pre">else</span></tt> blocks, for
+three reasons: 1) adding another branch isn't much work, 2) it's
+explicit about the supported method names, and 3) there won't be any
+security holes in doing so.</p>
+<p>An alternative approach is to have a dictionary mapping method names
+to the corresponding functions and restrict the legal method names 
+to the keys of this dictionary:</p>
+<pre class="literal-block">
+def echo (*params):
+    # Just returns the parameters it's passed
+    return params
+
+def get_time ():
+    now = time.gmtime(time.time())
+    return xmlrpclib.DateTime(now)
+
+methods = {'echo' : echo, 
+           'get_time' : get_time}
+
+def rpc_process (meth, params):
+    func = methods.get[meth]
+    if methods.has_key(meth):
+        # params is ignored
+        now = time.gmtime(time.time())
+        return xmlrpclib.DateTime(now)
+    else:
+        raise RuntimeError, &quot;Unknown XML-RPC method: %r&quot; % meth
+</pre>
+<p>This approach works nicely when there are many methods and the
+<tt class="literal"><span class="pre">if...elif...else</span></tt> statement would be unworkably long.</p>
+<p>$Id: web-services.txt 21603 2003-05-09 19:17:04Z akuchlin $</p>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/web-services.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/web-services.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/web-services.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/web-services.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,165 @@
+Implementing Web Services with Quixote
+======================================
+
+This document will show you how to implement Web services using
+Quixote.
+
+
+An XML-RPC Service
+------------------
+
+XML-RPC is the simplest protocol commonly used to expose a Web
+service.  In XML-RPC, there are a few basic data types such as
+integers, floats, strings, and dates, and a few aggregate types such
+as arrays and structs.  The xmlrpclib module, part of the Python 2.2
+standard library and available separately from
+http://www.pythonware.com/products/xmlrpc/, converts between Python's
+standard data types and the XML-RPC data types.
+
+==============  =====================
+XML-RPC Type	Python Type or Class
+--------------  ---------------------
+<int>		int
+<double>	float
+<string>	string
+<array>		list
+<struct>	dict
+<boolean>	xmlrpclib.Boolean
+<base64>	xmlrpclib.Binary
+<dateTime>	xmlrpclib.DateTime
+==============  =====================
+
+
+Making XML-RPC Calls
+--------------------
+
+Making an XML-RPC call using xmlrpclib is easy.  An XML-RPC server
+lives at a particular URL, so the first step is to create an
+xmlrpclib.ServerProxy object pointing at that URL. ::
+
+    >>> import xmlrpclib
+    >>> s = xmlrpclib.ServerProxy(
+                 'http://www.stuffeddog.com/speller/speller-rpc.cgi')
+
+Now you can simply make a call to the spell-checking service offered
+by this server::
+
+    >>> s.speller.spellCheck('my speling isnt gud', {})
+    [{'word': 'speling', 'suggestions': ['apeling', 'spelding',
+      'spelling', 'sperling', 'spewing', 'spiling'], 'location': 4},
+    {'word': 'isnt', 'suggestions': [``isn't'', 'ist'], 'location': 12}]
+    >>> 
+
+This call results in the following XML being sent::
+
+    <?xml version='1.0'?>
+    <methodCall>
+         <methodName>speller.spellCheck</methodName>
+         <params>
+             <param>
+                    <value><string>my speling isnt gud</string></value>
+             </param>
+             <param>
+                     <value><struct></struct></value>
+             </param>
+         </params>
+    </methodCall>
+
+
+Writing a Quixote Service
+-------------------------
+
+In the quixote.util module, Quixote provides a function,
+``xmlrpc(request, func)``, that processes the body of an XML-RPC
+request.  ``request`` is the HTTPRequest object that Quixote passes to
+every function it invokes.  ``func`` is a user-supplied function that
+receives the name of the XML-RPC method being called and a tuple
+containing the method's parameters.  If there's a bug in the function
+you supply and it raises an exception, the ``xmlrpc()`` function will
+catch the exception and return a ``Fault`` to the remote caller.
+
+Here's an example of implementing a simple XML-RPC handler with a
+single method, ``get_time()``, that simply returns the current
+time.  The first task is to expose a URL for accessing the service. ::
+
+    from quixote.util import xmlrpc
+
+    _q_exports = ['rpc']
+
+    def rpc (request):
+        return xmlrpc(request, rpc_process)
+
+    def rpc_process (meth, params):
+        ...
+
+When the above code is placed in the __init__.py file for the Python
+package corresponding to your Quixote application, it exposes the URL
+``http://<hostname>/rpc`` as the access point for the XML-RPC service.
+
+Next, we need to fill in the contents of the ``rpc_process()``
+function::
+
+    import time
+
+    def rpc_process (meth, params):
+	if meth == 'get_time':
+	    # params is ignored
+	    now = time.gmtime(time.time())
+	    return xmlrpclib.DateTime(now)
+	else:
+	    raise RuntimeError, "Unknown XML-RPC method: %r" % meth
+
+``rpc_process()`` receives the method name and the parameters, and its
+job is to run the right code for the method, returning a result that
+will be marshalled into XML-RPC.  The body of ``rpc_process()`` will
+therefore usually be an ``if`` statement that checks the name of the
+method, and calls another function to do the actual work.  In this case,
+``get_time()`` is very simple so the two lines of code it requires are
+simply included in the body of ``rpc_process()``.
+
+If the method name doesn't belong to a supported method, execution
+will fall through to the ``else`` clause, which will raise a
+RuntimeError exception.  Quixote's ``xmlrpc()`` will catch this
+exception and report it to the caller as an XML-RPC fault, with the
+error code set to 1.
+
+As you add additional XML-RPC services, the ``if`` statement in
+``rpc_process()`` will grow more branches.  You might be tempted to pass
+the method name to ``getattr()`` to select a method from a module or
+class.  That would work, too, and avoids having a continually growing
+set of branches, but you should be careful with this and be sure that
+there are no private methods that a remote caller could access.  I
+generally prefer to have the ``if... elif... elif... else`` blocks, for
+three reasons: 1) adding another branch isn't much work, 2) it's
+explicit about the supported method names, and 3) there won't be any
+security holes in doing so.
+
+An alternative approach is to have a dictionary mapping method names
+to the corresponding functions and restrict the legal method names 
+to the keys of this dictionary::
+
+    def echo (*params):
+	# Just returns the parameters it's passed
+	return params
+
+    def get_time ():
+	now = time.gmtime(time.time())
+	return xmlrpclib.DateTime(now)
+
+    methods = {'echo' : echo, 
+	       'get_time' : get_time}
+
+    def rpc_process (meth, params):
+	func = methods.get[meth]
+	if methods.has_key(meth):
+	    # params is ignored
+	    now = time.gmtime(time.time())
+	    return xmlrpclib.DateTime(now)
+	else:
+	    raise RuntimeError, "Unknown XML-RPC method: %r" % meth
+
+This approach works nicely when there are many methods and the
+``if...elif...else`` statement would be unworkably long.
+
+
+$Id: web-services.txt 21603 2003-05-09 19:17:04Z akuchlin $

Added: packages/quixote1/branches/upstream/current/doc/widgets.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/widgets.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/widgets.html (added)
+++ packages/quixote1/branches/upstream/current/doc/widgets.html Mon May  8 19:16:16 2006
@@ -1,0 +1,503 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Quixote Widget Classes</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="quixote-widget-classes">
+<h1 class="title">Quixote Widget Classes</h1>
+<p>[This is reference documentation.  If you haven't yet read &quot;Lesson 5:
+widgets&quot; of demo.txt, you should go and do so now.  This document also
+assumes you have a good understanding of HTML forms and form elements.
+If not, you could do worse than pick up a copy of <em>HTML: The Definitive
+Guide</em> by Chuck Musciano &amp; Bill Kennedy (O'Reilly).  I usually keep it
+within arm's reach.]</p>
+<p>Web forms are built out of form elements: string input, select lists,
+checkboxes, submit buttons, and so forth.  Quixote provides a family of
+classes for handling these form elements, or widgets, in the
+quixote.form.widget module.  The class hierarchy is:</p>
+<pre class="literal-block">
+Widget [A]
+|
++--StringWidget
+|  |
+|  +--PasswordWidget
+|  |
+|  +--NumberWidget [*] [A]
+|     |
+|     +-FloatWidget [*]
+|     +-IntWidget [*]
+|     
++--TextWidget
+| 
++--CheckboxWidget
+| 
++--SelectWidget [A]
+|  |
+|  +--SingleSelectWidget
+|  |  |
+|  |  +-RadiobuttonsWidget
+|  |  |
+|  |  +-OptionSelectWidget [*]
+|  |    
+|  +--MultipleSelectWidget
+|    
++--SubmitButtonWidget
+| 
++--HiddenWidget
+|
++--ListWidget [*]
+
+[*] Widget classes that do not correspond exactly with a particular
+    HTML form element
+[A] Abstract classes 
+</pre>
+<div class="section" id="widget-the-base-class">
+<h1><a name="widget-the-base-class">Widget: the base class</a></h1>
+<p>Widget is the abstract base class for the widget hierarchy.  It provides
+the following facilities:</p>
+<ul class="simple">
+<li>widget name (<tt class="literal"><span class="pre">name</span></tt> attribute, <tt class="literal"><span class="pre">set_name()</span></tt> method)</li>
+<li>widget value (<tt class="literal"><span class="pre">value</span></tt> attribute, <tt class="literal"><span class="pre">set_value()</span></tt> and <tt class="literal"><span class="pre">clear()</span></tt> methods)</li>
+<li><tt class="literal"><span class="pre">__str__()</span></tt> and <tt class="literal"><span class="pre">__repr__()</span></tt> methods</li>
+<li>some facilities for writing composite widget classes</li>
+</ul>
+<p>The Widget constructor signature is:</p>
+<pre class="literal-block">
+Widget(name : string, value : any = None)
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">name</span></tt></dt>
+<dd>the name of the widget.  For non-compound widgets (ie. everything in
+the above class hierarchy), this will be used as the &quot;name&quot;
+attribute for the main HTML tag that defines the form element.</dd>
+<dt><tt class="literal"><span class="pre">value</span></tt></dt>
+<dd>the current value of the form element.  The type of 'value' depends
+on the widget class.  Most widget classes support only a single
+type, eg. StringWidget always deals with strings and IntWidget with
+integers.  The SelectWidget classes are different; see the
+descriptions below for details.</dd>
+</dl>
+</div>
+<div class="section" id="common-widget-methods">
+<h1><a name="common-widget-methods">Common widget methods</a></h1>
+<p>The Widget base class also provides a couple of useful
+methods:</p>
+<dl>
+<dt><tt class="literal"><span class="pre">set_name(name:string)</span></tt></dt>
+<dd>use this to change the widget name supplied to the constructor.
+Unless you know what you're doing, you should do this before
+rendering or parsing the widget.</dd>
+<dt><tt class="literal"><span class="pre">set_value(value:any)</span></tt></dt>
+<dd>use this to set the widget value; this is the same as supplying
+a value to the constructor (and the same type rules apply, ie.
+the type of 'value' depends on the widget class).</dd>
+<dt><tt class="literal"><span class="pre">clear()</span></tt></dt>
+<dd>clear the widget's current value.  Equivalent to
+<tt class="literal"><span class="pre">widget.set_value(None)</span></tt>.</dd>
+</dl>
+<p>The following two methods will be used on every widget object you
+create; if you write your own widget classes, you will almost certainly
+have to define both of these:</p>
+<dl>
+<dt><tt class="literal"><span class="pre">render(request:HTTPRequest)</span></tt> <span class="classifier-delimiter">:</span> <span class="classifier"><tt class="literal"><span class="pre">string</span></tt></span></dt>
+<dd>return a chunk of HTML that implements the form element
+corresponding to this widget.</dd>
+<dt><tt class="literal"><span class="pre">parse(request:HTTPRequest)</span></tt> <span class="classifier-delimiter">:</span> <span class="classifier"><tt class="literal"><span class="pre">any</span></tt></span></dt>
+<dd>extract the form value for this widget from <tt class="literal"><span class="pre">request.form</span></tt>, parse it
+according to the rules for this widget class, and return the
+resulting value.  The return value depends on the widget class, and
+will be of the same type as the value passed to the constructor
+and/or <tt class="literal"><span class="pre">set_value()</span></tt>.</dd>
+</dl>
+</div>
+<div class="section" id="stringwidget">
+<h1><a name="stringwidget">StringWidget</a></h1>
+<p>Used for short, single-line string input with no validation (ie. any
+string will be accepted.)  Generates an <tt class="literal"><span class="pre">&lt;input</span> <span class="pre">type=&quot;text&quot;&gt;</span></tt> form
+element.</p>
+<div class="section" id="constructor">
+<h2><a name="constructor">Constructor</a></h2>
+<pre class="literal-block">
+StringWidget(name : string,
+             value : string = None,
+             size : int = None,
+             maxlength : int = None)
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">size</span></tt></dt>
+<dd>used as the <tt class="literal"><span class="pre">size</span></tt> attribute of the generated <tt class="literal"><span class="pre">&lt;input&gt;</span></tt> tag;
+controls the physical size of the input field.</dd>
+<dt><tt class="literal"><span class="pre">maxlength</span></tt></dt>
+<dd>used as the <tt class="literal"><span class="pre">maxlength</span></tt> attribute; controls the maximum amount
+of input.</dd>
+</dl>
+</div>
+<div class="section" id="examples">
+<h2><a name="examples">Examples</a></h2>
+<pre class="literal-block">
+&gt;&gt;&gt; StringWidget(&quot;foo&quot;, value=&quot;hello&quot;).render(request)
+'&lt;input name=&quot;foo&quot; type=&quot;text&quot; value=&quot;hello&quot;&gt;'
+
+&gt;&gt;&gt; StringWidget(&quot;foo&quot;, size=10, maxlength=20).render(request)
+'&lt;input name=&quot;foo&quot; type=&quot;text&quot; size=&quot;10&quot; maxlength=&quot;20&quot;&gt;'
+</pre>
+</div>
+</div>
+<div class="section" id="passwordwidget">
+<h1><a name="passwordwidget">PasswordWidget</a></h1>
+<p>PasswordWidget is identical to StringWidget except for the type of the
+HTML form element: <tt class="literal"><span class="pre">password</span></tt> instead of <tt class="literal"><span class="pre">text</span></tt>.</p>
+</div>
+<div class="section" id="textwidget">
+<h1><a name="textwidget">TextWidget</a></h1>
+<p>Used for multi-line text input.  The value is a single string with
+newlines right where the browser supplied them.  (<tt class="literal"><span class="pre">\r\n</span></tt>, if present,
+is converted to <tt class="literal"><span class="pre">\n</span></tt>.)  Generates a <tt class="literal"><span class="pre">&lt;textarea&gt;</span></tt> form element.</p>
+<div class="section" id="id1">
+<h2><a name="id1">Constructor</a></h2>
+<pre class="literal-block">
+TextWidget(name : string,
+           value : string = None,
+           cols : int = None,
+           rows : int = None,
+           wrap : string = &quot;physical&quot;)
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">cols</span></tt>, <tt class="literal"><span class="pre">rows</span></tt></dt>
+<dd>number of columns/rows in the textarea</dd>
+<dt><tt class="literal"><span class="pre">wrap</span></tt></dt>
+<dd>controls how the browser wraps text and includes newlines in the
+submitted form value; consult an HTML book for details.</dd>
+</dl>
+</div>
+</div>
+<div class="section" id="checkboxwidget">
+<h1><a name="checkboxwidget">CheckboxWidget</a></h1>
+<p>Used for single boolean (on/off) value.  The value you supply can be
+anything, since Python has a boolean interpretation for all values; the
+value returned by <tt class="literal"><span class="pre">parse()</span></tt> will always be 0 or 1 (but you shouldn't
+rely on that!).  Generates an <tt class="literal"><span class="pre">&lt;input</span> <span class="pre">type=&quot;checkbox&quot;&gt;</span></tt> form element.</p>
+<div class="section" id="id2">
+<h2><a name="id2">Constructor</a></h2>
+<pre class="literal-block">
+CheckboxWidget(name : string,
+               value : boolean = false)
+</pre>
+</div>
+<div class="section" id="id3">
+<h2><a name="id3">Examples</a></h2>
+<pre class="literal-block">
+&gt;&gt;&gt; CheckboxWidget(&quot;foo&quot;, value=0).render(request)
+'&lt;input name=&quot;foo&quot; type=&quot;checkbox&quot; value=&quot;yes&quot;&gt;'
+
+&gt;&gt;&gt; CheckboxWidget(&quot;foo&quot;, value=&quot;you bet&quot;).render(request)
+'&lt;input name=&quot;foo&quot; type=&quot;checkbox&quot; value=&quot;yes&quot; checked&gt;'
+</pre>
+</div>
+</div>
+<div class="section" id="radiobuttonswidget">
+<h1><a name="radiobuttonswidget">RadiobuttonsWidget</a></h1>
+<p>Used for a <em>set</em> of related radiobuttons, ie. several <tt class="literal"><span class="pre">&lt;input</span>
+<span class="pre">type=&quot;radio&quot;&gt;</span></tt> tags with the same name and different values.  The set
+of values are supplied to the constructor as <tt class="literal"><span class="pre">allowed_values</span></tt>, which
+may be a list of any Python objects (not just strings).  The current
+value must be either <tt class="literal"><span class="pre">None</span></tt> (the default) or one of the values in
+<tt class="literal"><span class="pre">allowed_values</span></tt>; if you supply a <tt class="literal"><span class="pre">value</span></tt> not in <tt class="literal"><span class="pre">allowed_values</span></tt>,
+it will be ignored.  <tt class="literal"><span class="pre">parse()</span></tt> will return either <tt class="literal"><span class="pre">None</span></tt> or one of
+the values in <tt class="literal"><span class="pre">allowed_values</span></tt>.</p>
+<div class="section" id="id4">
+<h2><a name="id4">Constructor</a></h2>
+<pre class="literal-block">
+RadiobuttonsWidget(name : string,
+                   value : any = None,
+                   allowed_values : [any] = None,
+                   descriptions : [string] = map(str, allowed_values),
+                   quote : boolean = true,
+                   delim : string = &quot;\n&quot;)
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">allowed_values</span></tt></dt>
+<dd><p class="first">specifies how many <tt class="literal"><span class="pre">&lt;input</span> <span class="pre">type=&quot;radio&quot;&gt;</span></tt> tags to generate and the
+values for each.  Eg. <tt class="literal"><span class="pre">allowed_values=[&quot;foo&quot;,</span> <span class="pre">&quot;bar&quot;]</span></tt> will result in
+(roughly):</p>
+<pre class="last literal-block">
+&lt;input type=&quot;radio&quot; value=&quot;foo&quot;&gt;
+&lt;input type=&quot;radio&quot; value=&quot;bar&quot;&gt;
+</pre>
+</dd>
+<dt><tt class="literal"><span class="pre">descriptions</span></tt></dt>
+<dd>the text that will actually be shown to the user in the web page
+that includes this widget.  Handy when the elements of
+<tt class="literal"><span class="pre">allowed_values</span></tt> are too terse, or don't have a meaningful
+<tt class="literal"><span class="pre">str()</span></tt>, or you want to add some additional cues for the user.  If
+not supplied, <tt class="literal"><span class="pre">map(str,</span> <span class="pre">allowed_values)</span></tt> is used, with the
+exception that <tt class="literal"><span class="pre">None</span></tt> in <tt class="literal"><span class="pre">allowed_values</span></tt> becomes <tt class="literal"><span class="pre">&quot;&quot;</span></tt> (the
+empty string) in <tt class="literal"><span class="pre">descriptions</span></tt>.  If supplied, <tt class="literal"><span class="pre">descriptions</span></tt>
+must be the same length as <tt class="literal"><span class="pre">allowed_values</span></tt>.</dd>
+<dt><tt class="literal"><span class="pre">quote</span></tt></dt>
+<dd>if true (the default), the elements of 'descriptions' will be
+HTML-quoted (using <tt class="literal"><span class="pre">quixote.html.html_quote()</span></tt>) when the widget is
+rendered.  This is essential if you might have characters like
+<tt class="literal"><span class="pre">&amp;</span></tt> or <tt class="literal"><span class="pre">&lt;</span></tt> in your descriptions.  However, you'll want to set
+<tt class="literal"><span class="pre">quote</span></tt> to false if you are deliberately including HTML markup
+in your descriptions.</dd>
+<dt><tt class="literal"><span class="pre">delim</span></tt></dt>
+<dd>the delimiter to separate the radiobuttons with when rendering
+the whole widget.  The default ensures that your HTML is readable
+(by putting each <tt class="literal"><span class="pre">&lt;input&gt;</span></tt> tag on a separate line), and that there
+is horizontal whitespace between each radiobutton.</dd>
+</dl>
+</div>
+<div class="section" id="id5">
+<h2><a name="id5">Examples</a></h2>
+<pre class="literal-block">
+&gt;&gt;&gt; colours = [&quot;red&quot;, &quot;green&quot;, &quot;blue&quot;, &quot;pink&quot;]
+&gt;&gt;&gt; widget = RadiobuttonsWidget(&quot;foo&quot;, allowed_values=colours)
+&gt;&gt;&gt; print widget.render(request)
+&lt;input name=&quot;foo&quot; type=&quot;radio&quot; value=&quot;0&quot;&gt;red&lt;/input&gt;
+&lt;input name=&quot;foo&quot; type=&quot;radio&quot; value=&quot;1&quot;&gt;green&lt;/input&gt;
+&lt;input name=&quot;foo&quot; type=&quot;radio&quot; value=&quot;2&quot;&gt;blue&lt;/input&gt;
+&lt;input name=&quot;foo&quot; type=&quot;radio&quot; value=&quot;3&quot;&gt;pink&lt;/input&gt;
+</pre>
+<p>(Note that the actual form values, ie. what the browser returns to the
+server, are always stringified indices into the 'allowed_values' list.
+This is irrelevant to you, since SingleSelectWidget takes care of
+converting <tt class="literal"><span class="pre">&quot;1&quot;</span></tt> to <tt class="literal"><span class="pre">1</span></tt> and looking up <tt class="literal"><span class="pre">allowed_values[1]</span></tt>.)</p>
+<pre class="literal-block">
+&gt;&gt;&gt; values = [val1, val2, val3]
+&gt;&gt;&gt; descs = [&quot;thing &lt;b&gt;1&lt;/b&gt;&quot;,
+             &quot;thing &lt;b&gt;2&lt;/b&gt;&quot;,
+             &quot;thing &lt;b&gt;3&lt;/b&gt;&quot;]
+&gt;&gt;&gt; widget = RadiobuttonsWidget(&quot;bar&quot;,
+                 allowed_values=values,
+                 descriptions=descs,
+                 value=val3,
+                 delim=&quot;&lt;br&gt;\n&quot;,
+                 quote=0)
+&gt;&gt;&gt; print widget.render(request)
+&lt;input name=&quot;bar&quot; type=&quot;radio&quot; value=&quot;0&quot;&gt;thing &lt;b&gt;1&lt;/b&gt;&lt;/input&gt;&lt;br&gt;
+&lt;input name=&quot;bar&quot; type=&quot;radio&quot; value=&quot;1&quot;&gt;thing &lt;b&gt;2&lt;/b&gt;&lt;/input&gt;&lt;br&gt;
+&lt;input name=&quot;bar&quot; type=&quot;radio&quot; value=&quot;2&quot; checked=&quot;checked&quot;&gt;thing &lt;b&gt;3&lt;/b&gt;&lt;/input&gt;
+</pre>
+</div>
+</div>
+<div class="section" id="singleselectwidget">
+<h1><a name="singleselectwidget">SingleSelectWidget</a></h1>
+<p>Used to select a single value from a list that's too long or ungainly
+for a set of radiobuttons.  (Most browsers implement this as a scrolling
+list; UNIX versions of Netscape 4.x and earlier used a pop-up menu.)
+The value can be any Python object; <tt class="literal"><span class="pre">parse()</span></tt> will return either
+<tt class="literal"><span class="pre">None</span></tt> or one of the values you supply to the constructor as
+<tt class="literal"><span class="pre">allowed_values</span></tt>.  Generates a <tt class="literal"><span class="pre">&lt;select&gt;...&lt;/select&gt;</span></tt> tag, with one
+<tt class="literal"><span class="pre">&lt;option&gt;</span></tt> tag for each element of <tt class="literal"><span class="pre">allowed_values</span></tt>.</p>
+<div class="section" id="id6">
+<h2><a name="id6">Constructor</a></h2>
+<pre class="literal-block">
+SingleSelectWidget(name : string,
+                   value : any = None,
+                   allowed_values : [any] = None,
+                   descriptions : [string] = map(str, allowed_values),
+                   quote : boolean = true,
+                   size : int = None)
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">allowed_values</span></tt></dt>
+<dd>determines the set of <tt class="literal"><span class="pre">&lt;option&gt;</span></tt> tags that will go inside the
+<tt class="literal"><span class="pre">&lt;select&gt;</span></tt> tag; these can be any Python values (not just strings).
+<tt class="literal"><span class="pre">parse()</span></tt> will return either one of the <tt class="literal"><span class="pre">allowed_values</span></tt> or <tt class="literal"><span class="pre">None</span></tt>.
+If you supply a <tt class="literal"><span class="pre">value</span></tt> that is not in <tt class="literal"><span class="pre">allowed_values</span></tt>, it
+will be ignored.</dd>
+<dt><tt class="literal"><span class="pre">descriptions</span></tt></dt>
+<dd>(same as RadiobuttonsWidget above)</dd>
+<dt><tt class="literal"><span class="pre">quote</span></tt></dt>
+<dd>(same as RadiobuttonsWidget above)</dd>
+<dt><tt class="literal"><span class="pre">size</span></tt></dt>
+<dd>corresponds to the <tt class="literal"><span class="pre">size</span></tt> attribute of the <tt class="literal"><span class="pre">&lt;select&gt;</span></tt> tag: ask
+the browser to show a select list with <tt class="literal"><span class="pre">size</span></tt> items visible.
+Not always respected by the browser; consult an HTML book.</dd>
+</dl>
+</div>
+<div class="section" id="id7">
+<h2><a name="id7">Examples</a></h2>
+<pre class="literal-block">
+&gt;&gt;&gt; widget = SingleSelectWidget(&quot;foo&quot;,
+                                allowed_values=[&quot;abc&quot;, 123, 5.5])
+&gt;&gt;&gt; print widget.render(request)
+&lt;select name=&quot;foo&quot;&gt;
+&lt;option value=&quot;0&quot;&gt;abc
+&lt;option value=&quot;1&quot;&gt;123
+&lt;option value=&quot;2&quot;&gt;5.5
+&lt;/select&gt;
+
+&gt;&gt;&gt; widget = SingleSelectWidget(&quot;bar&quot;,
+                                value=val2,  
+                                allowed_values=[val1, val2, val3],
+                                descriptions=[&quot;foo&quot;, &quot;bar&quot;, &quot;foo &amp; bar&quot;],
+                                size=3)
+&gt;&gt;&gt; print widget.render(request)
+&lt;select name=&quot;bar&quot; size=&quot;3&quot;&gt;
+&lt;option value=&quot;0&quot;&gt;foo
+&lt;option selected value=&quot;1&quot;&gt;bar
+&lt;option value=&quot;2&quot;&gt;foo &amp;amp; bar
+&lt;/select&gt;
+</pre>
+</div>
+</div>
+<div class="section" id="multipleselectwidget">
+<h1><a name="multipleselectwidget">MultipleSelectWidget</a></h1>
+<p>Used to select multiple values from a list.  Everything is just like
+SingleSelectWidget, except that <tt class="literal"><span class="pre">value</span></tt> can be a list of objects
+selected from <tt class="literal"><span class="pre">allowed_values</span></tt> (in which case every object in <tt class="literal"><span class="pre">value</span></tt>
+will initially be selected).  Generates a <tt class="literal"><span class="pre">&lt;select</span> <span class="pre">multiple&gt;...&lt;/select&gt;</span></tt>
+tag, with one <tt class="literal"><span class="pre">&lt;option&gt;</span></tt> tag for each element of <tt class="literal"><span class="pre">allowed_values</span></tt>.</p>
+<div class="section" id="id8">
+<h2><a name="id8">Constructor</a></h2>
+<pre class="literal-block">
+MultipleSelectWidget(name : string,
+                     value : any | [any] = None,
+                     allowed_values : [any] = None,
+                     descriptions : [string] = map(str, allowed_values),
+                     quote : boolean = true,
+                     size : int = None)
+</pre>
+</div>
+</div>
+<div class="section" id="submitbuttonwidget">
+<h1><a name="submitbuttonwidget">SubmitButtonWidget</a></h1>
+<p>Used for generating submit buttons.  Note that HTML submit buttons are
+rather weird, and Quixote preserves this weirdness -- the Widget classes
+are meant to be a fairly thin wrapper around HTML form elements, after
+all.</p>
+<p>In particular, the widget value for a submit button controls two things:
+what the user sees in their browser (the text in the button) and what
+the browser returns as the value for that form element.  You can't
+control the two separately, as you can with radiobuttons or selection
+widgets.</p>
+<p>Also, SubmitButtonWidget is the only widget with an optional <tt class="literal"><span class="pre">name</span></tt>.
+In many simple forms, all you care about is the fact that the form was
+submitted -- which submit button the user used doesn't matter.</p>
+<div class="section" id="id9">
+<h2><a name="id9">Constructor</a></h2>
+<pre class="literal-block">
+SubmitButtonWidget(name : string = None,
+                   value : string = None)
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">value</span></tt></dt>
+<dd>the text that will be shown in the user's browser, <em>and</em> the
+value that will be returned for this form element (widget)
+if the user selects this submit button.</dd>
+</dl>
+</div>
+<div class="section" id="id10">
+<h2><a name="id10">Examples</a></h2>
+<blockquote>
+<pre class="doctest-block">
+&gt;&gt;&gt; SubmitButtonWidget(value=&quot;Submit Form&quot;).render(request)
+'&lt;input type=&quot;submit&quot; value=&quot;Submit Form&quot;&gt;'
+</pre>
+</blockquote>
+</div>
+</div>
+<div class="section" id="hiddenwidget">
+<h1><a name="hiddenwidget">HiddenWidget</a></h1>
+<p>Used to generate HTML hidden widgets, which can be useful for carrying
+around non-sensitive application state.  (The Quixote form framework
+uses hidden widgets for form tokens as a measure against cross-site
+request forgery [CSRF] attacks.  So by &quot;sensitive&quot; I mean &quot;information
+which should not be revealed&quot;, rather than &quot;security-related&quot;.  If you
+wouldn't put it in a cookie or in email, don't put it in a hidden form
+element.)</p>
+<div class="section" id="id11">
+<h2><a name="id11">Constructor</a></h2>
+<pre class="literal-block">
+HiddenWidget(name : string,
+             value : string)
+</pre>
+</div>
+<div class="section" id="id12">
+<h2><a name="id12">Examples</a></h2>
+<pre class="literal-block">
+&gt;&gt;&gt; HiddenWidget(&quot;form_id&quot;, &quot;2452345135&quot;).render(request)
+'&lt;input type=&quot;hidden&quot; name=&quot;form_id&quot; value=&quot;2452345135&quot;&gt;'
+</pre>
+</div>
+</div>
+<div class="section" id="intwidget">
+<h1><a name="intwidget">IntWidget</a></h1>
+<p>The first derived widget class: this is a subclass of StringWidget
+specifically for entering integer values.  As such, this is the first
+widget class we've covered that can reject certain user input.  (The
+selection widgets all have to validate their input in case of broken or
+malicious clients, but they just drop bogus values.)  If the user enters
+a string that Python's built-in <tt class="literal"><span class="pre">int()</span></tt> can't convert to an integer,
+IntWidget's <tt class="literal"><span class="pre">parse()</span></tt> method raises FormValueError (also defined in
+the quixote.form.widget module).  This exception is handled by Quixote's
+form framework, but if you're using widget objects on their own, you'll
+have to handle it yourself.</p>
+<p><tt class="literal"><span class="pre">IntWidget.parse()</span></tt> always returns an integer or <tt class="literal"><span class="pre">None</span></tt>.</p>
+<div class="section" id="id13">
+<h2><a name="id13">Constructor</a></h2>
+<pre class="literal-block">
+IntWidget(name : string,
+          value : int = None,
+          size : int = None,
+          maxlength : int = None)
+</pre>
+<p>Constructor arguments are as for StringWidget, except that <tt class="literal"><span class="pre">value</span></tt>
+must be an integer (or <tt class="literal"><span class="pre">None</span></tt>).  Note that <tt class="literal"><span class="pre">size</span></tt> and
+<tt class="literal"><span class="pre">maxlength</span></tt> have exactly the same meaning: they control the size of
+the input widget and the maximum number of characters of input.</p>
+<p>[Examples]</p>
+<blockquote>
+<pre class="doctest-block">
+&gt;&gt;&gt; IntWidget(&quot;num&quot;, value=37, size=5).render(request)
+'&lt;input type=&quot;string&quot; name=&quot;num&quot; value=&quot;37&quot; size=&quot;5&quot;&gt;'
+</pre>
+</blockquote>
+</div>
+</div>
+<div class="section" id="floatwidget">
+<h1><a name="floatwidget">FloatWidget</a></h1>
+<p>FloatWidget is identical to IntWidget, except:</p>
+<ul class="simple">
+<li><tt class="literal"><span class="pre">value</span></tt> must be a float</li>
+<li><tt class="literal"><span class="pre">parse()</span></tt> returns a float or <tt class="literal"><span class="pre">None</span></tt></li>
+<li><tt class="literal"><span class="pre">parse()</span></tt> raises FormValueError if the string entered by the
+user cannot be converted by Python's built-in <tt class="literal"><span class="pre">float()</span></tt> function</li>
+</ul>
+</div>
+<div class="section" id="optionselectwidget">
+<h1><a name="optionselectwidget">OptionSelectWidget</a></h1>
+<p>OptionSelectWidget is simply a SingleSelectWidget that uses a bit of
+Javascript to automatically submit the current form as soon as the user
+selects a value.  This is useful for very simple one-element forms where
+you don't want to bother with a submit button, or for very complex forms
+where you need to revamp the user interface based on a user's selection.
+Your form-processing code could then detect that style of form
+submission, and regenerate a slightly different form for the user.  (Or
+you could treat it as a full-blown form submission, if the only widget
+of interest is the OptionSelectWidget.)</p>
+<p>For example, if you're asking a user for their address, some of the
+details will vary depending on which country they're in.  You might make
+the country widget an OptionSelectWidget: if the user selects &quot;Canada&quot;,
+you'll ask them for a province and a postal code; if they select &quot;United
+States&quot;, you ask for a state and a zip code; and so forth.  (I don't
+really recommend a user interface that works this way: you'll spend way
+too much time getting the details right [&quot;How many states does Australia
+have again?&quot;], and you're bound to get something wrong -- there are over
+200 countries in the world, after all.)</p>
+<p>Be warned that since OptionSelectWidget relies on Javascript to work,
+using it makes immediately makes your application less portable and more
+fragile.  One thing to avoid: form elements with a name of <tt class="literal"><span class="pre">submit</span></tt>,
+since that masks the Javascript function called by OptionSelectWidget.</p>
+<p>$Id: widgets.txt 20217 2003-01-16 20:51:53Z akuchlin $</p>
+</div>
+</div>
+</body>
+</html>

Added: packages/quixote1/branches/upstream/current/doc/widgets.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/widgets.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/widgets.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/widgets.txt Mon May  8 19:16:16 2006
@@ -1,0 +1,524 @@
+Quixote Widget Classes
+======================
+
+[This is reference documentation.  If you haven't yet read "Lesson 5:
+widgets" of demo.txt, you should go and do so now.  This document also
+assumes you have a good understanding of HTML forms and form elements.
+If not, you could do worse than pick up a copy of *HTML: The Definitive
+Guide* by Chuck Musciano & Bill Kennedy (O'Reilly).  I usually keep it
+within arm's reach.]
+
+Web forms are built out of form elements: string input, select lists,
+checkboxes, submit buttons, and so forth.  Quixote provides a family of
+classes for handling these form elements, or widgets, in the
+quixote.form.widget module.  The class hierarchy is::
+
+    Widget [A]
+    |
+    +--StringWidget
+    |  |
+    |  +--PasswordWidget
+    |  |
+    |  +--NumberWidget [*] [A]
+    |     |
+    |     +-FloatWidget [*]
+    |     +-IntWidget [*]
+    |     
+    +--TextWidget
+    | 
+    +--CheckboxWidget
+    | 
+    +--SelectWidget [A]
+    |  |
+    |  +--SingleSelectWidget
+    |  |  |
+    |  |  +-RadiobuttonsWidget
+    |  |  |
+    |  |  +-OptionSelectWidget [*]
+    |  |    
+    |  +--MultipleSelectWidget
+    |    
+    +--SubmitButtonWidget
+    | 
+    +--HiddenWidget
+    |
+    +--ListWidget [*]
+
+    [*] Widget classes that do not correspond exactly with a particular
+        HTML form element
+    [A] Abstract classes 
+
+
+Widget: the base class
+----------------------
+
+Widget is the abstract base class for the widget hierarchy.  It provides
+the following facilities:
+
+* widget name (``name`` attribute, ``set_name()`` method)
+* widget value (``value`` attribute, ``set_value()`` and ``clear()`` methods)
+* ``__str__()`` and ``__repr__()`` methods
+* some facilities for writing composite widget classes
+
+The Widget constructor signature is::
+
+    Widget(name : string, value : any = None)
+  
+``name``
+  the name of the widget.  For non-compound widgets (ie. everything in
+  the above class hierarchy), this will be used as the "name"
+  attribute for the main HTML tag that defines the form element.
+
+``value``
+  the current value of the form element.  The type of 'value' depends
+  on the widget class.  Most widget classes support only a single
+  type, eg. StringWidget always deals with strings and IntWidget with
+  integers.  The SelectWidget classes are different; see the
+  descriptions below for details.
+
+
+Common widget methods
+---------------------
+
+The Widget base class also provides a couple of useful
+methods:
+
+``set_name(name:string)``
+  use this to change the widget name supplied to the constructor.
+  Unless you know what you're doing, you should do this before
+  rendering or parsing the widget.
+
+``set_value(value:any)``
+  use this to set the widget value; this is the same as supplying
+  a value to the constructor (and the same type rules apply, ie.
+  the type of 'value' depends on the widget class).
+
+``clear()``
+  clear the widget's current value.  Equivalent to
+  ``widget.set_value(None)``.
+
+The following two methods will be used on every widget object you
+create; if you write your own widget classes, you will almost certainly
+have to define both of these:
+
+``render(request:HTTPRequest)`` : ``string``
+  return a chunk of HTML that implements the form element
+  corresponding to this widget.
+
+``parse(request:HTTPRequest)`` : ``any``
+  extract the form value for this widget from ``request.form``, parse it
+  according to the rules for this widget class, and return the
+  resulting value.  The return value depends on the widget class, and
+  will be of the same type as the value passed to the constructor
+  and/or ``set_value()``.
+
+
+StringWidget
+------------
+
+Used for short, single-line string input with no validation (ie. any
+string will be accepted.)  Generates an ``<input type="text">`` form
+element.
+
+Constructor
+~~~~~~~~~~~
+::
+
+    StringWidget(name : string,
+                 value : string = None,
+                 size : int = None,
+                 maxlength : int = None)
+
+``size``
+  used as the ``size`` attribute of the generated ``<input>`` tag;
+  controls the physical size of the input field.
+
+``maxlength``
+  used as the ``maxlength`` attribute; controls the maximum amount
+  of input.
+
+Examples
+~~~~~~~~
+::
+
+    >>> StringWidget("foo", value="hello").render(request)
+    '<input name="foo" type="text" value="hello">'
+
+    >>> StringWidget("foo", size=10, maxlength=20).render(request)
+    '<input name="foo" type="text" size="10" maxlength="20">'
+
+
+PasswordWidget
+--------------
+
+PasswordWidget is identical to StringWidget except for the type of the
+HTML form element: ``password`` instead of ``text``.
+
+
+TextWidget
+----------
+
+Used for multi-line text input.  The value is a single string with
+newlines right where the browser supplied them.  (``\r\n``, if present,
+is converted to ``\n``.)  Generates a ``<textarea>`` form element.
+
+Constructor
+~~~~~~~~~~~
+::
+
+    TextWidget(name : string,
+               value : string = None,
+               cols : int = None,
+               rows : int = None,
+               wrap : string = "physical")
+
+``cols``, ``rows``
+  number of columns/rows in the textarea
+``wrap``
+  controls how the browser wraps text and includes newlines in the
+  submitted form value; consult an HTML book for details.
+
+
+CheckboxWidget
+--------------
+
+Used for single boolean (on/off) value.  The value you supply can be
+anything, since Python has a boolean interpretation for all values; the
+value returned by ``parse()`` will always be 0 or 1 (but you shouldn't
+rely on that!).  Generates an ``<input type="checkbox">`` form element.
+
+Constructor
+~~~~~~~~~~~
+::
+
+    CheckboxWidget(name : string,
+                   value : boolean = false)
+
+Examples
+~~~~~~~~
+::
+
+    >>> CheckboxWidget("foo", value=0).render(request)
+    '<input name="foo" type="checkbox" value="yes">'
+
+    >>> CheckboxWidget("foo", value="you bet").render(request)
+    '<input name="foo" type="checkbox" value="yes" checked>'
+
+
+RadiobuttonsWidget
+------------------
+
+Used for a *set* of related radiobuttons, ie. several ``<input
+type="radio">`` tags with the same name and different values.  The set
+of values are supplied to the constructor as ``allowed_values``, which
+may be a list of any Python objects (not just strings).  The current
+value must be either ``None`` (the default) or one of the values in
+``allowed_values``; if you supply a ``value`` not in ``allowed_values``,
+it will be ignored.  ``parse()`` will return either ``None`` or one of
+the values in ``allowed_values``.
+
+Constructor
+~~~~~~~~~~~
+::
+
+    RadiobuttonsWidget(name : string,
+                       value : any = None,
+                       allowed_values : [any] = None,
+                       descriptions : [string] = map(str, allowed_values),
+                       quote : boolean = true,
+                       delim : string = "\n")
+
+``allowed_values``
+  specifies how many ``<input type="radio">`` tags to generate and the
+  values for each.  Eg. ``allowed_values=["foo", "bar"]`` will result in
+  (roughly)::
+
+    <input type="radio" value="foo">
+    <input type="radio" value="bar">
+
+``descriptions``
+  the text that will actually be shown to the user in the web page
+  that includes this widget.  Handy when the elements of
+  ``allowed_values`` are too terse, or don't have a meaningful
+  ``str()``, or you want to add some additional cues for the user.  If
+  not supplied, ``map(str, allowed_values)`` is used, with the
+  exception that ``None`` in ``allowed_values`` becomes ``""`` (the
+  empty string) in ``descriptions``.  If supplied, ``descriptions``
+  must be the same length as ``allowed_values``.
+
+``quote``
+  if true (the default), the elements of 'descriptions' will be
+  HTML-quoted (using ``quixote.html.html_quote()``) when the widget is
+  rendered.  This is essential if you might have characters like
+  ``&`` or ``<`` in your descriptions.  However, you'll want to set
+  ``quote`` to false if you are deliberately including HTML markup
+  in your descriptions.
+
+``delim``
+  the delimiter to separate the radiobuttons with when rendering
+  the whole widget.  The default ensures that your HTML is readable
+  (by putting each ``<input>`` tag on a separate line), and that there
+  is horizontal whitespace between each radiobutton.
+
+Examples
+~~~~~~~~
+::
+
+    >>> colours = ["red", "green", "blue", "pink"]
+    >>> widget = RadiobuttonsWidget("foo", allowed_values=colours)
+    >>> print widget.render(request)
+    <input name="foo" type="radio" value="0">red</input>
+    <input name="foo" type="radio" value="1">green</input>
+    <input name="foo" type="radio" value="2">blue</input>
+    <input name="foo" type="radio" value="3">pink</input>
+  
+(Note that the actual form values, ie. what the browser returns to the
+server, are always stringified indices into the 'allowed_values' list.
+This is irrelevant to you, since SingleSelectWidget takes care of
+converting ``"1"`` to ``1`` and looking up ``allowed_values[1]``.)
+
+::
+
+    >>> values = [val1, val2, val3]
+    >>> descs = ["thing <b>1</b>",
+                 "thing <b>2</b>",
+                 "thing <b>3</b>"]
+    >>> widget = RadiobuttonsWidget("bar",
+                     allowed_values=values,
+                     descriptions=descs,
+                     value=val3,
+                     delim="<br>\n",
+                     quote=0)
+    >>> print widget.render(request)
+    <input name="bar" type="radio" value="0">thing <b>1</b></input><br>
+    <input name="bar" type="radio" value="1">thing <b>2</b></input><br>
+    <input name="bar" type="radio" value="2" checked="checked">thing <b>3</b></input>
+
+
+SingleSelectWidget
+------------------
+
+Used to select a single value from a list that's too long or ungainly
+for a set of radiobuttons.  (Most browsers implement this as a scrolling
+list; UNIX versions of Netscape 4.x and earlier used a pop-up menu.)
+The value can be any Python object; ``parse()`` will return either
+``None`` or one of the values you supply to the constructor as
+``allowed_values``.  Generates a ``<select>...</select>`` tag, with one
+``<option>`` tag for each element of ``allowed_values``.
+
+Constructor
+~~~~~~~~~~~
+::
+
+    SingleSelectWidget(name : string,
+                       value : any = None,
+                       allowed_values : [any] = None,
+                       descriptions : [string] = map(str, allowed_values),
+                       quote : boolean = true,
+                       size : int = None)
+
+``allowed_values``
+  determines the set of ``<option>`` tags that will go inside the
+  ``<select>`` tag; these can be any Python values (not just strings).
+  ``parse()`` will return either one of the ``allowed_values`` or ``None``.
+  If you supply a ``value`` that is not in ``allowed_values``, it
+  will be ignored.
+
+``descriptions``
+  (same as RadiobuttonsWidget above)
+
+``quote``
+  (same as RadiobuttonsWidget above)
+
+``size``
+  corresponds to the ``size`` attribute of the ``<select>`` tag: ask
+  the browser to show a select list with ``size`` items visible.
+  Not always respected by the browser; consult an HTML book.
+
+Examples
+~~~~~~~~
+::
+
+    >>> widget = SingleSelectWidget("foo",
+                                    allowed_values=["abc", 123, 5.5])
+    >>> print widget.render(request)
+    <select name="foo">
+    <option value="0">abc
+    <option value="1">123
+    <option value="2">5.5
+    </select>
+
+    >>> widget = SingleSelectWidget("bar",
+                                    value=val2,  
+                                    allowed_values=[val1, val2, val3],
+                                    descriptions=["foo", "bar", "foo & bar"],
+                                    size=3)
+    >>> print widget.render(request)
+    <select name="bar" size="3">
+    <option value="0">foo
+    <option selected value="1">bar
+    <option value="2">foo &amp; bar
+    </select>
+
+
+MultipleSelectWidget
+--------------------
+
+Used to select multiple values from a list.  Everything is just like
+SingleSelectWidget, except that ``value`` can be a list of objects
+selected from ``allowed_values`` (in which case every object in ``value``
+will initially be selected).  Generates a ``<select multiple>...</select>``
+tag, with one ``<option>`` tag for each element of ``allowed_values``.
+
+Constructor
+~~~~~~~~~~~
+::
+
+    MultipleSelectWidget(name : string,
+                         value : any | [any] = None,
+                         allowed_values : [any] = None,
+                         descriptions : [string] = map(str, allowed_values),
+                         quote : boolean = true,
+                         size : int = None)
+
+
+SubmitButtonWidget
+------------------
+
+Used for generating submit buttons.  Note that HTML submit buttons are
+rather weird, and Quixote preserves this weirdness -- the Widget classes
+are meant to be a fairly thin wrapper around HTML form elements, after
+all.
+
+In particular, the widget value for a submit button controls two things:
+what the user sees in their browser (the text in the button) and what
+the browser returns as the value for that form element.  You can't
+control the two separately, as you can with radiobuttons or selection
+widgets.
+
+Also, SubmitButtonWidget is the only widget with an optional ``name``.
+In many simple forms, all you care about is the fact that the form was
+submitted -- which submit button the user used doesn't matter.
+
+Constructor
+~~~~~~~~~~~
+::
+
+    SubmitButtonWidget(name : string = None,
+                       value : string = None)
+
+``value``
+  the text that will be shown in the user's browser, *and* the
+  value that will be returned for this form element (widget)
+  if the user selects this submit button.
+
+Examples
+~~~~~~~~
+
+    >>> SubmitButtonWidget(value="Submit Form").render(request)
+    '<input type="submit" value="Submit Form">'
+
+
+HiddenWidget
+------------
+
+Used to generate HTML hidden widgets, which can be useful for carrying
+around non-sensitive application state.  (The Quixote form framework
+uses hidden widgets for form tokens as a measure against cross-site
+request forgery [CSRF] attacks.  So by "sensitive" I mean "information
+which should not be revealed", rather than "security-related".  If you
+wouldn't put it in a cookie or in email, don't put it in a hidden form
+element.)
+
+Constructor
+~~~~~~~~~~~
+::
+
+    HiddenWidget(name : string,
+                 value : string)
+
+Examples
+~~~~~~~~
+::
+
+    >>> HiddenWidget("form_id", "2452345135").render(request)
+    '<input type="hidden" name="form_id" value="2452345135">'
+
+
+IntWidget
+---------
+
+The first derived widget class: this is a subclass of StringWidget
+specifically for entering integer values.  As such, this is the first
+widget class we've covered that can reject certain user input.  (The
+selection widgets all have to validate their input in case of broken or
+malicious clients, but they just drop bogus values.)  If the user enters
+a string that Python's built-in ``int()`` can't convert to an integer,
+IntWidget's ``parse()`` method raises FormValueError (also defined in
+the quixote.form.widget module).  This exception is handled by Quixote's
+form framework, but if you're using widget objects on their own, you'll
+have to handle it yourself.
+
+``IntWidget.parse()`` always returns an integer or ``None``.
+
+Constructor
+~~~~~~~~~~~
+::
+
+    IntWidget(name : string,
+              value : int = None,
+              size : int = None,
+              maxlength : int = None)
+
+Constructor arguments are as for StringWidget, except that ``value``
+must be an integer (or ``None``).  Note that ``size`` and
+``maxlength`` have exactly the same meaning: they control the size of
+the input widget and the maximum number of characters of input.
+
+[Examples]
+
+    >>> IntWidget("num", value=37, size=5).render(request)
+    '<input type="string" name="num" value="37" size="5">'
+
+
+FloatWidget
+-----------
+
+FloatWidget is identical to IntWidget, except:
+
+* ``value`` must be a float
+* ``parse()`` returns a float or ``None``
+* ``parse()`` raises FormValueError if the string entered by the
+  user cannot be converted by Python's built-in ``float()`` function
+
+
+OptionSelectWidget
+------------------
+
+OptionSelectWidget is simply a SingleSelectWidget that uses a bit of
+Javascript to automatically submit the current form as soon as the user
+selects a value.  This is useful for very simple one-element forms where
+you don't want to bother with a submit button, or for very complex forms
+where you need to revamp the user interface based on a user's selection.
+Your form-processing code could then detect that style of form
+submission, and regenerate a slightly different form for the user.  (Or
+you could treat it as a full-blown form submission, if the only widget
+of interest is the OptionSelectWidget.)
+
+For example, if you're asking a user for their address, some of the
+details will vary depending on which country they're in.  You might make
+the country widget an OptionSelectWidget: if the user selects "Canada",
+you'll ask them for a province and a postal code; if they select "United
+States", you ask for a state and a zip code; and so forth.  (I don't
+really recommend a user interface that works this way: you'll spend way
+too much time getting the details right ["How many states does Australia
+have again?"], and you're bound to get something wrong -- there are over
+200 countries in the world, after all.)
+
+Be warned that since OptionSelectWidget relies on Javascript to work,
+using it makes immediately makes your application less portable and more
+fragile.  One thing to avoid: form elements with a name of ``submit``,
+since that masks the Javascript function called by OptionSelectWidget.
+
+
+$Id: widgets.txt 20217 2003-01-16 20:51:53Z akuchlin $

Added: packages/quixote1/branches/upstream/current/errors.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/errors.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/errors.py (added)
+++ packages/quixote1/branches/upstream/current/errors.py Mon May  8 19:16:16 2006
@@ -1,0 +1,170 @@
+"""quixote.errors
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/errors.py $
+$Id: errors.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Exception classes used by Quixote
+"""
+from quixote.html import htmltext, htmlescape
+
+__revision__ = "$Id: errors.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+
+class PublishError(Exception):
+    """PublishError exceptions are raised due to some problem with the
+    data provided by the client and are raised during the publishing
+    process.  Quixote will abort the current request and return an error
+    page to the client.
+
+    public_msg should be a user-readable message that reveals no
+    inner workings of your application; it will always be shown.
+
+    private_msg will only be shown if the config option SECURE_ERRORS is
+    false; Quixote uses this to give you more detail about why the error
+    occurred.  You might want to use it for similar, application-specific
+    information.  (SECURE_ERRORS should always be true in a production
+    environment, since these details about the inner workings of your
+    application could conceivably be useful to attackers.)
+
+    The formatting done by the Quixote versions of these exceptions is
+    very simple.  Applications will probably wish to raise application
+    specific subclasses which do more sophisticated formatting or provide
+    a _q_except handler to format the exception.
+
+    """
+
+    status_code = 400 # bad request
+    title = "Publishing error"
+    description = "no description"
+
+    def __init__(self, public_msg=None, private_msg=None):
+        self.public_msg = public_msg
+        self.private_msg = private_msg # cleared if SECURE_ERRORS is true
+
+    def __str__(self):
+        return self.private_msg or self.public_msg or "???"
+
+    def format(self, request):
+        msg = htmlescape(self.title)
+        if not isinstance(self.title, htmltext):
+            msg = str(msg) # for backwards compatibility
+        if self.public_msg:
+            msg = msg + ": " + self.public_msg
+        if self.private_msg:
+            msg = msg + ": " + self.private_msg
+        return msg
+
+
+class TraversalError(PublishError):
+    """
+    Raised when a client attempts to access a resource that does not
+    exist or is otherwise unavailable to them (eg. a Python function
+    not listed in its module's _q_exports list).
+
+    path should be the path to the requested resource; if not
+    supplied, the current request object will be fetched and its
+    get_path() method called.
+    """
+
+    status_code = 404 # not found
+    title = "Page not found"
+    description = ("The requested link does not exist on this site.  If "
+                   "you arrived here by following a link from an external "
+                   "page, please inform that page's maintainer.")
+
+    def __init__(self, public_msg=None, private_msg=None, path=None):
+        PublishError.__init__(self, public_msg, private_msg)
+        if path is None:
+            import quixote
+            path = quixote.get_request().get_path()
+        self.path = path
+
+    def format(self, request):
+        msg = htmlescape(self.title) + ": " + self.path
+        if not isinstance(self.title, htmltext):
+            msg = str(msg) # for backwards compatibility
+        if self.public_msg:
+            msg = msg + ": " + self.public_msg
+        if self.private_msg:
+            msg = msg + ": " + self.private_msg
+        return msg
+
+class RequestError(PublishError):
+    """
+    Raised when Quixote is unable to parse an HTTP request (or its CGI
+    representation).  This is a lower-level error than QueryError -- it
+    either means that Quixote is not smart enough to handle the request
+    being passed to it, or the user-agent is broken and/or malicious.
+    """
+    status_code = 400
+    title = "Invalid request"
+    description = "Unable to parse HTTP request."
+
+
+class QueryError(PublishError):
+    """Should be raised if bad data was provided in the query part of a
+    URL or in the content of a POST request.  What constitutes bad data is
+    solely application dependent (eg: letters in a form field when the
+    application expects a number).
+    """
+
+    status_code = 400
+    title = "Invalid query"
+    description = ("An error occurred while handling your request.  The "
+                   "query data provided as part of the request is invalid.")
+
+
+
+class AccessError(PublishError):
+    """Should be raised if the client does not have access to the
+    requested resource.  Usually applications will raise this error from
+    an _q_access method.
+    """
+
+    status_code = 403
+    title = "Access denied"
+    description = ("An error occurred while handling your request.  "
+                   "Access to the requested resource was not permitted.")
+
+
+
+class SessionError(PublishError):
+    """Raised when a session cookie has an invalid session ID.  This
+    could be either a broken/malicious client or an expired session.
+    """
+
+    status_code = 400
+    title = "Expired or invalid session"
+    description = ("Your session is invalid or has expired.  "
+                   "Please reload this page to start a new session.")
+
+    def __init__(self, public_msg=None, private_msg=None, session_id=None):
+        PublishError.__init__(self, public_msg, private_msg)
+        self.session_id = session_id
+
+    def format(self, request):
+        from quixote import get_session_manager
+        get_session_manager().revoke_session_cookie(request)
+        msg = PublishError.format(self, request)
+        if self.session_id:
+            msg = msg + ": " + self.session_id
+        return msg
+
+
+def default_exception_handler(request, exc):
+    """(request : HTTPRequest, exc : PublishError) -> string
+
+    Format a PublishError exception as a web page.  This is the default
+    handler called if no '_q_exception_handler' function was found while
+    traversing the path.
+    """
+    return htmltext("""\
+    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"
+        "http://www.w3.org/TR/REC-html40/strict.dtd">
+    <html>
+      <head><title>Error: %s</title></head>
+      <body>
+      <p>%s</p>
+      <p>%s</p>
+      </body>
+    </html>
+    """) % (exc.title, exc.description, exc.format(request))

Added: packages/quixote1/branches/upstream/current/fcgi.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/fcgi.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/fcgi.py (added)
+++ packages/quixote1/branches/upstream/current/fcgi.py Mon May  8 19:16:16 2006
@@ -1,0 +1,462 @@
+#!/usr/local/bin/python1.5
+#------------------------------------------------------------------------
+#               Copyright (c) 1998 by Total Control Software
+#                         All Rights Reserved
+#------------------------------------------------------------------------
+#
+# Module Name:  fcgi.py
+#
+# Description:  Handles communication with the FastCGI module of the
+#               web server without using the FastCGI developers kit, but
+#               will also work in a non-FastCGI environment, (straight CGI.)
+#               This module was originally fetched from someplace on the
+#               Net (I don't remember where and I can't find it now...) and
+#               has been significantly modified to fix several bugs, be more
+#               readable, more robust at handling large CGI data and return
+#               document sizes, and also to fit the model that we had previously
+#               used for FastCGI.
+#
+#     WARNING:  If you don't know what you are doing, don't tinker with this
+#               module!
+#
+# Creation Date:    1/30/98 2:59:04PM
+#
+# License:      This is free software.  You may use this software for any
+#               purpose including modification/redistribution, so long as
+#               this header remains intact and that you do not claim any
+#               rights of ownership or authorship of this software.  This
+#               software has been tested, but no warranty is expressed or
+#               implied.
+#
+#------------------------------------------------------------------------
+
+__revision__ = "$Id: fcgi.py 20729 2003-02-12 22:00:03Z nascheme $"
+
+
+import  os, sys, string, socket, errno, struct
+from    cStringIO   import StringIO
+import  cgi
+
+#---------------------------------------------------------------------------
+
+# Set various FastCGI constants
+# Maximum number of requests that can be handled
+FCGI_MAX_REQS=1
+FCGI_MAX_CONNS = 1
+
+# Supported version of the FastCGI protocol
+FCGI_VERSION_1 = 1
+
+# Boolean: can this application multiplex connections?
+FCGI_MPXS_CONNS=0
+
+# Record types
+FCGI_BEGIN_REQUEST = 1 ; FCGI_ABORT_REQUEST = 2 ; FCGI_END_REQUEST   = 3
+FCGI_PARAMS        = 4 ; FCGI_STDIN         = 5 ; FCGI_STDOUT        = 6
+FCGI_STDERR        = 7 ; FCGI_DATA          = 8 ; FCGI_GET_VALUES    = 9
+FCGI_GET_VALUES_RESULT = 10
+FCGI_UNKNOWN_TYPE = 11
+FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
+
+# Types of management records
+ManagementTypes = [FCGI_GET_VALUES]
+
+FCGI_NULL_REQUEST_ID = 0
+
+# Masks for flags component of FCGI_BEGIN_REQUEST
+FCGI_KEEP_CONN = 1
+
+# Values for role component of FCGI_BEGIN_REQUEST
+FCGI_RESPONDER = 1 ; FCGI_AUTHORIZER = 2 ; FCGI_FILTER = 3
+
+# Values for protocolStatus component of FCGI_END_REQUEST
+FCGI_REQUEST_COMPLETE = 0               # Request completed nicely
+FCGI_CANT_MPX_CONN    = 1               # This app can't multiplex
+FCGI_OVERLOADED       = 2               # New request rejected; too busy
+FCGI_UNKNOWN_ROLE     = 3               # Role value not known
+
+
+error = 'fcgi.error'
+
+
+#---------------------------------------------------------------------------
+
+# The following function is used during debugging; it isn't called
+# anywhere at the moment
+
+def error(msg):
+    "Append a string to /tmp/err"
+    errf = open('/tmp/err', 'a+')
+    errf.write(msg+'\n')
+    errf.close()
+
+#---------------------------------------------------------------------------
+
+class record:
+    "Class representing FastCGI records"
+    def __init__(self):
+        self.version = FCGI_VERSION_1
+        self.recType = FCGI_UNKNOWN_TYPE
+        self.reqId   = FCGI_NULL_REQUEST_ID
+        self.content = ""
+
+    #----------------------------------------
+    def readRecord(self, sock, unpack=struct.unpack):
+        (self.version, self.recType, self.reqId, contentLength,
+         paddingLength) = unpack(">BBHHBx", sock.recv(8))
+
+        content = ""
+        while len(content) < contentLength:
+            content = content + sock.recv(contentLength - len(content))
+        self.content = content
+
+        if paddingLength != 0:
+            padding = sock.recv(paddingLength)
+
+        # Parse the content information
+        if self.recType == FCGI_BEGIN_REQUEST:
+            (self.role, self.flags) = unpack(">HB", content[:3])
+
+        elif self.recType == FCGI_UNKNOWN_TYPE:
+            self.unknownType = ord(content[0])
+
+        elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
+            self.values = {}
+            pos = 0
+            while pos < len(content):
+                name, value, pos = readPair(content, pos)
+                self.values[name] = value
+
+        elif self.recType == FCGI_END_REQUEST:
+            (self.appStatus, self.protocolStatus) = unpack(">IB", content[0:5])
+
+    #----------------------------------------
+    def writeRecord(self, sock, pack=struct.pack):
+        content = self.content
+        if self.recType == FCGI_BEGIN_REQUEST:
+            content = pack(">HBxxxxx", self.role, self.flags)
+
+        elif self.recType == FCGI_UNKNOWN_TYPE:
+            content = pack(">Bxxxxxx", self.unknownType)
+
+        elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
+            content = ""
+            for i in self.values.keys():
+                content = content + writePair(i, self.values[i])
+
+        elif self.recType == FCGI_END_REQUEST:
+            content = pack(">IBxxx", self.appStatus, self.protocolStatus)
+
+        cLen = len(content)
+        eLen = (cLen + 7) & (0xFFFF - 7)    # align to an 8-byte boundary
+        padLen = eLen - cLen
+
+        hdr = pack(">BBHHBx", self.version, self.recType, self.reqId, cLen,
+                   padLen)
+
+        ##debug.write('Sending fcgi record: %s\n' % repr(content[:50]) )
+        sock.send(hdr + content + padLen*'\000')
+
+#---------------------------------------------------------------------------
+
+_lowbits = ~(1L << 31) # everything but the 31st bit
+
+def readPair(s, pos):
+    nameLen = ord(s[pos]) ; pos = pos+1
+    if nameLen & 128:
+        pos = pos + 3
+        nameLen = int(struct.unpack(">I", s[pos-4:pos])[0] & _lowbits)
+    valueLen = ord(s[pos]) ; pos = pos+1
+    if valueLen & 128:
+        pos = pos + 3
+        valueLen = int(struct.unpack(">I", s[pos-4:pos])[0] & _lowbits)
+    return ( s[pos:pos+nameLen], s[pos+nameLen:pos+nameLen+valueLen],
+             pos+nameLen+valueLen )
+
+#---------------------------------------------------------------------------
+
+_highbit = (1L << 31)
+
+def writePair(name, value):
+    l = len(name)
+    if l < 128:
+        s = chr(l)
+    else:
+        s = struct.pack(">I", l | _highbit)
+    l = len(value)
+    if l < 128:
+        s = s + chr(l)
+    else:
+        s = s + struct.pack(">I", l | _highbit)
+    return s + name + value
+
+#---------------------------------------------------------------------------
+
+def HandleManTypes(r, conn):
+    if r.recType == FCGI_GET_VALUES:
+        r.recType = FCGI_GET_VALUES_RESULT
+        v = {}
+        vars = {'FCGI_MAX_CONNS' : FCGI_MAX_CONNS,
+                'FCGI_MAX_REQS'  : FCGI_MAX_REQS,
+                'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS}
+        for i in r.values.keys():
+            if vars.has_key(i): v[i] = vars[i]
+        r.values = vars
+        r.writeRecord(conn)
+
+#---------------------------------------------------------------------------
+#---------------------------------------------------------------------------
+
+
+_isFCGI = 1         # assume it is until we find out for sure
+
+def isFCGI():
+    return _isFCGI
+
+
+
+#---------------------------------------------------------------------------
+
+
+_init = None
+_sock = None
+
+class FCGI:
+    def __init__(self):
+        self.haveFinished = 0
+        if _init == None:
+            _startup()
+        if not _isFCGI:
+            self.haveFinished = 1
+            self.inp = sys.__stdin__
+            self.out = sys.__stdout__
+            self.err = sys.__stderr__
+            self.env = os.environ
+            return
+
+        if os.environ.has_key('FCGI_WEB_SERVER_ADDRS'):
+            good_addrs = string.split(os.environ['FCGI_WEB_SERVER_ADDRS'], ',')
+            good_addrs = map(string.strip, good_addrs)        # Remove whitespace
+        else:
+            good_addrs = None
+
+        self.conn, addr = _sock.accept()
+        stdin, data = "", ""
+        self.env = {}
+        self.requestId = 0
+        remaining = 1
+
+        # Check if the connection is from a legal address
+        if good_addrs != None and addr not in good_addrs:
+            raise error, 'Connection from invalid server!'
+
+        while remaining:
+            r = record()
+            r.readRecord(self.conn)
+
+            if r.recType in ManagementTypes:
+                HandleManTypes(r, self.conn)
+
+            elif r.reqId == 0:
+                # Oh, poopy.  It's a management record of an unknown
+                # type.  Signal the error.
+                r2 = record()
+                r2.recType = FCGI_UNKNOWN_TYPE
+                r2.unknownType = r.recType
+                r2.writeRecord(self.conn)
+                continue                # Charge onwards
+
+            # Ignore requests that aren't active
+            elif r.reqId != self.requestId and r.recType != FCGI_BEGIN_REQUEST:
+                continue
+
+            # If we're already doing a request, ignore further BEGIN_REQUESTs
+            elif r.recType == FCGI_BEGIN_REQUEST and self.requestId != 0:
+                continue
+
+            # Begin a new request
+            if r.recType == FCGI_BEGIN_REQUEST:
+                self.requestId = r.reqId
+                if r.role == FCGI_AUTHORIZER:   remaining = 1
+                elif r.role == FCGI_RESPONDER:  remaining = 2
+                elif r.role == FCGI_FILTER:     remaining = 3
+
+            elif r.recType == FCGI_PARAMS:
+                if r.content == "":
+                    remaining = remaining-1
+                else:
+                    for i in r.values.keys():
+                        self.env[i] = r.values[i]
+
+            elif r.recType == FCGI_STDIN:
+                if r.content == "":
+                    remaining = remaining-1
+                else:
+                    stdin = stdin+r.content
+
+            elif r.recType == FCGI_DATA:
+                if r.content == "":
+                    remaining = remaining-1
+                else:
+                    data = data+r.content
+        # end of while remaining:
+
+        self.inp = StringIO(stdin)
+        self.err = StringIO()
+        self.out = StringIO()
+        self.data = StringIO(data)
+
+    def __del__(self):
+        self.Finish()
+
+    def Finish(self, status=0):
+        if not self.haveFinished:
+            self.haveFinished = 1
+
+            self.err.seek(0,0)
+            self.out.seek(0,0)
+
+            ##global debug
+            ##debug = open("/tmp/quixote-debug.log", "a+")
+            ##debug.write("fcgi.FCGI.Finish():\n")
+
+            r = record()
+            r.recType = FCGI_STDERR
+            r.reqId = self.requestId
+            data = self.err.read()
+            ##debug.write("  sending stderr (%s)\n" % `self.err`)
+            ##debug.write("  data = %s\n" % `data`)
+            while data:
+                chunk, data = self.getNextChunk(data)
+                ##debug.write("  chunk, data = %s, %s\n" % (`chunk`, `data`))
+                r.content = chunk
+                r.writeRecord(self.conn)
+            r.content = ""
+            r.writeRecord(self.conn)      # Terminate stream
+
+            r.recType = FCGI_STDOUT
+            data = self.out.read()
+            ##debug.write("  sending stdout (%s)\n" % `self.out`)
+            ##debug.write("  data = %s\n" % `data`)
+            while data:
+                chunk, data = self.getNextChunk(data)
+                r.content = chunk
+                r.writeRecord(self.conn)
+            r.content = ""
+            r.writeRecord(self.conn)      # Terminate stream
+
+            r = record()
+            r.recType = FCGI_END_REQUEST
+            r.reqId = self.requestId
+            r.appStatus = status
+            r.protocolStatus = FCGI_REQUEST_COMPLETE
+            r.writeRecord(self.conn)
+            self.conn.close()
+
+            #debug.close()
+
+
+    def getFieldStorage(self):
+        method = 'GET'
+        if self.env.has_key('REQUEST_METHOD'):
+            method = string.upper(self.env['REQUEST_METHOD'])
+        if method == 'GET':
+            return cgi.FieldStorage(environ=self.env, keep_blank_values=1)
+        else:
+            return cgi.FieldStorage(fp=self.inp,
+                                    environ=self.env,
+                                    keep_blank_values=1)
+
+    def getNextChunk(self, data):
+        chunk = data[:8192]
+        data = data[8192:]
+        return chunk, data
+
+
+Accept = FCGI       # alias for backwards compatibility
+#---------------------------------------------------------------------------
+
+def _startup():
+    global _isFCGI, _init, _sock
+    # This function won't work on Windows at all.
+    if sys.platform[:3] == 'win':
+        _isFCGI = 0
+        return
+
+    _init = 1
+    try:
+        s = socket.fromfd(sys.stdin.fileno(), socket.AF_INET,
+                          socket.SOCK_STREAM)
+        s.getpeername()
+    except socket.error, (err, errmsg):
+        if err != errno.ENOTCONN:       # must be a non-fastCGI environment
+            _isFCGI = 0
+            return
+
+    _sock = s
+
+
+#---------------------------------------------------------------------------
+
+def _test():
+    counter = 0
+    try:
+        while isFCGI():
+            req = Accept()
+            counter = counter+1
+
+            try:
+                fs = req.getFieldStorage()
+                size = string.atoi(fs['size'].value)
+                doc = ['*' * size]
+            except:
+                doc = ['<HTML><HEAD>'
+                       '<TITLE>FCGI TestApp</TITLE>'
+                       '</HEAD>\n<BODY>\n']
+                doc.append('<H2>FCGI TestApp</H2><P>')
+                doc.append('<b>request count</b> = %d<br>' % counter)
+                doc.append('<b>pid</b> = %s<br>' % os.getpid())
+                if req.env.has_key('CONTENT_LENGTH'):
+                    cl = string.atoi(req.env['CONTENT_LENGTH'])
+                    doc.append('<br><b>POST data (%s):</b><br><pre>' % cl)
+                    keys = fs.keys()
+                    keys.sort()
+                    for k in keys:
+                        val = fs[k]
+                        if type(val) == type([]):
+                            doc.append('    <b>%-15s :</b>  %s\n'
+                                       % (k, val))
+                        else:
+                            doc.append('    <b>%-15s :</b>  %s\n'
+                                       % (k, val.value))
+                    doc.append('</pre>')
+
+
+                doc.append('<P><HR><P><pre>')
+                keys = req.env.keys()
+                keys.sort()
+                for k in keys:
+                    doc.append('<b>%-20s :</b>  %s\n' % (k, req.env[k]))
+                doc.append('\n</pre><P><HR>\n')
+                doc.append('</BODY></HTML>\n')
+
+
+            doc = string.join(doc, '')
+            req.out.write('Content-length: %s\r\n'
+                        'Content-type: text/html\r\n'
+                        'Cache-Control: no-cache\r\n'
+                        '\r\n'
+                            % len(doc))
+            req.out.write(doc)
+
+            req.Finish()
+    except:
+        import traceback
+        f = open('traceback', 'w')
+        traceback.print_exc( file = f )
+#        f.write('%s' % doc)
+
+if __name__ == '__main__':
+    #import pdb
+    #pdb.run('_test()')
+    _test()

Added: packages/quixote1/branches/upstream/current/form/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/form/__init__.py Mon May  8 19:16:16 2006
@@ -1,0 +1,37 @@
+"""quixote.form
+
+The web interface framework, consisting of Form and Widget base classes
+(and a bunch of standard widget classes recognized by Form).
+Application developers will typically create a Form subclass for each
+form in their application; each form object will contain a number
+of widget objects.  Custom widgets can be created by inheriting
+and/or composing the standard widget classes.
+"""
+
+# created 2000/09/19 - 22, GPW
+
+__revision__ = "$Id: __init__.py 22253 2003-08-13 20:15:01Z rmasse $"
+
+from quixote.form.form import Form, register_widget_class, FormTokenWidget
+from quixote.form.widget import Widget, StringWidget, FileWidget, \
+     PasswordWidget, TextWidget, CheckboxWidget, RadiobuttonsWidget, \
+     SingleSelectWidget, SelectWidget, OptionSelectWidget, \
+     MultipleSelectWidget, ListWidget, SubmitButtonWidget, HiddenWidget, \
+     FloatWidget, IntWidget, CollapsibleListWidget, FormValueError
+
+# Register the standard widget classes
+register_widget_class(StringWidget)
+register_widget_class(FileWidget)
+register_widget_class(PasswordWidget)
+register_widget_class(TextWidget)
+register_widget_class(CheckboxWidget)
+register_widget_class(RadiobuttonsWidget)
+register_widget_class(SingleSelectWidget)
+register_widget_class(OptionSelectWidget)
+register_widget_class(MultipleSelectWidget)
+register_widget_class(ListWidget)
+register_widget_class(SubmitButtonWidget)
+register_widget_class(HiddenWidget)
+register_widget_class(FloatWidget)
+register_widget_class(IntWidget)
+register_widget_class(CollapsibleListWidget)

Added: packages/quixote1/branches/upstream/current/form/form.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form/form.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form/form.py (added)
+++ packages/quixote1/branches/upstream/current/form/form.py Mon May  8 19:16:16 2006
@@ -1,0 +1,535 @@
+"""quixote.form.form
+
+Provides the Form class and bureaucracy for registering widget classes.
+(The standard widget classes are registered automatically.)
+"""
+
+__revision__ = "$Id: form.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+from types import StringType
+from quixote import get_session, get_publisher
+from quixote.html import url_quote, htmltag, htmltext, nl2br, TemplateIO
+from quixote.form.widget import FormValueError, HiddenWidget
+
+
+class FormTokenWidget (HiddenWidget):
+    def render(self, request):
+        self.value = get_session().create_form_token()
+        return HiddenWidget.render(self, request)
+
+
+JAVASCRIPT_MARKUP = htmltext('''\
+<script type="text/javascript">
+<!--
+%s
+// -->
+</script>
+''')
+
+class Form:
+    """
+    A form is the major element of an interactive web page.  A form
+    consists of the following:
+      * widgets (input/interaction elements)
+      * text
+      * layout
+      * code to process the form
+
+    All four of these are the responsibility of Form classes.
+    Typically, you will create one Form subclass for each form in your
+    application.  Thanks to the separation of responsibilities here,
+    it's not too hard to structure things so that a given form is
+    rendered and/or processed somewhat differently depending on context.
+    That separation is as follows:
+      * the constructor declares what widgets are in the form, and
+        any static text that is always associated with those widgets
+        (in particular, a widget title and "hint" text)
+      * the 'render()' method combines the widgets and their associated
+        text to create a (1-D) stream of HTML that represents the
+        (2-D) web page that will be presented to the user
+      * the 'process()' method parses the user input values from the form
+        and validates them
+      * the 'action()' method takes care of finishing whatever action
+        was requested by the user submitting the form -- commit
+        a database transaction, update session flags, redirect the
+        user to a new page, etc.
+
+    This class provides a default 'process()' method that just parses
+    each widget, storing any error messages for display on the next
+    'render()', and returns the results (if the form parses
+    successfully) in a dictionary.
+
+    This class also provides a default 'render()' method that lays out
+    widgets and text in a 3-column table: the first column is the widget
+    title, the second column is the widget itself, and the third column is
+    any hint and/or error text associated with the widget.  Also provided
+    are methods that can be used to construct this table a row at a time,
+    so you can use this layout for most widgets, but escape from it for
+    oddities.
+
+    Instance attributes:
+      widgets : { widget_name:string : widget:Widget }
+        dictionary of all widgets in the form
+      widget_order : [Widget]
+        same widgets as 'widgets', but ordered (because order matters)
+      submit_buttons : [SubmitButtonWidget]
+        the submit button widgets in the form
+
+      error : { widget_name:string : error_message:string }
+      hint : { widget_name:string : hint_text:string }
+      title : { widget_name:string : widget_title:string }
+      required : { widget_name:string : boolean }
+
+    """
+
+    TOKEN_NAME = "_form_id" # name of hidden token widget
+
+    def __init__(self, method="post", enctype=None, use_tokens=1):
+
+        if method not in ("post", "get"):
+            raise ValueError("Form method must be 'post' or 'get', "
+                             "not %r" % method)
+        self.method = method
+
+        if enctype is not None and enctype not in (
+            "application/x-www-form-urlencoded", "multipart/form-data"):
+            raise ValueError, ("Form enctype must be "
+                               "'application/x-www-form-urlencoded' or "
+                               "'multipart/form-data', not %r" % enctype)
+        self.enctype = enctype
+
+        # The first major component of a form: its widgets.  We want
+        # both easy access and order, so we have a dictionary and a list
+        # of the same objects.  The dictionary is keyed on widget name.
+        # These are populated by the 'add_*_widget()' methods.
+        self.widgets = {}
+        self.widget_order = []
+        self.submit_buttons = []
+        self.cancel_url = None
+
+        # The second major component: text.  It's up to the 'render()'
+        # method to figure out how to lay these out; the standard
+        # 'render()' does so in a fairly sensible way that should work
+        # for most of our forms.  These are also populated by the
+        # 'add_*_widget()' methods.
+        self.error = {}
+        self.hint = {}
+        self.title = {}
+        self.required = {}
+
+        config = get_publisher().config
+        if self.method == "post" and use_tokens and config.form_tokens:
+            # unique token for each form, this prevents many cross-site
+            # attacks and prevents a form from being submitted twice
+            self.add_widget(FormTokenWidget, self.TOKEN_NAME)
+            self.use_form_tokens = 1
+        else:
+            self.use_form_tokens = 0
+
+        # Subclasses should override this method to specify the actual
+        # widgets in this form -- typically this consists of a series of
+        # calls to 'add_widget()', which updates the data structures we
+        # just defined.
+
+
+    # -- Layout (rendering) methods ------------------------------------
+
+    # The third major component of a web form is layout.  These methods
+    # combine text and widgets in a 1-D stream of HTML, or in a 2-D web
+    # page (depending on your level of abstraction).
+
+    def render(self, request, action_url):
+        # render(request : HTTPRequest,
+        #           action_url : string)
+        #    -> HTML text
+        #
+        # Render a form as HTML.
+        assert type(action_url) in (StringType, htmltext)
+        r = TemplateIO(html=1)
+        r += self._render_start(request, action_url,
+                                enctype=self.enctype, method=self.method)
+        r += self._render_body(request)
+        r += self._render_finish(request)
+        return r.getvalue()
+
+    def _render_start(self, request, action,
+                      enctype=None, method='post', name=None):
+        r = TemplateIO(html=1)
+        r += htmltag('form', enctype=enctype, method=method,
+                     action=action, name=name)
+        r += self._render_hidden_widgets(request)
+        return r.getvalue()
+
+    def _render_finish(self, request):
+        r = TemplateIO(html=1)
+        r += htmltext('</form>')
+        r += self._render_javascript(request)
+        return r.getvalue()
+
+    def _render_sep(self, text, line=1):
+        return htmltext('<tr><td colspan="3">%s<strong><big>%s'
+                        '</big></strong></td></tr>') % \
+                                      (line and htmltext('<hr>') or '', text)
+
+    def _render_error(self, error):
+        if error:
+            return htmltext('<font color="red">%s</font><br />') % nl2br(error)
+        else:
+            return ''
+
+    def _render_hint(self, hint):
+        if hint:
+            return htmltext('<em>%s</em>') % hint
+        else:
+            return ''
+
+    def _render_widget_row(self, request, widget):
+        if widget.widget_type == 'hidden':
+            return ''
+        title = self.title[widget.name] or ''
+        if self.required.get(widget.name):
+            title = title + htmltext('&nbsp;*')
+        r = TemplateIO(html=1)
+        r += htmltext('<tr><th colspan="3" align="left">')
+        r += title
+        r += htmltext('</th></tr>'
+                      '<tr><td>&nbsp;&nbsp;</td><td>')
+        r += widget.render(request)
+        r += htmltext('</td><td>')
+        r += self._render_error(self.error.get(widget.name))
+        r += self._render_hint(self.hint.get(widget.name))
+        r += htmltext('</td></tr>')
+        return r.getvalue()
+
+    def _render_hidden_widgets(self, request):
+        r = TemplateIO(html=1)
+        for widget in self.widget_order:
+            if widget.widget_type == 'hidden':
+                r += widget.render(request)
+                r += self._render_error(self.error.get(widget.name))
+        return r.getvalue()
+
+    def _render_submit_buttons(self, request, ncols=3):
+        r = TemplateIO(html=1)
+        r += htmltext('<tr><td colspan="%d">\n') % ncols
+        for button in self.submit_buttons:
+            r += button.render(request)
+        r += htmltext('</td></tr>')
+        return r.getvalue()
+
+    def _render_visible_widgets(self, request):
+        r = TemplateIO(html=1)
+        for widget in self.widget_order:
+            r += self._render_widget_row(request, widget)
+        return r.getvalue()
+
+    def _render_error_notice(self, request):
+        if self.error:
+            r = htmltext('<tr><td colspan="3">'
+                         '<font color="red"><strong>Warning:</strong></font> '
+                         'there were errors processing your form.  '
+                         'See below for details.'
+                         '</td></tr>')
+        else:
+            r = ''
+        return r
+
+    def _render_required_notice(self, request):
+        if filter(None, self.required.values()):
+            r = htmltext('<tr><td colspan="3">'
+                         '<b>*</b> = <em>required field</em>'
+                         '</td></tr>')
+        else:
+            r = ''
+        return r
+
+    def _render_body(self, request):
+        r = TemplateIO(html=1)
+        r += htmltext('<table>')
+        r += self._render_error_notice(request)
+        r += self._render_required_notice(request)
+        r += self._render_visible_widgets(request)
+        r += self._render_submit_buttons(request)
+        r += htmltext('</table>')
+        return r.getvalue()
+
+    def _render_javascript(self, request):
+        """Render javacript code for the form, if any.
+           Insert code lexically sorted by code_id
+        """
+        javascript_code = request.response.javascript_code
+        if javascript_code:
+            form_code = []
+            code_ids = javascript_code.keys()
+            code_ids.sort()
+            for code_id in code_ids:
+                code = javascript_code[code_id]
+                if code:
+                    form_code.append(code)
+                    javascript_code[code_id] = ''
+            if form_code:
+                return JAVASCRIPT_MARKUP % htmltext(''.join(form_code))
+        return ''
+
+
+    # -- Processing methods --------------------------------------------
+
+    # The fourth and final major component: code to process the form.
+    # The standard 'process()' method just parses every widget and
+    # returns a { field_name : field_value } dictionary as 'values'.
+
+    def process(self, request):
+        """process(request : HTTPRequest) -> values : { string : any }
+
+        Process the form data, validating all input fields (widgets).
+        If any errors in input fields, adds error messages to the
+        'error' attribute (so that future renderings of the form will
+        include the errors).  Returns a dictionary mapping widget names to
+        parsed values.
+        """
+        self.error.clear()
+
+        values = {}
+        for widget in self.widget_order:
+            try:
+                val = widget.parse(request)
+            except FormValueError, exc:
+                self.error[widget.name] = exc.msg
+            else:
+                values[widget.name] = val
+
+        return values
+
+    def action(self, request, submit, values):
+        """action(request : HTTPRequest, submit : string,
+                  values : { string : any }) -> string
+
+        Carry out the action required by a form submission.  'submit' is the
+        name of submit button used to submit the form.  'values' is the
+        dictionary of parsed values from 'process()'.  Note that error
+        checking cannot be done here -- it must done in the 'process()'
+        method.
+        """
+        raise NotImplementedError, "sub-classes must implement 'action()'"
+
+    def handle(self, request):
+        """handle(request : HTTPRequest) -> string
+
+        Master method for handling forms.  It should be called after
+        initializing a form.  Controls form action based on a request.  You
+        probably should override 'process' and 'action' instead of
+        overriding this method.
+        """
+        action_url = self.get_action_url(request)
+        if not self.form_submitted(request):
+            return self.render(request, action_url)
+        submit = self.get_submit_button(request)
+        if submit == "cancel":
+            return request.redirect(self.cancel_url)
+        values = self.process(request)
+        if submit == "":
+            # The form was submitted by unknown submit button, assume that
+            # the submission was required to update the layout of the form.
+            # Clear the errors and re-render the form.
+            self.error.clear()
+            return self.render(request, action_url)
+
+        if self.use_form_tokens:
+            # before calling action() ensure that there is a valid token
+            # present
+            token = values.get(self.TOKEN_NAME)
+            if not request.session.has_form_token(token):
+                if not self.error:
+                    # if there are other errors then don't show the token
+                    # error, the form needs to be resubmitted anyhow
+                    self.error[self.TOKEN_NAME] = (
+                           "The form you have submitted is invalid.  It has "
+                           "already been submitted or has expired. Please "
+                           "review and resubmit the form.")
+            else:
+                request.session.remove_form_token(token)
+
+        if self.error:
+            return self.render(request, action_url)
+        else:
+            return self.action(request, submit, values)
+
+
+    # -- Convenience methods -------------------------------------------
+
+    def form_submitted(self, request):
+        """form_submitted(request : HTTPRequest) -> boolean
+
+        Return true if a form was submitted in the current request.
+        """
+        return len(request.form) > 0
+
+    def get_action_url(self, request):
+        action_url = url_quote(request.get_path())
+        query = request.get_environ("QUERY_STRING")
+        if query:
+            action_url += "?" + query
+        return action_url
+
+    def get_submit_button(self, request):
+        """get_submit_button(request : HTTPRequest) -> string | None
+
+        Get the name of the submit button that was used to submit the
+        current form.  If the browser didn't include this information in
+        the request, use the first submit button registered.
+        """
+        for button in self.submit_buttons:
+            if request.form.has_key(button.name):
+                return button.name
+        else:
+            if request.form and self.submit_buttons:
+                return ""
+            else:
+                return None
+
+    def get_widget(self, widget_name):
+        return self.widgets.get(widget_name)
+
+    def parse_widget(self, name, request):
+        """parse_widget(name : string, request : HTTPRequest) -> any
+
+        Parse the value of named widget.  If any parse errors, store the
+        error message (in self.error) for use in the next rendering of
+        the form and return None; otherwise, return the value parsed
+        from the widget (whose type depends on the widget type).
+        """
+        try:
+            return self.widgets[name].parse(request)
+        except FormValueError, exc:
+            self.error[name] = str(exc)
+            return None
+
+    def store_value(self, widget_name, request, target,
+                    mode="modifier",
+                    key=None,
+                    missing_error=None):
+        """store_value(widget_name : string,
+                       request : HTTPRequest,
+                       target : instance | dict,
+                       mode : string = "modifier",
+                       key : string = widget_name,
+                       missing_error : string = None)
+
+        Parse a widget and, if it parsed successfully, store its value
+        in 'target'.  The value is stored in 'target' by name 'key';
+        if 'key' is not supplied, it defaults to 'widget_name'.
+        How the value is stored depends on 'mode':
+          * modifier: call a modifier method, eg. if 'key' is "foo",
+            call 'target.set_foo(value)'
+          * direct: direct attribute update, eg. if 'key' is
+            "foo" do "target.foo = value"
+          * dict: dictionary update, eg. if 'key' is "foo" do
+            "target['foo'] = value"
+
+        If 'missing_error' is supplied, use it as an error message if
+        the field doesn't have a value -- ie. supplying 'missing_error'
+        means this field is required.
+        """
+        value = self.parse_widget(widget_name, request)
+        if (value is None or value == "") and missing_error:
+            self.error[widget_name] = missing_error
+            return None
+
+        if key is None:
+            key = widget_name
+        if mode == "modifier":
+            # eg. turn "name" into "target.set_name", and
+            # call it like "target.set_name(value)"
+            mod = getattr(target, "set_" + key)
+            mod(value)
+        elif mode == "direct":
+            if not hasattr(target, key):
+                raise AttributeError, \
+                      ("target object %s doesn't have attribute %s" %
+                       (`target`, key))
+            setattr(target, key, value)
+        elif mode == "dict":
+            target[key] = value
+        else:
+            raise ValueError, "unknown update mode %s" % `mode`
+
+    def clear_widget(self, widget_name):
+        self.widgets[widget_name].clear()
+
+    def get_widget_value(self, widget_name):
+        return self.widgets[widget_name].value
+
+    def set_widget_value(self, widget_name, value):
+        self.widgets[widget_name].set_value(value)
+
+
+    # -- Form population methods ---------------------------------------
+
+    def add_widget(self, widget_type, name, value=None,
+                   title=None, hint=None, required=0, **args):
+        """add_widget(widget_type : string | Widget,
+                      name : string,
+                      value : any = None,
+                      title : string = None,
+                      hint : string = None,
+                      required : boolean = 0,
+                      ...) -> Widget
+
+        Create a new Widget object and add it to the form.  The widget
+        class used depends on 'widget_type', and the expected type of
+        'value' also depends on the widget class.  Any extra keyword
+        args are passed to the widget constructor.
+
+        Returns the new Widget.
+        """
+        if self.widgets.has_key(name):
+            raise ValueError, "form already has '%s' variable" % name
+        klass = get_widget_class(widget_type)
+        new_widget = apply(klass, (name, value), args)
+
+        self.widgets[name] = new_widget
+        self.widget_order.append(new_widget)
+        self.title[name] = title
+        self.hint[name] = hint
+        self.required[name] = required
+        return new_widget
+
+    def add_submit_button(self, name, value):
+        global _widget_class
+        if self.widgets.has_key(name):
+            raise ValueError, "form already has '%s' variable" % name
+        new_widget = _widget_class['submit_button'](name, value)
+
+        self.widgets[name] = new_widget
+        self.submit_buttons.append(new_widget)
+
+    def add_cancel_button(self, caption, url):
+        if not isinstance(url, (StringType, htmltext)):
+            raise TypeError, "url must be a string (got %r)" % url
+        self.add_submit_button("cancel", caption)
+        self.cancel_url = url
+
+# class Form
+
+
+_widget_class = {}
+
+def register_widget_class(klass, widget_type=None):
+    global _widget_class
+    if widget_type is None:
+        widget_type = klass.widget_type
+    assert widget_type is not None, "widget_type must be defined"
+    _widget_class[widget_type] = klass
+
+def get_widget_class(widget_type):
+    global _widget_class
+    if callable(widget_type):
+        # Presumably someone passed a widget class object to
+        # Widget.create_subwidget() or Form.add_widget() --
+        # don't bother with the widget class registry at all.
+        return widget_type
+    else:
+        try:
+            return _widget_class[widget_type]
+        except KeyError:
+            raise ValueError("unknown widget type %r" % widget_type)

Added: packages/quixote1/branches/upstream/current/form/widget.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form/widget.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form/widget.py (added)
+++ packages/quixote1/branches/upstream/current/form/widget.py Mon May  8 19:16:16 2006
@@ -1,0 +1,845 @@
+"""quixote.form.widget
+
+Provides the basic web widget classes: Widget itself, plus StringWidget,
+TextWidget, CheckboxWidget, etc.
+"""
+
+# created 2000/09/20, GPW
+
+__revision__ = "$Id: widget.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import struct
+from types import FloatType, IntType, ListType, StringType, TupleType
+from quixote import get_request
+from quixote.html import htmltext, htmlescape, htmltag, ValuelessAttr
+from quixote.upload import Upload
+
+
+class FormValueError (Exception):
+    """Raised whenever a widget has problems parsing its value."""
+
+    def __init__(self, msg):
+        self.msg = msg
+
+
+    def __str__(self):
+        return str(self.msg)
+
+
+class Widget:
+    """Abstract base class for web widgets.  The key elements
+    of a web widget are:
+      - name
+      - widget type (how the widget looks/works in the browser)
+      - value
+
+    The name and value are instance attributes (because they're specific to
+    a particular widget in a particular context); widget type is a
+    class attributes.
+
+    Instance attributes:
+      name : string
+      value : any
+
+    Feel free to access these directly; to set them, use the 'set_*()'
+    modifier methods.
+    """
+
+    # Subclasses must define.  'widget_type' is just a string, e.g.
+    # "string", "text", "checkbox".
+    widget_type = None
+
+    def __init__(self, name, value=None):
+        assert self.__class__ is not Widget, "abstract class"
+        self.set_name(name)
+        self.set_value(value)
+
+
+    def __repr__(self):
+        return "<%s at %x: %s>" % (self.__class__.__name__,
+                                   id(self),
+                                   self.name)
+
+
+    def __str__(self):
+        return "%s: %s" % (self.widget_type, self.name)
+
+
+    def set_name(self, name):
+        self.name = name
+
+
+    def set_value(self, value):
+        self.value = value
+
+
+    def clear(self):
+        self.value = None
+
+    # -- Subclasses must implement these -------------------------------
+
+    def render(self, request):
+        """render(request) -> HTML text"""
+        raise NotImplementedError
+
+
+    def parse(self, request):
+        """parse(request) -> any"""
+        value = request.form.get(self.name)
+        if type(value) is StringType and value.strip():
+            self.value = value
+        else:
+            self.value = None
+
+        return self.value
+
+    # -- Convenience methods for subclasses ----------------------------
+
+    # This one's really only for composite widgets; lives here until
+    # we have a demonstrated need for a CompositeWidget class.
+    def get_subwidget_name(self, name):
+        return "%s$%s" % (self.name, name)
+
+
+    def create_subwidget(self, widget_type, widget_name, value=None, **args):
+        from quixote.form.form import get_widget_class
+        klass = get_widget_class(widget_type)
+        name = self.get_subwidget_name(widget_name)
+        return apply(klass, (name, value), args)
+
+# class Widget
+
+# -- Fundamental widget types ------------------------------------------
+# These correspond to the standard types of input tag in HTML:
+#   text     StringWidget
+#   password PasswordWidget
+#   radio    RadiobuttonWidget
+#   checkbox CheckboxWidget
+#
+# and also to the other basic form elements:
+#   <textarea>  TextWidget
+#   <select>    SingleSelectWidget
+#   <select multiple>
+#               MultipleSelectWidget
+
+class StringWidget (Widget):
+    """Widget for entering a single string: corresponds to
+    '<input type="text">' in HTML.
+
+    Instance attributes:
+      value : string
+      size : int
+      maxlength : int
+    """
+
+    widget_type = "string"
+
+    # This lets PasswordWidget be a trivial subclass
+    html_type = "text"
+
+    def __init__(self, name, value=None,
+                 size=None, maxlength=None):
+        Widget.__init__(self, name, value)
+        self.size = size
+        self.maxlength = maxlength
+
+
+    def render(self, request, **attributes):
+        return htmltag("input", xml_end=1,
+                       type=self.html_type,
+                       name=self.name,
+                       size=self.size,
+                       maxlength=self.maxlength,
+                       value=self.value,
+                       **attributes)
+
+
+class FileWidget (StringWidget):
+    """Trivial subclass of StringWidget for uploading files.
+
+    Instance attributes: none
+    """
+    widget_type = "file"
+    html_type = "file"
+
+    def parse(self, request):
+        """parse(request) -> any"""
+        value = request.form.get(self.name)
+        if isinstance(value, Upload):
+            self.value = value
+        else:
+            self.value = None
+        return self.value
+
+
+class PasswordWidget (StringWidget):
+    """Trivial subclass of StringWidget for entering passwords (different
+    widget type because HTML does it that way).
+
+    Instance attributes: none
+    """
+
+    widget_type = "password"
+    html_type = "password"
+
+
+class TextWidget (Widget):
+    """Widget for entering a long, multi-line string; corresponds to
+    the HTML "<textarea>" tag.
+
+    Instance attributes:
+      value : string
+      cols : int
+      rows : int
+      wrap : string
+        (see an HTML book for details on text widget wrap options)
+      css_class : string
+    """
+
+    widget_type = "text"
+
+    def __init__(self, name, value=None, cols=None, rows=None, wrap=None,
+                 css_class=None):
+        Widget.__init__(self, name, value)
+        self.cols = cols
+        self.rows = rows
+        self.wrap = wrap
+        self.css_class = css_class
+
+    def render(self, request):
+        return (htmltag("textarea", name=self.name,
+                        cols=self.cols,
+                        rows=self.rows,
+                        wrap=self.wrap,
+                        css_class=self.css_class) +
+                htmlescape(self.value or "") +
+                htmltext("</textarea>"))
+
+
+    def parse(self, request):
+        value = Widget.parse(self, request)
+        if value:
+            value = value.replace("\r\n", "\n")
+            self.value = value
+        return self.value
+
+
+class CheckboxWidget (Widget):
+    """Widget for a single checkbox: corresponds to "<input
+    type=checkbox>".  Do not put multiple CheckboxWidgets with the same
+    name in the same form.
+
+    Instance attributes:
+      value : boolean
+    """
+
+    widget_type = "checkbox"
+
+    def render(self, request):
+        return htmltag("input", xml_end=1,
+                       type="checkbox",
+                       name=self.name,
+                       value="yes",
+                       checked=self.value and ValuelessAttr or None)
+
+
+    def parse(self, request):
+        self.value = request.form.has_key(self.name)
+        return self.value
+
+
+class SelectWidget (Widget):
+    """Widget for single or multiple selection; corresponds to
+    <select name=...>
+      <option value="Foo">Foo</option>
+      ...
+    </select>
+
+    Instance attributes:
+      options : [ (value:any, description:any, key:string) ]
+      value : any
+        The value is None or an element of dict(options.values()).
+      size : int
+        The number of options that should be presented without scrolling.
+    """
+
+    # NB. 'widget_type' not set here because this is an abstract class: it's
+    # set by subclasses SingleSelectWidget and MultipleSelectWidget.
+
+    def __init__(self, name, value=None,
+                 allowed_values=None,
+                 descriptions=None,
+                 options=None,
+                 size=None,
+                 sort=0,
+                 verify_selection=1):
+        assert self.__class__ is not SelectWidget, "abstract class"
+        self.options = []
+        # if options passed, cannot pass allowed_values or descriptions
+        if allowed_values is not None:
+            assert options is None, (
+                'cannot pass both allowed_values and options')
+            assert allowed_values, (
+                'cannot pass empty allowed_values list')
+            self.set_allowed_values(allowed_values, descriptions, sort)
+        elif options is not None:
+            assert descriptions is None, (
+                'cannot pass both options and descriptions')
+            assert options, (
+                'cannot pass empty options list')
+            self.set_options(options, sort)
+        self.set_name(name)
+        self.set_value(value)
+        self.size = size
+        self.verify_selection = verify_selection
+
+
+    def get_allowed_values(self):
+        return [item[0] for item in self.options]
+
+
+    def get_descriptions(self):
+        return [item[1] for item in self.options]
+
+
+    def set_value(self, value):
+        self.value = None
+        for object, description, key in self.options:
+            if value == object:
+                self.value = value
+                break
+
+
+    def _generate_keys(self, values, descriptions):
+        """Called if no keys were provided.  Try to generate a set of keys
+        that will be consistent between rendering and parsing.
+        """
+        # try to use ZODB object IDs
+        keys = []
+        for value in values:
+            if value is None:
+                oid = ""
+            else:
+                oid = getattr(value, "_p_oid", None)
+                if not oid:
+                    break
+                hi, lo = struct.unpack(">LL", oid)
+                oid = "%x" % ((hi << 32) | lo)
+            keys.append(oid)
+        else:
+            # found OID for every value
+            return keys
+        # can't use OIDs, try using descriptions
+        used_keys = {}
+        keys = map(str, descriptions)
+        for key in keys:
+            if used_keys.has_key(key):
+                raise ValueError, "duplicated descriptions (provide keys)"
+            used_keys[key] = 1
+        return keys
+
+
+    def set_options(self, options, sort=0):
+        """(options: [objects:any], sort=0)
+         or
+           (options: [(object:any, description:any)], sort=0)
+         or
+           (options: [(object:any, description:any, key:any)], sort=0)
+        """
+
+        """
+        Set the options list.  The list of options can be a list of objects, in
+        which case the descriptions default to map(htmlescape, objects)
+        applying htmlescape() to each description and
+        key.
+        If keys are provided they must be distinct.  If the sort keyword
+        argument is true, sort the options by case-insensitive lexicographic
+        order of descriptions, except that options with value None appear
+        before others.
+        """
+        if options:
+            first = options[0]
+            values = []
+            descriptions = []
+            keys = []
+            if type(first) is TupleType:
+                if len(first) == 2:
+                    for value, description in options:
+                        values.append(value)
+                        descriptions.append(description)
+                elif len(first) == 3:
+                    for value, description, key in options:
+                        values.append(value)
+                        descriptions.append(description)
+                        keys.append(str(key))
+                else:
+                    raise ValueError, 'invalid options %r' % options
+            else:
+                values = descriptions = options
+
+            if not keys:
+                keys = self._generate_keys(values, descriptions)
+
+            options = zip(values, descriptions, keys)
+
+            if sort:
+                def make_sort_key(option):
+                    value, description, key = option
+                    if value is None:
+                        return ('', option)
+                    else:
+                        return (str(description).lower(), option)
+                doptions = map(make_sort_key, options)
+                doptions.sort()
+                options = [item[1] for item in doptions]
+        self.options = options
+
+
+    def parse_single_selection(self, parsed_key):
+        for value, description, key in self.options:
+            if key == parsed_key:
+                return value
+        else:
+            if self.verify_selection:
+                raise FormValueError, "invalid value selected"
+            else:
+                return self.options[0][0]
+
+
+    def set_allowed_values(self, allowed_values, descriptions=None, sort=0):
+        """(allowed_values:[any], descriptions:[any], sort:boolean=0)
+
+        Set the options for this widget.  The allowed_values and descriptions
+        parameters must be sequences of the same length.  The sort option
+        causes the options to be sorted using case-insensitive lexicographic
+        order of descriptions, except that options with value None appear
+        before others.
+        """
+        if descriptions is None:
+            self.set_options(allowed_values, sort)
+        else:
+            assert len(descriptions) == len(allowed_values)
+            self.set_options(zip(allowed_values, descriptions), sort)
+
+
+    def is_selected(self, value):
+        return value == self.value
+
+
+    def render(self, request):
+        if self.widget_type == "multiple_select":
+            multiple = ValuelessAttr
+        else:
+            multiple = None
+        if self.widget_type == "option_select":
+            onchange = "submit()"
+        else:
+            onchange = None
+        tags = [htmltag("select", name=self.name,
+                        multiple=multiple, onchange=onchange,
+                        size=self.size)]
+        for object, description, key in self.options:
+            if self.is_selected(object):
+                selected = ValuelessAttr
+            else:
+                selected = None
+            if description is None:
+                description = ""
+            r = htmltag("option", value=key, selected=selected)
+            tags.append(r + htmlescape(description) + htmltext('</option>'))
+        tags.append(htmltext("</select>"))
+        return htmltext("\n").join(tags)
+
+
+class SingleSelectWidget (SelectWidget):
+    """Widget for single selection.
+    """
+
+    widget_type = "single_select"
+
+    def parse(self, request):
+        parsed_key = request.form.get(self.name)
+        self.value = None
+        if parsed_key:
+            if type(parsed_key) is ListType:
+                raise FormValueError, "cannot select multiple values"
+            self.value = self.parse_single_selection(parsed_key)
+        return self.value
+
+
+class RadiobuttonsWidget (SingleSelectWidget):
+    """Widget for a *set* of related radiobuttons -- all have the
+    same name, but different values (and only one of those values
+    is returned by the whole group).
+
+    Instance attributes:
+      delim : string = None
+        string to emit between each radiobutton in the group.  If
+        None, a single newline is emitted.
+    """
+
+    widget_type = "radiobuttons"
+
+    def __init__(self, name, value=None,
+                 allowed_values=None,
+                 descriptions=None,
+                 options=None,
+                 delim=None):
+        SingleSelectWidget.__init__(self, name, value, allowed_values,
+                                    descriptions, options)
+        if delim is None:
+            self.delim = "\n"
+        else:
+            self.delim = delim
+
+
+    def render(self, request):
+        tags = []
+        for object, description, key in self.options:
+            if self.is_selected(object):
+                checked = ValuelessAttr
+            else:
+                checked = None
+            r = htmltag("input", xml_end=True,
+                        type="radio",
+                        name=self.name,
+                        value=key,
+                        checked=checked)
+            tags.append(r + htmlescape(description))
+        return htmlescape(self.delim).join(tags)
+
+
+class MultipleSelectWidget (SelectWidget):
+    """Widget for multiple selection.
+
+    Instance attributes:
+      value : [any]
+        for multipe selects, the value is None or a list of
+        elements from dict(self.options).values()
+    """
+
+    widget_type = "multiple_select"
+
+    def set_value(self, value):
+        allowed_values = self.get_allowed_values()
+        if value in allowed_values:
+            self.value = [ value ]
+        elif type(value) in (ListType, TupleType):
+            self.value = [ element
+                           for element in value
+                           if element in allowed_values ] or None
+        else:
+            self.value = None
+
+
+    def is_selected(self, value):
+        if self.value is None:
+            return value is None
+        else:
+            return value in self.value
+
+
+    def parse(self, request):
+        parsed_keys = request.form.get(self.name)
+        self.value = None
+        if parsed_keys:
+            if type(parsed_keys) is ListType:
+                self.value =  [value
+                               for value, description, key in self.options
+                               if key in parsed_keys] or None
+            else:
+                self.value = [self.parse_single_selection(parsed_keys)]
+        return self.value
+
+
+class SubmitButtonWidget (Widget):
+    """
+    Instance attributes:
+      value : boolean
+    """
+
+    widget_type = "submit_button"
+
+    def __init__(self, name=None, value=None):
+        Widget.__init__(self, name, value)
+
+
+    def render(self, request):
+        value = (self.value and htmlescape(self.value) or None)
+        return htmltag("input", xml_end=1, type="submit",
+                       name=self.name, value=value)
+
+
+    def parse(self, request):
+        return request.form.get(self.name)
+
+
+    def is_submitted(self):
+        return self.parse(get_request())
+
+
+class HiddenWidget (Widget):
+    """
+    Instance attributes:
+      value : string
+    """
+
+    widget_type = "hidden"
+
+    def render(self, request):
+        if self.value is None:
+            value = None
+        else:
+            value = htmlescape(self.value)
+        return htmltag("input", xml_end=1,
+                       type="hidden",
+                       name=self.name,
+                       value=value)
+
+
+    def set_current_value(self, value):
+        self.value = value
+        request = get_request()
+        if request.form:
+            request.form[self.name] = value
+
+
+    def get_current_value(self):
+        request = get_request()
+        if request.form:
+            return self.parse(request)
+        else:
+            return self.value
+
+# -- Derived widget types ----------------------------------------------
+# (these don't correspond to fundamental widget types in HTML,
+# so they're separated)
+
+class NumberWidget (StringWidget):
+    """
+    Instance attributes: none
+    """
+
+    # Parameterize the number type (either float or int) through
+    # these class attributes:
+    type_object = None                  # eg. int, float
+    type_error = None                   # human-readable error message
+    type_converter = None               # eg. int(), float()
+
+    def __init__(self, name,
+                 value=None,
+                 size=None, maxlength=None):
+        assert self.__class__ is not NumberWidget, "abstract class"
+        assert value is None or type(value) is self.type_object, (
+            "form value '%s' not a %s: got %r" % (name,
+                                                  self.type_object,
+                                                  value))
+        StringWidget.__init__(self, name, value, size, maxlength)
+
+
+    def parse(self, request):
+        value = StringWidget.parse(self, request)
+        if value:
+            try:
+                self.value = self.type_converter(value)
+            except ValueError:
+                raise FormValueError, self.type_error
+        return self.value
+
+
+class FloatWidget (NumberWidget):
+    """
+    Instance attributes:
+      value : float
+    """
+
+    widget_type = "float"
+    type_object = FloatType
+    type_converter = float
+    type_error = "must be a number"
+
+
+class IntWidget (NumberWidget):
+    """
+    Instance attributes:
+      value : int
+    """
+
+    widget_type = "int"
+    type_object = IntType
+    type_converter = int
+    type_error = "must be an integer"
+
+
+class OptionSelectWidget (SingleSelectWidget):
+    """Widget for single selection with automatic submission and early
+    parsing.  This widget parses the request when it is created.  This
+    allows its value to be used to decide what other widgets need to be
+    created in a form.  It's a powerful feature but it can be hard to
+    understand what's going on.
+
+    Instance attributes:
+      value : any
+    """
+
+    widget_type = "option_select"
+
+    def __init__(self, *args, **kwargs):
+        SingleSelectWidget.__init__(self, *args, **kwargs)
+
+        request = get_request()
+        if request.form:
+            SingleSelectWidget.parse(self, request)
+        if self.value is None:
+            self.value = self.options[0][0]
+
+
+    def render(self, request):
+        return (SingleSelectWidget.render(self, request) +
+                htmltext('<noscript>'
+                         '<input type="submit" name="" value="apply" />'
+                         '</noscript>'))
+
+
+    def parse(self, request):
+        return self.value
+
+
+    def get_current_option(self):
+        return self.value
+
+
+class ListWidget (Widget):
+    """Widget for lists of objects.
+
+    Instance attributes:
+      value : [any]
+    """
+
+    widget_type = "list"
+
+    def __init__(self, name, value=None,
+                 element_type=None,
+                 element_name="row",
+                 **args):
+        assert value is None or type(value) is ListType, (
+            "form value '%s' not a list: got %r" % (name, value))
+        assert type(element_name) in (StringType, htmltext), (
+            "form value '%s' element_name not a string: "
+            "got %r" % (name, element_name))
+
+        Widget.__init__(self, name, value)
+
+        if element_type is None:
+            self.element_type = "string"
+        else:
+            self.element_type = element_type
+        self.args = args
+
+        self.added_elements_widget = self.create_subwidget(
+            "hidden", "added_elements")
+
+        added_elements = int(self.added_elements_widget.get_current_value() or
+                             '1')
+
+        self.add_button = self.create_subwidget(
+            "submit_button", "add_element",
+            value="Add %s" % element_name)
+
+        if self.add_button.is_submitted():
+            added_elements += 1
+            self.added_elements_widget.set_current_value(str(added_elements))
+
+        self.element_widgets = []
+        self.element_count = 0
+
+        if self.value is not None:
+            for element in self.value:
+                self.add_element(element)
+
+        for index in range(added_elements):
+            self.add_element()
+
+    def add_element(self, value=None):
+        self.element_widgets.append(
+            self.create_subwidget(self.element_type,
+                                  "element_%d" % self.element_count,
+                                  value=value,
+                                  **self.args))
+        self.element_count += 1
+
+    def render(self, request):
+        tags = []
+        for element_widget in self.element_widgets:
+            tags.append(element_widget.render(request))
+        tags.append(self.add_button.render(request))
+        tags.append(self.added_elements_widget.render(request))
+        return htmltext('<br />\n').join(tags)
+
+    def parse(self, request):
+        self.value = []
+        for element_widget in self.element_widgets:
+            value = element_widget.parse(request)
+            if value is not None:
+                self.value.append(value)
+        self.value = self.value or None
+        return self.value
+
+
+
+class CollapsibleListWidget (ListWidget):
+    """Widget for lists of objects with associated delete buttons.
+
+    CollapsibleListWidget behaves like ListWidget except that each element
+    is rendered with an associated delete button.  Pressing the delete
+    button will cause the associated element name to be added to a hidden
+    widget that remembers all deletions until the form is submitted.
+    Only elements that are not marked as deleted will be rendered and
+    ultimately added to the value of the widget.
+
+    Instance attributes:
+      value : [any]
+    """
+
+    widget_type = "collapsible_list"
+
+    def __init__(self, name, value=None, element_name="row", **args):
+        self.name = name
+        self.element_name = element_name
+        self.deleted_elements_widget = self.create_subwidget(
+            "hidden", "deleted_elements")
+        self.element_delete_buttons = []
+        self.deleted_elements = (
+            self.deleted_elements_widget.get_current_value() or '')
+        ListWidget.__init__(self, name, value=value,
+                            element_name=element_name,
+                            **args)
+
+    def add_element(self, value=None):
+        element_widget_name = "element_%d" % self.element_count
+        if self.deleted_elements.find(element_widget_name) == -1:
+            delete_button = self.create_subwidget(
+                "submit_button", "delete_" + element_widget_name,
+                value="Delete %s" % self.element_name)
+            if delete_button.is_submitted():
+                self.element_count += 1
+                self.deleted_elements += element_widget_name
+                self.deleted_elements_widget.set_current_value(
+                    self.deleted_elements)
+            else:
+                self.element_delete_buttons.append(delete_button)
+                ListWidget.add_element(self, value=value)
+        else:
+            self.element_count += 1
+
+    def render(self, request):
+        tags = []
+        for element_widget, element_delete_button in zip(
+                self.element_widgets, self.element_delete_buttons):
+            if self.deleted_elements.find(element_widget.name) == -1:
+                tags.append(element_widget.render(request) +
+                            element_delete_button.render(request))
+        tags.append(self.add_button.render(request))
+        tags.append(self.added_elements_widget.render(request))
+        tags.append(self.deleted_elements_widget.render(request))
+        return htmltext('<br />\n').join(tags)

Added: packages/quixote1/branches/upstream/current/form2/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form2/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form2/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/form2/__init__.py Mon May  8 19:16:16 2006
@@ -1,0 +1,18 @@
+"""$URL: svn+ssh://svn/repos/trunk/quixote/form2/__init__.py $
+$Id: __init__.py 23720 2004-03-17 16:45:59Z nascheme $
+
+The web interface framework, consisting of Form and Widget base classes
+(and a bunch of standard widget classes recognized by Form).
+Application developers will typically create a Form instance each
+form in their application; each form object will contain a number
+of widget objects.  Custom widgets can be created by inheriting
+and/or composing the standard widget classes.
+"""
+
+from quixote.form2.form import Form, FormTokenWidget
+from quixote.form2.widget import Widget, StringWidget, FileWidget, \
+    PasswordWidget, TextWidget, CheckboxWidget, RadiobuttonsWidget, \
+    SingleSelectWidget, SelectWidget, OptionSelectWidget, \
+    MultipleSelectWidget, SubmitWidget, HiddenWidget, \
+    FloatWidget, IntWidget, subname, WidgetValueError, CompositeWidget, \
+    WidgetList

Added: packages/quixote1/branches/upstream/current/form2/compatibility.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form2/compatibility.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form2/compatibility.py (added)
+++ packages/quixote1/branches/upstream/current/form2/compatibility.py Mon May  8 19:16:16 2006
@@ -1,0 +1,97 @@
+'''$URL: svn+ssh://svn/repos/trunk/quixote/form2/compatibility.py $
+$Id: compatibility.py 25234 2004-09-30 17:36:19Z nascheme $
+
+A Form subclass that provides close to the same API as the old form
+class (useful for transitioning existing forms).
+'''
+
+from quixote.form2 import Form as _Form, Widget, StringWidget, FileWidget, \
+    PasswordWidget, TextWidget, CheckboxWidget, RadiobuttonsWidget, \
+    SingleSelectWidget, SelectWidget, OptionSelectWidget, \
+    MultipleSelectWidget, SubmitWidget, HiddenWidget, \
+    FloatWidget, IntWidget
+from quixote.html import url_quote
+
+_widget_names = {
+    "string" : StringWidget,
+    "file" : FileWidget,
+    "password" : PasswordWidget,
+    "text" : TextWidget,
+    "checkbox" : CheckboxWidget,
+    "single_select" : SingleSelectWidget,
+    "radiobuttons" : RadiobuttonsWidget,
+    "multiple_select" : MultipleSelectWidget,
+    "submit_button" : SubmitWidget,
+    "hidden" : HiddenWidget,
+    "float" : FloatWidget,
+    "int" : IntWidget,
+    "option_select" : OptionSelectWidget,
+}
+
+
+class Form(_Form):
+    def __init__(self, *args, **kwargs):
+        _Form.__init__(self, *args, **kwargs)
+        self.cancel_url = None
+
+    def add_widget(self, widget_class, name, value=None,
+                   title=None, hint=None, required=0, **kwargs):
+        try:
+            widget_class = _widget_names[widget_class]
+        except KeyError:
+            pass
+        self.add(widget_class, name, value=value, title=title, hint=hint,
+                 required=required, **kwargs)
+
+    def add_submit_button(self, name, value):
+        self.add_submit(name, value)
+
+    def add_cancel_button(self, caption, url):
+        self.add_submit("cancel", caption)
+        self.cancel_url = url
+
+    def get_action_url(self, request):
+        action_url = url_quote(request.get_path())
+        query = request.get_environ("QUERY_STRING")
+        if query:
+            action_url += "?" + query
+        return action_url
+
+    def render(self, request, action_url=None):
+        if action_url:
+            self.action_url = action_url
+        return _Form.render(self)
+
+    def process(self, request):
+        values = {}
+        for name, widget in self._names.items():
+            values[name] = widget.parse(request)
+        return values
+
+    def action(self, request, submit, values):
+        raise NotImplementedError, "sub-classes must implement 'action()'"
+
+    def handle(self, request):
+        """handle(request : HTTPRequest) -> string
+
+        Master method for handling forms.  It should be called after
+        initializing a form.  Controls form action based on a request.  You
+        probably should override 'process' and 'action' instead of
+        overriding this method.
+        """
+        if not self.is_submitted():
+            return self.render(request, self.action_url)
+        submit = self.get_submit()
+        if submit == "cancel":
+            return request.redirect(self.cancel_url)
+        values = self.process(request)
+        if submit == True:
+            # The form was submitted by an unregistered submit button, assume
+            # that the submission was required to update the layout of the form.
+            self.clear_errors()
+            return self.render(request, self.action_url)
+
+        if self.has_errors():
+            return self.render(request, self.action_url)
+        else:
+            return self.action(request, submit, values)

Added: packages/quixote1/branches/upstream/current/form2/css.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form2/css.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form2/css.py (added)
+++ packages/quixote1/branches/upstream/current/form2/css.py Mon May  8 19:16:16 2006
@@ -1,0 +1,76 @@
+
+BASIC_FORM_CSS = """\
+form.quixote div.title {
+    font-weight: bold;
+}
+
+form.quixote br.submit,
+form.quixote br.widget,
+br.quixoteform {
+    clear: left;
+}
+
+form.quixote div.submit br.widget {
+    display: none;
+}
+
+form.quixote div.widget {
+    float: left;
+    padding: 4px;
+    padding-right: 1em;
+    margin-bottom: 6px;
+}
+
+/* pretty forms (attribute selector hides from broken browsers (e.g. IE) */
+form.quixote[action] {
+    float: left;
+}
+
+form.quixote[action] > div.widget {
+    float: none;
+}
+
+form.quixote[action] > br.widget {
+    display: none;
+}
+
+form.quixote div.widget div.widget {
+    padding: 0;
+    margin-bottom: 0;
+}
+
+form.quixote div.SubmitWidget {
+    float: left
+}
+
+form.quixote div.content {
+    margin-left: 0.6em; /* indent content */
+}
+
+form.quixote div.content div.content {
+    margin-left: 0; /* indent content only for top-level widgets */
+}
+
+form.quixote div.error {
+    color: #c00;
+    font-size: small;
+    margin-top: .1em;
+}
+
+form.quixote div.hint {
+    font-size: small;
+    font-style: italic;
+    margin-top: .1em;
+}
+
+form.quixote div.errornotice {
+    color: #c00;
+    padding: 0.5em;
+    margin: 0.5em;
+}
+
+form.quixote div.FormTokenWidget,
+form.quixote.div.HiddenWidget {
+    display: none;
+}
+"""

Added: packages/quixote1/branches/upstream/current/form2/form.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form2/form.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form2/form.py (added)
+++ packages/quixote1/branches/upstream/current/form2/form.py Mon May  8 19:16:16 2006
@@ -1,0 +1,373 @@
+"""$URL: svn+ssh://svn/repos/trunk/quixote/form2/form.py $
+$Id: form.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Provides the Form class and related classes.  Forms are a convenient
+way of building HTML forms that are composed of Widget objects.
+"""
+
+from quixote import get_request, get_session, get_publisher
+from quixote.html import url_quote, htmltag, htmltext, TemplateIO
+from quixote.form2.widget import HiddenWidget, StringWidget, TextWidget, \
+    CheckboxWidget, SingleSelectWidget, RadiobuttonsWidget, \
+    MultipleSelectWidget, ResetWidget, SubmitWidget, FloatWidget, \
+    IntWidget
+
+
+try:
+    True, False, bool
+except NameError:
+    True = 1
+    False = 0
+    def bool(v):
+        return not not v
+
+
+class FormTokenWidget(HiddenWidget):
+
+    def _parse(self, request):
+        token = request.form.get(self.name)
+        session = get_session()
+        if not session.has_form_token(token):
+            self.error = 'invalid'
+        else:
+            session.remove_form_token(token)
+
+    def render_error(self, error):
+        return ''
+
+    def render(self):
+        self.value = get_session().create_form_token()
+        return HiddenWidget.render(self)
+
+
+class Form:
+    """
+    Provides a high-level mechanism for collecting and processing user
+    input that is based on HTML forms.
+
+    Instance attributes:
+      widgets : [Widget]
+        widgets that are not subclasses of SubmitWidget or HiddenWidget
+      submit_widgets : [SubmitWidget]
+        subclasses of SubmitWidget, normally rendered at the end of the
+        form
+      hidden_widgets : [HiddenWidget]
+        subclasses of HiddenWidget, normally rendered at the beginning
+        of the form
+      _names : { name:string : Widget }
+        names used in the form and the widgets associated with them
+    """
+
+    TOKEN_NAME = "_form_id" # name of hidden token widget
+
+    JAVASCRIPT_MARKUP = htmltext('<script type="text/javascript">\n'
+                                 '<!--\n'
+                                 '%s\n'
+                                 '// -->\n'
+                                 '</script>\n')
+
+    def __init__(self,
+                 name=None,
+                 method="post",
+                 action_url=None,
+                 enctype=None,
+                 use_tokens=True,
+                 attrs=None):
+
+        if method not in ("post", "get"):
+            raise ValueError("Form method must be 'post' or 'get', "
+                             "not %r" % method)
+        self.name = name
+        self.method = method
+        self.action_url = action_url or self._get_default_action_url()
+        if not attrs:
+            attrs = {'class': 'quixote'}
+        elif 'class' not in attrs:
+            attrs = attrs.copy()
+            attrs['class'] = 'quixote'
+        self.attrs = attrs
+        self.widgets = []
+        self.submit_widgets = []
+        self.hidden_widgets = []
+        self._names = {}
+
+        if enctype is not None and enctype not in (
+            "application/x-www-form-urlencoded", "multipart/form-data"):
+            raise ValueError, ("Form enctype must be "
+                               "'application/x-www-form-urlencoded' or "
+                               "'multipart/form-data', not %r" % enctype)
+        self.enctype = enctype
+
+        if use_tokens and self.method == "post":
+            config = get_publisher().config
+            if config.form_tokens:
+                # unique token for each form, this prevents many cross-site
+                # attacks and prevents a form from being submitted twice
+                self.add(FormTokenWidget, self.TOKEN_NAME, value=None)
+
+    def _get_default_action_url(self):
+        request = get_request()
+        action_url = url_quote(request.get_path())
+        query = request.get_environ("QUERY_STRING")
+        if query:
+            action_url += "?" + query
+        return action_url
+
+    # -- Form data access methods --------------------------------------
+
+    def __getitem__(self, name):
+        """(name:string) -> any
+        Return a widget's value.  Raises KeyError if widget named 'name'
+        does not exist.
+        """
+        try:
+            return self._names[name].parse()
+        except KeyError:
+            raise KeyError, 'no widget named %r' % name
+
+    def has_key(self, name):
+        """Return true if the widget named 'name' is in the form."""
+        return self._names.has_key(name)
+
+    def get(self, name, default=None):
+        """(name:string, default=None) -> any
+        Return a widget's value.  Returns 'default' if widget named 'name'
+        does not exist.
+        """
+        widget = self._names.get(name)
+        if widget is not None:
+            return widget.parse()
+        else:
+            return default
+
+    def get_widget(self, name):
+        """(name:string) -> Widget | None
+        Return the widget named 'name'.  Returns None if the widget does
+        not exist.
+        """
+        return self._names.get(name)
+
+    def get_submit_widgets(self):
+        """() -> [SubmitWidget]
+        """
+        return self.submit_widgets
+
+    def get_all_widgets(self):
+        """() -> [Widget]
+        Return all the widgets that have been added to the form.  Note that
+        this while this list includes submit widgets and hidden widgets, it
+        does not include sub-widgets (e.g. widgets that are part of
+        CompositeWidgets)
+        """
+        return self._names.values()
+
+    # -- Form processing and error checking ----------------------------
+
+    def is_submitted(self):
+        """() -> bool
+
+        Return true if a form was submitted.  If the form method is 'POST'
+        and the page was not requested using 'POST', then the form is not
+        considered to be submitted.  If the form method is 'GET' then the
+        form is considered submitted if there is any form data in the
+        request.
+        """
+        request = get_request()
+        if self.method == 'post':
+            if request.get_method() == 'POST':
+                return True
+            else:
+                return False
+        else:
+            return bool(request.form)
+
+    def has_errors(self):
+        """() -> bool
+
+        Ensure that all components of the form have parsed themselves. Return
+        true if any of them have errors.
+        """
+        request = get_request()
+        has_errors = False
+        if self.is_submitted():
+            for widget in self.get_all_widgets():
+                if widget.has_error(request=request):
+                    has_errors =  True
+        return has_errors
+
+    def clear_errors(self):
+        """Ensure that all components of the form have parsed themselves.
+        Clear any errors that might have occured during parsing.
+        """
+        request = get_request()
+        for widget in self.get_all_widgets():
+            widget.clear_error(request)
+
+    def get_submit(self):
+        """() -> string | bool
+
+        Get the name of the submit button that was used to submit the
+        current form.  If the form is submitted but not by any known
+        SubmitWidget then return True.  Otherwise, return False.
+        """
+        request = get_request()
+        for button in self.submit_widgets:
+            if button.parse(request):
+                return button.name
+        else:
+            if self.is_submitted():
+                return True
+            else:
+                return False
+
+    def set_error(self, name, error):
+        """(name : string, error : string)
+        Set the error attribute of the widget named 'name'.
+        """
+        widget = self._names.get(name)
+        if not widget:
+            raise KeyError, "unknown name %r" % name
+        widget.set_error(error)
+
+    # -- Form population methods ---------------------------------------
+
+    def add(self, widget_class, name, *args, **kwargs):
+        if self._names.has_key(name):
+            raise ValueError, "form already has '%s' widget" % name
+        widget = widget_class(name, *args, **kwargs)
+        self._names[name] = widget
+        if isinstance(widget, SubmitWidget):
+            self.submit_widgets.append(widget) # will be rendered at end
+        elif isinstance(widget, HiddenWidget):
+            self.hidden_widgets.append(widget) # will be render at beginning
+        else:
+            self.widgets.append(widget)
+
+    # convenience methods
+
+    def add_submit(self, name, value=None, **kwargs):
+        self.add(SubmitWidget, name, value, **kwargs)
+
+    def add_reset(self, name, value=None, **kwargs):
+        self.add(ResetWidget, name, value, **kwargs)
+
+    def add_hidden(self, name, value=None, **kwargs):
+        self.add(HiddenWidget, name, value, **kwargs)
+
+    def add_string(self, name, value=None, **kwargs):
+        self.add(StringWidget, name, value, **kwargs)
+
+    def add_text(self, name, value=None, **kwargs):
+        self.add(TextWidget, name, value, **kwargs)
+
+    def add_checkbox(self, name, value=None, **kwargs):
+        self.add(CheckboxWidget, name, value, **kwargs)
+
+    def add_single_select(self, name, value=None, **kwargs):
+        self.add(SingleSelectWidget, name, value, **kwargs)
+
+    def add_multiple_select(self, name, value=None, **kwargs):
+        self.add(MultipleSelectWidget, name, value, **kwargs)
+
+    def add_radiobuttons(self, name, value=None, **kwargs):
+        self.add(RadiobuttonsWidget, name, value, **kwargs)
+
+    def add_float(self, name, value=None, **kwargs):
+        self.add(FloatWidget, name, value, **kwargs)
+
+    def add_int(self, name, value=None, **kwargs):
+        self.add(IntWidget, name, value, **kwargs)
+
+
+    # -- Layout (rendering) methods ------------------------------------
+
+    def render(self):
+        """() -> HTML text
+        Render a form as HTML.
+        """
+        r = TemplateIO(html=True)
+        r += self._render_start()
+        r += self._render_body()
+        r += self._render_finish()
+        return r.getvalue()
+
+    def _render_start(self):
+        r = TemplateIO(html=True)
+        r += htmltag('form', name=self.name, method=self.method,
+                     enctype=self.enctype, action=self.action_url,
+                     **self.attrs)
+        r += self._render_hidden_widgets()
+        return r.getvalue()
+
+    def _render_finish(self):
+        r = TemplateIO(html=True)
+        r += htmltext('</form><br class="quixoteform" />')
+        code = get_request().response.javascript_code
+        if code:
+            r += self._render_javascript(code)
+        return r.getvalue()
+
+    def _render_widgets(self):
+        r = TemplateIO(html=True)
+        for widget in self.widgets:
+            r += widget.render()
+        return r.getvalue()
+
+    def _render_hidden_widgets(self):
+        r = TemplateIO(html=True)
+        for widget in self.hidden_widgets:
+            r += widget.render()
+        return r.getvalue()
+
+    def _render_submit_widgets(self):
+        r = TemplateIO(html=True)
+        if self.submit_widgets:
+            r += htmltext('<div class="submit">')
+            for widget in self.submit_widgets:
+                r += widget.render()
+            r += htmltext('</div><br class="submit" />')
+        return r.getvalue()
+
+    def _render_error_notice(self):
+        token_widget = self.get_widget(self.TOKEN_NAME)
+        if token_widget is not None and token_widget.has_error():
+            # form tokens are enabled but the token data in the request
+            # does not match anything in the session.  It could be an
+            # a cross-site attack but most likely the back button has
+            # be used
+            return htmltext('<div class="errornotice">'
+                            'The form you have submitted is invalid.  Most '
+                            'likely it has been successfully submitted once '
+                            'already.  Please review the the form data '
+                            'and submit the form again.'
+                            '</div>')
+        else:
+            return htmltext('<div class="errornotice">'
+                            'There were errors processing your form.  '
+                            'See below for details.'
+                            '</div>')
+
+    def _render_javascript(self, javascript_code):
+        """Render javacript code for the form.  Insert code lexically
+        sorted by code_id.
+        """
+        form_code = []
+        code_ids = javascript_code.keys()
+        code_ids.sort()
+        for code_id in code_ids:
+            code = javascript_code[code_id]
+            if code:
+                form_code.append(code)
+                javascript_code[code_id] = ''
+        if form_code:
+            return self.JAVASCRIPT_MARKUP % htmltext(''.join(form_code))
+        else:
+            return ''
+
+    def _render_body(self):
+        r = TemplateIO(html=True)
+        if self.has_errors():
+            r += self._render_error_notice()
+        r += self._render_widgets()
+        r += self._render_submit_widgets()
+        return r.getvalue()

Added: packages/quixote1/branches/upstream/current/form2/widget.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form2/widget.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form2/widget.py (added)
+++ packages/quixote1/branches/upstream/current/form2/widget.py Mon May  8 19:16:16 2006
@@ -1,0 +1,954 @@
+"""$URL: svn+ssh://svn/repos/trunk/quixote/form2/widget.py $
+$Id: widget.py 25244 2004-10-01 14:05:13Z dbinger $
+
+Provides the basic web widget classes: Widget itself, plus StringWidget,
+TextWidget, CheckboxWidget, etc.
+"""
+
+import struct
+from types import FloatType, IntType, ListType, StringType, TupleType
+from quixote import get_request
+from quixote.html import htmltext, htmlescape, htmltag, TemplateIO
+from quixote.upload import Upload
+
+try:
+    True, False
+except NameError:
+    True = 1
+    False = 0
+
+
+def subname(prefix, name):
+    """Create a unique name for a sub-widget or sub-component."""
+    # $ is nice because it's valid as part of a Javascript identifier
+    return "%s$%s" % (prefix, name)
+
+
+def merge_attrs(base, overrides):
+    """({string: any}, {string: any}) -> {string: any}
+    """
+    items = []
+    if base:
+        items.extend(base.items())
+    if overrides:
+        items.extend(overrides.items())
+    attrs = {}
+    for name, val in items:
+        if name.endswith('_'):
+            name = name[:-1]
+        attrs[name] = val
+    return attrs
+
+
+class WidgetValueError(Exception):
+    """May be raised a widget has problems parsing its value."""
+
+    def __init__(self, msg):
+        self.msg = msg
+
+    def __str__(self):
+        return str(self.msg)
+
+
+class Widget:
+    """Abstract base class for web widgets.
+
+    Instance attributes:
+      name : string
+      value : any
+      error : string
+      title : string
+      hint : string
+      required : bool
+      attrs : {string: any}
+      _parsed : bool
+
+    Feel free to access these directly; to set them, use the 'set_*()'
+    modifier methods.
+    """
+
+    def __init__(self, name, value=None, title="", hint="", required=False,
+                 render_br=True, attrs=None, **kwattrs):
+        assert self.__class__ is not Widget, "abstract class"
+        self.name = name
+        self.value = value
+        self.error = None
+        self.title = title
+        self.hint = hint
+        self.required = required
+        self.render_br = render_br
+        self.attrs = merge_attrs(attrs, kwattrs)
+        self._parsed = False
+
+    def __repr__(self):
+        return "<%s at %x: %s>" % (self.__class__.__name__,
+                                   id(self),
+                                   self.name)
+
+    def __str__(self):
+        return "%s: %s" % (self.__class__.__name__, self.name)
+
+    def get_name(self):
+        return self.name
+
+    def set_value(self, value):
+        self.value = value
+
+    def set_error(self, error):
+        self.error = error
+
+    def get_error(self, request=None):
+        self.parse(request=request)
+        return self.error
+
+    def has_error(self, request=None):
+        return bool(self.get_error(request=request))
+
+    def clear_error(self, request=None):
+        self.parse(request=request)
+        self.error = None
+
+    def set_title(self, title):
+        self.title = title
+
+    def get_title(self):
+        return self.title
+
+    def set_hint(self, hint):
+        self.hint = hint
+
+    def get_hint(self):
+        return self.hint
+
+    def is_required(self):
+        return self.required
+
+    def parse(self, request=None):
+        if not self._parsed:
+            self._parsed = True
+            if request is None:
+                request = get_request()
+            if request.form or request.get_method() == 'POST':
+                try:
+                    self._parse(request)
+                except WidgetValueError, exc:
+                    self.set_error(str(exc))
+                if (self.required and self.value is None and
+                    not self.has_error()):
+                    self.set_error('required')
+        return self.value
+
+    def _parse(self, request):
+        # subclasses may override but this is not part of the public API
+        value = request.form.get(self.name)
+        if type(value) is StringType and value.strip():
+            self.value = value
+        else:
+            self.value = None
+
+    def render_title(self, title):
+        if title:
+            if self.required:
+                title += htmltext('<span class="required">*</span>')
+            return htmltext('<div class="title">%s</div>') % title
+        else:
+            return ''
+
+    def render_hint(self, hint):
+        if hint:
+            return htmltext('<div class="hint">%s</div>') % hint
+        else:
+            return ''
+
+    def render_error(self, error):
+        if error:
+            return htmltext('<div class="error">%s</div>') % error
+        else:
+            return ''
+
+    def render(self):
+        r = TemplateIO(html=True)
+        classnames = '%s widget' % self.__class__.__name__
+        r += htmltext('<div class="%s">') % classnames
+        r += self.render_title(self.get_title())
+        r += htmltext('<div class="content">')
+        r += self.render_content()
+        r += self.render_hint(self.get_hint())
+        r += self.render_error(self.get_error())
+        r += htmltext('</div>')
+        r += htmltext('</div>')
+        if self.render_br:
+            r += htmltext('<br class="%s" />') % classnames
+        r += htmltext('\n')
+        return r.getvalue()
+
+    def render_content(self):
+        raise NotImplementedError
+
+# class Widget
+
+# -- Fundamental widget types ------------------------------------------
+# These correspond to the standard types of input tag in HTML:
+#   text     StringWidget
+#   password PasswordWidget
+#   radio    RadiobuttonsWidget
+#   checkbox CheckboxWidget
+#
+# and also to the other basic form elements:
+#   <textarea>  TextWidget
+#   <select>    SingleSelectWidget
+#   <select multiple>
+#               MultipleSelectWidget
+
+class StringWidget(Widget):
+    """Widget for entering a single string: corresponds to
+    '<input type="text">' in HTML.
+
+    Instance attributes:
+      value : string
+    """
+
+    # This lets PasswordWidget be a trivial subclass
+    HTML_TYPE = "text"
+
+    def render_content(self):
+        return htmltag("input", xml_end=True,
+                       type=self.HTML_TYPE,
+                       name=self.name,
+                       value=self.value,
+                       **self.attrs)
+
+
+class FileWidget(StringWidget):
+    """Subclass of StringWidget for uploading files.
+
+    Instance attributes: none
+    """
+
+    HTML_TYPE = "file"
+
+    def _parse(self, request):
+        parsed_value = request.form.get(self.name)
+        if isinstance(parsed_value, Upload):
+            self.value = parsed_value
+        else:
+            self.value = None
+
+
+class PasswordWidget(StringWidget):
+    """Trivial subclass of StringWidget for entering passwords (different
+    widget type because HTML does it that way).
+
+    Instance attributes: none
+    """
+
+    HTML_TYPE = "password"
+
+
+class TextWidget(Widget):
+    """Widget for entering a long, multi-line string; corresponds to
+    the HTML "<textarea>" tag.
+
+    Instance attributes:
+      value : string
+    """
+
+    def _parse(self, request):
+        Widget._parse(self, request)
+        if self.value and self.value.find("\r\n") >= 0:
+            self.value = self.value.replace("\r\n", "\n")
+
+    def render_content(self):
+        return (htmltag("textarea", name=self.name, **self.attrs) +
+                htmlescape(self.value or "") +
+                htmltext("</textarea>"))
+
+
+class CheckboxWidget(Widget):
+    """Widget for a single checkbox: corresponds to "<input
+    type=checkbox>".  Do not put multiple CheckboxWidgets with the same
+    name in the same form.
+
+    Instance attributes:
+      value : boolean
+    """
+
+    def _parse(self, request):
+        self.value = request.form.has_key(self.name)
+
+    def render_content(self):
+        return htmltag("input", xml_end=True,
+                       type="checkbox",
+                       name=self.name,
+                       value="yes",
+                       checked=self.value and "checked" or None,
+                       **self.attrs)
+
+
+
+class SelectWidget(Widget):
+    """Widget for single or multiple selection; corresponds to
+    <select name=...>
+      <option value="Foo">Foo</option>
+      ...
+    </select>
+
+    Instance attributes:
+      options : [ (value:any, description:any, key:string) ]
+      value : any
+        The value is None or an element of dict(options.values()).
+    """
+
+    def __init__(self, name, value=None, options=None, sort=False,
+                 verify_selection=True, **kwargs):
+        assert self.__class__ is not SelectWidget, "abstract class"
+        Widget.__init__(self, name, value, **kwargs)
+        self.options = []
+        if not options:
+            raise ValueError, "a non-empty list of 'options' is required"
+        else:
+            self.set_options(options, sort)
+        self.verify_selection = verify_selection
+
+    def get_allowed_values(self):
+        return [item[0] for item in self.options]
+
+    def get_descriptions(self):
+        return [item[1] for item in self.options]
+
+    def set_value(self, value):
+        self.value = None
+        for object, description, key in self.options:
+            if value == object:
+                self.value = value
+                break
+
+    def _generate_keys(self, values, descriptions):
+        """Called if no keys were provided.  Try to generate a set of keys
+        that will be consistent between rendering and parsing.
+        """
+        # try to use ZODB object IDs
+        keys = []
+        for value in values:
+            if value is None:
+                oid = ""
+            else:
+                oid = getattr(value, "_p_oid", None)
+                if not oid:
+                    break
+                hi, lo = struct.unpack(">LL", oid)
+                oid = "%x" % ((hi << 32) | lo)
+            keys.append(oid)
+        else:
+            # found OID for every value
+            return keys
+        # can't use OIDs, try using descriptions
+        used_keys = {}
+        keys = map(str, descriptions)
+        for key in keys:
+            if used_keys.has_key(key):
+                raise ValueError, "duplicated descriptions (provide keys)"
+            used_keys[key] = 1
+        return keys
+
+    def set_options(self, options, sort=False):
+        """(options: [objects:any], sort=False)
+         or
+           (options: [(object:any, description:any)], sort=False)
+         or
+           (options: [(object:any, description:any, key:any)], sort=False)
+        """
+
+        """
+        Set the options list.  The list of options can be a list of objects, in
+        which case the descriptions default to map(htmlescape, objects)
+        applying htmlescape() to each description and
+        key.
+        If keys are provided they must be distinct.  If the sort keyword
+        argument is true, sort the options by case-insensitive lexicographic
+        order of descriptions, except that options with value None appear
+        before others.
+        """
+        if options:
+            first = options[0]
+            values = []
+            descriptions = []
+            keys = []
+            if type(first) is TupleType:
+                if len(first) == 2:
+                    for value, description in options:
+                        values.append(value)
+                        descriptions.append(description)
+                elif len(first) == 3:
+                    for value, description, key in options:
+                        values.append(value)
+                        descriptions.append(description)
+                        keys.append(str(key))
+                else:
+                    raise ValueError, 'invalid options %r' % options
+            else:
+                values = descriptions = options
+
+            if not keys:
+                keys = self._generate_keys(values, descriptions)
+
+            options = zip(values, descriptions, keys)
+
+            if sort:
+                def make_sort_key(option):
+                    value, description, key = option
+                    if value is None:
+                        return ('', option)
+                    else:
+                        return (str(description).lower(), option)
+                doptions = map(make_sort_key, options)
+                doptions.sort()
+                options = [item[1] for item in doptions]
+        self.options = options
+
+    def _parse_single_selection(self, parsed_key, default=None):
+        for value, description, key in self.options:
+            if key == parsed_key:
+                return value
+        else:
+            if self.verify_selection:
+                self.error = "invalid value selected"
+                return default
+            elif self.options:
+                return self.options[0][0]
+            else:
+                return default
+
+    def set_allowed_values(self, allowed_values, descriptions=None,
+                           sort=False):
+        """(allowed_values:[any], descriptions:[any], sort:boolean=False)
+
+        Set the options for this widget.  The allowed_values and descriptions
+        parameters must be sequences of the same length.  The sort option
+        causes the options to be sorted using case-insensitive lexicographic
+        order of descriptions, except that options with value None appear
+        before others.
+        """
+        if descriptions is None:
+            self.set_options(allowed_values, sort)
+        else:
+            assert len(descriptions) == len(allowed_values)
+            self.set_options(zip(allowed_values, descriptions), sort)
+
+    def is_selected(self, value):
+        return value == self.value
+
+    def render_content(self):
+        tags = [htmltag("select", name=self.name, **self.attrs)]
+        for object, description, key in self.options:
+            if self.is_selected(object):
+                selected = 'selected'
+            else:
+                selected = None
+            if description is None:
+                description = ""
+            r = htmltag("option", value=key, selected=selected)
+            tags.append(r + htmlescape(description) + htmltext('</option>'))
+        tags.append(htmltext("</select>"))
+        return htmltext("\n").join(tags)
+
+
+class SingleSelectWidget(SelectWidget):
+    """Widget for single selection.
+    """
+
+    SELECT_TYPE = "single_select"
+
+    def _parse(self, request):
+        parsed_key = request.form.get(self.name)
+        if parsed_key:
+            if type(parsed_key) is ListType:
+                self.error = "cannot select multiple values"
+            else:
+                self.value = self._parse_single_selection(parsed_key)
+        else:
+            self.value = None
+
+
+class RadiobuttonsWidget(SingleSelectWidget):
+    """Widget for a *set* of related radiobuttons -- all have the
+    same name, but different values (and only one of those values
+    is returned by the whole group).
+
+    Instance attributes:
+      delim : string = None
+        string to emit between each radiobutton in the group.  If
+        None, a single newline is emitted.
+    """
+
+    SELECT_TYPE = "radiobuttons"
+
+    def __init__(self, name, value=None, options=None, delim=None, **kwargs):
+        SingleSelectWidget.__init__(self, name, value, options=options,
+                                    **kwargs)
+        if delim is None:
+            self.delim = "\n"
+        else:
+            self.delim = delim
+
+    def render_content(self):
+        tags = []
+        for object, description, key in self.options:
+            if self.is_selected(object):
+                checked = 'checked'
+            else:
+                checked = None
+            r = htmltag("input", xml_end=True,
+                        type="radio",
+                        name=self.name,
+                        value=key,
+                        checked=checked,
+                        **self.attrs)
+            tags.append(r + htmlescape(description))
+        return htmlescape(self.delim).join(tags)
+
+
+class MultipleSelectWidget(SelectWidget):
+    """Widget for multiple selection.
+
+    Instance attributes:
+      value : [any]
+        for multipe selects, the value is None or a list of
+        elements from dict(self.options).values()
+    """
+
+    SELECT_TYPE = "multiple_select"
+
+    def __init__(self, name, value=None, options=None, **kwargs):
+        SelectWidget.__init__(self, name, value, options=options,
+                              multiple='multiple', **kwargs)
+
+    def set_value(self, value):
+        allowed_values = self.get_allowed_values()
+        if value in allowed_values:
+            self.value = [ value ]
+        elif type(value) in (ListType, TupleType):
+            self.value = [ element
+                           for element in value
+                           if element in allowed_values ] or None
+        else:
+            self.value = None
+
+    def is_selected(self, value):
+        if self.value is None:
+            return value is None
+        else:
+            return value in self.value
+
+    def _parse(self, request):
+        parsed_keys = request.form.get(self.name)
+        if parsed_keys:
+            if type(parsed_keys) is ListType:
+                self.value =  [value
+                               for value, description, key in self.options
+                               if key in parsed_keys] or None
+            else:
+                _marker = []
+                value = self._parse_single_selection(parsed_keys, _marker)
+                if value is _marker:
+                    self.value = None
+                else:
+                    self.value = [value]
+        else:
+            self.value = None
+
+
+class ButtonWidget(Widget):
+    """
+    Instance attributes:
+      label : string
+      value : boolean
+    """
+
+    HTML_TYPE = "button"
+
+    def __init__(self, name, value=None, **kwargs):
+        Widget.__init__(self, name, value=None, **kwargs)
+        self.set_label(value)
+
+    def set_label(self, label):
+        self.label = label
+
+    def get_label(self):
+        return self.label
+
+    def render_content(self):
+        # slightly different behavior here, we always render the
+        # tag using the 'value' passed in as a parameter.  'self.value'
+        # is a boolean that is true if the button's name appears
+        # in the request.
+        value = (self.label and htmlescape(self.label) or None)
+        return htmltag("input", xml_end=True, type=self.HTML_TYPE,
+                       name=self.name, value=value, **self.attrs)
+
+    def _parse(self, request):
+        self.value = request.form.has_key(self.name)
+
+
+class SubmitWidget(ButtonWidget):
+    HTML_TYPE = "submit"
+
+class ResetWidget(SubmitWidget):
+    HTML_TYPE = "reset"
+
+
+class HiddenWidget(Widget):
+    """
+    Instance attributes:
+      value : string
+    """
+
+    def set_error(self, error):
+        if error is not None:
+            raise TypeError, 'error not allowed on hidden widgets'
+
+    def render_content(self):
+        if self.value is None:
+            value = None
+        else:
+            value = htmlescape(self.value)
+        return htmltag("input", xml_end=True,
+                       type="hidden",
+                       name=self.name,
+                       value=value,
+                       **self.attrs)
+
+    def render(self):
+        return self.render_content() # Input elements of type hidden have no decoration.
+
+# -- Derived widget types ----------------------------------------------
+# (these don't correspond to fundamental widget types in HTML,
+# so they're separated)
+
+class NumberWidget(StringWidget):
+    """
+    Instance attributes: none
+    """
+
+    # Parameterize the number type (either float or int) through
+    # these class attributes:
+    TYPE_OBJECT = None                  # eg. int, float
+    TYPE_ERROR = None                   # human-readable error message
+    TYPE_CONVERTER = None               # eg. int(), float()
+
+    def __init__(self, name, value=None, **kwargs):
+        assert self.__class__ is not NumberWidget, "abstract class"
+        assert value is None or type(value) is self.TYPE_OBJECT, (
+            "form value '%s' not a %s: got %r" % (name,
+                                                  self.TYPE_OBJECT,
+                                                  value))
+        StringWidget.__init__(self, name, value, **kwargs)
+
+    def _parse(self, request):
+        StringWidget._parse(self, request)
+        if self.value is not None:
+            try:
+                self.value = self.TYPE_CONVERTER(self.value)
+            except ValueError:
+                self.error = self.TYPE_ERROR
+
+
+class FloatWidget(NumberWidget):
+    """
+    Instance attributes:
+      value : float
+    """
+    TYPE_OBJECT = FloatType
+    TYPE_CONVERTER = float
+    TYPE_ERROR = "must be a number"
+
+
+class IntWidget(NumberWidget):
+    """
+    Instance attributes:
+      value : int
+    """
+    TYPE_OBJECT = IntType
+    TYPE_CONVERTER = int
+    TYPE_ERROR = "must be an integer"
+
+
+class OptionSelectWidget(SingleSelectWidget):
+    """Widget for single selection with automatic submission. Parse
+    will always return a value from it's options, even if the form is
+    not submitted. This allows its value to be used to decide what
+    other widgets need to be created in a form.  It's a powerful
+    feature but it can be hard to understand what's going on.
+
+    Instance attributes:
+      value : any
+    """
+
+    SELECT_TYPE = "option_select"
+
+    def __init__(self, name, value=None, options=None, **kwargs):
+        SingleSelectWidget.__init__(self, name, value, options=options,
+                                    onchange='submit()', **kwargs)
+
+    def parse(self, request=None):
+        if not self._parsed:
+            if request is None:
+                request = get_request()
+            self._parse(request)
+            self._parsed = True
+        return self.value
+
+    def _parse(self, request):
+        parsed_key = request.form.get(self.name)
+        if parsed_key:
+            if type(parsed_key) is ListType:
+                self.error = "cannot select multiple values"
+            else:
+                self.value = self._parse_single_selection(parsed_key)
+        elif self.value is None:
+            self.value = self.options[0][0]
+
+    def render_content(self):
+        return (SingleSelectWidget.render_content(self) +
+                htmltext('<noscript>'
+                         '<input type="submit" name="" value="apply" />'
+                         '</noscript>'))
+
+
+class CompositeWidget(Widget):
+    """
+    Instance attributes:
+      widgets : [Widget]
+      _names : {name:string : Widget}
+    """
+    def __init__(self, name, value=None, **kwargs):
+        Widget.__init__(self, name, value, **kwargs)
+        self.widgets = []
+        self._names = {}
+
+    def _parse(self, request):
+        for widget in self.widgets:
+            widget.parse(request)
+
+    def __getitem__(self, name):
+        return self._names[name].parse()
+
+    def get(self, name):
+        widget = self._names.get(name)
+        if widget:
+            return widget.parse()
+        return None
+
+    def get_widget(self, name):
+        return self._names.get(name)
+
+    def get_widgets(self):
+        return self.widgets
+
+    def clear_error(self, request=None):
+        Widget.clear_error(self, request)
+        for widget in self.widgets:
+            widget.clear_error(request)
+
+    def set_widget_error(self, name, error):
+        self._names[name].set_error(error)
+
+    def has_error(self, request=None):
+        has_error = False
+        if Widget.has_error(self, request=request):
+            has_error = True
+        for widget in self.widgets:
+            if widget.has_error(request=request):
+                has_error = True
+        return has_error
+
+    def add(self, widget_class, name, *args, **kwargs):
+        if self._names.has_key(name):
+            raise ValueError, 'the name %r is already used' % name
+        widget = widget_class(subname(self.name, name), *args, **kwargs)
+        self._names[name] = widget
+        self.widgets.append(widget)
+
+    def render_content(self):
+        r = TemplateIO(html=True)
+        for widget in self.get_widgets():
+            r += widget.render()
+        return r.getvalue()
+
+
+class WidgetList(CompositeWidget):
+    """A variable length list of widgets.  There is only one
+    title and hint but each element of the list can have its own
+    error.  You can also set an error on the WidgetList itself (e.g. as a
+    result of higher-level processing).
+
+    Instance attributes:
+      element_names : [string]
+    """
+
+    def __init__(self, name, value=None,
+                 element_type=StringWidget,
+                 element_kwargs={},
+                 element_name="row", **kwargs):
+        assert value is None or type(value) is list, (
+            "value '%s' not a list: got %r" % (name, value))
+        assert issubclass(element_type, Widget), (
+            "value '%s' element_type not a Widget: "
+            "got %r" % (name, element_type))
+        assert type(element_kwargs) is dict, (
+            "value '%s' element_kwargs not a dict: "
+            "got %r" % (name, element_kwargs))
+        assert type(element_name) in (str, htmltext), (
+            "value '%s' element_name not a string: "
+            "got %r" % (name, element_name))
+
+        CompositeWidget.__init__(self, name, value, **kwargs)
+        self.element_names = []
+
+        self.add(HiddenWidget, 'added_elements')
+        added_elements_widget = self.get_widget('added_elements')
+
+
+        def add_element(value=None):
+            name = "element%d" % len(self.element_names)
+            self.add(element_type, name, value=value, **element_kwargs)
+            self.element_names.append(name)
+
+        # Add element widgets for initial value
+        if value is not None:
+            for element_value in value:
+                add_element(value=element_value)
+
+        # Add at least one additional element widget
+        num_added = int(added_elements_widget.parse() or 1)
+        for i in range(num_added):
+            add_element()
+
+        # Add submit to add more element widgets
+        self.add(SubmitWidget, 'add_element', value='Add %s' % element_name)
+        if self.get('add_element'):
+            add_element()
+            num_added += 1
+        added_elements_widget.set_value(num_added)
+
+    def _parse(self, request):
+        values = []
+        for name in self.element_names:
+            value = self.get(name)
+            if value is not None:
+                values.append(value)
+        self.value = values or None
+
+    def render_content(self):
+        r = TemplateIO(html=True)
+        add_element_widget = self.get_widget('add_element')
+        for widget in self.get_widgets():
+            if widget is add_element_widget:
+                continue
+            r += widget.render()
+        r += add_element_widget.render()
+        return r.getvalue()
+
+    def render(self):
+        r = TemplateIO(html=True)
+        r += self.render_title(self.get_title())
+        add_element_widget = self.get_widget('add_element')
+        for widget in self.get_widgets():
+            if widget is add_element_widget:
+                continue
+            r += widget.render()
+        r += add_element_widget.render()
+        r += self.render_hint(self.get_hint())
+        return r.getvalue()
+
+
+class WidgetDict(CompositeWidget):
+    """A variable length dict of widgets.  There is only one
+    title and hint but each element of the list can have its own
+    error.  You can also set an error on the WidgetList itself (e.g. as a
+    result of higher-level processing).
+
+    Instance attributes:
+      element_names : [string]
+    """
+
+    def __init__(self, name, value=None, title='', hint='',
+                 element_key_type=StringWidget,
+                 element_value_type=StringWidget,
+                 element_key_kwargs={},
+                 element_value_kwargs={},
+                 element_name='row', **kwargs):
+        assert value is None or type(value) is dict, (
+            'value %r not a dict: got %r' % (name, value))
+        assert issubclass(element_key_type, Widget), (
+            "value '%s' element_key_type not a Widget: "
+            "got %r" % (name, element_key_type))
+        assert issubclass(element_value_type, Widget), (
+            "value '%s' element_value_type not a Widget: "
+            "got %r" % (name, element_value_type))
+        assert type(element_key_kwargs) is dict, (
+            "value '%s' element_key_kwargs not a dict: "
+            "got %r" % (name, element_key_kwargs))
+        assert type(element_value_kwargs) is dict, (
+            "value '%s' element_value_kwargs not a dict: "
+            "got %r" % (name, element_value_kwargs))
+        assert type(element_name) in (str, htmltext), (
+            'value %r element_name not a string: '
+            'got %r' % (name, element_name))
+
+        CompositeWidget.__init__(self, name, value, **kwargs)
+        self.element_names = []
+
+        self.add(HiddenWidget, 'added_elements')
+        added_elements_widget = self.get_widget('added_elements')
+
+        def add_element(key=None, value=None):
+            name = 'element%d' % len(self.element_names)
+            self.add(element_key_type, name + 'key',
+                     value=key, render_br=False, **element_key_kwargs)
+            self.add(element_value_type, name + 'value',
+                     value=value, **element_value_kwargs)
+            self.element_names.append(name)
+
+        # Add element widgets for initial value
+        if value is not None:
+            for key, element_value in value.items():
+                add_element(key=key, value=element_value)
+
+        # Add at least one additional element widget
+        num_added = int(added_elements_widget.parse() or 1)
+        for i in range(num_added):
+            add_element()
+
+        # Add submit to add more element widgets
+        self.add(SubmitWidget, 'add_element', value='Add %s' % element_name)
+        if self.get('add_element'):
+            add_element()
+            num_added += 1
+        added_elements_widget.set_value(num_added)
+
+    def _parse(self, request):
+        values = {}
+        for name in self.element_names:
+            key = self.get(name + 'key')
+            value = self.get(name + 'value')
+            if key and value:
+                values[key] = value
+        self.value = values or None
+
+    def render_content(self):
+        r = TemplateIO(html=True)
+        for name in self.element_names:
+            if name in ('add_element', 'added_elements'):
+                continue
+            key_widget = self.get_widget(name + 'key')
+            value_widget = self.get_widget(name + 'value')
+            r += htmltext('%s<div class="widget">: </div>%s') % (
+                key_widget.render(),
+                value_widget.render())
+            if self.render_br:
+                r += htmltext('<br clear="left" class="widget" />')
+            r += htmltext('\n')
+        r += self.get_widget('add_element').render()
+        r += self.get_widget('added_elements').render()
+        return r.getvalue()

Added: packages/quixote1/branches/upstream/current/html.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/html.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/html.py (added)
+++ packages/quixote1/branches/upstream/current/html.py Mon May  8 19:16:16 2006
@@ -1,0 +1,185 @@
+"""Various functions for dealing with HTML.
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/html.py $
+$Id: html.py 25234 2004-09-30 17:36:19Z nascheme $
+
+These functions are fairly simple but it is critical that they be
+used correctly.  Many security problems are caused by quoting errors
+(cross site scripting is one example).  The HTML and XML standards on
+www.w3c.org and www.xml.com should be studied, especially the sections
+on character sets, entities, attribute and values.
+
+htmltext and htmlescape
+-----------------------
+
+This type and function are meant to be used with [html] PTL template type.
+The htmltext type designates data that does not need to be escaped and the
+htmlescape() function calls str() on the argment, escapes the resulting
+string and returns a htmltext instance.  htmlescape() does nothing to
+htmltext instances.
+
+
+html_quote
+----------
+
+Use for quoting data that will be used within attribute values or as
+element contents (if the [html] template type is not being used).
+Examples:
+
+    '<title>%s</title>' % html_quote(title)
+    '<input type="hidden" value="%s" />' % html_quote(data)
+    '<a href="%s">something</a>' % html_quote(url)
+
+Note that the \" character should be used to surround attribute values.
+
+
+url_quote
+---------
+
+Use for quoting data to be included as part of a URL, for example:
+
+    input = "foo bar"
+    ...
+    '<a href="/search?keyword=%s">' % url_quote(input)
+
+Note that URLs are usually used as attribute values and should be quoted
+using html_quote.  For example:
+
+    url = 'http://example.com/?a=1&copy=0'
+    ...
+    '<a href="%s">do something</a>' % html_quote(url)
+
+If html_quote is not used, old browsers would treat "&copy" as an entity
+reference and replace it with the copyright character.  XML processors should
+treat it as an invalid entity reference.
+
+"""
+
+__revision__ = "$Id: html.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import urllib
+from types import UnicodeType
+
+try:
+    # faster C implementation
+    from quixote._c_htmltext import htmltext, htmlescape, _escape_string, \
+        TemplateIO
+except ImportError:
+    from quixote._py_htmltext import htmltext, htmlescape, _escape_string, \
+        TemplateIO
+
+ValuelessAttr = ["valueless_attr"] # magic singleton object
+
+def htmltag(tag, xml_end=0, css_class=None, **attrs):
+    """Create a HTML tag.
+    """
+    r = ["<%s" % tag]
+    if css_class is not None:
+        attrs['class'] = css_class
+    for (attr, val) in attrs.items():
+        if val is ValuelessAttr:
+            val = attr
+        if val is not None:
+            r.append(' %s="%s"' % (attr, _escape_string(str(val))))
+    if xml_end:
+        r.append(" />")
+    else:
+        r.append(">")
+    return htmltext("".join(r))
+
+
+def href(url, text, title=None, **attrs):
+    return (htmltag("a", href=url, title=title, **attrs) +
+            htmlescape(text) +
+            htmltext("</a>"))
+
+
+def nl2br(value):
+    """nl2br(value : any) -> htmltext
+
+    Insert <br /> tags before newline characters.
+    """
+    text = htmlescape(value)
+    return htmltext(text.s.replace('\n', '<br />\n'))
+
+
+def url_quote(value, fallback=None):
+    """url_quote(value : any [, fallback : string]) -> string
+
+    Quotes 'value' for use in a URL; see urllib.quote().  If value is None,
+    then the behavior depends on the fallback argument.  If it is not
+    supplied then an error is raised.  Otherwise, the fallback value is
+    returned unquoted.
+    """
+    if value is None:
+        if fallback is None:
+            raise ValueError, "value is None and no fallback supplied"
+        else:
+            return fallback
+    if isinstance(value,  UnicodeType):
+        value = value.encode('iso-8859-1')
+    else:
+        value = str(value)
+    return urllib.quote(value)
+
+
+#
+# The rest of this module is for Quixote applications that were written
+# before 'htmltext'.  If you are writing a new application, ignore them.
+#
+
+def html_quote(value, fallback=None):
+    """html_quote(value : any [, fallback : string]) -> str
+
+    Quotes 'value' for use in an HTML page.  The special characters &,
+    <, > are replaced by SGML entities.  If value is None, then the
+    behavior depends on the fallback argument.  If it is not supplied
+    then an error is raised.  Otherwise, the fallback value is returned
+    unquoted.
+    """
+    if value is None:
+        if fallback is None:
+            raise ValueError, "value is None and no fallback supplied"
+        else:
+            return fallback
+    elif isinstance(value,  UnicodeType):
+        value = value.encode('iso-8859-1')
+    else:
+        value = str(value)
+    value = value.replace("&", "&amp;") # must be done first
+    value = value.replace("<", "&lt;")
+    value = value.replace(">", "&gt;")
+    value = value.replace('"', "&quot;")
+    return value
+
+
+def value_quote(value):
+    """Quote HTML attribute values.  This function is of marginal
+    utility since html_quote can be used.
+
+    XHTML 1.0 requires that all values be quoted.  weblint claims
+    that some clients don't understand single quotes.  For compatibility
+    with HTML, XHTML 1.0 requires that ampersands be encoded.
+    """
+    assert value is not None, "can't pass None to value_quote"
+    value = str(value).replace('&', '&amp;')
+    value = value.replace('"', '&quot;')
+    return '"%s"' % value
+
+
+def link(url, text, title=None, name=None, **kwargs):
+    return render_tag("a", href=url, title=title, name=name,
+                      **kwargs) + str(text) + "</a>"
+
+
+def render_tag(tag, xml_end=0, **attrs):
+    r = "<%s" % tag
+    for (attr, val) in attrs.items():
+        if val is ValuelessAttr:
+            r += ' %s="%s"' % (attr, attr)
+        elif val is not None:
+            r += " %s=%s" % (attr, value_quote(val))
+    if xml_end:
+        r += " />"
+    else:
+        r += ">"
+    return r

Added: packages/quixote1/branches/upstream/current/http_request.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/http_request.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/http_request.py (added)
+++ packages/quixote1/branches/upstream/current/http_request.py Mon May  8 19:16:16 2006
@@ -1,0 +1,522 @@
+"""quixote.http_request
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/http_request.py $
+$Id: http_request.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Provides the HTTPRequest class and related code for parsing HTTP
+requests, such as the FileUpload class.
+
+Derived from Zope's HTTPRequest module (hence the different
+copyright and license from the rest of Quixote).
+"""
+
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+__revision__ = "$Id: http_request.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import re
+import time
+import urlparse, urllib
+from cgi import FieldStorage
+from types import ListType
+
+from quixote.http_response import HTTPResponse
+from quixote.html import html_quote
+
+
+# Various regexes for parsing specific bits of HTTP, all from RFC 2616.
+
+# These are needed by 'get_encoding()', to parse the "Accept-Encoding"
+# header.  LWS is linear whitespace; the latter two assume that LWS
+# has been removed.
+_http_lws_re = re.compile("(\r\n)?[ \t]+")
+_http_list_re = re.compile(r",+")
+_http_encoding_re = re.compile(r"([^;]+)(;q=([\d.]+))?$")
+
+# These are needed by 'guess_browser_version()', for parsing the
+# "User-Agent" header.
+#   token = 1*<any CHAR except CTLs or separators>
+#   CHAR = any 7-bit US ASCII character (0-127)
+#   separators are  ( ) < > @ , ; : \ " / [ ] ? = { }
+#
+# The user_agent RE is a simplification; it only looks for one "product",
+# possibly followed by a comment.
+_http_token_pat = r'[^\x00-\x20\(\)\<\>\@\,\;\:\\\"\/\[\]\?\=\{\}\x7F-\xFF]+'
+_http_product_pat = r'(%s)(?:/(%s))?' % (_http_token_pat, _http_token_pat)
+_http_product_re = re.compile(_http_product_pat)
+_comment_delim_re = re.compile(r';\s*')
+
+
+def get_content_type(environ):
+    ctype = environ.get("CONTENT_TYPE")
+    if ctype:
+        return ctype.split(";")[0]
+    else:
+        return None
+
+
+class HTTPRequest:
+    """
+    Model a single HTTP request and all associated data: environment
+    variables, form variables, cookies, etc.
+
+    To access environment variables associated with the request, use
+    get_environ(): eg. request.get_environ('SERVER_PORT', 80).
+
+    To access form variables, use get_form_var(), eg.
+    request.get_form_var("name").
+
+    To access cookies, use get_cookie().
+
+    Various bits and pieces of the requested URL can be accessed with
+    get_url(), get_path(), get_server()
+
+    The HTTPResponse object corresponding to this request is available
+    in the 'response' attribute.  This is rarely needed: eg. to send an
+    error response, you should raise one of the exceptions in errors.py;
+    to send a redirect, you should use the request's redirect() method,
+    which lets you specify relative URLs.  However, if you need to tweak
+    the response object in other ways, you can do so via 'response'.
+    Just keep in mind that Quixote discards the original response object
+    when handling an exception.
+    """
+
+    def __init__(self, stdin, environ, content_type=None):
+        self.stdin = stdin
+        self.environ = environ
+        if content_type is None:
+            self.content_type = get_content_type(environ)
+        else:
+            self.content_type = content_type
+        self.form = {}
+        self.session = None
+        self.response = HTTPResponse()
+        self.start_time = None
+
+        # The strange treatment of SERVER_PORT_SECURE is because IIS
+        # sets this environment variable to "0" for non-SSL requests
+        # (most web servers -- well, Apache at least -- simply don't set
+        # it in that case).
+        if (environ.get('HTTPS', 'off').lower() == 'on' or
+            environ.get('SERVER_PORT_SECURE', '0') != '0'):
+            self.scheme = "https"
+        else:
+            self.scheme = "http"
+
+        k = self.environ.get('HTTP_COOKIE', '')
+        if k:
+            self.cookies = parse_cookie(k)
+        else:
+            self.cookies = {}
+
+        # IIS breaks PATH_INFO because it leaves in the path to
+        # the script, so SCRIPT_NAME is "/cgi-bin/q.py" and PATH_INFO
+        # is "/cgi-bin/q.py/foo/bar".  The following code fixes
+        # PATH_INFO to the expected value "/foo/bar".
+        web_server = environ.get('SERVER_SOFTWARE', 'unknown')
+        if web_server.find('Microsoft-IIS') != -1:
+            script = environ['SCRIPT_NAME']
+            path = environ['PATH_INFO']
+            if path.startswith(script):
+                path = path[len(script):]
+                self.environ['PATH_INFO'] = path
+
+    def add_form_value(self, key, value):
+        if self.form.has_key(key):
+            found = self.form[key]
+            if type(found) is ListType:
+                found.append(value)
+            else:
+                found = [found, value]
+                self.form[key] = found
+        else:
+            self.form[key] = value
+
+    def process_inputs(self):
+        """Process request inputs.
+        """
+        self.start_time = time.time()
+        if self.get_method() != 'GET':
+            # Avoid consuming the contents of stdin unless we're sure
+            # there's actually form data.
+            if self.content_type == "multipart/form-data":
+                raise RuntimeError(
+                    "cannot handle multipart/form-data requests")
+            elif self.content_type == "application/x-www-form-urlencoded":
+                fp = self.stdin
+            else:
+                return
+        else:
+            fp = None
+
+        fs = FieldStorage(fp=fp, environ=self.environ, keep_blank_values=1)
+        if fs.list:
+            for item in fs.list:
+                self.add_form_value(item.name, item.value)
+
+    def get_header(self, name, default=None):
+        """get_header(name : string, default : string = None) -> string
+
+        Return the named HTTP header, or an optional default argument
+        (or None) if the header is not found.  Note that both original
+        and CGI-ified header names are recognized, e.g. 'Content-Type',
+        'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE' should all return the
+        Content-Type header, if available.
+        """
+        environ = self.environ
+        name = name.replace("-", "_").upper()
+        val = environ.get(name)
+        if val is not None:
+            return val
+        if name[:5] != 'HTTP_':
+            name = 'HTTP_' + name
+        return environ.get(name, default)
+
+    def get_cookie(self, cookie_name, default=None):
+        return self.cookies.get(cookie_name, default)
+
+    def get_form_var(self, var_name, default=None):
+        return self.form.get(var_name, default)
+
+    def get_method(self):
+        """Returns the HTTP method for this request
+        """
+        return self.environ.get('REQUEST_METHOD', 'GET')
+
+    def formiter(self):
+        return self.form.iteritems()
+
+    def get_scheme(self):
+        return self.scheme
+
+    # The following environment variables are useful for reconstructing
+    # the original URL, all of which are specified by CGI 1.1:
+    #
+    #   SERVER_NAME            "www.example.com"
+    #   SCRIPT_NAME            "/q"
+    #   PATH_INFO              "/debug/dump_sessions"
+    #   QUERY_STRING           "session_id=10.27.8.40...."
+
+    def get_server(self):
+        """get_server() -> string
+
+        Return the server name with an optional port number, eg.
+        "www.example.com" or "foo.bar.com:8000".
+        """
+        http_host = self.environ.get("HTTP_HOST")
+        if http_host:
+            return http_host
+        server_name = self.environ["SERVER_NAME"].strip()
+        server_port = self.environ.get("SERVER_PORT")
+        if (not server_port or
+            (self.get_scheme() == "http" and server_port == "80") or
+            (self.get_scheme() == "https" and server_port == "443")):
+            return server_name
+        else:
+            return server_name + ":" + server_port
+
+    def get_path(self, n=0):
+        """get_path(n : int = 0) -> string
+
+        Return the path of the current request, chopping off 'n' path
+        components from the right.  Eg. if the path is "/bar/baz/qux",
+        n=0 would return "/bar/baz/qux" and n=2 would return "/bar".
+        Note that the query string, if any, is not included.
+
+        A path with a trailing slash should just be considered as having
+        an empty last component.  Eg. if the path is "/bar/baz/", then:
+          get_path(0) == "/bar/baz/"
+          get_path(1) == "/bar/baz"
+          get_path(2) == "/bar"
+
+        If 'n' is negative, then components from the left of the path
+        are returned.  Continuing the above example,
+          get_path(-1) = "/bar"
+          get_path(-2) = "/bar/baz"
+          get_path(-3) = "/bar/baz/"
+
+        Raises ValueError if absolute value of n is larger than the number of
+        path components."""
+
+        path_info = self.environ.get('PATH_INFO', '')
+        path = self.environ['SCRIPT_NAME'] + path_info
+        if n == 0:
+            return path
+        else:
+            path_comps = path.split('/')
+            if abs(n) > len(path_comps)-1:
+                raise ValueError, "n=%d too big for path '%s'" % (n, path)
+            if n > 0:
+                return '/'.join(path_comps[:-n])
+            elif n < 0:
+                return '/'.join(path_comps[:-n+1])
+            else:
+                assert 0, "Unexpected value for n (%s)" % n
+
+    def get_url(self, n=0):
+        """get_url(n : int = 0) -> string
+
+        Return the URL of the current request, chopping off 'n' path
+        components from the right.  Eg. if the URL is
+        "http://foo.com/bar/baz/qux", n=2 would return
+        "http://foo.com/bar".  Does not include the query string (if
+        any).
+        """
+        return "%s://%s%s" % (self.get_scheme(), self.get_server(),
+                              urllib.quote(self.get_path(n)))
+
+    def get_environ(self, key, default=None):
+        """get_environ(key : string) -> string
+
+        Fetch a CGI environment variable from the request environment.
+        See http://hoohoo.ncsa.uiuc.edu/cgi/env.html
+        for the variables specified by the CGI standard.
+        """
+        return self.environ.get(key, default)
+
+    def get_encoding(self, encodings):
+        """get_encoding(encodings : [string]) -> string
+
+        Parse the "Accept-encoding" header. 'encodings' is a list of
+        encodings supported by the server sorted in order of preference.
+        The return value is one of 'encodings' or None if the client
+        does not accept any of the encodings.
+        """
+        accept_encoding = self.get_header("accept-encoding") or ""
+        found_encodings = self._parse_pref_header(accept_encoding)
+        if found_encodings:
+            for encoding in encodings:
+                if found_encodings.has_key(encoding):
+                    return encoding
+        return None
+
+    def get_accepted_types(self):
+        """get_accepted_types() : {string:float}
+        Return a dictionary mapping MIME types the client will accept
+        to the corresponding quality value (1.0 if no value was specified).
+        """
+        accept_types = self.environ.get('HTTP_ACCEPT', "")
+        return self._parse_pref_header(accept_types)
+
+
+    def _parse_pref_header(self, S):
+        """_parse_pref_header(S:string) : {string:float}
+        Parse a list of HTTP preferences (content types, encodings) and
+        return a dictionary mapping strings to the quality value.
+        """
+
+        found = {}
+        # remove all linear whitespace
+        S = _http_lws_re.sub("", S)
+        for coding in _http_list_re.split(S):
+            m = _http_encoding_re.match(coding)
+            if m:
+                encoding = m.group(1).lower()
+                q = m.group(3) or 1.0
+                try:
+                    q = float(q)
+                except ValueError:
+                    continue
+                if encoding == "*":
+                    continue # stupid, ignore it
+                if q > 0:
+                    found[encoding] = q
+        return found
+
+
+    def dump_html(self):
+        row_fmt=('<tr valign="top"><th align="left">%s</th><td>%s</td></tr>')
+        lines = ["<h3>form</h3>",
+                 "<table>"]
+
+        for k,v in self.form.items():
+            lines.append(row_fmt % (html_quote(k), html_quote(v)))
+        lines += ["</table>",
+                  "<h3>cookies</h3>",
+                  "<table>"]
+        for k,v in self.cookies.items():
+            lines.append(row_fmt % (html_quote(k), html_quote(v)))
+
+        lines += ["</table>",
+                  "<h3>environ</h3>"
+                  "<table>"]
+        for k,v in self.environ.items():
+            lines.append(row_fmt % (html_quote(k), html_quote(str(v))))
+        lines.append("</table>")
+
+        return "\n".join(lines)
+
+    def dump(self):
+        result=[]
+        row='%-15s %s'
+
+        result.append("Form:")
+        L = self.form.items() ; L.sort()
+        for k,v in L:
+            result.append(row % (k,v))
+
+        result.append("")
+        result.append("Cookies:")
+        L = self.cookies.items() ; L.sort()
+        for k,v in L:
+            result.append(row % (k,v))
+
+
+        result.append("")
+        result.append("Environment:")
+        L = self.environ.items() ; L.sort()
+        for k,v in L:
+            result.append(row % (k,v))
+        return "\n".join(result)
+
+    def guess_browser_version(self):
+        """guess_browser_version() -> (name : string, version : string)
+
+        Examine the User-agent request header to try to figure out what
+        the current browser is.  Returns either (name, version) where
+        each element is a string, (None, None) if we couldn't parse the
+        User-agent header at all, or (name, None) if we got the name but
+        couldn't figure out the version.
+
+        Handles Microsoft's little joke of pretending to be Mozilla,
+        eg. if the "User-Agent" header is
+          Mozilla/5.0 (compatible; MSIE 5.5)
+        returns ("MSIE", "5.5").  Konqueror does the same thing, and
+        it's handled the same way.
+        """
+        ua = self.get_header('user-agent')
+        if ua is None:
+            return (None, None)
+
+        # The syntax for "User-Agent" in RFC 2616 is fairly simple:
+        #
+        #  User-Agent      = "User-Agent" ":" 1*( product | comment )
+        #  product         = token ["/" product-version ]
+        #  product-version = token
+        #  comment         = "(" *( ctext | comment ) ")"
+        #  ctext           = <any TEXT excluding "(" and ")">
+        #  token           = 1*<any CHAR except CTLs or tspecials>
+        #  tspecials       = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" |
+        #                    "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" |
+        #                    "}" | SP | HT
+        #
+        # This function handles the most-commonly-used subset of this syntax,
+        # namely
+        #   User-Agent = "User-Agent" ":" product 1*SP [comment]
+        # ie. one product string followed by an optional comment;
+        # anything after that first comment is ignored.  This should be
+        # enough to distinguish Mozilla/Netscape, MSIE, Opera, and
+        # Konqueror.
+
+        m = _http_product_re.match(ua)
+        if not m:
+            import sys
+            sys.stderr.write("couldn't parse User-Agent header: %r\n" % ua)
+            return (None, None)
+
+        name, version = m.groups()
+        ua = ua[m.end():].lstrip()
+
+        if ua.startswith('('):
+            # we need to handle nested comments since MSIE uses them
+            depth = 1
+            chars = []
+            for c in ua[1:]:
+                if c == '(':
+                    depth += 1
+                elif c == ')':
+                    depth -= 1
+                    if depth == 0:
+                        break
+                elif depth == 1:
+                    # nested comments are discarded
+                    chars.append(c)
+            comment = ''.join(chars)
+        else:
+            comment = ''
+        if comment:
+            comment_chunks = _comment_delim_re.split(comment)
+        else:
+            comment_chunks = []
+
+        if ("compatible" in comment_chunks and
+            len(comment_chunks) > 1 and comment_chunks[1]):
+            # A-ha!  Someone is kidding around, pretending to be what
+            # they are not.  Most likely MSIE masquerading as Mozilla,
+            # but lots of other clients (eg. Konqueror) do the same.
+            real_ua = comment_chunks[1]
+            if "/" in real_ua:
+                (name, version) = real_ua.split("/", 1)
+            else:
+                if real_ua.startswith("MSIE") and ' ' in real_ua:
+                    (name, version) = real_ua.split(" ", 1)
+                else:
+                    name = real_ua
+                    version = None
+            return (name, version)
+
+        # Either nobody is pulling our leg, or we didn't find anything
+        # that looks vaguely like a user agent in the comment.  So use
+        # what we found outside the comment, ie. what the spec says we
+        # should use (sigh).
+        return (name, version)
+
+    # guess_browser_version ()
+
+    def redirect(self, location, permanent=0):
+        """redirect(location : string, permanent : boolean = false)
+           -> string
+
+        Create a redirection response.  If the location is relative, then it
+        will automatically be made absolute.  The return value is an HTML
+        document indicating the new URL (useful if the client browser does
+        not honor the redirect).
+        """
+        location = urlparse.urljoin(self.get_url(), location)
+        return self.response.redirect(location, permanent)
+
+
+_qparm_re = re.compile(r'([\0- ]*'
+                       r'([^\0- ;,=\"]+)="([^"]*)"'
+                       r'([\0- ]*[;,])?[\0- ]*)')
+_parm_re = re.compile(r'([\0- ]*'
+                      r'([^\0- ;,="]+)=([^\0- ;,"]*)'
+                      r'([\0- ]*[;,])?[\0- ]*)')
+
+def parse_cookie(text):
+    result = {}
+
+    pos = 0
+    while 1:
+        mq = _qparm_re.match(text, pos)
+        m = _parm_re.match(text, pos)
+        if mq is not None:
+            # Match quoted correct cookies
+            name = mq.group(2)
+            value = mq.group(3)
+            pos = mq.end()
+        elif m is not None:
+            # Match evil MSIE cookies ;)
+            name = m.group(2)
+            value = m.group(3)
+            pos = m.end()
+        else:
+            # this may be an invalid cookie.
+            # We'll simply bail without raising an error
+            # if the cookie is invalid.
+            return result
+
+        if not result.has_key(name):
+            result[name] = value
+
+    return result

Added: packages/quixote1/branches/upstream/current/http_response.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/http_response.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/http_response.py (added)
+++ packages/quixote1/branches/upstream/current/http_response.py Mon May  8 19:16:16 2006
@@ -1,0 +1,421 @@
+"""quixote.http_response
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/http_response.py $
+$Id: http_response.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Provides the HTTPResponse class.
+
+Derived from Zope's HTTPResponse module (hence the different
+copyright and license from the rest of Quixote).
+"""
+
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+__revision__ = "$Id: http_response.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import time
+from rfc822 import formatdate
+from types import StringType, IntType
+
+status_reasons = {
+    100: 'Continue',
+    101: 'Switching Protocols',
+    102: 'Processing',
+    200: 'OK',
+    201: 'Created',
+    202: 'Accepted',
+    203: 'Non-Authoritative Information',
+    204: 'No Content',
+    205: 'Reset Content',
+    206: 'Partial Content',
+    207: 'Multi-Status',
+    300: 'Multiple Choices',
+    301: 'Moved Permanently',
+    302: 'Moved Temporarily',
+    303: 'See Other',
+    304: 'Not Modified',
+    305: 'Use Proxy',
+    307: 'Temporary Redirect',
+    400: 'Bad Request',
+    401: 'Unauthorized',
+    402: 'Payment Required',
+    403: 'Forbidden',
+    404: 'Not Found',
+    405: 'Method Not Allowed',
+    406: 'Not Acceptable',
+    407: 'Proxy Authentication Required',
+    408: 'Request Time-out',
+    409: 'Conflict',
+    410: 'Gone',
+    411: 'Length Required',
+    412: 'Precondition Failed',
+    413: 'Request Entity Too Large',
+    414: 'Request-URI Too Large',
+    415: 'Unsupported Media Type',
+    416: 'Requested range not satisfiable',
+    417: 'Expectation Failed',
+    422: 'Unprocessable Entity',
+    423: 'Locked',
+    424: 'Failed Dependency',
+    500: 'Internal Server Error',
+    501: 'Not Implemented',
+    502: 'Bad Gateway',
+    503: 'Service Unavailable',
+    504: 'Gateway Time-out',
+    505: 'HTTP Version not supported',
+    507: 'Insufficient Storage',
+}
+
+
+class HTTPResponse:
+    """
+    An object representation of an HTTP response.
+
+    The Response type encapsulates all possible responses to HTTP
+    requests.  Responses are normally created by the Quixote publisher
+    or by the HTTPRequest class (every request must have a response,
+    after all).
+
+    Instance attributes:
+      status_code : int
+        HTTP response status code (integer between 100 and 599)
+      reason_phrase : string
+        the reason phrase that accompanies status_code (usually
+        set automatically by the set_status() method)
+      headers : { string : string }
+        most of the headers included with the response; every header set
+        by 'set_header()' goes here.  Does not include "Status" or
+        "Set-Cookie" headers (unless someone uses set_header() to set
+        them, but that would be foolish).
+      body : string
+        the response body, None by default.  If the body is never
+        set (ie. left as None), the response will not include
+        "Content-type" or "Content-length" headers.  These headers
+        are set as soon as the body is set (with set_body()), even
+        if the body is an empty string.
+      buffered : bool
+        if false, response data will be flushed as soon as it is
+        written (the default is true).  This is most useful for
+        responses that use the Stream() protocol.  Note that whether the
+        client actually receives the partial response data is highly
+        dependent on the web server
+      cookies : { name:string : { attrname : value } }
+        collection of cookies to set in this response; it is expected
+        that the user-agent will remember the cookies and send them on
+        future requests.  The cookie value is stored as the "value"
+        attribute.  The other attributes are as specified by RFC 2109.
+      cache : int | None
+        the number of seconds the response may be cached.  The default is 0,
+        meaning don't cache at all.  This variable is used to set the HTTP
+        expires header.  If set to None then the expires header will not be
+        added.
+      javascript_code : { string : string }
+        a collection of snippets of JavaScript code to be included in
+        the response.  The collection is built by calling add_javascript(),
+        but actually including the code in the HTML document is somebody
+        else's problem.
+    """
+
+    def __init__(self, status=200, body=None):
+        """
+        Creates a new HTTP response.
+        """
+        self.set_status(status)
+        self.headers = {}
+
+        if body is not None:
+            self.set_body(body)
+        else:
+            self.body = None
+
+        self.cookies = {}
+        self.cache = 0
+        self.buffered = 1
+        self.javascript_code = None
+
+    def set_status(self, status, reason=None):
+        """set_status(status : int, reason : string = None)
+
+        Sets the HTTP status code of the response.  'status' must be an
+        integer in the range 100 .. 599.  'reason' must be a string; if
+        not supplied, the default reason phrase for 'status' will be
+        used.  If 'status' is a non-standard status code, the generic
+        reason phrase for its group of status codes will be used; eg.
+        if status == 493, the reason for status 400 will be used.
+        """
+        if type(status) is not IntType:
+            raise TypeError, "status must be an integer"
+        if not (100 <= status <= 599):
+            raise ValueError, "status must be between 100 and 599"
+
+        self.status_code = status
+        if reason is None:
+            if status_reasons.has_key(status):
+                reason = status_reasons[status]
+            else:
+                # Eg. for generic 4xx failures, use the reason
+                # associated with status 400.
+                reason = status_reasons[status - (status % 100)]
+        else:
+            reason = str(reason)
+
+        self.reason_phrase = reason
+
+    def set_header(self, name, value):
+        """set_header(name : string, value : string)
+
+        Sets an HTTP return header "name" with value "value", clearing
+        the previous value set for the header, if one exists.
+        """
+        self.headers[name.lower()] = value
+
+    def get_header(self, name, default=None):
+        """get_header(name : string, default=None) -> value : string
+
+        Gets an HTTP return header "name".  If none exists then 'default' is
+        returned.
+        """
+        return self.headers.get(name.lower(), default)
+
+    def set_content_type(self, ctype):
+        """set_content_type(ctype : string)
+
+        Set the "Content-type" header to the MIME type specified in ctype.
+        Shortcut for set_header("Content-type", ctype).
+        """
+        self.headers["content-type"] = ctype
+
+    def set_body(self, body):
+        """set_body(body : any)
+
+        Sets the return body equal to the argument "body". Also updates the
+        "Content-length" header if the length is of the body is known.  If
+        the "Content-type" header has not yet been set, it is set to
+        "text/html".
+        """
+        if isinstance(body, Stream):
+            self.body = body
+            if body.length is not None:
+                self.set_header('content-length', body.length)
+        else:
+            self.body = str(body)
+            self.set_header('content-length', len(self.body))
+        if not self.headers.has_key('content-type'):
+            self.set_header('content-type', 'text/html; charset=iso-8859-1')
+
+    def expire_cookie(self, name, **attrs):
+        """
+        Cause an HTTP cookie to be removed from the browser
+
+        The response will include an HTTP header that will remove the cookie
+        corresponding to "name" on the client, if one exists.  This is
+        accomplished by sending a new cookie with an expiration date
+        that has already passed.  Note that some clients require a path
+        to be specified - this path must exactly match the path given
+        when creating the cookie.  The path can be specified as a keyword
+        argument.
+        """
+        dict = {'max_age': 0, 'expires': 'Thu, 01-Jan-1970 00:00:00 GMT'}
+        dict.update(attrs)
+        self.set_cookie(name, "deleted", **dict)
+
+    def set_cookie(self, name, value, **attrs):
+        """set_cookie(name : string, value : string, **attrs)
+
+        Set an HTTP cookie on the browser.
+
+        The response will include an HTTP header that sets a cookie on
+        cookie-enabled browsers with a key "name" and value "value".
+        Cookie attributes such as "expires" and "domains" may be
+        supplied as keyword arguments; see RFC 2109 for a full list.
+        (For the "secure" attribute, use any true value.)
+
+        This overrides any previous value for this cookie.  Any
+        previously-set attributes for the cookie are preserved, unless
+        they are explicitly overridden with keyword arguments to this
+        call.
+        """
+        cookies = self.cookies
+        if cookies.has_key(name):
+            cookie = cookies[name]
+        else:
+            cookie = cookies[name] = {}
+        cookie.update(attrs)
+        cookie['value'] = value
+
+    def add_javascript(self, code_id, code):
+        """Add javascript code to be included in the response.
+
+        code_id is used to ensure that the same piece of code is not
+        included twice.  The caller must be careful to avoid
+        unintentional code_id and javascript identifier collisions.
+        Note that the response object only provides a mechanism for
+        collecting code -- actually including it in the HTML document
+        that is the response body is somebody else's problem.  (For
+        an example, see Form._render_javascript().)
+        """
+        if self.javascript_code is None:
+            self.javascript_code = {code_id: code}
+        elif not self.javascript_code.has_key(code_id):
+            self.javascript_code[code_id] = code
+
+    def redirect(self, location, permanent=0):
+        """Cause a redirection without raising an error"""
+        if not isinstance(location, StringType):
+            raise TypeError, "location must be a string (got %s)" % `location`
+        # Ensure that location is a full URL
+        if location.find('://') == -1:
+            raise ValueError, "URL must include the server name"
+        if permanent:
+            status = 301
+        else:
+            status = 302
+        self.set_status(status)
+        self.headers['location'] = location
+        self.set_content_type('text/plain')
+        return "Your browser should have redirected you to %s" % location
+
+    def _gen_cookie_headers(self):
+        """_gen_cookie_headers() -> [string]
+
+        Build a list of "Set-Cookie" headers based on all cookies
+        set with 'set_cookie()', and return that list.
+        """
+        cookie_list = []
+        for (name, attrs) in self.cookies.items():
+
+            # Note that as of May 98, IE4 ignores cookies with
+            # quoted cookie attr values, so only the value part
+            # of name=value pairs may be quoted.
+
+            # 'chunks' is a list of "name=val" chunks; will be joined
+            # with "; " to create the "Set-cookie" header.
+            chunks = ['%s="%s"' % (name, attrs['value'])]
+
+            for (name, val) in attrs.items():
+                name = name.lower()
+                if val is None:
+                    continue
+                if name in ('expires', 'domain', 'path', 'max_age', 'comment'):
+                    name = name.replace('_', '-')
+                    chunks.append("%s=%s" % (name, val))
+                elif name == 'secure' and val:
+                    chunks.append("secure")
+
+            cookie_list.append(("Set-Cookie", ("; ".join(chunks))))
+
+        # Should really check size of cookies here!
+
+        return cookie_list
+
+    def generate_headers(self):
+        """generate_headers() -> [(name:string, value:string)]
+
+        Generate a list of headers to be returned as part of the response.
+        """
+        headers = []
+
+        # "Status" header must come first.
+        headers.append(("Status", "%03d %s" % (self.status_code,
+                                               self.reason_phrase)))
+
+        for name, value in self.headers.items():
+            headers.append((name.title(), value))
+
+        # All the "Set-Cookie" headers.
+        if self.cookies:
+            headers.extend(self._gen_cookie_headers())
+
+        # Date header
+        now = time.time()
+        if not self.headers.has_key("date"):
+            headers.append(("Date", formatdate(now)))
+
+        # Cache directives
+        if self.cache is None:
+            pass # don't mess with the expires header
+        elif not self.headers.has_key("expires"):
+            if self.cache > 0:
+                expire_date = formatdate(now + self.cache)
+            else:
+                expire_date = "-1" # allowed by HTTP spec and may work better
+                                   # with some clients
+            headers.append(("Expires", expire_date))
+
+        return headers
+
+
+    def write(self, file):
+        """write(file : file)
+
+        Write the HTTP response headers and body to 'file'.  This is not
+        a complete HTTP response, as it doesn't start with a response
+        status line as specified by RFC 2616.  It does, however, start
+        with a "Status" header as described by the CGI spec.  It
+        is expected that this response is parsed by the web server
+        and turned into a complete HTTP response.
+        """
+        # XXX currently we write a response like this:
+        #  Status: 200 OK
+        #  Content-type: text/html; charset=iso-8859-1
+        #  Content-length: 100
+        #  Set-Cookie: foo=bar
+        #  Set-Cookie: bar=baz
+        #
+        #  <html><body>This is a document</body></html>
+        #
+        # which has to be interpreted by the web server to create
+        # a true HTTP response -- that is, this is for a
+        # "parsed header" CGI driver script.
+        #
+        # We should probably have provisions for operating in
+        # "non-parsed header" mode, where the CGI script is responsible
+        # for generating a complete HTTP response with no help from the
+        # server.
+        flush_output = not self.buffered and hasattr(file, 'flush')
+        for name, value in self.generate_headers():
+            file.write("%s: %s\r\n" % (name, value))
+        file.write("\r\n")
+        if self.body is not None:
+            if isinstance(self.body, Stream):
+                for chunk in self.body:
+                    file.write(chunk)
+                    if flush_output:
+                        file.flush()
+            else:
+                file.write(self.body)
+        if flush_output:
+            file.flush()
+
+
+class Stream:
+    """
+    A wrapper around response data that can be streamed.  The 'iterable'
+    argument must support the iteration protocol.  Items returned by 'next()'
+    must be strings.  Beware that exceptions raised while writing the stream
+    will not be handled gracefully.
+
+    Instance attributes:
+      iterable : any
+        an object that supports the iteration protocol.  The items produced
+        by the stream must be strings.
+      length: int | None
+        the number of bytes that will be produced by the stream, None
+        if it is not known.  Used to set the Content-Length header.
+    """
+    def __init__(self, iterable, length=None):
+        self.iterable = iterable
+        self.length = length
+
+    def __iter__(self):
+        return iter(self.iterable)

Added: packages/quixote1/branches/upstream/current/mod_python_handler.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/mod_python_handler.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/mod_python_handler.py (added)
+++ packages/quixote1/branches/upstream/current/mod_python_handler.py Mon May  8 19:16:16 2006
@@ -1,0 +1,81 @@
+"""quixote.mod_python_handler
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/mod_python_handler.py $
+$Id: mod_python_handler.py 25236 2004-09-30 18:25:52Z nascheme $
+
+mod_python handler for Quixote.  See the "mod_python configuration"
+section of doc/web-server.txt for details.
+"""
+
+import sys
+from mod_python import apache
+from quixote import Publisher, enable_ptl
+from quixote.config import Config
+
+class ErrorLog:
+    def __init__(self, publisher):
+        self.publisher = publisher
+
+    def write(self, msg):
+        self.publisher.log(msg)
+
+    def close(self):
+        pass
+
+class ModPythonPublisher(Publisher):
+    def __init__(self, package, config=None):
+        Publisher.__init__(self, package, config)
+        self.error_log = self.__error_log = ErrorLog(self) # may be overwritten
+        self.setup_logs()
+        self.__apache_request = None
+
+    def log(self, msg):
+        if self.error_log is self.__error_log:
+            try:
+                self.__apache_request.log_error(msg)
+            except AttributeError:
+                apache.log_error(msg)
+        else:
+            Publisher.log(self, msg)
+
+    def publish_modpython(self, req):
+        """publish_modpython() -> None
+
+        Entry point from mod_python.
+        """
+        self.__apache_request = req
+        try:
+            self.publish(apache.CGIStdin(req),
+                         apache.CGIStdout(req),
+                         sys.stderr,
+                         apache.build_cgi_env(req))
+
+            return apache.OK
+        finally:
+            self.__apache_request = None
+
+enable_ptl()
+
+name2publisher = {}
+
+def handler(req):
+    opts = req.get_options()
+    try:
+        package = opts['quixote-root-namespace']
+    except KeyError:
+        package = None
+
+    try:
+        configfile = opts['quixote-config-file']
+        config = Config()
+        config.read_file(configfile)
+    except KeyError:
+        config = None
+
+    if not package:
+        return apache.HTTP_INTERNAL_SERVER_ERROR
+
+    pub = name2publisher.get(package)
+    if pub is None:
+        pub = ModPythonPublisher(package, config)
+        name2publisher[package] = pub
+    return pub.publish_modpython(req)

Added: packages/quixote1/branches/upstream/current/ptl_compile.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/ptl_compile.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/ptl_compile.py (added)
+++ packages/quixote1/branches/upstream/current/ptl_compile.py Mon May  8 19:16:16 2006
@@ -1,0 +1,379 @@
+#!/www/python/bin/python
+#$HeadURL: svn+ssh://svn/repos/trunk/quixote/ptl_compile.py $
+#$Id: ptl_compile.py 25234 2004-09-30 17:36:19Z nascheme $
+
+"""
+Compile a PTL template.
+
+First the tokens "template" are replaced with "def".  Next, the file is
+parsed into a parse tree.  This tree is converted into a modified AST.
+It is during this state that the semantics are modified by adding extra
+nodes to the tree.  Finally bytecode is generated using the compiler
+package.
+
+Note that script/module requires the compiler package.
+"""
+
+__revision__ = "$Id: ptl_compile.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import sys
+import os
+import stat
+import symbol
+import token
+import parser
+import re
+
+assert sys.hexversion >= 0x20000b1, 'PTL requires Python 2.0 or newer'
+
+from compiler import pycodegen, transformer, walk
+from compiler import ast
+from compiler.consts import OP_ASSIGN
+if sys.hexversion >= 0x20200b1:
+    from compiler import misc, syntax
+
+
+# magic names inserted into the code
+IO_MODULE = "quixote.html"
+IO_CLASS = "TemplateIO"
+IO_INSTANCE = "_q_output"
+HTML_TEMPLATE_PREFIX = "_q_html_template_"
+PLAIN_TEMPLATE_PREFIX = "_q_plain_template_"
+TEMPLATE_PREFIX = "_q_template_"
+MARKUP_MODULE = "quixote.html"
+MARKUP_CLASS = "htmltext"
+MARKUP_MANGLED_CLASS = "_q_htmltext"
+
+class TemplateTransformer(transformer.Transformer):
+
+    def __init__(self, *args, **kwargs):
+        transformer.Transformer.__init__(self, *args, **kwargs)
+        self.__template_type = [] # stack, "html", "plain" or None
+
+    def file_input(self, nodelist):
+        # Add a "from IO_MODULE import IO_CLASS" statement to the
+        # beginning of the module.
+        doc = None # self.get_docstring(nodelist, symbol.file_input)
+
+        io_imp = ast.From(IO_MODULE, [(IO_CLASS, None)])
+        markup_imp = ast.From(MARKUP_MODULE, [(MARKUP_CLASS, None)])
+        markup_assign = ast.Assign([ast.AssName(MARKUP_MANGLED_CLASS,
+                                                OP_ASSIGN)],
+                                   ast.Name(MARKUP_CLASS))
+
+        # Add an IO_INSTANCE binding for module level expressions (like
+        # doc strings).  This instance will not be returned.
+        io_instance = ast.CallFunc(ast.Name(IO_CLASS), [])
+        io_assign_name = ast.AssName(IO_INSTANCE, OP_ASSIGN)
+        io_assign = ast.Assign([io_assign_name], io_instance)
+
+        stmts = [ io_imp, io_assign, markup_imp, markup_assign ]
+
+        for node in nodelist:
+            if node[0] != token.ENDMARKER and node[0] != token.NEWLINE:
+                self.com_append_stmt(stmts, node)
+
+        return ast.Module(doc, ast.Stmt(stmts))
+
+    def funcdef(self, nodelist):
+        if len(nodelist) == 6:
+            assert nodelist[0][0] == symbol.decorators
+            decorators = self.decorators(nodelist[0][1:])
+        else:
+            assert len(nodelist) == 5
+            decorators = None
+
+        lineno = nodelist[-4][2]
+        name = nodelist[-4][1]
+        args = nodelist[-3][2]
+
+        if not re.match('_q_((html|plain)_)?template_', name):
+            # just a normal function, let base class handle it
+            self.__template_type.append(None)
+            n = transformer.Transformer.funcdef(self, nodelist)
+
+        else:
+            if name.startswith(PLAIN_TEMPLATE_PREFIX):
+                name = name[len(PLAIN_TEMPLATE_PREFIX):]
+                template_type = "plain"
+            elif name.startswith(HTML_TEMPLATE_PREFIX):
+                name = name[len(HTML_TEMPLATE_PREFIX):]
+                template_type = "html"
+            elif name.startswith(TEMPLATE_PREFIX):
+                name = name[len(TEMPLATE_PREFIX):]
+                template_type = "plain"
+            else:
+                raise RuntimeError, 'unknown prefix on %s' % name
+
+            self.__template_type.append(template_type)
+
+            # Add "IO_INSTANCE = IO_CLASS()" statement at the beginning of
+            # the function and a "return IO_INSTANCE" at the end.
+            if args[0] == symbol.varargslist:
+                names, defaults, flags = self.com_arglist(args[1:])
+            else:
+                names = defaults = ()
+                flags = 0
+            doc = None # self.get_docstring(nodelist[-1])
+
+            # code for function
+            code = self.com_node(nodelist[-1])
+
+            # create an instance, assign to IO_INSTANCE
+            klass = ast.Name(IO_CLASS)
+            args = [ast.Const(template_type == "html")]
+            instance = ast.CallFunc(klass, args)
+            assign_name = ast.AssName(IO_INSTANCE, OP_ASSIGN)
+            assign = ast.Assign([assign_name], instance)
+
+            # return the IO_INSTANCE.getvalue(...)
+            func = ast.Getattr(ast.Name(IO_INSTANCE), "getvalue")
+            ret = ast.Return(ast.CallFunc(func, []))
+
+            # wrap original function code
+            code = ast.Stmt([assign, code, ret])
+
+            if sys.hexversion >= 0x20400a2:
+                n = ast.Function(decorators, name, names, defaults, flags, doc,
+                                 code)
+            else:
+                n = ast.Function(name, names, defaults, flags, doc, code)
+            n.lineno = lineno
+
+        self.__template_type.pop()
+        return n
+
+    def expr_stmt(self, nodelist):
+        if not self.__template_type or not self.__template_type[-1]:
+            return transformer.Transformer.expr_stmt(self, nodelist)
+
+        # Instead of discarding objects on the stack, call
+        # "IO_INSTANCE += obj".
+        exprNode = self.com_node(nodelist[-1])
+        if len(nodelist) == 1:
+            lval = ast.Name(IO_INSTANCE)
+            n = ast.AugAssign(lval, '+=', exprNode)
+            if hasattr(exprNode, 'lineno'):
+                n.lineno = exprNode.lineno
+        elif nodelist[1][0] == token.EQUAL:
+            nodes = [ ]
+            for i in range(0, len(nodelist) - 2, 2):
+                nodes.append(self.com_assign(nodelist[i], OP_ASSIGN))
+            n = ast.Assign(nodes, exprNode)
+            n.lineno = nodelist[1][2]
+        else:
+            lval = self.com_augassign(nodelist[0])
+            op = self.com_augassign_op(nodelist[1])
+            n = ast.AugAssign(lval, op[1], exprNode)
+            n.lineno = op[2]
+        return n
+
+    def atom_string(self, nodelist):
+        k = ''
+        for node in nodelist:
+            k = k + eval(node[1])
+        n = ast.Const(k)
+        if self.__template_type and self.__template_type[-1] == "html":
+            # change "foo" to _q_htmltext("foo")
+            n = ast.CallFunc(ast.Name(MARKUP_MANGLED_CLASS), [n])
+        return n
+
+
+_old_template_re = re.compile(r"^([ \t]*) template ([ \t]+)"
+                              r" ([a-zA-Z_][a-zA-Z_0-9]*)"   # name of template
+                              r" ([ \t]*[\(\\])",
+                              re.MULTILINE|re.VERBOSE)
+
+_template_re = re.compile(r"^([ \t]*) def (?:[ \t]+)"                # def
+                          r" ([a-zA-Z_][a-zA-Z_0-9]*)"               # <name>
+                          r" (?:[ \t]*) \[(plain|html)\] (?:[ \t]*)" # <type>
+                          r" (?:[ \t]*[\(\\])",                      # (
+                          re.MULTILINE|re.VERBOSE)
+
+def translate_tokens(buf):
+    """
+    Since we can't modify the parser in the builtin parser module we
+    must do token translation here.  Luckily it does not affect line
+    numbers.
+
+    template foo(...): -> def _q_template__foo(...):
+
+    def foo [plain] (...): -> def _q_plain_template__foo(...):
+
+    def foo [html] (...): -> def _q_html_template__foo(...):
+
+    XXX This parser is too stupid.  For example, it doesn't understand
+    triple quoted strings.
+    """
+    global _template_re
+
+    # handle new style template declarations
+    buf = _template_re.sub(r"\1def _q_\3_template_\2(", buf)
+
+    # change old style template to def
+    buf = _old_template_re.sub(r"\1def\2%s\3\4" % TEMPLATE_PREFIX, buf)
+
+    return buf
+
+
+if sys.hexversion >= 0x20300b1:
+    def parse(buf, filename='<string>'):
+        buf = translate_tokens(buf)
+        try:
+            return TemplateTransformer().parsesuite(buf)
+        except SyntaxError, e:
+            # set the filename attribute
+            raise SyntaxError(str(e), (filename, e.lineno, e.offset, e.text))
+
+else:
+    # The parser module in Python <= 2.2 can raise ParserError.  Since
+    # the ParserError exception is basically useless, we use compile()
+    # to generate a better exception.
+    def parse(buf, filename='<string>'):
+        buf = translate_tokens(buf)
+        # compile() and parsermodule don't accept code that is missing a
+        # trailing newline.  The Python interpreter seems to add a newline when
+        # importing modules so we match that behavior.
+        if buf[-1:] != '\n':
+            buf += "\n"
+        try:
+            return TemplateTransformer().parsesuite(buf)
+        except (parser.ParserError, SyntaxError):
+            import __builtin__
+            try:
+                __builtin__.compile(buf, filename, 'exec')
+            except SyntaxError, exc:
+                # Another hack to fix the filename attribute.
+                raise SyntaxError(str(exc), (filename, exc.lineno, exc.offset,
+                                             exc.text))
+
+
+PTL_EXT = ".ptl"
+PTLC_EXT = ".ptlc"
+if sys.hexversion >= 0x20300a1:
+    PTLC_MAGIC = "PTLC\x04\x00"
+elif sys.hexversion >= 0x20200b1:
+    PTLC_MAGIC = "PTLC\x03\x00"
+elif sys.hexversion >= 0x20100b1:
+    PTLC_MAGIC = "PTLC\x02\x00"
+elif sys.hexversion >= 0x20000b1:
+    PTLC_MAGIC = "PTLC\x01\x00"
+else:
+    raise RuntimeError, 'python too old'
+
+class Template(pycodegen.Module):
+
+    if sys.hexversion >= 0x20200b1:
+        def _get_tree(self):
+            tree = parse(self.source, self.filename)
+            misc.set_filename(self.filename, tree)
+            syntax.check(tree)
+            return tree
+    else:
+        def compile(self):
+            ast = parse(self.source, self.filename)
+            gen = pycodegen.ModuleCodeGenerator(self.filename)
+            walk(ast, gen, 1)
+            self.code = gen.getCode()
+
+    def dump(self, f):
+        import marshal
+        import stat
+
+        f.write(PTLC_MAGIC)
+        mtime = os.stat(self.filename)[stat.ST_MTIME]
+        marshal.dump(mtime, f)
+        marshal.dump(self.code, f)
+
+
+def compile_template(input, filename, output=None):
+    """compile_template(input, filename, output=None) -> code
+
+    Compile an open file.  If output is not None then the code is written
+    with the magic template header.  The code object is returned.
+    """
+    buf = input.read()
+    template = Template(buf, filename)
+    template.compile()
+    if output:
+        template.dump(output)
+    return template.code
+
+def compile(inputname, outputname):
+    """compile(inputname, outputname)
+
+    Compile a template file.  The new template is writen to outputname.
+    """
+    input = open(inputname)
+    output = open(outputname, "wb")
+    try:
+        compile_template(input, inputname, output)
+    except:
+        # don't leave a corrupt .ptlc file around
+        output.close()
+        os.unlink(outputname)
+        raise
+
+def compile_dir(dir, maxlevels=10, force=0):
+    """Byte-compile all PTL modules in the given directory tree.
+       (Adapted from compile_dir in Python module: compileall.py)
+
+    Arguments (only dir is required):
+
+    dir:       the directory to byte-compile
+    maxlevels: maximum recursion level (default 10)
+    force:     if true, force compilation, even if timestamps are up-to-date
+    """
+    print 'Listing', dir, '...'
+    try:
+        names = os.listdir(dir)
+    except os.error:
+        print "Can't list", dir
+        names = []
+    names.sort()
+    success = 1
+    for name in names:
+        fullname = os.path.join(dir, name)
+        if os.path.isfile(fullname):
+            head, tail = name[:-4], name[-4:]
+            if tail == '.ptl':
+                cfile = fullname + 'c'
+                ftime = os.stat(fullname)[stat.ST_MTIME]
+                try:
+                    ctime = os.stat(cfile)[stat.ST_MTIME]
+                except os.error: ctime = 0
+                if (ctime > ftime) and not force:
+                    continue
+                print 'Compiling', fullname, '...'
+                try:
+                    ok = compile(fullname, cfile)
+                except KeyboardInterrupt:
+                    raise KeyboardInterrupt
+                except:
+                    # XXX compile catches SyntaxErrors
+                    if type(sys.exc_type) == type(''):
+                        exc_type_name = sys.exc_type
+                    else: exc_type_name = sys.exc_type.__name__
+                    print 'Sorry:', exc_type_name + ':',
+                    print sys.exc_value
+                    success = 0
+                else:
+                    if ok == 0:
+                        success = 0
+        elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
+              os.path.isdir(fullname) and not os.path.islink(fullname)):
+            if not compile_dir(fullname, maxlevels - 1, force):
+                success = 0
+    return success
+
+def main():
+    args = sys.argv[1:]
+    if not args:
+        print "no files to compile"
+    else:
+        for filename in args:
+            path, ext = os.path.splitext(filename)
+            compile(filename, path + PTLC_EXT)
+
+if __name__ == "__main__":
+    main()

Added: packages/quixote1/branches/upstream/current/ptl_import.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/ptl_import.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/ptl_import.py (added)
+++ packages/quixote1/branches/upstream/current/ptl_import.py Mon May  8 19:16:16 2006
@@ -1,0 +1,156 @@
+"""quixote.ptl_import
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/ptl_import.py $
+$Id: ptl_import.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Import hooks; when installed, these hooks allow importing .ptl files
+as if they were Python modules.
+
+Note: there's some unpleasant incompatibility between ZODB's import
+trickery and the import hooks here.  Bottom line: if you're using ZODB,
+import it *before* installing the Quixote/PTL import hooks.
+"""
+
+__revision__ = "$Id: ptl_import.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+
+import sys
+import os.path
+import imp, ihooks, new
+import marshal
+import stat
+import __builtin__
+
+from ptl_compile import compile_template, PTL_EXT, PTLC_EXT, PTLC_MAGIC
+
+assert sys.hexversion >= 0x20000b1, "need Python 2.0b1 or later"
+
+def _exec_module_code(code, name, filename):
+    if sys.modules.has_key(name):
+        mod = sys.modules[name] # necessary for reload()
+    else:
+        mod = new.module(name)
+        sys.modules[name] = mod
+    mod.__name__ = name
+    mod.__file__ = filename
+    exec code in mod.__dict__
+    return mod
+
+def _load_ptlc(name, filename, file=None):
+    if not file:
+        try:
+            file = open(filename, "rb")
+        except IOError:
+            return None
+    path, ext = os.path.splitext(filename)
+    ptl_filename = path + PTL_EXT
+    magic = file.read(len(PTLC_MAGIC))
+    if magic != PTLC_MAGIC:
+        return _load_ptl(name, ptl_filename)
+    ptlc_mtime = marshal.load(file)
+    try:
+        mtime = os.stat(ptl_filename)[stat.ST_MTIME]
+    except OSError:
+        mtime = ptlc_mtime
+    if mtime > ptlc_mtime:
+        return _load_ptl(name, ptl_filename)
+    code = marshal.load(file)
+    return _exec_module_code(code, name, filename)
+
+def _load_ptl(name, filename, file=None):
+    if not file:
+        try:
+            file = open(filename, "rb")
+        except IOError:
+            return None
+    path, ext = os.path.splitext(filename)
+    ptlc_filename = path + PTLC_EXT
+    try:
+        output = open(ptlc_filename, "wb")
+    except IOError, msg:
+        output = None
+    try:
+        code = compile_template(file, filename, output)
+    except:
+        # don't leave a corrupt .ptlc file around
+        if output:
+            output.close()
+            os.unlink(ptlc_filename)
+        raise
+    else:
+        if output:
+            output.close()
+    return _exec_module_code(code, name, filename)
+
+
+# Constant used to signal a PTL files
+PTLC_FILE = 128
+PTL_FILE = 129
+
+class PTLHooks(ihooks.Hooks):
+
+    def get_suffixes(self):
+        # add our suffixes
+        L = imp.get_suffixes()
+        return L + [(PTLC_EXT, 'rb', PTLC_FILE), (PTL_EXT, 'r', PTL_FILE)]
+
+class PTLLoader(ihooks.ModuleLoader):
+
+    def load_module(self, name, stuff):
+        file, filename, info = stuff
+        (suff, mode, type) = info
+
+        # If it's a PTL file, load it specially.
+        if type == PTLC_FILE:
+            return _load_ptlc(name, filename, file)
+
+        elif type == PTL_FILE:
+            return _load_ptl(name, filename, file)
+
+        else:
+            # Otherwise, use the default handler for loading
+            return ihooks.ModuleLoader.load_module( self, name, stuff)
+
+try:
+    import cimport
+except ImportError:
+    cimport = None
+
+class cModuleImporter(ihooks.ModuleImporter):
+    def __init__(self, loader=None):
+        self.loader = loader or ihooks.ModuleLoader()
+        cimport.set_loader(self.find_import_module)
+
+    def find_import_module(self, fullname, subname, path):
+        stuff = self.loader.find_module(subname, path)
+        if not stuff:
+            return None
+        return self.loader.load_module(fullname, stuff)
+
+    def install(self):
+        self.save_import_module = __builtin__.__import__
+        self.save_reload = __builtin__.reload
+        if not hasattr(__builtin__, 'unload'):
+            __builtin__.unload = None
+        self.save_unload = __builtin__.unload
+        __builtin__.__import__ = cimport.import_module
+        __builtin__.reload = cimport.reload_module
+        __builtin__.unload = self.unload
+
+_installed = 0
+
+def install():
+    global _installed
+    if not _installed:
+        hooks = PTLHooks()
+        loader = PTLLoader(hooks)
+        if cimport is not None:
+            importer = cModuleImporter(loader)
+        else:
+            importer = ihooks.ModuleImporter(loader)
+        ihooks.install(importer)
+        _installed = 1
+
+
+if __name__ == '__main__':
+    import ZODB
+    install()

Added: packages/quixote1/branches/upstream/current/ptlc_dump.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/ptlc_dump.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/ptlc_dump.py (added)
+++ packages/quixote1/branches/upstream/current/ptlc_dump.py Mon May  8 19:16:16 2006
@@ -1,0 +1,51 @@
+#! /usr/bin/env python
+"""
+Dump the information contained in a compiled PTL file.  Based on the
+dumppyc.py script in the Tools/compiler directory of the Python
+distribution.
+"""
+
+__revision__ = "$Id: ptlc_dump.py 20217 2003-01-16 20:51:53Z akuchlin $"
+
+import marshal
+import dis
+import types
+
+from ptl_compile import PTLC_MAGIC
+
+def dump(obj):
+    print obj
+    for attr in dir(obj):
+        print "\t", attr, repr(getattr(obj, attr))
+
+def loadCode(path):
+    f = open(path)
+    magic = f.read(len(PTLC_MAGIC))
+    if magic != PTLC_MAGIC:
+        raise ValueError, 'bad .ptlc magic for file "%s"' % path
+    mtime = marshal.load(f)
+    co = marshal.load(f)
+    f.close()
+    return co
+
+def walk(co, match=None):
+    if match is None or co.co_name == match:
+        dump(co)
+        print
+        dis.dis(co)
+    for obj in co.co_consts:
+        if type(obj) == types.CodeType:
+            walk(obj, match)
+
+def main(filename, codename=None):
+    co = loadCode(filename)
+    walk(co, codename)
+
+if __name__ == "__main__":
+    import sys
+    if len(sys.argv) == 3:
+        filename, codename = sys.argv[1:]
+    else:
+        filename = sys.argv[1]
+        codename = None
+    main(filename, codename)

Added: packages/quixote1/branches/upstream/current/publish.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/publish.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/publish.py (added)
+++ packages/quixote1/branches/upstream/current/publish.py Mon May  8 19:16:16 2006
@@ -1,0 +1,902 @@
+"""quixote.publish
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/publish.py $
+$Id: publish.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Logic for publishing modules and objects on the Web.
+"""
+
+__revision__ = "$Id: publish.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import sys, os, traceback, cStringIO
+import time, types, socket, re, warnings
+import struct
+try:
+    import zlib # for COMPRESS_PAGES option
+except ImportError:
+    pass
+
+from quixote import errors
+from quixote.html import htmltext
+from quixote.http_request import HTTPRequest, get_content_type
+from quixote.http_response import HTTPResponse, Stream
+from quixote.upload import HTTPUploadRequest
+from quixote.sendmail import sendmail
+
+try:
+    import cgitb                        # Only available in Python 2.2
+except ImportError:
+    cgitb = None
+
+def _get_module(name):
+    """Get a module object by name."""
+    __import__(name)
+    module = sys.modules[name]
+    return module
+
+# Error message to dispay when DISPLAY_EXCEPTIONS in config file is not
+# true.  Note that SERVER_ADMIN must be fetched from the environment and
+# plugged in here -- we can't do it now because the environment isn't
+# really setup for us yet if running as a FastCGI script.
+INTERNAL_ERROR_MESSAGE = """\
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"
+        "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html>
+<head><title>Internal Server Error</title></head>
+<body>
+<h1>Internal Server Error</h1>
+<p>An internal error occurred while handling your request.</p>
+
+<p>The server administrator should have been notified of the problem.
+You may wish to contact the server administrator (%s) and inform them of
+the time the error occurred, and anything you might have done to trigger
+the error.</p>
+
+<p>If you are the server administrator, more information may be
+available in either the server's error log or Quixote's error log.</p>
+</body>
+</html>
+"""
+
+class Publisher:
+    """
+    The core of Quixote and of any Quixote application.  This class is
+    responsible for converting each HTTP request into a search of
+    Python's package namespace and, ultimately, a call of a Python
+    function/method/callable object.
+
+    Each invocation of a driver script should have one Publisher
+    instance that lives for as long as the driver script itself.  Eg. if
+    your driver script is plain CGI, each Publisher instance will handle
+    exactly one HTTP request; if you have a FastCGI driver, then each
+    Publisher will handle every HTTP request handed to that driver
+    script process.
+
+    Instance attributes:
+      root_namespace : module | instance | class
+        the Python namespace that will be searched for objects to
+        fulfill each HTTP request
+      exit_now : boolean
+        used for internal state management.  If true, the loop in
+        publish_cgi() will terminate at the end of the current request.
+      access_log : file
+        file to which every access will be logged; set by
+        setup_logs() (None if no access log)
+      error_log : file
+        file to which application errors (exceptions caught by Quixote,
+        as well as anything printed to stderr by application code) will
+        be logged; set by setup_logs().  Set to sys.stderr if no
+        ERROR_LOG setting in the application config file.
+      config : Config
+        holds all configuration info for this application.  If the
+        application doesn't have a config file, uses the default values
+        from the quixote.config module.
+      _request : HTTPRequest
+        the HTTP request currently being processed.
+      namespace_stack : [ module | instance | class ]
+    """
+
+    def __init__(self, root_namespace, config=None):
+        from quixote.config import Config
+        global _publisher
+
+        if _publisher is not None:
+            raise RuntimeError, "only one instance of Publisher allowed"
+        _publisher = self
+
+        if type(root_namespace) is types.StringType:
+            self.root_namespace = _get_module(root_namespace)
+        else:
+            # Should probably check that root_namespace is really a
+            # namespace, ie. a module, class, or instance -- but it's
+            # tricky to know if something is really a class or instance
+            # (because of ExtensionClass), and who knows what other
+            # namespaces are lurking out there in the world?
+            self.root_namespace = root_namespace
+
+        # for PublishError exception handling
+        self.namespace_stack = [self.root_namespace]
+
+        self.exit_now = 0
+        self.access_log = None
+        self.error_log = sys.stderr     # possibly overridden in setup_logs()
+        sys.stdout = self.error_log     # print is handy for debugging
+
+        # Initialize default config object with all the default values from
+        # the config variables at the top of the config module, ie. if
+        # ERROR_LOG is set to "/var/log/quxiote-error.log", then
+        # config.ERROR_LOG will also be "/var/log/quixote-error.log".  If
+        # application FCGI/CGI scripts need to override any of these
+        # defaults, they can do so by direct manipulation of the config
+        # object, or by reading a config file:
+        #   app.read_config("myapp.conf")
+        if config is None:
+            self.config = Config()
+        else:
+            self.set_config(config)
+
+        self._request = None
+
+    def configure(self, **kwargs):
+        self.config.set_from_dict(kwargs)
+
+    def read_config(self, filename):
+        self.config.read_file(filename)
+
+    def set_config(self, config):
+        from quixote.config import Config
+        if not isinstance(config, Config):
+            raise TypeError, "'config' must be a Config instance"
+        self.config = config
+
+    def setup_logs(self):
+        """
+         Open all log files specified in the config file. Reassign
+        sys.stderr to go to the error log, and sys.stdout to go to
+        the debug log.
+        """
+
+        if self.config.access_log is not None:
+            try:
+                self.access_log = open(self.config.access_log, 'a', 1)
+            except IOError, exc:
+                sys.stderr.write("error opening access log %s: %s\n"
+                                 % (`self.config.access_log`, exc.strerror))
+
+        if self.config.error_log is not None:
+            try:
+                self.error_log = open(self.config.error_log, 'a', 1)
+                sys.stderr = self.error_log
+            except IOError, exc:
+                # leave self.error_log as it was, most likely sys.stderr
+                sys.stderr.write("error opening error log %s: %s\n"
+                                 % (`self.config.error_log`, exc.strerror))
+
+        if self.config.debug_log is not None:
+            try:
+                debug_log = open(self.config.debug_log, 'a', 1)
+                sys.stdout = debug_log
+            except IOError, exc:
+                sys.stderr.write("error opening debug log %s: %s\n"
+                                 % (`self.config.debug_log`, exc.strerror))
+
+
+    def shutdown_logs(self):
+        """
+        Close log files and restore sys.stdout and sys.stderr to their
+        original values.
+        """
+        if sys.stdout is sys.__stdout__:
+            raise RuntimeError, "'setup_logs()' never called"
+        if sys.stdout is not sys.stderr:
+            sys.stdout.close()
+        sys.stdout = sys.__stdout__
+        self.access_log.close()
+        if self.error_log is not sys.__stderr__:
+            self.error_log.close()
+            sys.stderr = sys.__stderr__
+
+    def log(self, msg):
+        """
+        Write an message to the error log with a time stamp.
+        """
+        timestamp = time.strftime("%Y-%m-%d %H:%M:%S",
+                                  time.localtime(time.time()))
+        self.error_log.write("[%s] %s\n" % (timestamp, msg))
+
+    debug = log # backwards compatibility
+
+    def create_request(self, stdin, env):
+        ctype = get_content_type(env)
+        if ctype == "multipart/form-data":
+            req = HTTPUploadRequest(stdin, env, content_type=ctype)
+            req.set_upload_dir(self.config.upload_dir,
+                               self.config.upload_dir_mode)
+            return req
+        else:
+            return HTTPRequest(stdin, env, content_type=ctype)
+
+    def parse_request(self, request):
+        """Parse the request information waiting in 'request'.
+        """
+        request.process_inputs()
+
+    def start_request(self, request):
+        """Called at the start of each request.  Overridden by
+        SessionPublisher to handle session details.
+        """
+        pass
+
+    def _set_request(self, request):
+        """Set the current request object.
+        """
+        self._request = request
+
+    def _clear_request(self):
+        """Unset the current request object.
+        """
+        self._request = None
+
+    def get_request(self):
+        """Return the current request object.
+        """
+        return self._request
+
+    def log_request(self, request):
+        """Log a request in the access_log file.
+        """
+        if self.access_log is not None:
+            if request.session:
+                user = request.session.user or "-"
+            else:
+                user = "-"
+            now = time.time()
+            seconds = now - request.start_time
+            timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(now))
+
+            env = request.environ
+
+            # Under Apache, REQUEST_URI == SCRIPT_NAME + PATH_INFO.
+            # Not everyone uses Apache, so we have to stick to
+            # environment variables in the CGI spec.  Note that this
+            # relies on PATH_INFO under IIS being fixed by HTTPRequest,
+            # because IIS gets it wrong.
+            request_uri = env.get('SCRIPT_NAME') + env.get('PATH_INFO', '')
+            query = env.get('QUERY_STRING', '')
+            if query:
+                query = "?" + query
+            proto = env.get('SERVER_PROTOCOL')
+
+            self.access_log.write('%s %s %s %d "%s %s %s" %s %r %0.2fsec\n' %
+                                   (request.environ.get('REMOTE_ADDR'),
+                                    str(user),
+                                    timestamp,
+                                    os.getpid(),
+                                    request.get_method(),
+                                    request_uri + query,
+                                    proto,
+                                    request.response.status_code,
+                                    request.environ.get('HTTP_USER_AGENT', ''),
+                                    seconds
+                                   ))
+
+
+    def finish_successful_request(self, request):
+        """Called at the end of a successful request.  Overridden by
+        SessionPublisher to handle session details."""
+        pass
+
+    def finish_interrupted_request(self, request, exc):
+        """
+        Called at the end of an interrupted request.  Requests are
+        interrupted by raising a PublishError exception.  This method
+        should return a string object which will be used as the result of
+        the request.
+
+        This method searches for the nearest namespace with a
+        _q_exception_handler attribute.  That attribute is expected to be
+        a function and is called with the request and exception instance
+        as arguments and should return the error page (e.g. a string).  If
+        the handler doesn't want to handle a particular error it can
+        re-raise it and the next nearest handler will be found.  If no
+        _q_exception_handler is found, the default Quixote handler is
+        used.
+        """
+
+        # Throw away the existing response object and start a new one
+        # for the error document we're going to create here.
+        request.response = HTTPResponse()
+
+        # set response status code so every custom doesn't have to do it
+        request.response.set_status(exc.status_code)
+
+        if self.config.secure_errors and exc.private_msg:
+            exc.private_msg = None # hide it
+
+        # walk up stack and find handler for the exception
+        stack = self.namespace_stack[:]
+        while 1:
+            handler = None
+            while stack:
+                object = stack.pop()
+                if hasattr(object, "_q_exception_handler"):
+                    handler = object._q_exception_handler
+                    break
+            if handler is None:
+                handler = errors.default_exception_handler
+
+            try:
+                return handler(request, exc)
+            except errors.PublishError:
+                assert handler is not errors.default_exception_handler
+                continue # exception was re-raised or another exception occured
+
+
+    def finish_failed_request(self, request):
+        """
+        Called at the end of an failed request.  Any exception (other
+        than PublishError) causes a request to fail.  This method should
+        return a string object which will be used as the result of the
+        request.
+        """
+        # build new response to be safe
+        original_response = request.response
+        request.response = HTTPResponse()
+        #self.log("caught an error (%s), reporting it." %
+        #         sys.exc_info()[1])
+
+        (exc_type, exc_value, tb) = sys.exc_info()
+        error_summary = traceback.format_exception_only(exc_type, exc_value)
+        error_summary = error_summary[0][0:-1] # de-listify and strip newline
+
+        plain_error_msg = self._generate_plaintext_error(request,
+                                                         original_response,
+                                                         exc_type, exc_value,
+                                                         tb)
+
+        if not self.config.display_exceptions:
+            # DISPLAY_EXCEPTIONS is false, so return the most
+            # secure (and cryptic) page.
+            request.response.set_header("Content-Type", "text/html")
+            user_error_msg = self._generate_internal_error(request)
+        elif self.config.display_exceptions == 'html' and cgitb is not None:
+            # Generate a spiffy HTML display using cgitb
+            request.response.set_header("Content-Type", "text/html")
+            user_error_msg = self._generate_cgitb_error(request,
+                                                        original_response,
+                                                        exc_type, exc_value,
+                                                        tb)
+        else:
+            # Generate a plaintext page containing the traceback
+            request.response.set_header("Content-Type", "text/plain")
+            user_error_msg = plain_error_msg
+
+        self.log("exception caught")
+        self.error_log.write(plain_error_msg)
+
+        if self.config.error_email:
+            self.mail_error(plain_error_msg, error_summary)
+
+        request.response.set_status(500)
+        return user_error_msg
+
+
+    def _generate_internal_error(self, request):
+        admin = request.environ.get('SERVER_ADMIN',
+                                    "<i>email address unknown</i>")
+        return INTERNAL_ERROR_MESSAGE % admin
+
+
+    def _generate_plaintext_error(self, request, original_response,
+                                  exc_type, exc_value, tb):
+        error_file = cStringIO.StringIO()
+
+        # format the traceback
+        traceback.print_exception(exc_type, exc_value, tb, file=error_file)
+
+        # include request and response dumps
+        error_file.write('\n')
+        error_file.write(request.dump())
+        error_file.write('\n')
+
+        return error_file.getvalue()
+
+
+    def _generate_cgitb_error(self, request, original_response,
+                              exc_type, exc_value, tb):
+        error_file = cStringIO.StringIO()
+        hook = cgitb.Hook(file=error_file)
+        hook(exc_type, exc_value, tb)
+        error_file.write('<h2>Original Request</h2>')
+        error_file.write(request.dump_html())
+        error_file.write('<h2>Original Response</h2><pre>')
+        original_response.write(error_file)
+        error_file.write('</pre>')
+        return error_file.getvalue()
+
+
+    def mail_error(self, msg, error_summary):
+        """Send an email notifying someone of a traceback."""
+        sendmail('Quixote Traceback (%s)' % error_summary,
+                 msg, [self.config.error_email],
+                 from_addr=(self.config.error_email, socket.gethostname()))
+
+    def get_namespace_stack(self):
+        """get_namespace_stack() ->  [ module | instance | class ]
+        """
+        return self.namespace_stack
+
+    def try_publish(self, request, path):
+        """try_publish(request : HTTPRequest, path : string) -> string
+
+        The master method that does all the work for a single request.  Uses
+        traverse_url() to get a callable object.  The object is called and
+        the output is returned.  Exceptions are handled by the caller.
+        """
+
+        self.start_request(request)
+
+        # Initialize the publisher's namespace_stack
+        self.namespace_stack = []
+
+        # Traverse package to a (hopefully-) callable object
+        object = _traverse_url(self.root_namespace, path, request,
+                               self.config.fix_trailing_slash,
+                               self.namespace_stack)
+
+        # None means no output -- traverse_url() just issued a redirect.
+        if object is None:
+            return None
+
+        # Anything else must be either a string...
+        if isstring(object):
+            output = object
+
+        # ...or a callable.
+        elif callable(object):
+            try:
+                output = object(request)
+            except SystemExit:
+                output = "SystemExit exception caught, shutting down"
+                self.log(output)
+                self.exit_now = 1
+
+            if output is None:
+                raise RuntimeError, 'callable %s returned None' % repr(object)
+
+        # Uh-oh: 'object' is neither a string nor a callable.
+        else:
+            raise RuntimeError(
+                "object is neither callable nor a string: %s" % repr(object))
+
+
+        # The callable ran OK, commit any changes to the session
+        self.finish_successful_request(request)
+
+        return output
+
+    _GZIP_HEADER = ("\037\213" # magic
+                    "\010" # compression method
+                    "\000" # flags
+                    "\000\000\000\000" # time, who cares?
+                    "\002"
+                    "\377")
+
+    _GZIP_THRESHOLD = 200 # responses smaller than this are not compressed
+
+    def compress_output(self, request, output):
+        encoding = request.get_encoding(["gzip", "x-gzip"])
+        n = len(output)
+        if n > self._GZIP_THRESHOLD and encoding:
+            co = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS,
+                                  zlib.DEF_MEM_LEVEL, 0)
+            chunks = [self._GZIP_HEADER,
+                      co.compress(output),
+                      co.flush(),
+                      struct.pack("<ll", zlib.crc32(output), len(output))]
+            output = "".join(chunks)
+            #self.log("gzip (original size %d, ratio %.1f)" %
+            #           (n, float(n)/len(output)))
+            request.response.set_header("Content-Encoding", encoding)
+        return output
+
+    def filter_output(self, request, output):
+        """Hook for post processing the output.  Subclasses may wish to
+        override (e.g. check HTML syntax).
+        """
+        if (output and
+                self.config.compress_pages and
+                not isinstance(output, Stream)):
+            output = self.compress_output(request, str(output))
+        return output
+
+    def process_request(self, request, env):
+        """process_request(request : HTTPRequest, env : dict) : string
+
+        Process a single request, given an HTTPRequest object.  The
+        try_publish() method will be called to do the work and
+        exceptions will be handled here.
+        """
+        self._set_request(request)
+        try:
+            self.parse_request(request)
+            output = self.try_publish(request, env.get('PATH_INFO', ''))
+        except errors.PublishError, exc:
+            # Exit the publishing loop and return a result right away.
+            output = self.finish_interrupted_request(request, exc)
+        except:
+            # Some other exception, generate error messages to the logs, etc.
+            output = self.finish_failed_request(request)
+        output = self.filter_output(request, output)
+        self.log_request(request)
+        return output
+
+    def publish(self, stdin, stdout, stderr, env):
+        """publish(stdin : file, stdout : file, stderr : file, env : dict)
+
+        Create an HTTPRequest object from the environment and from
+        standard input, process it, and write the response to standard
+        output.
+        """
+        request = self.create_request(stdin, env)
+        output = self.process_request(request, env)
+
+        # Output results from Response object
+        if output:
+            request.response.set_body(output)
+        try:
+            request.response.write(stdout)
+        except IOError, exc:
+            self.log('IOError caught while writing request (%s)' % exc)
+        self._clear_request()
+
+
+    def publish_cgi(self):
+        """publish_cgi()
+
+        Entry point from CGI scripts; it will execute the publish function
+        once and return.
+        """
+        if sys.platform == "win32":
+            # on Windows, stdin and stdout are in text mode by default
+            import msvcrt
+            msvcrt.setmode(sys.__stdin__.fileno(), os.O_BINARY)
+            msvcrt.setmode(sys.__stdout__.fileno(), os.O_BINARY)
+        self.publish(sys.__stdin__, sys.__stdout__, sys.__stderr__, os.environ)
+
+    def publish_fcgi(self):
+        """publish_fcgi()
+
+        Entry point from FCGI scripts; it will repeatedly do the publish()
+        function until there are no more requests.  This should also work
+        for CGI scripts but it is not as portable as publish_cgi().
+        """
+        from quixote import fcgi
+        while fcgi.isFCGI() and not self.exit_now:
+            f = fcgi.FCGI()
+            self.publish(f.inp, f.out, f.err, f.env)
+            f.Finish()
+            if self.config.run_once:
+                break
+
+
+# class Publisher
+
+
+class SessionPublisher(Publisher):
+
+    def __init__(self, root_namespace, config=None, session_mgr=None):
+        from quixote.session import SessionManager
+        Publisher.__init__(self, root_namespace, config)
+        if session_mgr is None:
+            self.session_mgr = SessionManager()
+        else:
+            self.session_mgr = session_mgr
+
+    def set_session_manager(self, session_mgr):
+        self.session_mgr = session_mgr
+
+    def start_request(self, request):
+        # Get the session object and stick it onto the request
+        request.session = self.session_mgr.get_session(request)
+        request.session.start_request(request)
+
+    def finish_successful_request(self, request):
+        if request.session is not None:
+            request.session.finish_request(request)
+            self.session_mgr.maintain_session(request, request.session)
+        self.session_mgr.commit_changes(request.session)
+
+    def finish_interrupted_request(self, request, exc):
+        output = Publisher.finish_interrupted_request(self, request, exc)
+
+        # commit the current transaction so that any changes to the
+        # session objects are saved and are visible on the next HTTP
+        # hit.  Remember, AccessError is a subclass of PublishError,
+        # so this code will be run for both typos in the URL and for
+        # the user not being logged in.
+        #
+        # The assumption here is that the UI code won't make changes
+        # to the core database before checking permissions and raising
+        # a PublishError; if you must do this (though it's hard to see
+        # why this would be necessary), you'll have to abort the
+        # current transaction, make your session changes, and then
+        # raise the PublishError.
+        #
+        # XXX We should really be able to commit session changes and
+        # database changes separately, but that requires ZODB
+        # incantations that we currently don't know.
+        self.session_mgr.commit_changes(request.session)
+
+        return output
+
+    def finish_failed_request(self, request):
+        if self.session_mgr:
+            self.session_mgr.abort_changes(request.session)
+        return Publisher.finish_failed_request(self, request)
+
+# class SessionPublisher
+
+_slash_pat = re.compile("//*")
+
+def _traverse_url(root_namespace, path, request, fix_trailing_slash,
+                  namespace_stack):
+    """traverse_url(root_namespace : any, path : string,
+                    request : HTTPRequest, fix_trailing_slash : bool,
+                    namespace_stack : list) -> (object : any)
+
+    Perform traversal based on the provided path, starting at the root
+    object.  It returns the script name and path info values for
+    the arrived-at object, along with the object itself and
+    a list of the namespaces traversed to get there.
+
+    It's expected that the final object is something callable like a
+    function or a method; intermediate objects along the way will
+    usually be packages or modules.
+
+    To prevent crackers from writing URLs that traverse private
+    objects, every package, module, or object along the way must have
+    a _q_exports attribute containing a list of publicly visible
+    names.  Not having a _q_exports attribute is an error, though
+    having _q_exports be an empty list is OK.  If a component of the path
+    isn't in _q_exports, that also produces an error.
+
+    Modifies the namespace_stack as it traverses the url, so that
+    any exceptions encountered along the way can be handled by the
+    nearest handler.
+    """
+
+    # If someone accesses a Quixote driver script without a trailing
+    # slash, we'll wind up here with an empty path.  This won't
+    # work; relative references in the page generated by the root
+    # namespace's _q_index() will be off.  Fix it by redirecting the
+    # user to the right URL; when the client follows the redirect,
+    # we'll wind up here again with path == '/'.
+    if (not path and fix_trailing_slash):
+        request.redirect(request.environ['SCRIPT_NAME'] + '/' ,
+                         permanent=1)
+        return None
+
+    # replace repeated slashes with a single slash
+    if path.find("//") != -1:
+        path = _slash_pat.sub("/", path)
+
+    # split path apart; /foo/bar/baz  -> ['foo', 'bar', 'baz']
+    #                   /foo/bar/     -> ['foo', 'bar', '']
+    path_components = path[1:].split('/')
+
+    # Traverse starting at the root
+    object = root_namespace
+    namespace_stack.append(object)
+
+    # Loop over the components of the path
+    for component in path_components:
+        if component == "":
+            # "/q/foo/" == "/q/foo/_q_index"
+            component = "_q_index"
+        object = _get_component(object, component, path, request,
+                               namespace_stack)
+
+    if not (isstring(object) or callable(object)):
+        # We went through all the components of the path and ended up at
+        # something which isn't callable, like a module or an instance
+        # without a __call__ method.
+        if path[-1] != '/':
+            if not request.form and fix_trailing_slash:
+                # This is for the convenience of users who type in paths.
+                # Repair the path and redirect.  This should not happen for
+                # URLs within the site.
+                request.redirect(request.get_path() + "/", permanent=1)
+                return None
+
+            else:
+                # Automatic redirects disabled or there is form data.  If
+                # there is form data then the programmer is using the
+                # wrong path.  A redirect won't work if the form data came
+                # from a POST anyhow.
+                raise errors.TraversalError(
+                    "object is neither callable nor string "
+                    "(missing trailing slash?)",
+                    private_msg=repr(object),
+                    path=path)
+        else:
+            raise errors.TraversalError(
+                "object is neither callable nor string",
+                private_msg=repr(object),
+                path=path)
+
+    return object
+
+
+def _lookup_export(name, exports):
+    """Search an exports list for a name.  Returns the internal name for
+    'name' or return None if 'name' is not in 'exports'.
+
+    Each element of the export list can be either a string or a 2-tuple
+    of strings that maps an external name into internal name.  The
+    mapping is useful when the desired external name is not a valid
+    Python identifier.
+    """
+    for value in exports:
+        if value == name:
+            internal_name = name
+            break
+        elif type(value) is types.TupleType:
+            if value[0] == name:
+                internal_name = value[1] # internal name is different
+                break
+    else:
+        if name == '_q_index':
+            internal_name = name # _q_index does not need to be in exports list
+        else:
+            internal_name = None # not found in exports
+    return internal_name
+
+
+def _get_component(container, component, path, request, namespace_stack):
+    """Get one component of a path from a namespace.
+    """
+    # First security check: if the container doesn't even have an
+    # _q_exports list, fail now: all Quixote-traversable namespaces
+    # (modules, packages, instances) must have an export list!
+    if not hasattr(container, '_q_exports'):
+        raise errors.TraversalError(
+                    private_msg="%r has no _q_exports list" % container)
+
+    # Second security check: call _q_access function if it's present.
+    if hasattr(container, '_q_access'):
+        # will raise AccessError if access failed
+        container._q_access(request)
+
+    # Third security check: make sure the current name component
+    # is in the export list or is '_q_index'.  If neither
+    # condition is true, check for a _q_lookup() and call it.
+    # '_q_lookup()' translates an arbitrary string into an object
+    # that we continue traversing.  (This is very handy; it lets
+    # you put user-space objects into your URL-space, eliminating
+    # the need for digging ID strings out of a query, or checking
+    # PATHINFO after Quixote's done with it.  But it is a
+    # compromise to security: it opens up the traversal algorithm
+    # to arbitrary names not listed in _q_exports!)  If
+    # _q_lookup() doesn't exist or is None, a TraversalError is
+    # raised.
+
+    # Check if component is in _q_exports.  The elements in
+    # _q_exports can be strings or 2-tuples mapping external names
+    # to internal names.
+    if component in container._q_exports or component == '_q_index':
+        internal_name = component
+    else:
+        # check for an explicit external to internal mapping
+        for value in container._q_exports:
+            if type(value) is types.TupleType:
+                if value[0] == component:
+                    internal_name = value[1]
+                    break
+        else:
+            internal_name = None
+
+    if internal_name is None:
+        # Component is not in exports list.
+        object = None
+        if hasattr(container, "_q_lookup"):
+            object = container._q_lookup(request, component)
+        elif hasattr(container, "_q_getname"):
+            warnings.warn("_q_getname() on %s used; should "
+                          "be replaced by _q_lookup()" % type(container))
+            object = container._q_getname(request, component)
+        if object is None:
+            raise errors.TraversalError(
+                private_msg="object %r has no attribute %r" % (
+                                                    container,
+                                                    component))
+
+    # From here on, you can assume that the internal_name is not None
+    elif hasattr(container, internal_name):
+        # attribute is in _q_exports and exists
+        object = getattr(container, internal_name)
+
+    elif internal_name == '_q_index':
+        if hasattr(container, "_q_lookup"):
+            object = container._q_lookup(request, "")
+        else:
+            raise errors.AccessError(
+                private_msg=("_q_index not found in %r" % container))
+
+    elif hasattr(container, "_q_resolve"):
+        object = container._q_resolve(internal_name)
+        if object is None:
+            raise RuntimeError, ("component listed in _q_exports, "
+                                 "but not returned by _q_resolve(%r)"
+                                 % internal_name)
+        else:
+            # Set the object, so _q_resolve won't need to be called again.
+            setattr(container, internal_name, object)
+
+    elif type(container) is types.ModuleType:
+        # try importing it as a sub-module.  If we get an ImportError
+        # here we don't catch it.  It means that something that
+        # doesn't exist was exported or an exception was raised from
+        # deeper in the code.
+        mod_name = container.__name__ + '.' + internal_name
+        object = _get_module(mod_name)
+
+    else:
+        # a non-existent attribute is in _q_exports,
+        # and the container is not a module.  Give up.
+        raise errors.TraversalError(
+                private_msg=("%r in _q_exports list, "
+                             "but not found in %r" % (component,
+                                                      container)))
+
+    namespace_stack.append(object)
+    return object
+
+
+
+# Publisher singleton, only one of these per process.
+_publisher = None
+
+def get_publisher():
+    global _publisher
+    return _publisher
+
+def get_request():
+    global _publisher
+    return _publisher.get_request()
+
+def get_path(n=0):
+    global _publisher
+    return _publisher.get_request().get_path(n)
+
+def redirect(location, permanent=0):
+    global _publisher
+    return _publisher.get_request().redirect(location, permanent)
+
+def get_session():
+    global _publisher
+    return _publisher.get_request().session
+
+def get_session_manager():
+    global _publisher
+    return _publisher.session_mgr
+
+def get_user():
+    global _publisher
+    session = _publisher.get_request().session
+    if session is None:
+        return None
+    else:
+        return session.user
+
+
+if sys.hexversion >= 0x02020000:    # Python 2.2 or greater
+    def isstring(x):
+        return isinstance(x, (str, unicode, htmltext))
+else:
+    if hasattr(types, 'UnicodeType'):
+        _string_types = (types.StringType, types.UnicodeType)
+    else:
+        _string_types = (types.StringType,)
+
+    def isstring(x):
+        return type(x) in _string_types

Added: packages/quixote1/branches/upstream/current/qx_distutils.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/qx_distutils.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/qx_distutils.py (added)
+++ packages/quixote1/branches/upstream/current/qx_distutils.py Mon May  8 19:16:16 2006
@@ -1,0 +1,57 @@
+"""quixote.qx_distutils
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/qx_distutils.py $
+$Id: qx_distutils.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Provides a version of the Distutils "build_py" command that knows about
+PTL files.
+
+This is installed with Quixote so other Quixote apps can use it in their
+setup scripts.
+"""
+
+# created 2001/08/28, Greg Ward (initially written for SPLAT!'s setup.py)
+
+__revision__ = "$Id: qx_distutils.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import os, string
+from glob import glob
+from types import StringType, ListType, TupleType
+from distutils.command.build_py import build_py
+
+# This bites -- way too much code had to be copied from
+# distutils/command/build.py just to add an extra file extension!
+
+class qx_build_py(build_py):
+
+    def find_package_modules(self, package, package_dir):
+        self.check_package(package, package_dir)
+        module_files = (glob(os.path.join(package_dir, "*.py")) +
+                        glob(os.path.join(package_dir, "*.ptl")))
+        modules = []
+        setup_script = os.path.abspath(self.distribution.script_name)
+
+        for f in module_files:
+            abs_f = os.path.abspath(f)
+            if abs_f != setup_script:
+                module = os.path.splitext(os.path.basename(f))[0]
+                modules.append((package, module, f))
+            else:
+                self.debug_print("excluding %s" % setup_script)
+        return modules
+
+    def build_module(self, module, module_file, package):
+        if type(package) is StringType:
+            package = string.split(package, '.')
+        elif type(package) not in (ListType, TupleType):
+            raise TypeError, \
+                  "'package' must be a string (dot-separated), list, or tuple"
+
+        # Now put the module source file into the "build" area -- this is
+        # easy, we just copy it somewhere under self.build_lib (the build
+        # directory for Python source).
+        outfile = self.get_module_outfile(self.build_lib, package, module)
+        if module_file.endswith(".ptl"): # XXX hack for PTL
+            outfile = outfile[0:outfile.rfind('.')] + ".ptl"
+        dir = os.path.dirname(outfile)
+        self.mkpath(dir)
+        return self.copy_file(module_file, outfile, preserve_mode=0)

Added: packages/quixote1/branches/upstream/current/sendmail.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/sendmail.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/sendmail.py (added)
+++ packages/quixote1/branches/upstream/current/sendmail.py Mon May  8 19:16:16 2006
@@ -1,0 +1,273 @@
+"""quixote.sendmail
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/sendmail.py $
+$Id: sendmail.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Tools for sending mail from Quixote applications.
+"""
+
+# created 2001/08/27, Greg Ward (with a long and complicated back-story)
+
+__revision__ = "$Id: sendmail.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import re
+from types import ListType, TupleType, StringType
+from smtplib import SMTP
+
+rfc822_specials_re = re.compile(r'[\(\)\<\>\@\,\;\:\\\"\.\[\]]')
+
+class RFC822Mailbox:
+    """
+    In RFC 822, a "mailbox" is either a bare e-mail address or a bare
+    e-mail address coupled with a chunk of text, most often someone's
+    name.  Eg. the following are all "mailboxes" in the RFC 822 grammar:
+      luser at example.com
+      Joe Luser <luser at example.com>
+      Paddy O'Reilly <paddy at example.ie>
+      "Smith, John" <smith at example.com>
+      Dick & Jane <dickjane at example.net>
+      "Tom, Dick, & Harry" <tdh at example.org>
+
+    This class represents an (addr_spec, real_name) pair and takes care
+    of quoting the real_name according to RFC 822's rules for you.
+    Just use the format() method and it will spit out a properly-
+    quoted RFC 822 "mailbox".
+    """
+
+    def __init__(self, *args):
+        """RFC822Mailbox(addr_spec : string, name : string)
+           RFC822Mailbox(addr_spec : string)
+           RFC822Mailbox((addr_spec : string, name : string))
+           RFC822Mailbox((addr_spec : string))
+
+        Create a new RFC822Mailbox instance.  The variety of call
+        signatures is purely for your convenience.
+        """
+        if (len(args) == 1 and type(args[0]) is TupleType):
+            args = args[0]
+
+        if len(args) == 1:
+            addr_spec = args[0]
+            real_name = None
+        elif len(args) == 2:
+            (addr_spec, real_name) = args
+        else:
+            raise TypeError(
+                "invalid number of arguments: "
+                "expected 1 or 2 strings or "
+                "a tuple of 1 or 2 strings")
+
+        self.addr_spec = addr_spec
+        self.real_name = real_name
+
+    def __str__(self):
+        return self.addr_spec
+
+    def __repr__(self):
+        return "<%s at %x: %s>" % (self.__class__.__name__, id(self), self)
+
+    def format(self):
+        if self.real_name and rfc822_specials_re.search(self.real_name):
+            return '"%s" <%s>' % (self.real_name.replace('"', '\\"'),
+                                  self.addr_spec)
+        elif self.real_name:
+            return '%s <%s>' % (self.real_name, self.addr_spec)
+
+        else:
+            return self.addr_spec
+
+
+def _ensure_mailbox(s):
+    """_ensure_mailbox(s : string |
+                          (string,) |
+                          (string, string) |
+                          RFC822Mailbox |
+                          None)
+       -> RFC822Mailbox | None
+
+    If s is a string, or a tuple of 1 or 2 strings, returns an
+    RFC822Mailbox encapsulating them as an addr_spec and real_name.  If
+    s is already an RFC822Mailbox, returns s.  If s is None, returns
+    None.
+    """
+    if s is None or isinstance(s, RFC822Mailbox):
+        return s
+    else:
+        return RFC822Mailbox(s)
+
+
+# Maximum number of recipients that will be explicitly listed in
+# any single message header.  Eg. if MAX_HEADER_RECIPIENTS is 10,
+# there could be up to 10 "To" recipients and 10 "CC" recipients
+# explicitly listed in the message headers.
+MAX_HEADER_RECIPIENTS = 10
+
+def _add_recip_headers(headers, field_name, addrs):
+    if not addrs:
+        return
+    addrs = [addr.format() for addr in addrs]
+
+    if len(addrs) == 1:
+        headers.append("%s: %s" % (field_name, addrs[0]))
+    elif len(addrs) <= MAX_HEADER_RECIPIENTS:
+        headers.append("%s: %s," % (field_name, addrs[0]))
+        for addr in addrs[1:-1]:
+            headers.append("    %s," % addr)
+        headers.append("    %s" % addrs[-1])
+    else:
+        headers.append("%s: (long recipient list suppressed) : ;" % field_name)
+
+
+def sendmail(subject, msg_body, to_addrs,
+             from_addr=None, cc_addrs=None,
+             extra_headers=None,
+             smtp_sender=None, smtp_recipients=None,
+             config=None):
+    """sendmail(subject : string,
+                msg_body : string,
+                to_addrs : [email_address],
+                from_addr : email_address = config.MAIL_SENDER,
+                cc_addrs : [email_address] = None,
+                extra_headers : [string] = None,
+                smtp_sender : email_address = (derived from from_addr)
+                smtp_recipients : [email_address] = (derived from to_addrs),
+                config : quixote.config.Config = (current publisher's config)):
+
+    Send an email message to a list of recipients via a local SMTP
+    server.  In normal use, you supply a list of primary recipient
+    e-mail addresses in 'to_addrs', an optional list of secondary
+    recipient addresses in 'cc_addrs', and a sender address in
+    'from_addr'.  sendmail() then constructs a message using those
+    addresses, 'subject', and 'msg_body', and mails the message to every
+    recipient address.  (Specifically, it connects to the mail server
+    named in the MAIL_SERVER config variable -- default "localhost" --
+    and instructs the server to send the message to every recipient
+    address in 'to_addrs' and 'cc_addrs'.)
+
+    'from_addr' is optional because web applications often have a common
+    e-mail sender address, such as "webmaster at example.com".  Just set
+    the Quixote config variable MAIL_FROM, and it will be used as the
+    default sender (both header and envelope) for all e-mail sent by
+    sendmail().
+
+    E-mail addresses can be specified a number of ways.  The most
+    efficient is to supply instances of RFC822Mailbox, which bundles a
+    bare e-mail address (aka "addr_spec" from the RFC 822 grammar) and
+    real name together in a readily-formattable object.  You can also
+    supply an (addr_spec, real_name) tuple, or an addr_spec on its own.
+    The latter two are converted into RFC822Mailbox objects for
+    formatting, which is why it may be more efficient to construct
+    RFC822Mailbox objects yourself.
+
+    Thus, the following are all equivalent in terms of who gets the
+    message:
+      sendmail(to_addrs=["joe at example.com"], ...)
+      sendmail(to_addrs=[("joe at example.com", "Joe User")], ...)
+      sendmail(to_addrs=[RFC822Mailbox("joe at example.com", "Joe User")], ...)
+    ...although the "To" header will be slightly different.  In the
+    first case, it will be
+      To: joe at example.com
+    while in the other two, it will be:
+      To: Joe User <joe at example.com>
+    which is a little more user-friendly.
+
+    In more advanced usage, you might wish to specify the SMTP sender
+    and recipient addresses separately.  For example, if you want your
+    application to send mail to users that looks like it comes from a
+    real human being, but you don't want that human being to get the
+    bounce messages from the mailing, you might do this:
+      sendmail(to_addrs=user_list,
+               ...,
+               from_addr=("realuser at example.com", "A Real User"),
+               smtp_sender="postmaster at example.com")
+
+    End users will see mail from "A Real User <realuser at example.com>" in
+    their inbox, but bounces will go to postmaster at example.com.
+
+    One use of different header and envelope recipients is for
+    testing/debugging.  If you want to test that your application is
+    sending the right mail to bigboss at example.com without filling
+    bigboss' inbox with dross, you might do this:
+      sendmail(to_addrs=["bigboss at example.com"],
+               ...,
+               smtp_recipients=["developers at example.com"])
+
+    This is so useful that it's a Quixote configuration option: just set
+    MAIL_DEBUG_ADDR to (eg.) "developers at example.com", and every message
+    that sendmail() would send out is diverted to the debug address.
+
+    Generally raises an exception on any SMTP errors; see smtplib (in
+    the standard library documentation) for details.
+    """
+    if config is None:
+        from quixote import get_publisher
+        config = get_publisher().config
+
+    if not isinstance(to_addrs, ListType):
+        raise TypeError("'to_addrs' must be a list")
+    if not (cc_addrs is None or isinstance(cc_addrs, ListType)):
+        raise TypeError("'cc_addrs' must be a list or None")
+
+    # Make sure we have a "From" address
+    if from_addr is None:
+        from_addr = config.mail_from
+    if from_addr is None:
+        raise RuntimeError(
+            "no from_addr supplied, and MAIL_FROM not set in config file")
+
+    # Ensure all of our addresses are really RFC822Mailbox objects.
+    from_addr = _ensure_mailbox(from_addr)
+    to_addrs = map(_ensure_mailbox, to_addrs)
+    if cc_addrs:
+        cc_addrs = map(_ensure_mailbox, cc_addrs)
+
+    # Start building the message headers.
+    headers = ["From: %s" % from_addr.format(),
+               "Subject: %s" % subject]
+    _add_recip_headers(headers, "To", to_addrs)
+
+    if cc_addrs:
+        _add_recip_headers(headers, "Cc", cc_addrs)
+
+    if extra_headers:
+        headers.extend(extra_headers)
+
+    if config.mail_debug_addr:
+        debug1 = ("[debug mode, message actually sent to %s]\n"
+                  % config.mail_debug_addr)
+        if smtp_recipients:
+            debug2 = ("[original SMTP recipients: %s]\n"
+                      % ", ".join(smtp_recipients))
+        else:
+            debug2 = ""
+
+        sep = ("-"*72) + "\n"
+        msg_body = debug1 + debug2 + sep + msg_body
+
+        smtp_recipients = [config.mail_debug_addr]
+
+    if smtp_sender is None:
+        smtp_sender = from_addr.addr_spec
+    else:
+        smtp_sender = _ensure_mailbox(smtp_sender).addr_spec
+
+    if smtp_recipients is None:
+        smtp_recipients = [addr.addr_spec for addr in to_addrs]
+        if cc_addrs:
+            smtp_recipients.extend([addr.addr_spec for addr in cc_addrs])
+    else:
+        smtp_recipients = [_ensure_mailbox(recip).addr_spec
+                           for recip in smtp_recipients]
+
+    message = "\n".join(headers) + "\n\n" + msg_body
+
+    # Sanity checks
+    assert type(smtp_sender) is StringType, \
+           "smtp_sender not a string: %r" % (smtp_sender,)
+    assert (type(smtp_recipients) is ListType and
+            map(type, smtp_recipients) == [StringType]*len(smtp_recipients)), \
+            "smtp_recipients not a list of strings: %r" % (smtp_recipients,)
+    smtp = SMTP(config.mail_server)
+    smtp.sendmail(smtp_sender, smtp_recipients, message)
+    smtp.quit()
+
+# sendmail ()

Added: packages/quixote1/branches/upstream/current/server/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/server/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/server/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/server/__init__.py Mon May  8 19:16:16 2006
@@ -1,0 +1,10 @@
+"""quixote.server
+
+This package is for HTTP servers, built using one or another
+framework, that publish a Quixote application.  These servers can make
+it easy to run a small application without having to install and
+configure a full-blown Web server such as Apache.
+
+"""
+
+__revision__ = "$Id: __init__.py 25234 2004-09-30 17:36:19Z nascheme $"

Added: packages/quixote1/branches/upstream/current/server/medusa_http.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/server/medusa_http.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/server/medusa_http.py (added)
+++ packages/quixote1/branches/upstream/current/server/medusa_http.py Mon May  8 19:16:16 2006
@@ -1,0 +1,146 @@
+#!/usr/bin/env python
+
+"""quixote.server.medusa_http
+
+An HTTP handler for Medusa that publishes a Quixote application.
+"""
+
+__revision__ = "$Id: medusa_http.py 25276 2004-10-06 15:46:53Z nascheme $"
+
+# A simple HTTP server, using Medusa, that publishes a Quixote application.
+
+import sys
+import asyncore, rfc822, socket, urllib
+from StringIO import StringIO
+from medusa import http_server, xmlrpc_handler
+from quixote.http_response import Stream
+from quixote.publish import Publisher
+
+
+class StreamProducer:
+    def __init__(self, stream):
+        self.iterator = iter(stream)
+
+    def more(self):
+        try:
+            return self.iterator.next()
+        except StopIteration:
+            return ''
+
+
+class QuixoteHandler:
+    def __init__(self, publisher, server_name, server):
+        """QuixoteHandler(publisher:Publisher, server_name:string,
+                        server:medusa.http_server.http_server)
+
+        Publish the specified Quixote publisher.  'server_name' will
+        be passed as the SERVER_NAME environment variable.
+        """
+        self.publisher = publisher
+        self.server_name = server_name
+        self.server = server
+
+    def match(self, request):
+        # Always match, since this is the only handler there is.
+        return 1
+
+    def handle_request(self, request):
+        msg = rfc822.Message(StringIO('\n'.join(request.header)))
+        length = int(msg.get('Content-Length', '0'))
+        if length:
+            request.collector = xmlrpc_handler.collector(self, request)
+        else:
+            self.continue_request('', request)
+
+    def continue_request(self, data, request):
+        msg = rfc822.Message(StringIO('\n'.join(request.header)))
+        remote_addr, remote_port = request.channel.addr
+        if '#' in request.uri:
+            # MSIE is buggy and sometimes includes fragments in URLs
+            [request.uri, fragment] = request.uri.split('#', 1)
+        if '?' in request.uri:
+            [path, query_string] = request.uri.split('?', 1)
+        else:
+            path = request.uri
+            query_string = ''
+
+        path = urllib.unquote(path)
+        server_port = str(self.server.port)
+        http_host = msg.get("Host")
+        if http_host:
+            if ":" in http_host:
+                server_name, server_port = http_host.split(":", 1)
+            else:
+                server_name = http_host
+        else:
+            server_name = (self.server.ip or
+                           socket.gethostbyaddr(socket.gethostname())[0])
+
+        environ = {'REQUEST_METHOD': request.command,
+                   'ACCEPT_ENCODING': msg.get('Accept-encoding', ''),
+                   'CONTENT_TYPE': msg.get('Content-type', ''),
+                   'CONTENT_LENGTH': len(data),
+                   "GATEWAY_INTERFACE": "CGI/1.1",
+                   'PATH_INFO': path,
+                   'QUERY_STRING': query_string,
+                   'REMOTE_ADDR': remote_addr,
+                   'REMOTE_PORT': str(remote_port),
+                   'REQUEST_URI': request.uri,
+                   'SCRIPT_NAME': '',
+                   "SCRIPT_FILENAME": '',
+                   'SERVER_NAME': server_name,
+                   'SERVER_PORT': server_port,
+                   'SERVER_PROTOCOL': 'HTTP/1.1',
+                   'SERVER_SOFTWARE': self.server_name,
+                   }
+        for title, header in msg.items():
+            envname = 'HTTP_' + title.replace('-', '_').upper()
+            environ[envname] = header
+
+        stdin = StringIO(data)
+        qreq = self.publisher.create_request(stdin, environ)
+        output = self.publisher.process_request(qreq, environ)
+
+        qresponse = qreq.response
+        if output:
+            qresponse.set_body(output)
+
+        # Copy headers from Quixote's HTTP response
+        for name, value in qresponse.generate_headers():
+            # XXX Medusa's HTTP request is buggy, and only allows unique
+            # headers.
+            request[name] = value
+
+        request.response(qresponse.status_code)
+
+        # XXX should we set a default Last-Modified time?
+        if qresponse.body is not None:
+            if isinstance(qresponse.body, Stream):
+                request.push(StreamProducer(qresponse.body))
+            else:
+                request.push(qresponse.body)
+
+        request.done()
+
+def main():
+    from quixote import enable_ptl
+    enable_ptl()
+
+    if len(sys.argv) == 2:
+        port = int(sys.argv[1])
+    else:
+        port = 8080
+    print 'Now serving the Quixote demo on port %d' % port
+    server = http_server.http_server('', port)
+    publisher = Publisher('quixote.demo')
+
+    # When initializing the Publisher in your own driver script,
+    # you'll want to parse a configuration file.
+    ##publisher.read_config("/full/path/to/demo.conf")
+    publisher.setup_logs()
+    dh = QuixoteHandler(publisher, 'Quixote/demo', server)
+    server.install_handler(dh)
+    asyncore.loop()
+
+if __name__ == '__main__':
+    main()

Added: packages/quixote1/branches/upstream/current/server/twisted_http.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/server/twisted_http.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/server/twisted_http.py (added)
+++ packages/quixote1/branches/upstream/current/server/twisted_http.py Mon May  8 19:16:16 2006
@@ -1,0 +1,275 @@
+#!/usr/bin/env python
+
+"""
+twist -- Demo of an HTTP server built on top of Twisted Python.
+"""
+
+__revision__ = "$Id: medusa_http.py 21221 2003-03-20 16:02:41Z akuchlin $"
+
+# based on qserv, created 2002/03/19, AMK
+# last mod 2003.03.24, Graham Fawcett
+# tested on Win32 / Twisted 0.18.0 / Quixote 0.6b5
+#
+# version 0.2 -- 2003.03.24 11:07 PM
+#   adds missing support for session management, and for
+#   standard Quixote response headers (expires, date)
+#
+# modified 2004/04/10 jsibre
+#   better support for Streams
+#   wraps output (whether Stream or not) into twisted type producer.
+#   modified to use reactor instead of Application (Appication
+#     has been deprecated)
+
+import urllib
+from twisted.protocols import http
+from twisted.web import server
+
+# imports for the TWProducer object
+from twisted.spread import pb
+from twisted.python import threadable
+from twisted.internet import abstract
+
+from quixote.http_response import Stream
+
+class QuixoteTWRequest(server.Request):
+
+    def process(self):
+        self.publisher = self.channel.factory.publisher
+        environ = self.create_environment()
+        # this seek is important, it doesn't work without it (it doesn't
+        # matter for GETs, but POSTs will not work properly without it.)
+        self.content.seek(0, 0)
+        qxrequest = self.publisher.create_request(self.content, environ)
+        self.quixote_publish(qxrequest, environ)
+        resp = qxrequest.response
+        self.setResponseCode(resp.status_code)
+        for hdr, value in resp.generate_headers():
+            self.setHeader(hdr, value)
+        if resp.body is not None:
+            TWProducer(resp.body, self)
+        else:
+            self.finish()
+
+
+    def quixote_publish(self, qxrequest, env):
+        """
+        Warning, this sidesteps the Publisher.publish method,
+        Hope you didn't override it...
+        """
+        pub = self.publisher
+        output = pub.process_request(qxrequest, env)
+
+        # don't write out the output, just set the response body
+        # the calling method will do the rest.
+        if output:
+            qxrequest.response.set_body(output)
+
+        pub._clear_request()
+
+
+    def create_environment(self):
+        """
+        Borrowed heavily from twisted.web.twcgi
+        """
+        # Twisted doesn't decode the path for us,
+        # so let's do it here.  This is also
+        # what medusa_http.py does, right or wrong.
+        if '%' in self.path:
+            self.path = urllib.unquote(self.path)
+
+        serverName = self.getRequestHostname().split(':')[0]
+        env = {"SERVER_SOFTWARE":   server.version,
+               "SERVER_NAME":       serverName,
+               "GATEWAY_INTERFACE": "CGI/1.1",
+               "SERVER_PROTOCOL":   self.clientproto,
+               "SERVER_PORT":       str(self.getHost()[2]),
+               "REQUEST_METHOD":    self.method,
+               "SCRIPT_NAME":       '',
+               "SCRIPT_FILENAME":   '',
+               "REQUEST_URI":       self.uri,
+               "HTTPS":             (self.isSecure() and 'on') or 'off',
+               "ACCEPT_ENCODING":   self.getHeader('Accept-encoding'),
+               'CONTENT_TYPE':      self.getHeader('Content-type'),
+               'HTTP_COOKIE':       self.getHeader('Cookie'),
+               'HTTP_REFERER':      self.getHeader('Referer'),
+               'HTTP_USER_AGENT':   self.getHeader('User-agent'),
+               'SERVER_PROTOCOL':   'HTTP/1.1',
+        }
+
+        client = self.getClient()
+        if client is not None:
+            env['REMOTE_HOST'] = client
+        ip = self.getClientIP()
+        if ip is not None:
+            env['REMOTE_ADDR'] = ip
+        xx, xx, remote_port = self.transport.getPeer()
+        env['REMOTE_PORT'] = remote_port
+        env["PATH_INFO"] = self.path
+
+        qindex = self.uri.find('?')
+        if qindex != -1:
+            env['QUERY_STRING'] = self.uri[qindex+1:]
+        else:
+            env['QUERY_STRING'] = ''
+
+        # Propogate HTTP headers
+        for title, header in self.getAllHeaders().items():
+            envname = title.replace('-', '_').upper()
+            if title not in ('content-type', 'content-length'):
+                envname = "HTTP_" + envname
+            env[envname] = header
+
+        return env
+
+
+class TWProducer(pb.Viewable):
+    """
+    A class to represent the transfer of data over the network.
+
+    JES Note: This has more stuff in it than is minimally neccesary.
+    However, since I'm no twisted guru, I built this by modifing
+    twisted.web.static.FileTransfer.  FileTransfer has stuff in it
+    that I don't really understand, but know that I probably don't
+    need. I'm leaving it in under the theory that if anyone ever
+    needs that stuff (e.g. because they're running with multiple
+    threads) it'll be MUCH easier for them if I had just left it in
+    than if they have to figure out what needs to be in there.
+    Furthermore, I notice no performance penalty for leaving it in.
+    """
+    request = None
+    def __init__(self, data, request):
+        self.request = request
+        self.data = ""
+        self.size = 0
+        self.stream = None
+        self.streamIter = None
+
+        self.outputBufferSize = abstract.FileDescriptor.bufferSize
+
+        if isinstance(data, Stream):    # data could be a Stream
+            self.stream = data
+            self.streamIter = iter(data)
+            self.size = data.length
+        elif data:                      # data could be a string
+            self.data = data
+            self.size = len(data)
+        else:                           # data could be None
+            # We'll just leave self.data as ""
+            pass
+
+        request.registerProducer(self, 0)
+
+
+    def resumeProducing(self):
+        """
+        This is twisted's version of a producer's '.more()', or
+        an iterator's '.next()'.  That is, this function is
+        responsible for returning some content.
+        """
+        if not self.request:
+            return
+
+        if self.stream:
+            # If we were provided a Stream, let's grab some data
+            # and push it into our data buffer
+
+            buffer = [self.data]
+            bytesInBuffer = len(buffer[-1])
+            while bytesInBuffer < self.outputBufferSize:
+                try:
+                    buffer.append(self.streamIter.next())
+                    bytesInBuffer += len(buffer[-1])
+                except StopIteration:
+                    # We've exhausted the Stream, time to clean up.
+                    self.stream = None
+                    self.streamIter = None
+                    break
+            self.data = "".join(buffer)
+
+        if self.data:
+            chunkSize = min(self.outputBufferSize, len(self.data))
+            data, self.data = self.data[:chunkSize], self.data[chunkSize:]
+        else:
+            data = ""
+
+        if data:
+            self.request.write(data)
+
+        if not self.data:
+            self.request.unregisterProducer()
+            self.request.finish()
+            self.request = None
+
+    def pauseProducing(self):
+        pass
+
+    def stopProducing(self):
+        self.data    = ""
+        self.request = None
+        self.stream  = None
+        self.streamIter = None
+
+    # Remotely relay producer interface.
+
+    def view_resumeProducing(self, issuer):
+        self.resumeProducing()
+
+    def view_pauseProducing(self, issuer):
+        self.pauseProducing()
+
+    def view_stopProducing(self, issuer):
+        self.stopProducing()
+
+    synchronized = ['resumeProducing', 'stopProducing']
+
+threadable.synchronize(TWProducer)
+
+
+
+class QuixoteFactory(http.HTTPFactory):
+
+    def __init__(self, publisher):
+        self.publisher = publisher
+        http.HTTPFactory.__init__(self, None)
+
+    def buildProtocol(self, addr):
+        p = http.HTTPFactory.buildProtocol(self, addr)
+        p.requestFactory = QuixoteTWRequest
+        return p
+
+
+def Server(namespace, http_port):
+    from twisted.internet import reactor
+    from quixote.publish import Publisher
+
+    #  If you want SSL, make sure you have OpenSSL,
+    #  uncomment the follownig, and uncomment the
+    #  listenSSL() call below.
+
+    ##from OpenSSL import SSL
+    ##class ServerContextFactory:
+    ##    def getContext(self):
+    ##        ctx = SSL.Context(SSL.SSLv23_METHOD)
+    ##        ctx.use_certificate_file('/path/to/pem/encoded/ssl_cert_file')
+    ##        ctx.use_privatekey_file('/path/to/pem/encoded/ssl_key_file')
+    ##        return ctx
+
+    publisher = Publisher(namespace)
+    ##publisher.setup_logs()
+    qf = QuixoteFactory(publisher)
+
+    reactor.listenTCP(http_port, qf)
+    ##reactor.listenSSL(http_port, qf, ServerContextFactory())
+
+    return reactor
+
+
+def run(namespace, port):
+    app = Server(namespace, port)
+    app.run()
+
+
+if __name__ == '__main__':
+    from quixote import enable_ptl
+    enable_ptl()
+    run('quixote.demo', 8080)

Added: packages/quixote1/branches/upstream/current/session.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/session.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/session.py (added)
+++ packages/quixote1/branches/upstream/current/session.py Mon May  8 19:16:16 2006
@@ -1,0 +1,576 @@
+"""quixote.session
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/session.py $
+$Id: session.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Quixote session management.  There are two levels to Quixote's
+session management system:
+  - SessionManager
+  - Session
+
+A SessionManager is responsible for creating sessions, setting and reading
+session cookies, maintaining the collection of all sessions, and so forth.
+There is one SessionManager instance per Quixote process.
+
+A Session is the umbrella object for a single session (notionally, a (user,
+host, browser_process) triple).  Simple applications can probably get away
+with putting all session data into a Session object (or, better, into an
+application-specific subclass of Session).
+
+The default implementation provided here is not persistent: when the
+Quixote process shuts down, all session data is lost.  See
+doc/session-mgmt.txt for information on session persistence.
+"""
+
+from time import time, localtime, strftime
+
+from quixote import get_publisher
+from quixote.errors import SessionError
+from quixote.util import randbytes
+
+class SessionManager:
+    """
+    SessionManager acts as a dictionary of all sessions, mapping session
+    ID strings to individual session objects.  Session objects are
+    instances of Session (or a custom subclass for your application).
+    SessionManager is also responsible for creating and destroying
+    sessions, for generating and interpreting session cookies, and for
+    session persistence (if any -- this implementation is not
+    persistent).
+
+    Most applications can just use this class directly; sessions will
+    be kept in memory-based dictionaries, and will be lost when the
+    Quixote process dies.  Alternatively an application can subclass
+    SessionManager to implement specific behaviour, such as persistence.
+
+    Instance attributes:
+      session_class : class
+        the class that is instantiated to create new session objects
+        (in new_session())
+      sessions : mapping { session_id:string : Session }
+        the collection of sessions managed by this SessionManager
+    """
+
+    ACCESS_TIME_RESOLUTION = 1 # in seconds
+
+
+    def __init__(self, session_class=None, session_mapping=None):
+        """SessionManager(session_class : class = Session,
+                          session_mapping : mapping = {})
+
+        Create a new session manager.  There should be one session
+        manager per publisher (really SessionPublisher), ie. one
+        per process.  Note that SessionPublisher's constructor will
+        take care of creating a session manager for you if you don't
+        do it yourself.
+
+        session_class is used by the new_session() method -- it returns
+        an instance of session_class.
+        """
+        self.sessions = {}
+        if session_class is None:
+            self.session_class = Session
+        else:
+            self.session_class = session_class
+        if session_mapping is None:
+            self.sessions = {}
+        else:
+            self.sessions = session_mapping
+
+    def __repr__(self):
+        return "<%s at %x>" % (self.__class__.__name__, id(self))
+
+
+    # -- Mapping interface ---------------------------------------------
+    # (subclasses shouldn't need to override any of this, unless
+    # your application passes in a session_mapping object that
+    # doesn't provide all of the mapping methods needed here)
+
+    def keys(self):
+        """keys() -> [string]
+
+        Return the list of session IDs of sessions in this session manager.
+        """
+        return self.sessions.keys()
+
+    def sorted_keys(self):
+        """sorted_keys() -> [string]
+
+        Return the same list as keys(), but sorted.
+        """
+        keys = self.keys()
+        keys.sort()
+        return keys
+
+    def values(self):
+        """values() -> [Session]
+
+        Return the list of sessions in this session manager.
+        """
+        return self.sessions.values()
+
+    def items(self):
+        """items() -> [(string, Session)]
+
+        Return the list of (session_id, session) pairs in this session
+        manager.
+        """
+        return self.sessions.items()
+
+    def get(self, session_id, default=None):
+        """get(session_id : string, default : any = None) -> Session
+
+        Return the session object identified by 'session_id', or None if
+        no such session.
+        """
+        return self.sessions.get(session_id, default)
+
+    def __getitem__(self, session_id):
+        """__getitem__(session_id : string) -> Session
+
+        Return the session object identified by 'session_id'.  Raise KeyError
+        if no such session.
+        """
+        return self.sessions[session_id]
+
+    def has_key(self, session_id):
+        """has_key(session_id : string) -> boolean
+
+        Return true if a session identified by 'session_id' exists in
+        the session manager.
+        """
+        return self.sessions.has_key(session_id)
+
+    # has_session() is a synonym for has_key() -- if you override
+    # has_key(), be sure to repeat this alias!
+    has_session = has_key
+
+    def __setitem__(self, session_id, session):
+        """__setitem__(session_id : string, session : Session)
+
+        Store 'session' in the session manager under 'session_id'.
+        """
+        if not isinstance(session, self.session_class):
+            raise TypeError("session not an instance of %r: %r"
+                            % (self.session_class, session))
+        assert session.id is not None, "session ID not set"
+        assert session_id == session.id, "session ID mismatch"
+        self.sessions[session_id] = session
+
+    def __delitem__(self, session_id):
+        """__getitem__(session_id : string) -> Session
+
+        Remove the session object identified by 'session_id' from the session
+        manager.  Raise KeyError if no such session.
+        """
+        del self.sessions[session_id]
+
+    # -- Transactional interface ---------------------------------------
+    # Useful for applications that provide a transaction-oriented
+    # persistence mechanism.  You'll still need to provide a mapping
+    # object that works with your persistence mechanism; these two
+    # methods let you hook into your transaction machinery after a
+    # request is finished processing.
+
+    def abort_changes(self, session):
+        """abort_changes(session : Session)
+
+        Placeholder for subclasses that implement transactional
+        persistence: forget about saving changes to the current
+        session.  Called by SessionPublisher when a request fails,
+        ie. when it catches an exception other than PublishError.
+        """
+        pass
+
+    def commit_changes(self, session):
+        """commit_changes(session : Session)
+
+        Placeholder for subclasses that implement transactional
+        persistence: commit changes to the current session.  Called by
+        SessionPublisher when a request completes successfully, or is
+        interrupted by a PublishError exception.
+        """
+        pass
+
+
+    # -- Session management --------------------------------------------
+    # these build on the storage mechanism implemented by the
+    # above mapping methods, and are concerned with all the high-
+    # level details of managing web sessions
+
+    def new_session(self, request, id):
+        """new_session(request : HTTPRequest, id : string)
+           -> Session
+
+        Return a new session object, ie. an instance of the session_class
+        class passed to the constructor (defaults to Session).
+        """
+        return self.session_class(request, id)
+
+    def _get_session_id(self, request, config):
+        """_get_session_id(request : HTTPRequest) -> string
+
+        Find the ID of the current session by looking for the session
+        cookie in 'request'.  Return None if no such cookie or the
+        cookie has been expired, otherwise return the cookie's value.
+        """
+        id = request.cookies.get(config.session_cookie_name)
+        if id == "" or id == "*del*":
+            return None
+        else:
+            return id
+
+    def _make_session_id(self):
+        # Generate a session ID, which is just the value of the session
+        # cookie we are about to drop on the user.  (It's also the key
+        # used with the session manager mapping interface.)
+        id = None
+        while id is None or self.has_session(id):
+            id = randbytes(8)  # 64-bit random number
+        return id
+
+    def _create_session(self, request):
+        # Create a new session object, with no ID for now - one will
+        # be assigned later if we save the session.
+        return self.new_session(request, None)
+
+    def get_session(self, request):
+        """get_session(request : HTTPRequest) -> Session
+
+        Fetch or create a session object for the current session, and
+        return it.  If a session cookie is found in the HTTP request
+        object 'request', use it to look up and return an existing
+        session object.  If no session cookie is found, create a new
+        session.  If the session cookie refers to a non-existent
+        session, raise SessionError.  If the check_session_addr config
+        variable is true, then a mismatch between the IP address stored
+        in an existing session the IP address of the current request
+        also causes SessionError.
+
+        Note that this method does *not* cause the new session to be
+        stored in the session manager, nor does it drop a session cookie
+        on the user.  Those are both the responsibility of
+        maintain_session(), called at the end of a request.
+        """
+        config = get_publisher().config
+        id = self._get_session_id(request, config)
+        if id is not None:
+            session = self.get(id)
+            if session is None:
+                # Note that it's important to revoke the session cookie
+                # so the user doesn't keep getting "Expired session ID"
+                # error pages.  However, it has to be done in the
+                # response object for the error document, which doesn't
+                # exist yet.  Thus, the code that formats SessionError
+                # exceptions -- SessionError.format() by default -- is
+                # responsible for revoking the session cookie.  Yuck.
+                raise SessionError(session_id=id)
+            if (config.check_session_addr and
+                session.get_remote_address() !=
+                request.get_environ("REMOTE_ADDR")):
+                raise SessionError("Remote IP address does not match the "
+                                   "IP address that created the session",
+                                   session_id=id)
+
+        if id is None or session is None:
+            # Generate a session ID and create the session.
+            session = self._create_session(request)
+
+        session._set_access_time(self.ACCESS_TIME_RESOLUTION)
+        return session
+
+    # get_session ()
+
+    def maintain_session(self, request, session):
+        """maintain_session(request : HTTPRequest, session : Session)
+
+        Maintain session information.  This method is called by
+        SessionPublisher after servicing an HTTP request, just before
+        the response is returned.  If a session contains information it
+        is saved and a cookie dropped on the client.  If not, the
+        session is discarded and the client will be instructed to delete
+        the session cookie (if any).
+        """
+        if not session.has_info():
+            # Session has no useful info -- forget it.  If it previously
+            # had useful information and no longer does, we have to
+            # explicitly forget it.
+            if session.id and self.has_session(session.id):
+                del self[session.id]
+                self.revoke_session_cookie(request)
+            return
+
+        if session.id is None:
+            # This is the first time this session has had useful
+            # info -- store it and set the session cookie.
+            session.id = self._make_session_id()
+            self[session.id] = session
+            self.set_session_cookie(request, session.id)
+
+        elif session.is_dirty():
+            # We have already stored this session, but it's dirty
+            # and needs to be stored again.  This will never happen
+            # with the default Session class, but it's there for
+            # applications using a persistence mechanism that requires
+            # repeatedly storing the same object in the same mapping.
+            self[session.id] = session
+
+    def _set_cookie(self, request, value, **attrs):
+        config = get_publisher().config
+        name = config.session_cookie_name
+        if config.session_cookie_path:
+            path = config.session_cookie_path
+        else:
+            path = request.environ['SCRIPT_NAME']
+            if not path.endswith("/"):
+                path += "/"
+        domain = config.session_cookie_domain
+        request.response.set_cookie(name, value, domain=domain,
+                                    path=path, **attrs)
+        return name
+
+    def set_session_cookie(self, request, session_id):
+        """set_session_cookie(request : HTTPRequest, session_id : string)
+
+        Ensure that a session cookie with value 'session_id' will be
+        returned to the client via 'request.response'.
+        """
+        self._set_cookie(request, session_id)
+
+    def revoke_session_cookie(self, request):
+        """revoke_session_cookie(request : HTTPRequest)
+
+        Remove the session cookie from the remote user's session by
+        resetting the value and maximum age in 'request.response'.  Also
+        remove the cookie from 'request' so that further processing of
+        this request does not see the cookie's revoked value.
+        """
+        cookie_name = self._set_cookie(request, "", max_age=0)
+        if request.cookies.has_key(cookie_name):
+            del request.cookies[cookie_name]
+
+    def expire_session(self, request):
+        """expire_session(request : HTTPRequest)
+
+        Expire the current session, ie. revoke the session cookie from
+        the client and remove the session object from the session
+        manager and from 'request'.
+        """
+        self.revoke_session_cookie(request)
+        try:
+            del self[request.session.id]
+        except KeyError:
+            # This can happen if the current session hasn't been saved
+            # yet, eg. if someone tries to leave a session with no
+            # interesting data.  That's not a big deal, so ignore it.
+            pass
+        request.session = None
+
+    def has_session_cookie(self, request, must_exist=0):
+        """has_session_cookie(request : HTTPRequest,
+                              must_exist : boolean = false)
+           -> boolean
+
+        Return true if 'request' already has a cookie identifying a
+        session object.  If 'must_exist' is true, the cookie must
+        correspond to a currently existing session; otherwise (the
+        default), we just check for the existence of the session cookie
+        and don't inspect its content at all.
+        """
+        config = get_publisher().config
+        id = request.cookies.get(config.session_cookie_name)
+        if id is None:
+            return 0
+        if must_exist:
+            return self.has_session(id)
+        else:
+            return 1
+
+# SessionManager
+
+
+class Session:
+    """
+    Holds information about the current session.  The only information
+    that is likely to be useful to applications is the 'user' attribute,
+    which applications can use as they please.
+
+    Instance attributes:
+      id : string
+        the session ID (generated by SessionManager and used as the
+        value of the session cookie)
+      user : any
+        an object to identify the human being on the other end of the
+        line.  It's up to you whether to store just a string in 'user',
+        or some more complex data structure or object.
+      _remote_address : string
+        IP address of user owning this session (only set when the
+        session is created -- requests for this session from a different
+        IP address will either raise SessionError or be treated
+        normally, depending on the CHECK_SESSION_ADDR config variable)
+      _creation_time : float
+      _access_time : float
+        two ways of keeping track of the "age" of the session.
+        Note that '__access_time' is maintained by the SessionManager that
+        owns this session, using _set_access_time().
+      _form_tokens : [string]
+        outstanding form tokens.  This is used as a queue that can grow
+        up to MAX_FORM_TOKENS.  Tokens are removed when forms are submitted.
+
+    Feel free to access 'id' and 'user' directly, but do not modify
+    'id'.  The preferred way to set 'user' is with the set_user() method
+    (which you might want to override for type-checking).
+    """
+
+    MAX_FORM_TOKENS = 16 # maximum number of outstanding form tokens
+
+    def __init__(self, request, id):
+        self.id = id
+        self.user = None
+        self._remote_address = request.get_environ("REMOTE_ADDR")
+        self._creation_time = self._access_time = time()
+        self._form_tokens = [] # queue
+
+    def __repr__(self):
+        return "<%s at %x: %s>" % (self.__class__.__name__, id(self), self.id)
+
+    def __str__(self):
+        if self.user:
+            return "session %s (user %s)" % (self.id, self.user)
+        else:
+            return "session %s (no user)" % self.id
+
+    def has_info(self):
+        """has_info() -> boolean
+
+        Return true if this session contains any information that must
+        be saved.
+        """
+        return self.user or self._form_tokens
+
+    def is_dirty(self):
+        """is_dirty() -> boolean
+
+        Return true if this session has changed since it was last saved
+        such that it needs to be saved again.
+
+        Default implementation always returns false since the default
+        storage mechanism is an in-memory dictionary, and you don't have
+        to put the same object into the same slot of a dictionary twice.
+        If sessions are stored to, eg., files in a directory or slots in
+        a hash file, is_dirty() should probably be an alias or wrapper
+        for has_info().  See doc/session-mgmt.txt.
+        """
+        return 0
+
+    def dump(self, file=None, header=1, deep=1):
+        time_fmt = "%Y-%m-%d %H:%M:%S"
+        ctime = strftime(time_fmt, localtime(self._creation_time))
+        atime = strftime(time_fmt, localtime(self._access_time))
+
+        if header:
+            file.write('session %s:' % self.id)
+        file.write('  user %s' % self.user)
+        file.write('  _remote_address: %s' % self._remote_address)
+        file.write('  created %s, last accessed %s' % (ctime, atime))
+        file.write('  _form_tokens: %s\n' % self._form_tokens)
+
+    # dump()
+
+
+    # -- Hooks into the Quixote main loop ------------------------------
+
+    def start_request(self, request):
+        """start_request(request : HTTPRequest)
+
+        Called near the beginning of each request: after the HTTPRequest
+        object has been built and this Session object has been fetched
+        or built, but before we traverse the URL or call the callable
+        object found by URL traversal.
+        """
+        if self.user:
+            request.environ['REMOTE_USER'] = str(self.user)
+
+    def finish_request(self, request):
+        """finish_request(request : HTTPRequest)
+
+        Called near the end of each request: after a callable object has
+        been found and (successfully) called.  Not called if there were
+        any errors processing the request.
+        """
+        pass
+
+
+    # -- Simple accessors and modifiers --------------------------------
+
+    def set_user(self, user):
+        self.user = user
+
+    def get_remote_address(self):
+        """Return the IP address (dotted-quad string) that made the
+        initial request in this session.
+        """
+        return self._remote_address
+
+    def get_creation_time(self):
+        """Return the time that this session was created (seconds
+        since epoch).
+        """
+        return self._creation_time
+
+    def get_access_time(self):
+        """Return the time that this session was last accessed (seconds
+        since epoch).
+        """
+        return self._access_time
+
+    def get_creation_age(self, _now=None):
+        """Return the number of seconds since session was created."""
+        # _now arg is not strictly necessary, but there for consistency
+        # with get_access_age()
+        return (_now or time()) - self._creation_time
+
+    def get_access_age(self, _now=None):
+        """Return the number of seconds since session was last accessed."""
+        # _now arg is for SessionManager's use
+        return (_now or time()) - self._access_time
+
+
+    # -- Methods for SessionManager only -------------------------------
+
+    def _set_access_time(self, resolution):
+        now = time()
+        if now - self._access_time > resolution:
+            self._access_time = now
+
+
+    # -- Form token methods --------------------------------------------
+
+    def create_form_token(self):
+        """create_form_token() -> string
+
+        Create a new form token and add it to a queue of outstanding form
+        tokens for this session.  A maximum of MAX_FORM_TOKENS are saved.
+        The new token is returned.
+        """
+        token = randbytes(8)
+        self._form_tokens.append(token)
+        extra = len(self._form_tokens) - self.MAX_FORM_TOKENS
+        if extra > 0:
+            del self._form_tokens[:extra]
+        return token
+
+    def has_form_token(self, token):
+        """has_form_token(token : string) -> boolean
+
+        Return true if 'token' is in the queue of outstanding tokens.
+        """
+        return token in self._form_tokens
+
+    def remove_form_token(self, token):
+        """remove_form_token(token : string)
+
+        Remove 'token' from the queue of outstanding tokens.
+        """
+        self._form_tokens.remove(token)
+
+# Session

Added: packages/quixote1/branches/upstream/current/setup.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/setup.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/setup.py (added)
+++ packages/quixote1/branches/upstream/current/setup.py Mon May  8 19:16:16 2006
@@ -1,0 +1,64 @@
+#!/usr/bin/env python
+#$HeadURL: svn+ssh://svn/repos/trunk/quixote/setup.py $
+#$Id: setup.py 25278 2004-10-06 15:49:42Z nascheme $
+
+# Setup script for Quixote
+
+__revision__ = "$Id: setup.py 25278 2004-10-06 15:49:42Z nascheme $"
+
+import sys, os
+from distutils import core
+from distutils.extension import Extension
+from qx_distutils import qx_build_py
+
+# a fast htmltext type
+htmltext = Extension(name="quixote._c_htmltext",
+                     sources=["src/_c_htmltext.c"])
+
+# faster import hook for PTL modules
+cimport = Extension(name="quixote.cimport",
+                    sources=["src/cimport.c"])
+
+kw = {'name': "Quixote",
+      'version': "1.2",
+      'description': "A highly Pythonic Web application framework",
+      'author': "MEMS Exchange",
+      'author_email': "quixote at mems-exchange.org",
+      'url': "http://www.mems-exchange.org/software/quixote/",
+      'license': "CNRI Open Source License (see LICENSE.txt)",
+
+      'package_dir': {'quixote':os.curdir},
+      'packages': ['quixote',  'quixote.demo', 'quixote.form',
+                   'quixote.form2', 'quixote.server'],
+
+      'ext_modules': [],
+
+      'cmdclass': {'build_py': qx_build_py},
+     }
+
+
+build_extensions = sys.platform != 'win32'
+
+if build_extensions:
+    # The _c_htmltext module requires Python 2.2 features.
+    if sys.hexversion >= 0x20200a1:
+        kw['ext_modules'].append(htmltext)
+    kw['ext_modules'].append(cimport)
+
+# If we're running Python 2.3, add extra information
+if hasattr(core, 'setup_keywords'):
+    if 'classifiers' in core.setup_keywords:
+        kw['classifiers'] = ['Development Status :: 5 - Production/Stable',
+          'Environment :: Web Environment',
+          'License :: OSI Approved :: Python License (CNRI Python License)',
+          'Intended Audience :: Developers',
+          'Operating System :: Unix',
+          'Operating System :: Microsoft :: Windows',
+          'Operating System :: MacOS :: MacOS X',
+          'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+          ]
+    if 'download_url' in core.setup_keywords:
+        kw['download_url'] = ('http://www.mems-exchange.org/software/files'
+                              '/quixote/Quixote-%s.tar.gz' % kw['version'])
+
+core.setup(**kw)

Added: packages/quixote1/branches/upstream/current/src/Makefile
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/src/Makefile?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/src/Makefile (added)
+++ packages/quixote1/branches/upstream/current/src/Makefile Mon May  8 19:16:16 2006
@@ -1,0 +1,2 @@
+all:
+	python setup.py build

Added: packages/quixote1/branches/upstream/current/src/_c_htmltext.c
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/src/_c_htmltext.c?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/src/_c_htmltext.c (added)
+++ packages/quixote1/branches/upstream/current/src/_c_htmltext.c Mon May  8 19:16:16 2006
@@ -1,0 +1,991 @@
+/* htmltext type and the htmlescape function  */
+
+#include "Python.h"
+#include "structmember.h"
+
+typedef struct {
+	PyObject_HEAD
+	PyStringObject *s;
+} htmltextObject;
+
+static PyTypeObject htmltext_Type;
+
+#define htmltextObject_Check(v)	((v)->ob_type == &htmltext_Type)
+
+#define htmltext_STR(v) ((PyObject *)(((htmltextObject *)v)->s))
+
+typedef struct {
+	PyObject_HEAD
+	PyObject *obj;
+} QuoteWrapperObject;
+
+static PyTypeObject QuoteWrapper_Type;
+
+#define QuoteWrapper_Check(v)	((v)->ob_type == &QuoteWrapper_Type)
+
+
+typedef struct {
+	PyObject_HEAD
+	PyObject *obj;
+} DictWrapperObject;
+
+static PyTypeObject DictWrapper_Type;
+
+#define DictWrapper_Check(v)	((v)->ob_type == &DictWrapper_Type)
+
+
+typedef struct {
+	PyObject_HEAD
+	int html;
+	char *buf;
+	size_t size;
+	size_t pos;
+} TemplateIO_Object;
+
+static PyTypeObject TemplateIO_Type;
+
+#define TemplateIO_Check(v)	((v)->ob_type == &TemplateIO_Type)
+
+
+static PyObject *
+type_error(const char *msg)
+{
+	PyErr_SetString(PyExc_TypeError, msg);
+	return NULL;
+}
+
+static PyObject *
+escape_string(PyObject *s)
+{
+	PyObject *new_s;
+	char *ss, *new_ss;
+	size_t i, j, extra_space, size, new_size;
+	if (!PyString_Check(s))
+		return type_error("str object required");
+	ss = PyString_AS_STRING(s);
+	size = PyString_GET_SIZE(s);
+	extra_space = 0;
+	for (i=0; i < size; i++) {
+		switch (ss[i]) {
+		case '&':
+			extra_space += 4;
+			break;
+		case '<':
+		case '>':
+			extra_space += 3;
+			break;
+		case '"':
+			extra_space += 5;
+			break;
+		}
+	}
+	if (extra_space == 0) {
+		Py_INCREF(s);
+		return (PyObject *)s;
+	}
+	new_size = size + extra_space;
+	new_s = PyString_FromStringAndSize(NULL, new_size);
+	if (new_s == NULL)
+		return NULL;
+	new_ss = PyString_AsString(new_s);
+	for (i=0, j=0; i < size; i++) {
+		switch (ss[i]) {
+		case '&':
+			new_ss[j++] = '&';
+			new_ss[j++] = 'a';
+			new_ss[j++] = 'm';
+			new_ss[j++] = 'p';
+			new_ss[j++] = ';';
+			break;
+		case '<':
+			new_ss[j++] = '&';
+			new_ss[j++] = 'l';
+			new_ss[j++] = 't';
+			new_ss[j++] = ';';
+			break;
+		case '>':
+			new_ss[j++] = '&';
+			new_ss[j++] = 'g';
+			new_ss[j++] = 't';
+			new_ss[j++] = ';';
+			break;
+		case '"':
+			new_ss[j++] = '&';
+			new_ss[j++] = 'q';
+			new_ss[j++] = 'u';
+			new_ss[j++] = 'o';
+			new_ss[j++] = 't';
+			new_ss[j++] = ';';
+			break;
+		default:
+			new_ss[j++] = ss[i];
+			break;
+		}
+	}
+	assert (j == new_size);
+	return (PyObject *)new_s;
+}
+
+static PyObject *
+quote_wrapper_new(PyObject *o)
+{
+	QuoteWrapperObject *self;
+	if (htmltextObject_Check(o) ||
+	    PyInt_Check(o) ||
+	    PyFloat_Check(o) ||
+	    PyLong_Check(o)) {
+		/* no need for wrapper */
+		Py_INCREF(o);
+		return o;
+	}
+	self = PyObject_New(QuoteWrapperObject, &QuoteWrapper_Type);
+	if (self == NULL)
+		return NULL;
+	Py_INCREF(o);
+	self->obj = o;
+	return (PyObject *)self;
+}
+
+static void
+quote_wrapper_dealloc(QuoteWrapperObject *self)
+{
+	Py_DECREF(self->obj);
+	PyObject_Del(self);
+}
+
+static PyObject *
+quote_wrapper_repr(QuoteWrapperObject *self)
+{
+	PyObject *qs;
+	PyObject *s = PyObject_Repr(self->obj);
+	if (s == NULL)
+		return NULL;
+	qs = escape_string(s);
+	Py_DECREF(s);
+	return qs;
+}
+
+static PyObject *
+quote_wrapper_str(QuoteWrapperObject *self)
+{
+	PyObject *qs;
+	PyObject *s = PyObject_Str(self->obj);
+	if (s == NULL)
+		return NULL;
+	qs = escape_string(s);
+	Py_DECREF(s);
+	return qs;
+}
+
+static PyObject *
+dict_wrapper_new(PyObject *o)
+{
+	DictWrapperObject *self;
+	self = PyObject_New(DictWrapperObject, &DictWrapper_Type);
+	if (self == NULL)
+		return NULL;
+	Py_INCREF(o);
+	self->obj = o;
+	return (PyObject *)self;
+}
+
+static void
+dict_wrapper_dealloc(DictWrapperObject *self)
+{
+	Py_DECREF(self->obj);
+	PyObject_Del(self);
+}
+
+static PyObject *
+dict_wrapper_subscript(DictWrapperObject *self, PyObject *key)
+{
+	PyObject *v, *w;;
+	v = PyObject_GetItem(self->obj, key);
+	if (v == NULL) {
+		return NULL;
+	}
+	w = quote_wrapper_new(v); 
+	Py_DECREF(v);
+	return w;
+}
+
+static PyObject *
+htmltext_from_string(PyObject *s)
+{
+	/* note, this takes a reference */
+	PyObject *self;
+	if (s == NULL)
+		return NULL;
+	assert (PyString_Check(s));
+	self = PyType_GenericAlloc(&htmltext_Type, 0);
+	if (self == NULL) {
+		return NULL;
+	}
+	((htmltextObject *)self)->s = (PyStringObject *)s;
+	return self;
+}
+
+static PyObject *
+htmltext_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+	htmltextObject *self;
+	PyObject *s;
+	static char *kwlist[] = {"s", 0};
+	if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:htmltext", kwlist,
+					 &s))
+		return NULL;
+	s = PyObject_Str(s);
+	if (s == NULL)
+		return NULL;
+	self = (htmltextObject *)type->tp_alloc(type, 0);
+	if (self == NULL) {
+		Py_DECREF(s);
+		return NULL;
+	}
+	self->s = (PyStringObject *)s;
+	return (PyObject *)self;
+}
+
+/* htmltext methods */
+
+static void
+htmltext_dealloc(htmltextObject *self)
+{
+	Py_DECREF(self->s);
+	self->ob_type->tp_free((PyObject *)self);
+}
+
+static long
+htmltext_hash(PyObject *self)
+{
+	return PyObject_Hash(htmltext_STR(self));
+}
+
+static PyObject *
+htmltext_str(htmltextObject *self)
+{
+	Py_INCREF(self->s);
+	return (PyObject *)self->s;
+}
+
+static PyObject *
+htmltext_repr(htmltextObject *self)
+{
+	PyObject *sr, *rv;
+	sr = PyObject_Repr((PyObject *)self->s);
+	if (sr == NULL)
+		return NULL;
+	rv = PyString_FromFormat("<htmltext %s>", PyString_AsString(sr));
+	Py_DECREF(sr);
+	return rv;
+}
+
+static PyObject *
+htmltext_richcompare(PyObject *a, PyObject *b, int op)
+{
+	PyObject *sa, *sb;
+	if (PyString_Check(a)) {
+		sa = a;
+	} else if (htmltextObject_Check(a)) {
+		sa = htmltext_STR(a);
+	} else {
+		goto fail;
+	}
+	if (PyString_Check(b)) {
+		sb = b;
+	} else if (htmltextObject_Check(b)) {
+		sb = htmltext_STR(b);
+	} else {
+		goto fail;
+	}
+	return sa->ob_type->tp_richcompare(sa, sb, op);
+
+fail:
+	Py_INCREF(Py_NotImplemented);
+	return Py_NotImplemented;
+}
+
+static long
+htmltext_length(htmltextObject *self)
+{
+	return ((PyStringObject *)self->s)->ob_size;
+}
+
+
+static PyObject *
+wrap_arg(PyObject *arg)
+{
+	PyObject *warg;
+	if (htmltextObject_Check(arg)) {
+		/* don't bother with wrapper object */
+		warg = arg;
+		Py_INCREF(arg);
+	} else {
+		warg = quote_wrapper_new(arg); 
+	}
+	return warg;
+}
+
+static PyObject *
+htmltext_format(htmltextObject *self, PyObject *args)
+{
+	/* wrap the format arguments with QuoteWrapperObject */
+	int do_dict = 0;
+	PyObject *rv, *wargs;
+	if (args->ob_type->tp_as_mapping && !PyTuple_Check(args) &&
+	    !PyString_Check(args)) {
+		char *fmt = PyString_AS_STRING(htmltext_STR(self));
+		size_t i, n = PyString_GET_SIZE(htmltext_STR(self));
+		char last = 0;
+		/* second check necessary since '%s' % {} => '{}' */
+		for (i=0; i < n; i++) {
+			if (last == '%' && fmt[i] == '(') {
+				do_dict = 1;
+				break;
+			}
+			last = fmt[i];
+		}
+	}
+	if (do_dict) {
+		wargs = dict_wrapper_new(args);
+		if (wargs == NULL)
+			return NULL;
+	}
+	else if (PyTuple_Check(args)) {
+		long i, n = PyTuple_GET_SIZE(args);
+		wargs = PyTuple_New(n);
+		for (i=0; i < n; i++) {
+			PyObject *wvalue = wrap_arg(PyTuple_GET_ITEM(args, i));
+			if (wvalue == NULL) {
+				Py_DECREF(wargs);
+				return NULL;
+			}
+			PyTuple_SetItem(wargs, i, wvalue);
+		}
+	}
+	else {
+		wargs = wrap_arg(args);
+		if (wargs == NULL) {
+			return NULL;
+		}
+	}
+	rv = PyString_Format((PyObject *)self->s, wargs);
+	Py_DECREF(wargs);
+	return htmltext_from_string(rv);
+}
+
+static PyObject *
+htmltext_add(PyObject *v, PyObject *w)
+{
+	PyObject *qv, *qw;
+	if (htmltextObject_Check(v) && htmltextObject_Check(w)) {
+		qv = htmltext_STR(v);
+		qw = htmltext_STR(w);
+		Py_INCREF(qv);
+		Py_INCREF(qw);
+	}
+	else if (PyString_Check(w)) {
+		assert (htmltextObject_Check(v));
+		qv = htmltext_STR(v);
+		qw = escape_string(w);
+		if (qw == NULL)
+			return NULL;
+		Py_INCREF(qv);
+	}
+	else if (PyString_Check(v)) {
+		assert (htmltextObject_Check(w));
+		qv = escape_string(v);
+		if (qv == NULL)
+			return NULL;
+		qw = htmltext_STR(w);
+		Py_INCREF(qw);
+	}
+	else {
+		Py_INCREF(Py_NotImplemented);
+		return Py_NotImplemented;
+	}
+	PyString_ConcatAndDel(&qv, qw);
+	return htmltext_from_string(qv);
+}
+
+static PyObject *
+htmltext_repeat(htmltextObject *self, int n)
+{
+	PyObject *s = PySequence_Repeat(htmltext_STR(self), n);
+	if (s == NULL)
+		return NULL;
+	return htmltext_from_string(s);
+}
+
+static PyObject *
+htmltext_join(PyObject *self, PyObject *args)
+{
+	long i;
+	PyObject *qargs, *rv;
+	if (!PySequence_Check(args)) {
+		return type_error("argument must be a sequence");
+	}
+	qargs = PyList_New(PySequence_Size(args));
+	if (qargs == NULL)
+		return NULL;
+	for (i=0; i < PySequence_Size(args); i++) {
+		PyObject *value, *qvalue;
+		value = PySequence_GetItem(args, i);
+		if (value == NULL) {
+			goto error;
+		}
+		if (htmltextObject_Check(value)) {
+			qvalue = htmltext_STR(value);
+			Py_INCREF(qvalue);
+			Py_DECREF(value);
+		}
+		else if (PyString_Check(value)) {
+			qvalue = escape_string(value);
+			Py_DECREF(value);
+		}
+		else {
+			Py_DECREF(value);
+			type_error("join requires a list of strings");
+			goto error;
+		}
+		if (PyList_SetItem(qargs, i, qvalue) < 0) {
+			goto error;
+		}
+	}
+	rv = _PyString_Join(htmltext_STR(self), qargs);
+	Py_DECREF(qargs);
+	return htmltext_from_string(rv);
+
+error:
+	Py_DECREF(qargs);
+	return NULL;
+}
+
+static PyObject *
+quote_arg(PyObject *s)
+{
+	PyObject *ss;
+	if (PyString_Check(s)) {
+		ss = escape_string(s);
+		if (ss == NULL)
+			return NULL;
+	}
+	else if (htmltextObject_Check(s)) {
+		ss = htmltext_STR(s);
+		Py_INCREF(ss);
+	}
+	else {
+		return type_error("string object required");
+	}
+	return ss;
+}
+
+static PyObject *
+htmltext_call_method1(PyObject *self, PyObject *s, char *method)
+{
+	PyObject *ss, *rv;
+	ss = quote_arg(s);
+	if (ss == NULL)
+		return NULL;
+	rv = PyObject_CallMethod(htmltext_STR(self), method, "O", ss);
+	Py_DECREF(ss);
+	return rv;
+}
+
+static PyObject *
+htmltext_startswith(PyObject *self, PyObject *s)
+{
+	return htmltext_call_method1(self, s, "startswith");
+}
+
+static PyObject *
+htmltext_endswith(PyObject *self, PyObject *s)
+{
+	return htmltext_call_method1(self, s, "endswith");
+}
+
+static PyObject *
+htmltext_replace(PyObject *self, PyObject *args)
+{
+	PyObject *old, *new, *q_old, *q_new, *rv;
+	int maxsplit = -1;
+	if (!PyArg_ParseTuple(args,"OO|i:replace", &old, &new, &maxsplit))
+		return NULL;
+	q_old = quote_arg(old);
+	if (q_old == NULL)
+		return NULL;
+	q_new = quote_arg(new);
+	if (q_new == NULL) {
+		Py_DECREF(q_old);
+		return NULL;
+	}
+	rv = PyObject_CallMethod(htmltext_STR(self), "replace", "OOi",
+				 q_old, q_new, maxsplit);
+	Py_DECREF(q_old);
+	Py_DECREF(q_new);
+	return htmltext_from_string(rv);
+}
+
+
+static PyObject *
+htmltext_lower(PyObject *self)
+{
+	return htmltext_from_string(PyObject_CallMethod(htmltext_STR(self),
+							"lower", ""));
+}
+
+static PyObject *
+htmltext_upper(PyObject *self)
+{
+	return htmltext_from_string(PyObject_CallMethod(htmltext_STR(self),
+							"upper", ""));
+}
+
+static PyObject *
+htmltext_capitalize(PyObject *self)
+{
+	return htmltext_from_string(PyObject_CallMethod(htmltext_STR(self),
+							"capitalize", ""));
+}
+
+static PyObject *
+template_io_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+	TemplateIO_Object *self;
+	int html = 0;
+	static char *kwlist[] = {"html", 0};
+	if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i:TemplateIO",
+					 kwlist, &html))
+		return NULL;
+	self = (TemplateIO_Object *)type->tp_alloc(type, 0);
+	if (self == NULL) {
+		return NULL;
+	}
+	self->html = html != 0;
+	self->buf = NULL;
+	self->size = 0;
+	self->pos = 0;
+	return (PyObject *)self;
+}
+
+static void
+template_io_dealloc(TemplateIO_Object *self)
+{
+	if (self->size > 0)
+		PyMem_Free(self->buf);
+	self->ob_type->tp_free((PyObject *)self);
+}
+
+static PyObject *
+template_io_str(TemplateIO_Object *self)
+{
+	return PyString_FromStringAndSize(self->buf, self->pos);
+}
+
+static PyObject *
+template_io_getvalue(TemplateIO_Object *self)
+{
+	if (self->html) {
+		return htmltext_from_string(template_io_str(self));
+	}
+	else {
+		return template_io_str(self);
+	}
+}
+
+static PyObject *
+template_io_repr(TemplateIO_Object *self)
+{
+	PyObject *s, *sr, *rv;
+	s = template_io_str(self);
+	if (s == NULL)
+		return NULL;
+	sr = PyObject_Repr(s);
+	Py_DECREF(s);
+	if (sr == NULL)
+		return NULL;
+	rv = PyString_FromFormat("<TemplateIO %s>", PyString_AsString(sr));
+	Py_DECREF(sr);
+	return rv;
+}
+
+
+static PyObject *
+template_io_do_concat(TemplateIO_Object *self, char *s, size_t size)
+{
+	/* note this adds a reference to self */
+	if (self->pos + size > self->size) {
+		size_t new_size;
+		char *new_buf;
+		if (self->size > size)
+			new_size = self->size * 2;
+		else
+			new_size = size * 2;
+		new_buf = PyMem_Realloc(self->buf, new_size);
+		if (new_buf == NULL)
+			return NULL;
+		self->buf = new_buf;
+		self->size = new_size;
+	}
+	assert (self->pos + size <= self->size);
+	memcpy(self->buf + self->pos, s, size);
+	self->pos += size;
+	Py_INCREF(self);
+	return (PyObject *)self;
+}
+	
+
+static PyObject *
+template_io_iadd(TemplateIO_Object *self, PyObject *other)
+{
+	PyObject *rv;
+	PyObject *s = NULL;
+	if (!TemplateIO_Check(self))
+		return type_error("TemplateIO object required");
+	if (other == Py_None) {
+		Py_INCREF(self);
+		return (PyObject *)self;
+	}
+	else if (TemplateIO_Check(other)) {
+		TemplateIO_Object *o = (TemplateIO_Object *)other;
+		if (self->html && !o->html) {
+			PyObject *ss = PyString_FromStringAndSize(o->buf,
+								  o->pos);
+			if (ss == NULL)
+				return NULL;
+			s = escape_string(ss);
+			Py_DECREF(ss);
+			goto concat_str;
+		}
+		rv = template_io_do_concat(self, o->buf, o->pos);
+	}
+	else if (htmltextObject_Check(other)) {
+		PyStringObject *s = ((htmltextObject *)other)->s;
+		rv = template_io_do_concat(self,
+					   PyString_AS_STRING(s),
+					   PyString_GET_SIZE(s));
+	}
+	else {
+		if (self->html) {
+			PyObject *ss = PyObject_Str(other);
+			if (ss == NULL)
+				return NULL;
+			s = escape_string(ss);
+			Py_DECREF(ss);
+		} else {
+			s = PyObject_Str(other);
+		}
+concat_str:
+		if (s == NULL)
+			return NULL;
+		rv = template_io_do_concat(self, PyString_AS_STRING(s),
+					   PyString_GET_SIZE(s));
+		Py_XDECREF(s);
+	}
+	return rv;
+}
+
+static PyMethodDef htmltext_methods[] = {
+	{"join", (PyCFunction)htmltext_join, METH_O, ""},
+	{"startswith", (PyCFunction)htmltext_startswith, METH_O, ""},
+	{"endswith", (PyCFunction)htmltext_endswith, METH_O, ""},
+	{"replace", (PyCFunction)htmltext_replace, METH_VARARGS, ""},
+	{"lower", (PyCFunction)htmltext_lower, METH_NOARGS, ""},
+	{"upper", (PyCFunction)htmltext_upper, METH_NOARGS, ""},
+	{"capitalize", (PyCFunction)htmltext_capitalize, METH_NOARGS, ""},
+	{NULL, NULL}
+};
+
+static PyMemberDef htmltext_members[] = {
+	{"s", T_OBJECT, offsetof(htmltextObject, s), READONLY, "the string"},
+	{NULL},
+};
+
+static PySequenceMethods htmltext_as_sequence = {
+	(inquiry)htmltext_length,	/*sq_length*/
+	0,				/*sq_concat*/
+	(intargfunc)htmltext_repeat,	/*sq_repeat*/
+	0,				/*sq_item*/
+	0,				/*sq_slice*/
+	0,				/*sq_ass_item*/
+	0,				/*sq_ass_slice*/
+	0,				/*sq_contains*/
+};
+
+static PyNumberMethods htmltext_as_number = {
+	(binaryfunc)htmltext_add, /*nb_add*/
+	0, /*nb_subtract*/
+	0, /*nb_multiply*/
+	0, /*nb_divide*/
+	(binaryfunc)htmltext_format, /*nb_remainder*/
+	0, /*nb_divmod*/
+	0, /*nb_power*/
+	0, /*nb_negative*/
+	0, /*nb_positive*/
+	0, /*nb_absolute*/
+	0, /*nb_nonzero*/
+	0, /*nb_invert*/
+	0, /*nb_lshift*/
+	0, /*nb_rshift*/
+	0, /*nb_and*/
+	0, /*nb_xor*/
+	0, /*nb_or*/
+	0, /*nb_coerce*/
+	0, /*nb_int*/
+	0, /*nb_long*/
+	0, /*nb_float*/
+};
+
+static PyTypeObject htmltext_Type = {
+	PyObject_HEAD_INIT(NULL)
+	0,			/*ob_size*/
+	"htmltext",		/*tp_name*/
+	sizeof(htmltextObject),	/*tp_basicsize*/
+	0,			/*tp_itemsize*/
+	/* methods */
+	(destructor)htmltext_dealloc, /*tp_dealloc*/
+	0,			/*tp_print*/
+	0,			/*tp_getattr*/
+	0,			/*tp_setattr*/
+	0,			/*tp_compare*/
+	(unaryfunc)htmltext_repr,/*tp_repr*/
+	&htmltext_as_number,	/*tp_as_number*/
+	&htmltext_as_sequence,	/*tp_as_sequence*/
+	0,			/*tp_as_mapping*/
+	htmltext_hash,		/*tp_hash*/
+	0,			/*tp_call*/
+	(unaryfunc)htmltext_str,/*tp_str*/
+	PyObject_GenericGetAttr,/*tp_getattro*/
+	0,			/*tp_setattro*/
+	0,			/*tp_as_buffer*/
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE \
+		| Py_TPFLAGS_CHECKTYPES, /*tp_flags*/
+	0,			/*tp_doc*/
+	0,			/*tp_traverse*/
+	0,			/*tp_clear*/
+	htmltext_richcompare,	/*tp_richcompare*/
+	0,			/*tp_weaklistoffset*/
+	0,			/*tp_iter*/
+	0,			/*tp_iternext*/
+	htmltext_methods,	/*tp_methods*/
+	htmltext_members,	/*tp_members*/
+	0,			/*tp_getset*/
+	0,			/*tp_base*/
+	0,			/*tp_dict*/
+	0,			/*tp_descr_get*/
+	0,			/*tp_descr_set*/
+	0,			/*tp_dictoffset*/
+	0,			/*tp_init*/
+	PyType_GenericAlloc,	/*tp_alloc*/
+	htmltext_new,		/*tp_new*/
+	_PyObject_Del,		/*tp_free*/
+	0,			/*tp_is_gc*/
+};
+
+static PyNumberMethods quote_wrapper_as_number = {
+	0, /*nb_add*/
+	0, /*nb_subtract*/
+	0, /*nb_multiply*/
+	0, /*nb_divide*/
+	0, /*nb_remainder*/
+	0, /*nb_divmod*/
+	0, /*nb_power*/
+	0, /*nb_negative*/
+	0, /*nb_positive*/
+	0, /*nb_absolute*/
+	0, /*nb_nonzero*/
+	0, /*nb_invert*/
+	0, /*nb_lshift*/
+	0, /*nb_rshift*/
+	0, /*nb_and*/
+	0, /*nb_xor*/
+	0, /*nb_or*/
+	0, /*nb_coerce*/
+	0, /*nb_int*/
+	0, /*nb_long*/
+	0, /*nb_float*/
+};
+
+static PyTypeObject QuoteWrapper_Type = {
+	PyObject_HEAD_INIT(NULL)
+	0,			/*ob_size*/
+	"QuoteWrapper",		/*tp_name*/
+	sizeof(QuoteWrapperObject),	/*tp_basicsize*/
+	0,			/*tp_itemsize*/
+	/* methods */
+	(destructor)quote_wrapper_dealloc, /*tp_dealloc*/
+	0,			/*tp_print*/
+	0,			/*tp_getattr*/
+	0,			/*tp_setattr*/
+	0,			/*tp_compare*/
+	(unaryfunc)quote_wrapper_repr,/*tp_repr*/
+	&quote_wrapper_as_number,/*tp_as_number*/
+	0,			/*tp_as_sequence*/
+	0,			/*tp_as_mapping*/
+	0,			/*tp_hash*/
+	0,			/*tp_call*/
+	(unaryfunc)quote_wrapper_str,  /*tp_str*/
+};
+
+static PyMappingMethods dict_wrapper_as_mapping = {
+        0, /*mp_length*/
+        (binaryfunc)dict_wrapper_subscript, /*mp_subscript*/
+        0, /*mp_ass_subscript*/
+};
+
+static PyTypeObject DictWrapper_Type = {
+	PyObject_HEAD_INIT(NULL)
+	0,			/*ob_size*/
+	"DictWrapper",		/*tp_name*/
+	sizeof(DictWrapperObject),	/*tp_basicsize*/
+	0,			/*tp_itemsize*/
+	/* methods */
+	(destructor)dict_wrapper_dealloc, /*tp_dealloc*/
+	0,			/*tp_print*/
+	0,			/*tp_getattr*/
+	0,			/*tp_setattr*/
+	0,			/*tp_compare*/
+	0,			/*tp_repr*/
+	0,			/*tp_as_number*/
+	0,			/*tp_as_sequence*/
+	&dict_wrapper_as_mapping,/*tp_as_mapping*/
+};
+
+static PyNumberMethods template_io_as_number = {
+	0, /*nb_add*/
+	0, /*nb_subtract*/
+	0, /*nb_multiply*/
+	0, /*nb_divide*/
+	0, /*nb_remainder*/
+	0, /*nb_divmod*/
+	0, /*nb_power*/
+	0, /*nb_negative*/
+	0, /*nb_positive*/
+	0, /*nb_absolute*/
+	0, /*nb_nonzero*/
+	0, /*nb_invert*/
+	0, /*nb_lshift*/
+	0, /*nb_rshift*/
+	0, /*nb_and*/
+	0, /*nb_xor*/
+	0, /*nb_or*/
+	0, /*nb_coerce*/
+	0, /*nb_int*/
+	0, /*nb_long*/
+	0, /*nb_float*/
+	0, /*nb_oct*/
+	0, /*nb_hex*/
+	(binaryfunc)template_io_iadd, /*nb_inplace_add*/
+};
+
+static PyMethodDef template_io_methods[] = {
+	{"getvalue", (PyCFunction)template_io_getvalue, METH_NOARGS, ""},
+	{NULL, NULL}
+};
+
+static PyTypeObject TemplateIO_Type = {
+	PyObject_HEAD_INIT(NULL)
+	0,			/*ob_size*/
+	"TemplateIO",		/*tp_name*/
+	sizeof(TemplateIO_Object),/*tp_basicsize*/
+	0,			/*tp_itemsize*/
+	/* methods */
+	(destructor)template_io_dealloc, /*tp_dealloc*/
+	0,			/*tp_print*/
+	0,			/*tp_getattr*/
+	0,			/*tp_setattr*/
+	0,			/*tp_compare*/
+	(unaryfunc)template_io_repr,/*tp_repr*/
+	&template_io_as_number,	/*tp_as_number*/
+	0,			/*tp_as_sequence*/
+	0,			/*tp_as_mapping*/
+	0,			/*tp_hash*/
+	0,			/*tp_call*/
+	(unaryfunc)template_io_str,/*tp_str*/
+	PyObject_GenericGetAttr,/*tp_getattro*/
+	0,			/*tp_setattro*/
+	0,			/*tp_as_buffer*/
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+	0,			/*tp_doc*/
+	0,			/*tp_traverse*/
+	0,			/*tp_clear*/
+	0,			/*tp_richcompare*/
+	0,			/*tp_weaklistoffset*/
+	0,			/*tp_iter*/
+	0,			/*tp_iternext*/
+	template_io_methods,	/*tp_methods*/
+	0,			/*tp_members*/
+	0,			/*tp_getset*/
+	0,			/*tp_base*/
+	0,			/*tp_dict*/
+	0,			/*tp_descr_get*/
+	0,			/*tp_descr_set*/
+	0,			/*tp_dictoffset*/
+	0,			/*tp_init*/
+	PyType_GenericAlloc,	/*tp_alloc*/
+	template_io_new,	/*tp_new*/
+	_PyObject_Del,		/*tp_free*/
+	0,			/*tp_is_gc*/
+};
+
+/* --------------------------------------------------------------------- */
+
+static PyObject *
+html_escape(PyObject *self, PyObject *o)
+{
+	if (htmltextObject_Check(o)) {
+		Py_INCREF(o);
+		return o;
+	}
+	else {
+		PyObject *rv;
+		PyObject *s = PyObject_Str(o);
+		if (s == NULL)
+			return NULL;
+		rv = escape_string(s);
+		Py_DECREF(s);
+		return htmltext_from_string(rv);
+	}
+}
+
+static PyObject *
+py_escape_string(PyObject *self, PyObject *o)
+{
+	PyObject *rv;
+	if (!PyString_Check(o))
+		return type_error("string required");
+	rv = escape_string(o);
+	return rv;
+}
+
+/* List of functions defined in the module */
+
+static PyMethodDef htmltext_module_methods[] = {
+	{"htmlescape",		(PyCFunction)html_escape, METH_O},
+	{"_escape_string",	(PyCFunction)py_escape_string, METH_O},
+	{NULL,			NULL}
+};
+
+static char module_doc[] = "htmltext string type";
+
+void
+init_c_htmltext(void)
+{
+	PyObject *m;
+
+	/* Initialize the type of the new type object here; doing it here
+	 * is required for portability to Windows without requiring C++. */
+	htmltext_Type.ob_type = &PyType_Type;
+	QuoteWrapper_Type.ob_type = &PyType_Type;
+	TemplateIO_Type.ob_type = &PyType_Type;
+
+	/* Create the module and add the functions */
+	m = Py_InitModule4("_c_htmltext", htmltext_module_methods, module_doc,
+			   NULL, PYTHON_API_VERSION);
+
+	Py_INCREF((PyObject *)&htmltext_Type);
+	Py_INCREF((PyObject *)&QuoteWrapper_Type);
+	Py_INCREF((PyObject *)&TemplateIO_Type);
+	PyModule_AddObject(m, "htmltext", (PyObject *)&htmltext_Type);
+	PyModule_AddObject(m, "TemplateIO", (PyObject *)&TemplateIO_Type);
+}

Added: packages/quixote1/branches/upstream/current/src/cimport.c
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/src/cimport.c?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/src/cimport.c (added)
+++ packages/quixote1/branches/upstream/current/src/cimport.c Mon May  8 19:16:16 2006
@@ -1,0 +1,483 @@
+/* Mostly stolen from Python/import.c.  PSF license applies. */
+
+
+#include "Python.h"
+#include "osdefs.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/* Python function to find and load a module. */
+static PyObject *loader_hook;
+
+
+PyObject *
+call_find_load(char *fullname, char *subname, PyObject *path)
+{
+	PyObject *args, *m;
+
+	if (!(args = Py_BuildValue("(ssO)", fullname, subname,
+				   path != NULL ? path : Py_None)))
+		return NULL;
+
+	m = PyEval_CallObject(loader_hook, args);
+
+	Py_DECREF(args);
+	return m;
+}
+	
+
+/* Forward declarations for helper routines */
+static PyObject *get_parent(PyObject *globals, char *buf, int *p_buflen);
+static PyObject *load_next(PyObject *mod, PyObject *altmod,
+			   char **p_name, char *buf, int *p_buflen);
+static int mark_miss(char *name);
+static int ensure_fromlist(PyObject *mod, PyObject *fromlist,
+			   char *buf, int buflen, int recursive);
+static PyObject * import_submodule(PyObject *mod, char *name, char *fullname);
+
+
+static PyObject *
+import_module(char *name, PyObject *globals, PyObject *locals,
+		 PyObject *fromlist)
+{
+	char buf[MAXPATHLEN+1];
+	int buflen = 0;
+	PyObject *parent, *head, *next, *tail;
+
+	parent = get_parent(globals, buf, &buflen);
+	if (parent == NULL)
+		return NULL;
+
+	head = load_next(parent, Py_None, &name, buf, &buflen);
+	if (head == NULL)
+		return NULL;
+
+	tail = head;
+	Py_INCREF(tail);
+	while (name) {
+		next = load_next(tail, tail, &name, buf, &buflen);
+		Py_DECREF(tail);
+		if (next == NULL) {
+			Py_DECREF(head);
+			return NULL;
+		}
+		tail = next;
+	}
+
+	if (fromlist != NULL) {
+		if (fromlist == Py_None || !PyObject_IsTrue(fromlist))
+			fromlist = NULL;
+	}
+
+	if (fromlist == NULL) {
+		Py_DECREF(tail);
+		return head;
+	}
+
+	Py_DECREF(head);
+	if (!ensure_fromlist(tail, fromlist, buf, buflen, 0)) {
+		Py_DECREF(tail);
+		return NULL;
+	}
+
+	return tail;
+}
+
+static PyObject *
+get_parent(PyObject *globals, char *buf, int *p_buflen)
+{
+	static PyObject *namestr = NULL;
+	static PyObject *pathstr = NULL;
+	PyObject *modname, *modpath, *modules, *parent;
+
+	if (globals == NULL || !PyDict_Check(globals))
+		return Py_None;
+
+	if (namestr == NULL) {
+		namestr = PyString_InternFromString("__name__");
+		if (namestr == NULL)
+			return NULL;
+	}
+	if (pathstr == NULL) {
+		pathstr = PyString_InternFromString("__path__");
+		if (pathstr == NULL)
+			return NULL;
+	}
+
+	*buf = '\0';
+	*p_buflen = 0;
+	modname = PyDict_GetItem(globals, namestr);
+	if (modname == NULL || !PyString_Check(modname))
+		return Py_None;
+
+	modpath = PyDict_GetItem(globals, pathstr);
+	if (modpath != NULL) {
+		int len = PyString_GET_SIZE(modname);
+		if (len > MAXPATHLEN) {
+			PyErr_SetString(PyExc_ValueError,
+					"Module name too long");
+			return NULL;
+		}
+		strcpy(buf, PyString_AS_STRING(modname));
+		*p_buflen = len;
+	}
+	else {
+		char *start = PyString_AS_STRING(modname);
+		char *lastdot = strrchr(start, '.');
+		size_t len;
+		if (lastdot == NULL)
+			return Py_None;
+		len = lastdot - start;
+		if (len >= MAXPATHLEN) {
+			PyErr_SetString(PyExc_ValueError,
+					"Module name too long");
+			return NULL;
+		}
+		strncpy(buf, start, len);
+		buf[len] = '\0';
+		*p_buflen = len;
+	}
+
+	modules = PyImport_GetModuleDict();
+	parent = PyDict_GetItemString(modules, buf);
+	if (parent == NULL)
+		parent = Py_None;
+	return parent;
+	/* We expect, but can't guarantee, if parent != None, that:
+	   - parent.__name__ == buf
+	   - parent.__dict__ is globals
+	   If this is violated...  Who cares? */
+}
+
+/* altmod is either None or same as mod */
+static PyObject *
+load_next(PyObject *mod, PyObject *altmod, char **p_name, char *buf,
+	  int *p_buflen)
+{
+	char *name = *p_name;
+	char *dot = strchr(name, '.');
+	size_t len;
+	char *p;
+	PyObject *result;
+
+	if (dot == NULL) {
+		*p_name = NULL;
+		len = strlen(name);
+	}
+	else {
+		*p_name = dot+1;
+		len = dot-name;
+	}
+	if (len == 0) {
+		PyErr_SetString(PyExc_ValueError,
+				"Empty module name");
+		return NULL;
+	}
+
+	p = buf + *p_buflen;
+	if (p != buf)
+		*p++ = '.';
+	if (p+len-buf >= MAXPATHLEN) {
+		PyErr_SetString(PyExc_ValueError,
+				"Module name too long");
+		return NULL;
+	}
+	strncpy(p, name, len);
+	p[len] = '\0';
+	*p_buflen = p+len-buf;
+
+	result = import_submodule(mod, p, buf);
+	if (result == Py_None && altmod != mod) {
+		Py_DECREF(result);
+		/* Here, altmod must be None and mod must not be None */
+		result = import_submodule(altmod, p, p);
+		if (result != NULL && result != Py_None) {
+			if (mark_miss(buf) != 0) {
+				Py_DECREF(result);
+				return NULL;
+			}
+			strncpy(buf, name, len);
+			buf[len] = '\0';
+			*p_buflen = len;
+		}
+	}
+	if (result == NULL)
+		return NULL;
+
+	if (result == Py_None) {
+		Py_DECREF(result);
+		PyErr_Format(PyExc_ImportError,
+			     "No module named %.200s", name);
+		return NULL;
+	}
+
+	return result;
+}
+
+static int
+mark_miss(char *name)
+{
+	PyObject *modules = PyImport_GetModuleDict();
+	return PyDict_SetItemString(modules, name, Py_None);
+}
+
+static int
+ensure_fromlist(PyObject *mod, PyObject *fromlist, char *buf, int buflen,
+		int recursive)
+{
+	int i;
+
+	if (!PyObject_HasAttrString(mod, "__path__"))
+		return 1;
+
+	for (i = 0; ; i++) {
+		PyObject *item = PySequence_GetItem(fromlist, i);
+		int hasit;
+		if (item == NULL) {
+			if (PyErr_ExceptionMatches(PyExc_IndexError)) {
+				PyErr_Clear();
+				return 1;
+			}
+			return 0;
+		}
+		if (!PyString_Check(item)) {
+			PyErr_SetString(PyExc_TypeError,
+					"Item in ``from list'' not a string");
+			Py_DECREF(item);
+			return 0;
+		}
+		if (PyString_AS_STRING(item)[0] == '*') {
+			PyObject *all;
+			Py_DECREF(item);
+			/* See if the package defines __all__ */
+			if (recursive)
+				continue; /* Avoid endless recursion */
+			all = PyObject_GetAttrString(mod, "__all__");
+			if (all == NULL)
+				PyErr_Clear();
+			else {
+				if (!ensure_fromlist(mod, all, buf, buflen, 1))
+					return 0;
+				Py_DECREF(all);
+			}
+			continue;
+		}
+		hasit = PyObject_HasAttr(mod, item);
+		if (!hasit) {
+			char *subname = PyString_AS_STRING(item);
+			PyObject *submod;
+			char *p;
+			if (buflen + strlen(subname) >= MAXPATHLEN) {
+				PyErr_SetString(PyExc_ValueError,
+						"Module name too long");
+				Py_DECREF(item);
+				return 0;
+			}
+			p = buf + buflen;
+			*p++ = '.';
+			strcpy(p, subname);
+			submod = import_submodule(mod, subname, buf);
+			Py_XDECREF(submod);
+			if (submod == NULL) {
+				Py_DECREF(item);
+				return 0;
+			}
+		}
+		Py_DECREF(item);
+	}
+
+	/* NOTREACHED */
+}
+
+static PyObject *
+import_submodule(PyObject *mod, char *subname, char *fullname)
+{
+	PyObject *modules = PyImport_GetModuleDict();
+	PyObject *m;
+
+	/* Require:
+	   if mod == None: subname == fullname
+	   else: mod.__name__ + "." + subname == fullname
+	*/
+
+	if ((m = PyDict_GetItemString(modules, fullname)) != NULL) {
+		Py_INCREF(m);
+	}
+	else {
+		PyObject *path;
+
+		if (mod == Py_None)
+			path = NULL;
+		else {
+			path = PyObject_GetAttrString(mod, "__path__");
+			if (path == NULL) {
+				PyErr_Clear();
+				Py_INCREF(Py_None);
+				return Py_None;
+			}
+		}
+
+		m = call_find_load(fullname, subname, path);
+
+		if (m != NULL && mod != Py_None) {
+			if (PyObject_SetAttrString(mod, subname, m) < 0) {
+				Py_DECREF(m);
+				m = NULL;
+			}
+		}
+	}
+
+	return m;
+}
+
+
+PyObject *
+reload_module(PyObject *m)
+{
+	PyObject *modules = PyImport_GetModuleDict();
+	PyObject *path = NULL;
+	char *name, *subname;
+
+	if (m == NULL || !PyModule_Check(m)) {
+		PyErr_SetString(PyExc_TypeError,
+				"reload_module() argument must be module");
+		return NULL;
+	}
+	name = PyModule_GetName(m);
+	if (name == NULL)
+		return NULL;
+	if (m != PyDict_GetItemString(modules, name)) {
+		PyErr_Format(PyExc_ImportError,
+			     "reload(): module %.200s not in sys.modules",
+			     name);
+		return NULL;
+	}
+	subname = strrchr(name, '.');
+	if (subname == NULL)
+		subname = name;
+	else {
+		PyObject *parentname, *parent;
+		parentname = PyString_FromStringAndSize(name, (subname-name));
+		if (parentname == NULL)
+			return NULL;
+		parent = PyDict_GetItem(modules, parentname);
+		Py_DECREF(parentname);
+		if (parent == NULL) {
+			PyErr_Format(PyExc_ImportError,
+			    "reload(): parent %.200s not in sys.modules",
+			    name);
+			return NULL;
+		}
+		subname++;
+		path = PyObject_GetAttrString(parent, "__path__");
+		if (path == NULL)
+			PyErr_Clear();
+	}
+	m = call_find_load(name, subname, path);
+	Py_XDECREF(path);
+	return m;
+}
+
+
+static PyObject *
+cimport_import_module(PyObject *self, PyObject *args)
+{
+	char *name;
+	PyObject *globals = NULL;
+	PyObject *locals = NULL;
+	PyObject *fromlist = NULL;
+
+	if (!PyArg_ParseTuple(args, "s|OOO:import_module", &name, &globals,
+			      &locals, &fromlist))
+		return NULL;
+	return import_module(name, globals, locals, fromlist);
+}
+
+static PyObject *
+cimport_reload_module(PyObject *self, PyObject *args)
+{
+	PyObject *m;
+	if (!PyArg_ParseTuple(args, "O:reload_module", &m))
+		return NULL;
+	return reload_module(m);
+}
+
+static char doc_reload_module[] =
+"reload(module) -> module\n\
+\n\
+Reload the module.  The module must have been successfully imported before.";
+
+static PyObject *
+cimport_set_loader(PyObject *self, PyObject *args)
+{
+	PyObject *l = NULL;
+	if (!PyArg_ParseTuple(args, "O:set_loader", &l))
+		return NULL;
+	if (!PyCallable_Check(l)) {
+		PyErr_SetString(PyExc_TypeError, "callable object needed");
+		return NULL;
+	}
+	Py_XDECREF(loader_hook);
+	loader_hook = l;
+	Py_INCREF(loader_hook);
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+static char doc_set_loader[] = "\
+Set the function that will be used to import modules.\n\
+\n\
+The function should should have the signature:\n\
+\n\
+ loader(fullname : str, subname : str, path : [str] | None) -> module | None\n\
+\n\
+It should return the initialized module or None if it is not found.\n\
+";	
+
+
+static PyObject *
+cimport_get_loader(PyObject *self, PyObject *args)
+{
+	if (!PyArg_ParseTuple(args, ":get_loader"))
+		return NULL;
+	Py_INCREF(loader_hook);
+	return loader_hook;
+}
+
+static char doc_get_loader[] = "\
+Get the function that will be used to import modules.\n\
+";
+
+static char doc_import_module[] = "\
+import_module(name, globals, locals, fromlist) -> module\n\
+\n\
+Import a module.  The globals are only used to determine the context;\n\
+they are not modified.  The locals are currently unused.  The fromlist\n\
+should be a list of names to emulate ``from name import ...'', or an\n\
+empty list to emulate ``import name''.\n\
+\n\
+When importing a module from a package, note that import_module('A.B', ...)\n\
+returns package A when fromlist is empty, but its submodule B when\n\
+fromlist is not empty.\n\
+";
+
+
+static PyMethodDef cimport_methods[] = {
+	{"import_module",	cimport_import_module,	1, doc_import_module},
+	{"reload_module",	cimport_reload_module,	1, doc_reload_module},
+	{"get_loader",		cimport_get_loader,	1, doc_get_loader},
+	{"set_loader",		cimport_set_loader,	1, doc_set_loader},
+	{NULL,			NULL}		/* sentinel */
+};
+
+void
+initcimport(void)
+{
+	PyObject *m, *d;
+
+	m = Py_InitModule4("cimport", cimport_methods, "",
+			   NULL, PYTHON_API_VERSION);
+	d = PyModule_GetDict(m);
+
+}

Added: packages/quixote1/branches/upstream/current/src/setup.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/src/setup.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/src/setup.py (added)
+++ packages/quixote1/branches/upstream/current/src/setup.py Mon May  8 19:16:16 2006
@@ -1,0 +1,14 @@
+
+from distutils.core import setup
+from distutils.extension import Extension
+
+Import = Extension(name="cimport",
+                      sources=["cimport.c"])
+
+setup(name = "cimport",
+      version = "0.1",
+      description = "Import tools for Python",
+      author = "Neil Schemenauer",
+      author_email = "nas at mems-exchange.org",
+      ext_modules = [Import]
+      )

Added: packages/quixote1/branches/upstream/current/test/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/test/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/test/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/test/__init__.py Mon May  8 19:16:16 2006
@@ -1,0 +1,2 @@
+
+# Empty file to make this directory a package

Added: packages/quixote1/branches/upstream/current/test/ua_test.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/test/ua_test.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/test/ua_test.py (added)
+++ packages/quixote1/branches/upstream/current/test/ua_test.py Mon May  8 19:16:16 2006
@@ -1,0 +1,28 @@
+#!/usr/bin/env python
+
+# Test Quixote's ability to parse the "User-Agent" header, ie.
+# the 'guess_browser_version()' method of HTTPRequest.
+#
+# Reads User-Agent strings on stdin, and writes Quixote's interpretation
+# of each on stdout.  This is *not* an automated test!
+
+import sys, os
+from copy import copy
+from quixote.http_request import HTTPRequest
+
+env = copy(os.environ)
+file = sys.stdin
+while 1:
+    line = file.readline()
+    if not line:
+        break
+    if line[-1] == "\n":
+        line = line[:-1]
+
+    env["HTTP_USER_AGENT"] = line
+    req = HTTPRequest(None, env)
+    (name, version) = req.guess_browser_version()
+    if name is None:
+        print "%s -> ???" % line
+    else:
+        print "%s -> (%s, %s)" % (line, name, version)

Added: packages/quixote1/branches/upstream/current/test/utest_html.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/test/utest_html.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/test/utest_html.py (added)
+++ packages/quixote1/branches/upstream/current/test/utest_html.py Mon May  8 19:16:16 2006
@@ -1,0 +1,234 @@
+#!/www/python/bin/python
+"""
+$URL: svn+ssh://svn/repos/trunk/quixote/test/utest_html.py $
+$Id: utest_html.py 25234 2004-09-30 17:36:19Z nascheme $
+"""
+from sancho.utest import UTest
+from quixote import _py_htmltext
+
+escape = htmlescape = None # so that checker does not complain
+
+class Wrapper:
+    def __init__(self, s):
+        self.s = s
+
+    def __repr__(self):
+        return self.s
+
+    def __str__(self):
+        return self.s
+
+class Broken:
+    def __str__(self):
+        raise RuntimeError, 'eieee'
+
+    def __repr__(self):
+        raise RuntimeError, 'eieee'
+
+markupchars = '<>&"'
+quotedchars = '&lt;&gt;&amp;&quot;'
+
+class HTMLTest (UTest):
+
+    def _pre(self):
+        global htmltext, escape, htmlescape
+        htmltext = _py_htmltext.htmltext
+        escape = _py_htmltext._escape_string
+        htmlescape = _py_htmltext.htmlescape
+
+    def _post(self):
+        pass
+
+
+    def check_init(self):
+        assert str(htmltext('foo')) == 'foo'
+        assert str(htmltext(markupchars)) == markupchars
+        assert str(htmltext(None)) == 'None'
+        assert str(htmltext(1)) == '1'
+        try:
+            htmltext(Broken())
+            assert 0
+        except RuntimeError: pass
+
+    def check_escape(self):
+        assert htmlescape(markupchars) == quotedchars
+        assert isinstance(htmlescape(markupchars), htmltext)
+        assert escape(markupchars) == quotedchars
+        assert isinstance(escape(markupchars), str)
+        assert htmlescape(htmlescape(markupchars)) == quotedchars
+        try:
+            escape(1)
+            assert 0
+        except TypeError: pass
+
+    def check_cmp(self):
+        s = htmltext("foo")
+        assert s == 'foo'
+        assert s != 'bar'
+        assert s == htmltext('foo')
+        assert s != htmltext('bar')
+        assert htmltext('1') != 1
+        assert 1 != s
+
+    def check_len(self):
+        assert len(htmltext('foo')) == 3
+        assert len(htmltext(markupchars)) == len(markupchars)
+        assert len(htmlescape(markupchars)) == len(quotedchars)
+
+    def check_hash(self):
+        assert hash(htmltext('foo')) == hash('foo')
+        assert hash(htmltext(markupchars)) == hash(markupchars)
+        assert hash(htmlescape(markupchars)) == hash(quotedchars)
+
+    def check_concat(self):
+        s = htmltext("foo")
+        assert s + 'bar' == "foobar"
+        assert 'bar' + s == "barfoo"
+        assert s + htmltext('bar') == "foobar"
+        assert s + markupchars == "foo" + quotedchars
+        assert isinstance(s + markupchars, htmltext)
+        assert markupchars + s == quotedchars + "foo"
+        assert isinstance(markupchars + s, htmltext)
+        try:
+            s + 1
+            assert 0
+        except TypeError: pass
+        try:
+            1 + s
+            assert 0
+        except TypeError: pass
+
+    def check_repeat(self):
+        s = htmltext('a')
+        assert s * 3 == "aaa"
+        assert isinstance(s * 3, htmltext)
+        assert htmlescape(markupchars) * 3 == quotedchars * 3
+        try:
+            s * 'a'
+            assert 0
+        except TypeError: pass
+        try:
+            'a' * s
+            assert 0
+        except TypeError: pass
+        try:
+            s * s
+            assert 0
+        except TypeError: pass
+
+    def check_format(self):
+        s_fmt = htmltext('%s')
+        assert s_fmt % 'foo' == "foo"
+        assert isinstance(s_fmt % 'foo', htmltext)
+        assert s_fmt % markupchars == quotedchars
+        assert s_fmt % None == "None"
+        assert htmltext('%r') % Wrapper(markupchars) == quotedchars
+        assert htmltext('%s%s') % ('foo', htmltext(markupchars)) == (
+            "foo" + markupchars)
+        assert htmltext('%d') % 10 == "10"
+        assert htmltext('%.1f') % 10 == "10.0"
+        try:
+            s_fmt % Broken()
+            assert 0
+        except RuntimeError: pass
+        try:
+            htmltext('%r') % Broken()
+            assert 0
+        except RuntimeError: pass
+        try:
+            s_fmt % (1, 2)
+            assert 0
+        except TypeError: pass
+        assert htmltext('%d') % 12300000000000000000L == "12300000000000000000"
+
+    def check_dict_format(self):
+        assert htmltext('%(a)s %(a)r %(b)s') % (
+            {'a': 'foo&', 'b': htmltext('bar&')}) == "foo&amp; 'foo&amp;' bar&"
+        assert htmltext('%(a)s') % {'a': 'foo&'} == "foo&amp;"
+        assert isinstance(htmltext('%(a)s') % {'a': 'a'}, htmltext)
+        assert htmltext('%s') % {'a': 'foo&'} == "{'a': 'foo&amp;'}"
+        try:
+            htmltext('%(a)s') % 1
+            assert 0
+        except TypeError: pass
+        try:
+            htmltext('%(a)s') % {}
+            assert 0
+        except KeyError: pass
+
+    def check_join(self):
+        assert htmltext(' ').join(['foo', 'bar']) == "foo bar"
+        assert htmltext(' ').join(['foo', markupchars]) == (
+            "foo " + quotedchars)
+        assert htmlescape(markupchars).join(['foo', 'bar']) == (
+            "foo" + quotedchars + "bar")
+        assert htmltext(' ').join([htmltext(markupchars), 'bar']) == (
+            markupchars + " bar")
+        assert isinstance(htmltext('').join([]), htmltext)
+        try:
+            htmltext('').join(1)
+            assert 0
+        except TypeError: pass
+        try:
+            htmltext('').join([1])
+            assert 0
+        except TypeError: pass
+
+    def check_startswith(self):
+        assert htmltext('foo').startswith('fo')
+        assert htmlescape(markupchars).startswith(markupchars[:3])
+        assert htmltext(markupchars).startswith(htmltext(markupchars[:3]))
+        try:
+            htmltext('').startswith(1)
+            assert 0
+        except TypeError: pass
+
+    def check_endswith(self):
+        assert htmltext('foo').endswith('oo')
+        assert htmlescape(markupchars).endswith(markupchars[-3:])
+        assert htmltext(markupchars).endswith(htmltext(markupchars[-3:]))
+        try:
+            htmltext('').endswith(1)
+            assert 0
+        except TypeError: pass
+
+    def check_replace(self):
+        assert htmlescape('&').replace('&', 'foo') == "foo"
+        assert htmltext('&').replace(htmltext('&'), 'foo') == "foo"
+        assert htmltext('foo').replace('foo', htmltext('&')) == "&"
+        assert isinstance(htmltext('a').replace('a', 'b'), htmltext)
+        try:
+            htmltext('').replace(1, 'a')
+            assert 0
+        except TypeError: pass
+
+    def check_lower(self):
+        assert htmltext('aB').lower() == "ab"
+        assert isinstance(htmltext('a').lower(), htmltext)
+
+    def check_upper(self):
+        assert htmltext('aB').upper() == "AB"
+        assert isinstance(htmltext('a').upper(), htmltext)
+
+    def check_capitalize(self):
+        assert htmltext('aB').capitalize() == "Ab"
+        assert isinstance(htmltext('a').capitalize(), htmltext)
+
+
+try:
+    from quixote import _c_htmltext
+except ImportError:
+    _c_htmltext = None
+
+if _c_htmltext:
+    class CHTMLTest(HTMLTest):
+        def _pre(self):
+            # using globals like this is a bit of a hack since it assumes
+            # Sancho tests each class individually, oh well
+            global htmltext, escape, htmlescape
+            htmltext = _c_htmltext.htmltext
+            escape = _c_htmltext._escape_string
+            htmlescape = _c_htmltext.htmlescape
+
+if __name__ == "__main__":
+    HTMLTest()

Propchange: packages/quixote1/branches/upstream/current/test/utest_html.py
------------------------------------------------------------------------------
    svn:executable = 

Added: packages/quixote1/branches/upstream/current/upload.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/upload.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/upload.py (added)
+++ packages/quixote1/branches/upstream/current/upload.py Mon May  8 19:16:16 2006
@@ -1,0 +1,377 @@
+"""quixote.upload
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/upload.py $
+$Id: upload.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Code for handling HTTP upload requests.  Provides HTTPUploadRequest, a
+subclass of HTTPRequest that is created when handling an HTTP request
+whose Content-Type is "multipart/form-data".  Also provides the Upload
+class, which is used as the form value for "file upload" variables.
+"""
+
+__revision__ = "$Id: upload.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import os, string
+import errno
+from cgi import parse_header
+from rfc822 import Message
+from time import time, strftime, localtime
+
+from quixote.http_request import HTTPRequest
+from quixote.errors import RequestError
+from quixote.config import ConfigError
+
+CRLF = "\r\n"
+LF = "\n"
+
+
+def read_mime_part(file, boundary, lines=None, ofile=None):
+    """
+    Read lines from 'file' up to and including a MIME message boundary
+    derived from 'boundary'.  Return true if there is no more data to be
+    read from 'file', ie. we hit either a MIME outer boundary or EOF.
+
+    If 'lines' is supplied, each line is stripped of line-endings and
+    appended to 'lines'.  If 'ofile' is supplied, each line is written
+    to 'ofile' as-is (ie. with line-endings intact).  Neither the
+    boundary line nor a blank line preceding it (if any) will be
+    saved/written.  If neither 'lines' nor 'ofile' is supplied, the data
+    read is discarded.
+    """
+    # Algorithm based on read_lines_to_outerboundary() in cgi.py
+
+    next = "--" + boundary
+    last = "--" + boundary + "--"
+
+    # XXX reading arbitrary binary data (which is possible in a file
+    # upload) a line-at-a-time might be problematic.  Eg.  I have
+    # observed one .GDS file in the wild where the longest "line" was
+    # around 1 MB.  Most binary files that I looked at have reasonable
+    # "line" lengths though -- maximum 5-10k.  However, reading in
+    # fixed-size chunks would make spotting the MIME boundary tricky.
+    # One more reason why HTTP upload is stupid.
+
+    prev_delim = ""
+    while 1:
+        line = file.readline()
+
+        # Hit EOF -- nothing more to read.  (This should *not* happen
+        # in a well-formed MIME message, but let's assume the worst.)
+        if not line:
+            return 1
+
+        # Strip (but remember) line ending.
+        if line[-2:] == CRLF:
+            line = line[:-2]
+            delim = CRLF
+        elif line[-1:] == LF:
+            line = line[:-1]
+            delim = LF
+        else:
+            delim = ""
+
+        # If we hit the boundary line, return now.  Forget the current
+        # line *and* the delimiter of the previous line -- in
+        # particular, we do not want to preserve the blank line that
+        # comes after an uploaded file's contents and the following
+        # boundary line.
+        if line == next:           # hit boundary, but more to come
+            return 0
+        elif line == last:         # final boundary -- no more to read
+            return 1
+
+        if lines is not None:
+            lines.append(line)
+        if ofile is not None:
+            ofile.write(prev_delim + line)
+        prev_delim = delim
+
+
+SAFE_CHARS = string.letters + string.digits + "-@&+=_., "
+_safe_trans = None
+
+def make_safe(s):
+    global _safe_trans
+    if _safe_trans is None:
+        _safe_trans = ["_"] * 256
+        for c in SAFE_CHARS:
+            _safe_trans[ord(c)] = c
+        _safe_trans = "".join(_safe_trans)
+
+    return s.translate(_safe_trans)
+
+
+class Upload:
+    """
+    Represents a single uploaded file.  Uploaded files live in the
+    filesystem, *not* in memory -- this is not a file-like object!  It's
+    just a place to store a couple of filenames.  Specifically, feel
+    free to access the following instance attributes:
+
+      orig_filename
+        the complete filename supplied by the user-agent in the
+        request that uploaded this file.  Depending on the browser,
+        this might have the complete path of the original file
+        on the client system, in the client system's syntax -- eg.
+        "C:\foo\bar\upload_this" or "/foo/bar/upload_this" or
+        "foo:bar:upload_this".
+      base_filename
+        the base component of orig_filename, shorn of MS-DOS,
+        Mac OS, and Unix path components and with "unsafe"
+        characters neutralized (see make_safe())
+      tmp_filename
+        where you'll actually find the file on the current system
+      content_type
+        the content type provided by the user-agent in the request
+        that uploaded this file.
+    """
+
+    def __init__(self, orig_filename, content_type=None):
+        if orig_filename:
+            self.orig_filename = orig_filename
+            bspos = orig_filename.rfind("\\")
+            cpos = orig_filename.rfind(":")
+            spos = orig_filename.rfind("/")
+            if bspos != -1:                 # eg. "\foo\bar" or "D:\ding\dong"
+                filename = orig_filename[bspos+1:]
+            elif cpos != -1:                # eg. "C:foo" or ":ding:dong:foo"
+                filename = orig_filename[cpos+1:]
+            elif spos != -1:                # eg. "foo/bar/baz" or "/tmp/blah"
+                filename = orig_filename[spos+1:]
+            else:
+                filename = orig_filename
+
+            self.base_filename = make_safe(filename)
+        else:
+            self.orig_filename = None
+            self.base_filename = None
+
+        self.content_type = content_type
+        self.tmp_filename = None
+
+    def __str__(self):
+        return str(self.orig_filename)
+
+    def __repr__(self):
+        return "<%s at %x: %s>" % (self.__class__.__name__, id(self), self)
+
+    def _open(self, dir):
+        """
+        Generate a unique filename in 'dir'.  Open and return a
+        writeable file object from it.
+        """
+        flags = os.O_WRONLY|os.O_CREAT|os.O_EXCL
+        try:
+            flags |= os.O_BINARY    # for Windows
+        except AttributeError:
+            pass
+        tstamp = strftime("%Y%m%d.%H%M%S", localtime(time()))
+        counter = 0
+        while 1:
+            filename = "upload.%s.%s" % (tstamp, counter)
+            filename = os.path.join(dir, filename)
+            try:
+                fd = os.open(filename, flags)
+            except OSError, err:
+                if err.errno == errno.EEXIST:
+                    # Filename collision -- try again
+                    counter += 1
+                else:
+                    # Bomb on any other error.
+                    raise
+            else:
+                # Opened the file just fine; it now exists so no other
+                # process or thread will be able to grab that filename.
+                break
+
+        # Wrap a file object around the file descriptor.
+        return (os.fdopen(fd, "wb"), filename)
+
+    def receive(self, file, boundary, dir):
+        (ofile, filename) = self._open(dir)
+        done = read_mime_part(file, boundary, ofile=ofile)
+        ofile.close()
+        self.tmp_filename = filename
+        return done
+
+    def get_size(self):
+        """get_size() : int
+        Return the size of the file, measured in bytes, or None if
+        the file doesn't exist.
+        """
+        stats = os.stat(self.tmp_filename)
+        return stats.st_size
+
+class CountingFile:
+    """A file-like object that records the number of bytes read
+    from the underlying file.  Ignores seek(), because it's only
+    used by HTTPUploadRequest on an unseekable file (stdin).
+    """
+
+    def __init__(self, file):
+        self.__file = file
+        self.__bytesread = 0
+
+    def read(self, nbytes):
+        data = self.__file.read(nbytes)
+        self.__bytesread += len(data)
+        return data
+
+    def readline(self):
+        line = self.__file.readline()
+        self.__bytesread += len(line)
+        return line
+
+    def get_bytesread(self):
+        return self.__bytesread
+
+
+class HTTPUploadRequest(HTTPRequest):
+    """
+    Represents a single HTTP request with Content-Type
+    "multipart/form-data", which is used for HTTP uploads.  (It's
+    actually possible for any HTML form to specify an encoding type of
+    "multipart/form-data", even if there are no file uploads in that
+    form.  In that case, you'll still get an HTTPUploadRequest object --
+    but since this is a subclass of HTTPRequest, that shouldn't cause
+    you any problems.)
+
+    When processing the upload request, any uploaded files are stored
+    under a temporary filename in the directory specified by the
+    'upload_dir' instance attribute (which is normally set, by
+    Publisher, from the UPLOAD_DIR configuration variable).
+    HTTPUploadRequest then creates an Upload object which contains the
+    various filenames for this upload.
+
+    Other form variables are stored as usual in the 'form' dictionary,
+    to be fetched later with get_form_var().  Uploaded files can also be
+    accessed via get_form_var(), which returns the Upload object created
+    at upload-time, rather than a string.
+
+    Eg. if your upload form contains this:
+      <input type="file" name="upload">
+
+    then, when processing the form, you might do this:
+      upload = request.get_form_var("upload")
+
+    after which you could open the uploaded file immediately:
+      file = open(upload.tmp_filename)
+
+    or move it to a more permanent home before doing anything with it:
+      permanent_name = os.path.join(permanent_upload_dir,
+                                    upload.base_filename)
+      os.rename(upload.tmp_filename, permanent_name)
+    """
+
+    def __init__(self, stdin, environ, content_type=None):
+        HTTPRequest.__init__(self, stdin, environ, content_type)
+
+        self.upload_dir = None
+        self.upload_dir_mode = 0775
+
+    def set_upload_dir(self, dir, mode=None):
+        self.upload_dir = dir
+        if mode is not None:
+            self.upload_dir_mode = mode
+
+    def parse_content_type(self):
+        full_ctype = self.get_header('Content-Type')
+        if full_ctype is None:
+            raise RequestError("no Content-Type header")
+
+        (ctype, ctype_params) = parse_header(full_ctype)
+        boundary = ctype_params.get('boundary')
+
+        if not (ctype == "multipart/form-data" and boundary):
+            raise RequestError("expected Content-Type: multipart/form-data "
+                               "with a 'boundary' parameter: got %r"
+                               % full_ctype)
+
+        return (ctype, boundary)
+
+    def parse_content_disposition(self, full_cdisp):
+        (cdisp, cdisp_params) = parse_header(full_cdisp)
+        name = cdisp_params.get("name")
+
+        if not (cdisp == "form-data" and name):
+            raise RequestError("expected Content-Disposition: form-data "
+                               "with a 'name' parameter: got %r" % full_cdisp)
+
+        return (name, cdisp_params.get("filename"))
+
+    def check_upload_dir(self):
+        if not os.path.isdir(self.upload_dir):
+            print "creating %s with mode %o" % (self.upload_dir,
+                                                self.upload_dir_mode)
+            os.mkdir(self.upload_dir, self.upload_dir_mode)
+
+    def handle_upload(self, name, filename, file, boundary, content_type):
+        if self.upload_dir is None:
+            raise ConfigError("upload_dir not set")
+        upload = Upload(filename, content_type)
+        self.check_upload_dir()
+        done = upload.receive(file, boundary, self.upload_dir)
+        self.add_form_value(name, upload)
+        return done
+
+    def handle_regular_var(self, name, file, boundary):
+        lines = []
+        done = read_mime_part(file, boundary, lines=lines)
+        if len(lines) == 1:
+            value = lines[0]
+        else:
+            value = "\n".join(lines)
+        self.add_form_value(name, value)
+        #form_vars.append((name, value))
+        return done
+
+    def parse_body(self, file, boundary):
+        total_bytes = 0                 # total bytes read from 'file'
+        done = 0
+        while not done:
+            headers = Message(file)
+            cdisp = headers.get('content-disposition')
+            if not cdisp:
+                raise RequestError("expected Content-Disposition header "
+                                   "in body sub-part")
+            (name, filename) = self.parse_content_disposition(cdisp)
+            if filename:
+                content_type = headers.get('content-type')
+                done = self.handle_upload(name, filename, file,
+                                          boundary, content_type)
+            else:
+                done = self.handle_regular_var(name, file, boundary)
+
+    def check_length_read(self, file):
+        # Parse Content-Length header.
+        # XXX if we want to worry about disk free space, this should
+        # be done *before* parsing the body!
+        clen = self.get_header("Content-Length")
+        if clen is not None:
+            clen = int(clen)
+
+        total_bytes = file.get_bytesread()
+        if total_bytes != clen:
+            raise RequestError(
+                "upload request length mismatch: expected %d bytes, got %d"
+                % (clen, total_bytes))
+
+    def process_inputs(self):
+        self.start_time = time()
+
+        # Parse Content-Type header -- mainly to get the 'boundary'
+        # parameter.  Barf if not there or unexpected type.
+        (ctype, boundary) = self.parse_content_type()
+
+        # The meat of the body starts after the first occurrence of
+        # the boundary, so read up to that point.
+        file = CountingFile(self.stdin)
+        read_mime_part(file, boundary)
+
+        # Parse the parts of the message, ie. the form variables.  Some of
+        # these will presumably be "file upload" variables, so need to be
+        # treated specially.
+        self.parse_body(file, boundary)
+
+        # Ensure that we read exactly as many bytes as were promised
+        # by the Content-Length header.
+        self.check_length_read(file)

Added: packages/quixote1/branches/upstream/current/util.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/util.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/util.py (added)
+++ packages/quixote1/branches/upstream/current/util.py Mon May  8 19:16:16 2006
@@ -1,0 +1,326 @@
+"""quixote.util
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/util.py $
+$Id: util.py 25257 2004-10-05 16:28:23Z nascheme $
+
+Contains various useful functions and classes:
+
+  xmlrpc(request, func) : Processes the body of an XML-RPC request, and calls
+                          'func' with the method name and parameters.
+  StaticFile            : Wraps a file from a filesystem as a
+                          Quixote resource.
+  StaticDirectory       : Wraps a directory containing static files as
+                          a Quixote namespace.
+
+StaticFile and StaticDirectory were contributed by Hamish Lawson.
+See doc/static-files.txt for examples of their use.
+"""
+
+import sys
+import os
+import time
+import binascii
+import mimetypes
+import urllib
+import xmlrpclib
+from cStringIO import StringIO
+from rfc822 import formatdate
+from quixote import errors, html
+from quixote.http_response import Stream
+
+if hasattr(os, 'urandom'):
+    # available in Python 2.4 and also works on win32
+    def randbytes(bytes):
+        """Return bits of random data as a hex string."""
+        return binascii.hexlify(os.urandom(bytes))
+
+elif os.path.exists('/dev/urandom'):
+    # /dev/urandom is just as good as /dev/random for cookies (assuming
+    # SHA-1 is secure) and it never blocks.
+    def randbytes(bytes):
+        """Return bits of random data as a hex string."""
+        return binascii.hexlify(open("/dev/urandom").read(bytes))
+
+else:
+    # this is much less secure than the above function
+    import sha
+    class _PRNG:
+        def __init__(self):
+            self.state = sha.new(str(time.time() + time.clock()))
+            self.count = 0
+
+        def _get_bytes(self):
+            self.state.update('%s %d' % (time.time() + time.clock(),
+                                         self.count))
+            self.count += 1
+            return self.state.hexdigest()
+
+        def randbytes(self, bytes):
+            """Return bits of random data as a hex string."""
+            s = ""
+            chars = 2*bytes
+            while len(s) < chars:
+                s += self._get_bytes()
+            return s[:chars]
+
+    randbytes = _PRNG().randbytes
+
+
+def xmlrpc(request, func):
+    """xmlrpc(request:Request, func:callable) : string
+
+    Processes the body of an XML-RPC request, and calls 'func' with
+    two arguments, a string containing the method name and a tuple of
+    parameters.
+    """
+
+    # Get contents of POST body
+    if request.get_method() != 'POST':
+        request.response.set_status(405, "Only the POST method is accepted")
+        return "XML-RPC handlers only accept the POST method."
+
+    length = int(request.environ['CONTENT_LENGTH'])
+    data = request.stdin.read(length)
+
+    # Parse arguments
+    params, method = xmlrpclib.loads(data)
+
+    try:
+        result = func(method, params)
+    except xmlrpclib.Fault, exc:
+        result = exc
+    except:
+        # report exception back to client
+        result = xmlrpclib.dumps(
+            xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
+            )
+    else:
+        result = (result,)
+        result = xmlrpclib.dumps(result, methodresponse=1)
+
+    request.response.set_content_type('text/xml')
+    return result
+
+
+class FileStream(Stream):
+
+    CHUNK_SIZE = 20000
+
+    def __init__(self, fp, size=None):
+        self.fp = fp
+        self.length = size
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        chunk = self.fp.read(self.CHUNK_SIZE)
+        if not chunk:
+            raise StopIteration
+        return chunk
+
+
+class StaticFile:
+
+    """
+    Wrapper for a static file on the filesystem.
+    """
+
+    def __init__(self, path, follow_symlinks=0,
+                 mime_type=None, encoding=None, cache_time=None):
+        """StaticFile(path:string, follow_symlinks:bool)
+
+        Initialize instance with the absolute path to the file.  If
+        'follow_symlinks' is true, symbolic links will be followed.
+        'mime_type' specifies the MIME type, and 'encoding' the
+        encoding; if omitted, the MIME type will be guessed,
+        defaulting to text/plain.
+
+        Optional cache_time parameter indicates the number of
+        seconds a response is considered to be valid, and will
+        be used to set the Expires header in the response when
+        quixote gets to that part.  If the value is None then
+        the Expires header will not be set.
+        """
+
+        # Check that the supplied path is absolute and (if a symbolic link) may
+        # be followed
+        self.path = path
+        if not os.path.isabs(path):
+            raise ValueError, "Path %r is not absolute" % path
+        if os.path.islink(path) and not follow_symlinks:
+            raise errors.TraversalError(private_msg="Path %r is a symlink"
+                                        % path)
+
+        # Decide the Content-Type of the file
+        guess_mime, guess_enc = mimetypes.guess_type(os.path.basename(path),
+                                                     strict=0)
+        self.mime_type = mime_type or guess_mime or 'text/plain'
+        self.encoding = encoding or guess_enc or None
+        self.cache_time = cache_time
+
+    def __call__(self, request):
+        stat = os.stat(self.path)
+        last_modified = formatdate(stat.st_mtime)
+        if last_modified == request.get_header('If-Modified-Since'):
+            # handle exact match of If-Modified-Since header
+            request.response.set_status(304)
+            return ''
+
+        # Set the Content-Type for the response and return the file's contents.
+        request.response.set_content_type(self.mime_type)
+        if self.encoding:
+            request.response.set_header("Content-Encoding", self.encoding)
+
+        request.response.set_header('Last-Modified', last_modified)
+
+        if self.cache_time is None:
+            request.response.cache = None # don't set the Expires header
+        else:
+            # explicitly allow client to cache page by setting the Expires
+            # header, this is even more efficient than the using
+            # Last-Modified/If-Modified-Since since the browser does not need
+            # to contact the server
+            request.response.cache = self.cache_time
+
+        return FileStream(open(self.path, 'rb'), stat.st_size)
+
+
+class StaticDirectory:
+
+    """
+    Wrap a filesystem directory containing static files as a Quixote namespace.
+    """
+
+    _q_exports = []
+
+    FILE_CLASS = StaticFile
+
+    def __init__(self, path, use_cache=0, list_directory=0, follow_symlinks=0,
+                 cache_time=None, file_class=None, index_filenames=None):
+        """StaticDirectory(path:string, use_cache:bool, list_directory:bool,
+                           follow_symlinks:bool, cache_time:int,
+                           file_class=None, index_filenames:[string])
+
+        Initialize instance with the absolute path to the file.
+        If 'use_cache' is true, StaticFile instances will be cached in memory.
+        If 'list_directory' is true, users can request a directory listing.
+        If 'follow_symlinks' is true, symbolic links will be followed.
+
+        Optional parameter cache_time allows setting of Expires header in
+        response object (see note for StaticFile for more detail).
+
+        Optional parameter 'index_filenames' specifies a list of
+        filenames to be used as index files in the directory. First
+        file found searching left to right is returned.
+        """
+
+        # Check that the supplied path is absolute
+        self.path = path
+        if not os.path.isabs(path):
+            raise ValueError, "Path %r is not absolute" % path
+
+        self.use_cache = use_cache
+        self.cache = {}
+        self.list_directory = list_directory
+        self.follow_symlinks = follow_symlinks
+        self.cache_time = cache_time
+        if file_class is not None:
+            self.file_class = file_class
+        else:
+            self.file_class = self.FILE_CLASS
+        self.index_filenames = index_filenames
+
+    def _q_index(self, request):
+        """
+        If directory listings are allowed, generate a simple HTML
+        listing of the directory's contents with each item hyperlinked;
+        if the item is a subdirectory, place a '/' after it. If not allowed,
+        return a page to that effect.
+        """
+        if self.index_filenames:
+            for name in self.index_filenames:
+                try:
+                    obj = self._q_lookup(request, name)
+                except errors.TraversalError:
+                    continue
+                if not isinstance(obj, StaticDirectory) and callable(obj):
+                    return obj(request)
+        # FIXME: this is not a valid HTML document!
+        out = StringIO()
+        if self.list_directory:
+            template = html.htmltext('<a href="%s">%s</a>%s')
+            print >>out, (html.htmltext("<h1>%s</h1>")
+                          % request.environ['REQUEST_URI'])
+            print >>out, "<pre>"
+            print >>out, template % ('..', '..', '')
+            files = os.listdir(self.path)
+            files.sort()
+            for filename in files:
+                filepath = os.path.join(self.path, filename)
+                marker = os.path.isdir(filepath) and "/" or ""
+                print >>out, \
+                        template % (urllib.quote(filename), filename, marker)
+            print >>out, "</pre>"
+        else:
+            print >>out, "<h1>Directory listing denied</h1>"
+            print >>out, \
+                "<p>This directory does not allow its contents to be listed.</p>"
+        return out.getvalue()
+
+    def _q_lookup(self, request, name):
+        """
+        Get a file from the filesystem directory and return the StaticFile
+        or StaticDirectory wrapper of it; use caching if that is in use.
+        """
+        if name in ('.', '..'):
+            raise errors.TraversalError(private_msg="Attempt to use '.', '..'")
+        if self.cache.has_key(name):
+            # Get item from cache
+            item = self.cache[name]
+        else:
+            # Get item from filesystem; cache it if caching is in use.
+            item_filepath = os.path.join(self.path, name)
+            while os.path.islink(item_filepath):
+                if not self.follow_symlinks:
+                    raise errors.TraversalError
+                else:
+                    dest = os.readlink(item_filepath)
+                    item_filepath = os.path.join(self.path, dest)
+
+            if os.path.isdir(item_filepath):
+                # avoid passing post 1.0 keyword arguments to subclasses that
+                # may not support them
+                kwargs = {}
+                if self.index_filenames is not None:
+                    kwargs['index_filenames'] = self.index_filenames
+                item = self.__class__(item_filepath, self.use_cache,
+                                      self.list_directory,
+                                      self.follow_symlinks, self.cache_time,
+                                      self.file_class, **kwargs)
+            elif os.path.isfile(item_filepath):
+                item = self.file_class(item_filepath, self.follow_symlinks,
+                                       cache_time=self.cache_time)
+            else:
+                raise errors.TraversalError
+            if self.use_cache:
+                self.cache[name] = item
+        return item
+
+
+class Redirector:
+    """
+    A simple class that can be used from inside _q_lookup() to redirect
+    requests.
+    """
+
+    _q_exports = []
+
+    def __init__(self, location, permanent=0):
+        self.location = location
+        self.permanent = permanent
+
+    def _q_lookup(self, request, component):
+        return self
+
+    def __call__(self, request):
+        return request.redirect(self.location, self.permanent)




More information about the Python-modules-commits mailing list