summaryrefslogtreecommitdiff
path: root/pypers/europython05/Quixote-2.0
diff options
context:
space:
mode:
Diffstat (limited to 'pypers/europython05/Quixote-2.0')
-rwxr-xr-xpypers/europython05/Quixote-2.0/__init__.py26
-rwxr-xr-xpypers/europython05/Quixote-2.0/config.py175
-rwxr-xr-xpypers/europython05/Quixote-2.0/demo/__init__.py10
-rwxr-xr-xpypers/europython05/Quixote-2.0/demo/altdemo.py205
-rwxr-xr-xpypers/europython05/Quixote-2.0/demo/mini_demo.py39
-rwxr-xr-xpypers/europython05/Quixote-2.0/directory.py109
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/INSTALL.txt32
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/Makefile31
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/PTL.txt264
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/demo.txt221
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/form2conversion.txt358
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/multi-threaded.txt39
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/programming.txt157
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/session-mgmt.txt323
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/static-files.txt51
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/upgrading.txt324
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/web-server.txt258
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/web-services.txt169
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/widgets.txt524
-rwxr-xr-xpypers/europython05/Quixote-2.0/doc/win32.txt14
-rwxr-xr-xpypers/europython05/Quixote-2.0/errors.py141
-rwxr-xr-xpypers/europython05/Quixote-2.0/form/__init__.py18
-rwxr-xr-xpypers/europython05/Quixote-2.0/form/compatibility.py101
-rwxr-xr-xpypers/europython05/Quixote-2.0/form/css.py76
-rwxr-xr-xpypers/europython05/Quixote-2.0/form/form.py365
-rwxr-xr-xpypers/europython05/Quixote-2.0/form/widget.py951
-rwxr-xr-xpypers/europython05/Quixote-2.0/form1/__init__.py34
-rwxr-xr-xpypers/europython05/Quixote-2.0/form1/form.py534
-rwxr-xr-xpypers/europython05/Quixote-2.0/form1/widget.py842
-rwxr-xr-xpypers/europython05/Quixote-2.0/html/__init__.py106
-rwxr-xr-xpypers/europython05/Quixote-2.0/html/_c_htmltext.c1019
-rwxr-xr-xpypers/europython05/Quixote-2.0/html/_py_htmltext.py213
-rwxr-xr-xpypers/europython05/Quixote-2.0/html/test/utest_html.py368
-rwxr-xr-xpypers/europython05/Quixote-2.0/http_request.py759
-rwxr-xr-xpypers/europython05/Quixote-2.0/http_response.py475
-rwxr-xr-xpypers/europython05/Quixote-2.0/logger.py92
-rwxr-xr-xpypers/europython05/Quixote-2.0/ptl/__init__.py245
-rwxr-xr-xpypers/europython05/Quixote-2.0/ptl/cimport.c483
-rwxr-xr-xpypers/europython05/Quixote-2.0/ptl/install.py2
-rwxr-xr-xpypers/europython05/Quixote-2.0/ptl/ptl_compile.py314
-rwxr-xr-xpypers/europython05/Quixote-2.0/ptl/ptl_import.py148
-rwxr-xr-xpypers/europython05/Quixote-2.0/ptl/ptlrun.py5
-rwxr-xr-xpypers/europython05/Quixote-2.0/ptl/qx_distutils.py47
-rwxr-xr-xpypers/europython05/Quixote-2.0/ptl/test/utest_ptl.py58
-rwxr-xr-xpypers/europython05/Quixote-2.0/publish.py336
-rwxr-xr-xpypers/europython05/Quixote-2.0/publish1.py270
-rwxr-xr-xpypers/europython05/Quixote-2.0/sendmail.py273
-rwxr-xr-xpypers/europython05/Quixote-2.0/server/__init__.py5
-rwxr-xr-xpypers/europython05/Quixote-2.0/server/_fcgi.py466
-rwxr-xr-xpypers/europython05/Quixote-2.0/server/cgi_server.py27
-rwxr-xr-xpypers/europython05/Quixote-2.0/server/fastcgi_server.py28
-rwxr-xr-xpypers/europython05/Quixote-2.0/server/medusa_server.py116
-rwxr-xr-xpypers/europython05/Quixote-2.0/server/mod_python_handler.py106
-rwxr-xr-xpypers/europython05/Quixote-2.0/server/scgi_server.py84
-rwxr-xr-xpypers/europython05/Quixote-2.0/server/simple_server.py93
-rwxr-xr-xpypers/europython05/Quixote-2.0/server/twisted_server.py147
-rwxr-xr-xpypers/europython05/Quixote-2.0/server/util.py32
-rwxr-xr-xpypers/europython05/Quixote-2.0/session.py567
-rwxr-xr-xpypers/europython05/Quixote-2.0/setup.py65
-rwxr-xr-xpypers/europython05/Quixote-2.0/test/__init__.py2
-rwxr-xr-xpypers/europython05/Quixote-2.0/test/ua_test.py28
-rwxr-xr-xpypers/europython05/Quixote-2.0/test/utest_request.py43
-rwxr-xr-xpypers/europython05/Quixote-2.0/util.py390
63 files changed, 13803 insertions, 0 deletions
diff --git a/pypers/europython05/Quixote-2.0/__init__.py b/pypers/europython05/Quixote-2.0/__init__.py
new file mode 100755
index 0000000..44bbf4a
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/__init__.py
@@ -0,0 +1,26 @@
+"""quixote
+$HeadURL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/__init__.py $
+$Id: __init__.py 26539 2005-04-11 15:47:33Z dbinger $
+
+A highly Pythonic web application framework.
+"""
+
+__version__ = '2.0'
+
+# These are frequently needed by Quixote applications.
+from quixote.publish import \
+ get_publisher, get_request, get_response, get_path, redirect, \
+ get_session, get_session_manager, get_user, get_field, get_cookie
+
+
+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.
+ """
+ import quixote.ptl.install
diff --git a/pypers/europython05/Quixote-2.0/config.py b/pypers/europython05/Quixote-2.0/config.py
new file mode 100755
index 0000000..0a8a651
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/config.py
@@ -0,0 +1,175 @@
+"""
+$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/config.py $
+$Id: config.py 26378 2005-03-17 14:04:45Z dbinger $
+
+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.
+"""
+
+
+# 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.
+# 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@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 and debugging output; if None,
+# everything will be sent to standard error (normally ending up in the
+# Web server's error log file.
+ERROR_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.
+DISPLAY_EXCEPTIONS = None
+
+# Compress large pages using gzip if the client accepts that encoding.
+COMPRESS_PAGES = False
+
+# 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 = False
+
+# 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@example.com"
+ # or ("webmaster@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@example.com"
+
+
+# -- End config variables ----------------------------------------------
+# (no user serviceable parts after this point)
+
+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',
+ 'display_exceptions',
+ 'error_log',
+ 'compress_pages',
+ 'form_tokens',
+ 'session_cookie_domain',
+ 'session_cookie_name',
+ 'session_cookie_path',
+ 'check_session_addr',
+ 'mail_from',
+ 'mail_server',
+ 'mail_debug_addr',
+ ]
+
+ def __init__(self, **kwargs):
+ self.set_from_dict(globals()) # set defaults
+ for name, value in kwargs.items():
+ if name not in self.config_vars:
+ raise ValueError('unknown config variable %r' % name)
+ setattr(self, name, value)
+
+ 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, config_vars):
+ for name, value in config_vars.items():
+ if name.isupper():
+ name = name.lower()
+ if name not in self.config_vars:
+ raise ValueError('unknown config variable %r' % name)
+ setattr(self, name, value)
+
+ 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)
diff --git a/pypers/europython05/Quixote-2.0/demo/__init__.py b/pypers/europython05/Quixote-2.0/demo/__init__.py
new file mode 100755
index 0000000..4efe0a7
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/demo/__init__.py
@@ -0,0 +1,10 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/demo/__init__.py $
+$Id: __init__.py 25575 2004-11-11 16:56:44Z nascheme $
+"""
+from quixote import enable_ptl
+from quixote.publish import Publisher
+enable_ptl()
+
+def create_publisher():
+ from quixote.demo.root import RootDirectory
+ return Publisher(RootDirectory(), display_exceptions='plain')
diff --git a/pypers/europython05/Quixote-2.0/demo/altdemo.py b/pypers/europython05/Quixote-2.0/demo/altdemo.py
new file mode 100755
index 0000000..fc32ca5
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/demo/altdemo.py
@@ -0,0 +1,205 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/demo/altdemo.py $
+$Id: altdemo.py 26377 2005-03-16 23:32:15Z dbinger $
+
+An alternative Quixote demo. This version is contained in a single module
+and does not use PTL. The easiest way to run this demo is to use the
+simple HTTP server included with Quixote. For example:
+
+ $ server/simple_server.py --factory quixote.demo.altdemo.create_publisher
+
+The server listens on localhost:8080 by default. Debug and error output
+will be sent to the terminal.
+
+If you have installed durus, you can run the same demo, except with
+persistent sessions stored in a durus database, by running:
+
+ $ server/simple_server.py --factory quixote.demo.altdemo.create_durus_publisher
+
+"""
+
+from quixote import get_user, get_session, get_session_manager, get_field
+from quixote.directory import Directory
+from quixote.html import href, htmltext
+from quixote.publish import Publisher
+from quixote.session import Session, SessionManager
+from quixote.util import dump_request
+
+def format_page(title, content):
+ request = htmltext(
+ '<div style="font-size: smaller;background:#eee">'
+ '<h1>Request:</h1>%s</div>') % dump_request()
+ return htmltext(
+ '<html><head><title>%(title)s</title>'
+ '<style type="text/css">\n'
+ 'body { border: thick solid green; padding: 2em; }\n'
+ 'h1 { font-size: larger; }\n'
+ 'th { background: #aaa; text-align:left; font-size: smaller; }\n'
+ 'td { background: #ccc; font-size: smaller; }\n'
+ '</style>'
+ '</head><body>%(content)s%(request)s</body></html>') % locals()
+
+def format_request():
+ return format_page('Request', dump_request())
+
+def format_link_list(targets):
+ return htmltext('<ul>%s</ul>') % htmltext('').join([
+ htmltext('<li>%s</li>') % href(target, target) for target in targets])
+
+class RootDirectory(Directory):
+
+ _q_exports = ['', 'login', 'logout']
+
+ def _q_index(self):
+ content = htmltext('')
+ if not get_user():
+ content += htmltext('<p>%s</p>' % href('login', 'login'))
+ else:
+ content += htmltext(
+ '<p>Hello, %s.</p>') % get_user()
+ content += htmltext('<p>%s</p>' % href('logout', 'logout'))
+ sessions = get_session_manager().items()
+ if sessions:
+ sessions.sort()
+ content += htmltext('<table><tr>'
+ '<th></th>'
+ '<th>Session</th>'
+ '<th>User</th>'
+ '<th>Number of Requests</th>'
+ '</tr>')
+ this_session = get_session()
+ for index, (id, session) in enumerate(sessions):
+ if session is this_session:
+ formatted_id = htmltext(
+ '<span style="font-weight:bold">%s</span>' % id)
+ else:
+ formatted_id = id
+ content += htmltext(
+ '<tr><td>%s</td><td>%s</td><td>%s</td><td>%d</td>' % (
+ index,
+ formatted_id,
+ session.user or htmltext("<em>None</em>"),
+ session.num_requests))
+ content += htmltext('</table>')
+ return format_page("Quixote Session Management Demo", content)
+
+ def login(self):
+ content = htmltext('')
+ if get_field("name"):
+ session = get_session()
+ session.set_user(get_field("name")) # This is the important part.
+ content += htmltext(
+ '<p>Welcome, %s! Thank you for logging in.</p>') % get_user()
+ content += href("..", "go back")
+ else:
+ content += htmltext(
+ '<p>Please enter your name here:</p>\n'
+ '<form method="POST" action="login">'
+ '<input name="name" />'
+ '<input type="submit" />'
+ '</form>')
+ return format_page("Quixote Session Demo: Login", content)
+
+ def logout(self):
+ if get_user():
+ content = htmltext('<p>Goodbye, %s.</p>') % get_user()
+ else:
+ content = htmltext('<p>That would be redundant.</p>')
+ content += href("..", "start over")
+ get_session_manager().expire_session() # This is the important part.
+ return format_page("Quixote Session Demo: Logout", content)
+
+
+class DemoSession(Session):
+
+ def __init__(self, id):
+ Session.__init__(self, id)
+ self.num_requests = 0
+
+ def start_request(self):
+ """
+ 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)
+ 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
+
+
+def create_publisher():
+ return Publisher(RootDirectory(),
+ session_manager=SessionManager(session_class=DemoSession),
+ display_exceptions='plain')
+
+try:
+ # If durus is installed, define a create_durus_publisher() that
+ # uses a durus database to store persistent sessions.
+ import os, tempfile
+ from durus.persistent import Persistent
+ from durus.persistent_dict import PersistentDict
+ from durus.file_storage import FileStorage
+ from durus.connection import Connection
+ connection = None # set in create_durus_publisher()
+
+ class PersistentSession(DemoSession, Persistent):
+ pass
+
+ class PersistentSessionManager(SessionManager, Persistent):
+ def __init__(self):
+ sessions = PersistentDict()
+ SessionManager.__init__(self,
+ session_class=PersistentSession,
+ session_mapping=sessions)
+ def forget_changes(self, session):
+ print 'abort changes', get_session()
+ connection.abort()
+
+ def commit_changes(self, session):
+ print 'commit changes', get_session()
+ connection.commit()
+
+ def create_durus_publisher():
+ global connection
+ filename = os.path.join(tempfile.gettempdir(), 'quixote-demo.durus')
+ print 'Opening %r as a Durus database.' % filename
+ connection = Connection(FileStorage(filename))
+ root = connection.get_root()
+ session_manager = root.get('session_manager', None)
+ if session_manager is None:
+ session_manager = PersistentSessionManager()
+ connection.get_root()['session_manager'] = session_manager
+ connection.commit()
+ return Publisher(RootDirectory(),
+ session_manager=session_manager,
+ display_exceptions='plain')
+except ImportError:
+ pass # durus not installed.
diff --git a/pypers/europython05/Quixote-2.0/demo/mini_demo.py b/pypers/europython05/Quixote-2.0/demo/mini_demo.py
new file mode 100755
index 0000000..f422211
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/demo/mini_demo.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+"""
+A minimal Quixote demo. If you have the 'quixote' package in your Python
+path, you can run it like this:
+
+ $ python demo/mini_demo.py
+
+The server listens on localhost:8080 by default. Debug and error output
+will be sent to the terminal.
+"""
+
+from quixote.publish import Publisher
+from quixote.directory import Directory
+
+class RootDirectory(Directory):
+
+ _q_exports = ['', 'hello']
+
+ def _q_index(self):
+ return '''<html>
+ <body>Welcome to the Quixote demo. Here is a
+ <a href="hello">link</a>.
+ </body>
+ </html>
+ '''
+
+ def hello(self):
+ return '<html><body>Hello world!</body></html>'
+
+
+def create_publisher():
+ return Publisher(RootDirectory(),
+ display_exceptions='plain')
+
+
+if __name__ == '__main__':
+ from quixote.server.simple_server import run
+ print 'creating demo listening on http://localhost:8080/'
+ run(create_publisher, host='localhost', port=8080)
diff --git a/pypers/europython05/Quixote-2.0/directory.py b/pypers/europython05/Quixote-2.0/directory.py
new file mode 100755
index 0000000..e3a8816
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/directory.py
@@ -0,0 +1,109 @@
+"""$HeadURL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/directory.py $
+$Id: directory.py 26327 2005-03-10 11:30:45Z dbinger $
+
+Logic for traversing directory objects and generating output.
+"""
+import quixote
+from quixote.errors import TraversalError
+
+class Directory(object):
+ """
+ Instance attributes: none
+ """
+
+ # A list containing strings or 2-tuples of strings that map external
+ # names to internal names. Note that the empty string will be
+ # implicitly mapped to '_q_index'.
+ _q_exports = []
+
+ def _q_translate(self, component):
+ """(component : string) -> string | None
+
+ Translate a path component into a Python identifier. Returning
+ None signifies that the component does not exist.
+ """
+ if component in self._q_exports:
+ if component == '':
+ return '_q_index' # implicit mapping
+ else:
+ return component
+ else:
+ # check for an explicit external to internal mapping
+ for value in self._q_exports:
+ if isinstance(value, tuple):
+ if value[0] == component:
+ return value[1]
+ else:
+ return None
+
+ def _q_lookup(self, component):
+ """(component : string) -> object
+
+ Lookup a path component and return the corresponding object (usually
+ a Directory, a method or a string). Returning None signals that the
+ component does not exist.
+ """
+ return None
+
+ def _q_traverse(self, path):
+ """(path: [string]) -> object
+
+ Traverse a path and return the result.
+ """
+ assert len(path) > 0
+ component = path[0]
+ path = path[1:]
+ name = self._q_translate(component)
+ if name is not None:
+ obj = getattr(self, name)
+ else:
+ obj = self._q_lookup(component)
+ if obj is None:
+ raise TraversalError(private_msg=('directory %r has no component '
+ '%r' % (self, component)))
+ if path:
+ return obj._q_traverse(path)
+ elif callable(obj):
+ return obj()
+ else:
+ return obj
+
+ def __call__(self):
+ if "" in self._q_exports and not quixote.get_request().form:
+ # Fix missing trailing slash.
+ path = quixote.get_path()
+ print "Adding slash to: %r " % path
+ return quixote.redirect(path + "/", permanent=True)
+ else:
+ raise TraversalError(private_msg=('directory %r is not '
+ 'callable' % self))
+
+class AccessControlled(object):
+ """
+ A mix-in class that calls the _q_access() method before traversing
+ into the directory.
+ """
+ def _q_access(self):
+ pass
+
+ def _q_traverse(self, path):
+ self._q_access()
+ return super(AccessControlled, self)._q_traverse(path)
+
+
+class Resolving(object):
+ """
+ A mix-in class that provides the _q_resolve() method. _q_resolve()
+ is called if a component name appears in the _q_exports list but is
+ not an instance attribute. _q_resolve is expected to return the
+ component object.
+ """
+ def _q_resolve(self, name):
+ return None
+
+ def _q_translate(self, component):
+ name = super(Resolving, self)._q_translate(component)
+ if name is not None and not hasattr(self, name):
+ obj = self._q_resolve(name)
+ setattr(self, name, obj)
+ return name
diff --git a/pypers/europython05/Quixote-2.0/doc/INSTALL.txt b/pypers/europython05/Quixote-2.0/doc/INSTALL.txt
new file mode 100755
index 0000000..ccc18c8
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/INSTALL.txt
@@ -0,0 +1,32 @@
+Installing Quixote
+==================
+
+Quixote requires Python 2.3 or later.
+
+If you have a previously installed quixote, we strongly recommend that
+you remove it 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.)
+
+Now install the new version by running (in the distribution directory),
+
+ python setup.py install
+
+and you're done.
+
+Quick start
+===========
+
+In a terminal window, run server/simple_server.py.
+In a browser, open http://localhost:8080
+
+
+Upgrading a Quixote 1 application to Quixote 2.
+===============================================
+
+See upgrading.txt for details.
+
diff --git a/pypers/europython05/Quixote-2.0/doc/Makefile b/pypers/europython05/Quixote-2.0/doc/Makefile
new file mode 100755
index 0000000..b09d2f3
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/Makefile
@@ -0,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) .
diff --git a/pypers/europython05/Quixote-2.0/doc/PTL.txt b/pypers/europython05/Quixote-2.0/doc/PTL.txt
new file mode 100755
index 0000000..c0e4b0f
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/PTL.txt
@@ -0,0 +1,264 @@
+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``.
+
+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 ``.pyc``. 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. A similar problem has been reported for
+BioPython and win32com.client imports.)
+
+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.
+
diff --git a/pypers/europython05/Quixote-2.0/doc/demo.txt b/pypers/europython05/Quixote-2.0/doc/demo.txt
new file mode 100755
index 0000000..4272223
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/demo.txt
@@ -0,0 +1,221 @@
+Running the Quixote Demos
+=========================
+
+Quixote comes with some demonstration applications in the demo directory.
+After quixote is installed (see INSTALL.txt for instructions),
+you can run the demos using the scripts located in the server directory.
+
+Each server script is written for a specific method of connecting a
+quixote publisher to a web server, and you will ultimately want to
+choose the one that matches your needs. More information about the
+different server scripts may be found in the scripts themselves and in
+web-server.txt. To start, though, the easiest way to view the demos
+is as follows: in a terminal window, run server/simple_server.py, and
+in a browser, open http://localhost:8080.
+
+The simple_server.py script prints a usage message if you run it with
+a '--help' command line argument. You can run different demos by
+using the '--factory' option to identify a callable that creates the
+publisher you want to use. In particular, you might try these demos:
+
+ simple_server.py --factory quixote.demo.mini_demo.create_publisher
+
+or
+
+ simple_server.py --factory quixote.demo.altdemo.create_publisher
+
+
+
+Understanding the mini_demo
+---------------------------
+
+Start the mini demo by running the command:
+ simple_server.py --factory quixote.demo.mini_demo.create_publisher
+
+In a browser, load http://localhost:8080. In your browser, you should
+see "Welcome ..." page. In your terminal window, you will see a
+"localhost - - ..." line for each request. These are access log
+messages from the web server.
+
+Look at the source code in demo/mini_demo.py. Near the bottom you
+will find the create_publisher() function. The create_publisher()
+function creates a Publisher instance whose root directory is an
+instance of the RootDirectory class defined just above. When a
+request arrives, the Publisher calls the _q_traverse() method on the
+root directory. In this case, the RootDirectory is using the standard
+_q_traverse() implementation, inherited from Directory.
+
+Look, preferably in another window, at the source code for
+_q_traverse() in directory.py. The path argument provided to
+_q_traverse() is a list of string components of the path part of the
+URL, obtained by splitting the request location at each '/' and
+dropping the first element (which is always '') For example, if the
+path part of the URL is '/', the path argument to _q_traverse() is
+['']. If the path part of the URL is '/a', the path argument to
+_q_traverse() is ['a']. If the path part of the URL is '/a/', the
+path argument to _q_traverse() is ['a', ''].
+
+Looking at the code of _q_traverse(), observe that it starts by
+splitting off the first component of the path and calling
+_q_translate() to see if there is a designated attribute name
+corresponding to this component. For the '/' page, the component is
+'', and _q_translate() returns the attribute name '_q_index'. The
+_q_traverse() function goes on to lookup the _q_index method and
+return the result of calling it.
+
+Looking back at mini_demo.py, you can see that the RootDirectory class
+includes a _q_index() method, and this method does return the HTML for
+http://localhost:8080/
+
+As mentioned above, the _q_translate() identifies a "designated"
+attribute name for a given component. The default implementation uses
+self._q_exports to define this designation. In particular, if the
+component is in self._q_exports, then it is returned as the attribute
+name, except in the special case of '', which is translated to the
+special attribute name '_q_index'.
+
+When you click on the link on the top page, you get
+http://localhost:8080/hello. In this case, the path argument to the
+_q_traverse() call is ['hello'], and the return value is the result of
+calling the hello() method.
+
+Feeling bold? (Just kidding, this won't hurt at all.) Try opening
+http://localhost:8080/bogus. This is what happens when _q_traverse()
+raises a TraversalError. A TraversalError is no big deal, but how
+does quixote handle more exceptional exceptions? To see, you can
+introduce one by editing mini_demo.py. Try inserting the line "raise
+'ouch'" into the hello() method. Kill the demo server (Control-c) and
+start a new one with the same command as before. Now load the
+http://localhost:8080/hello page. You should see a plain text python
+traceback followed by some information extracted from the HTTP
+request. This information is always printed to the error log on an
+exception. Here, it is also displayed in the browser because the
+create_publisher() function made a publisher using the 'plain' value
+for the display_exceptions keyword argument. If you omit that keyword
+argument from the Publisher constructor, the browser will get an
+"Internal Server Error" message instead of the full traceback. If you
+provide the value 'html', the browser displays a prettier version of
+the traceback.
+
+One more thing to try here. Replace your 'raise "ouch"' line in the hello() method with 'print "ouch"'. If you restart the server and load the /hello page,
+you will see that print statements go the the error log (in this case, your
+terminal window). This can be useful.
+
+
+Understanding the root demo
+---------------------------
+
+Start the root demo by running the command:
+ simple_server.py --factory quixote.demo.create_publisher
+
+In a browser, open http://localhost:8080 as before.
+Click around at will.
+
+This is the default demo, but it is more complicated than the
+mini_demo described above. The create_publisher() function in
+quixote.demo.__init__.py creates a publisher whose root directory is
+an instance of quixote.demo.root.RootDirectory. Note that the source
+code is a file named "root.ptl". The suffix of "ptl" indicates that
+it is a PTL file, and the import must follow a call to
+quixote.enable_ptl() or else the source file will not be found or
+compiled. The quixote.demo.__init__.py file takes care of that.
+
+Take a look at the source code in root.ptl. You will see code that
+looks like regular python, except that some function definitions have
+"[html]" between the function name and the parameter list. These
+functions are ptl templates. For details about PTL, see the PTL.txt
+file.
+
+This RootDirectory class is similar to the one in mini_demo.py, in
+that it has a _q_index() method and '' appears in the _q_exports list.
+One new feature here is the presence of a tuple in the _q_exports
+list. Most of the time, the elements of the _q_exports lists are just
+strings that name attributes that should be available as URL
+components. This pattern does not work, however, when the particular
+URL component you want to use includes characters (like '.') that
+can't appear in Python attribute names. To work around these cases,
+the _q_exports list may contain tuples such as ("favicon.ico",
+"favicon_ico") to designate "favicon_ico" as the attribute name
+corresponding the the "favicon.ico" URL component.
+
+Looking at the RootDirectoryMethods, including plain(), css() and
+favon_ico(), you will see examples where, in addition to returning a
+string containing the body of the HTTP response, the function also
+makes side-effect modifications to the response object itself, to set
+the content type and the expiration time for the response.
+Most of the time, these direct modifications to the response are
+not needed. When they are, though, the get_response() function
+gives you direct access to the response instance.
+
+The RootDirectory here also sets an 'extras' attribute to be an
+instance of ExtraDirectory, imported from the quixote.demo.extras
+module. Note that 'extras' also appears in the _q_exports list. This
+is the ordinary way to extend your URL space through another '/'.
+For example, the URL path '/extras/' will result in a call to
+the ExtraDirectory instance's _q_index() method.
+
+The _q_lookup() method
+----------------------
+
+Now take a look at the ExtraDirectory class in extras.ptl. This class
+exhibits some more advanced publishing features. If you look back at
+the default _q_traverse() implementation (in directory.py), you will
+see that the _q_traverse does not give up if _q_translate() returns
+None, indicating that the path component has no designated
+corresponding attribute name. In this case, _q_traverse() tries
+calling self._q_lookup() to see if the object of interest can be found
+in a different way. Note that _q_lookup() takes the component as an
+argument and must return either (if there is more path to traverse) a
+Directory instance, or else (if the component is the last in the path)
+a callable or a string.
+
+In this particular case, the ExtrasDirectory._q_lookup() call returns
+an instance of IntegerUI (a subclass of Directory). The interest
+here, unlike the ExtrasDirectory() instance itself, is created
+on-the-fly during the traversal, especially for this particular
+component. Try loading http://localhost:8080/extras/12/ to see how
+this behaves.
+
+Note that the correct URL to get to the IntegerUI(12)._q_index() call
+ends with a '/'. This can sometimes be confusing to people who expect
+http://localhost:8080/extras/12 to yield the same page as
+http://localhost:8080/extras/12/. If given the path ['extras', '12'],
+the default _q_traverse() ends up *calling* the instance of IntegerUI.
+The Directory.__call__() (see directory.py) determines the result: if
+no form values were submitted and adding a slash would produce a page,
+the call returns the result of calling quixote.redirect(). The
+redirect() call here causes the server to issue a permanent redirect
+response to the path with the slash added. When this automatic
+redirect is used, a message is printed to the error log. If the
+conditions for a redirect are not met, the call falls back to raising
+a TraversalError. [Note, if you don't like this redirect behavior,
+override, replace, or delete Directory.__call__]
+
+The _q_lookup() pattern is useful when you want to allow URL
+components that you either don't know or don't want to list in
+_q_exports ahead of time.
+
+The _q_resolve() method
+-----------------------
+
+Note that the ExtraDirectory class inherits from Resolving (in
+addition to Directory). The Resolving mixin modifies the
+_q_traverse() so that, when a component has an attribute name
+designated by _q_translate(), but the Directory instance does not
+actually *have* that attribute, the _q_resolve() method is called to
+"resolve" the trouble. Typically, the _q_resolve() imports or
+constructs what *should* be the value of the designated attribute.
+The modified _q_translate() sets the attribute value so that the
+_q_resolve() won't be called again for the same attribute. The
+_q_resolve() pattern is useful when you want to delay the work of
+constructing the values for exported attributes.
+
+Forms
+-----
+
+You can't get very far writing web applications without writing forms.
+The root demo includes, at http://localhost:8080/extras/form, a page
+that demonstrates basic usage of the Form class and widgets defined in
+the quixote.form package.
+
+$Id: demo.txt 25695 2004-11-30 20:53:44Z dbinger $
diff --git a/pypers/europython05/Quixote-2.0/doc/form2conversion.txt b/pypers/europython05/Quixote-2.0/doc/form2conversion.txt
new file mode 100755
index 0000000..290db38
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/form2conversion.txt
@@ -0,0 +1,358 @@
+Converting form1 forms to use the form2 library
+===============================================
+
+Note:
+-----
+The packages names have changed in Quixote 2.
+
+Quixote form1 forms are now in the package quixote.form1.
+(In Quixote 1, they were in quixote.form.)
+
+Quixote form2 forms are now in the package quixote.form.
+(In Quixote 1, they were in quixote.form2.)
+
+
+Introduction
+------------
+
+These are some notes and examples for converting Quixote form1 forms,
+that is forms derived from ``quixote.form1.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.form.compatibility``
+ rather than from quixote.form1.
+
+ 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.form.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.form.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.form.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)
diff --git a/pypers/europython05/Quixote-2.0/doc/multi-threaded.txt b/pypers/europython05/Quixote-2.0/doc/multi-threaded.txt
new file mode 100755
index 0000000..f3be1a5
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/multi-threaded.txt
@@ -0,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 $
diff --git a/pypers/europython05/Quixote-2.0/doc/programming.txt b/pypers/europython05/Quixote-2.0/doc/programming.txt
new file mode 100755
index 0000000..d94adf7
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/programming.txt
@@ -0,0 +1,157 @@
+Quixote Programming Overview
+============================
+
+This document explains how a Quixote application is structured.
+The demo.txt file should probably be read before you read this file.
+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; the default value is false, to favor security over convenience.
+
+3) Finally, the bulk of the code will be called through a call (by the
+ Publisher) to the _q_traverse() method of an instance designated as
+ the ``root_directory``. Normally, the root_directory will be an
+ instance of the Directory class.
+
+
+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 publisher's process_request() method as needed to get
+ responses for one or more requests, writing the responses back
+ to the client(s).
+
+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 directory of your application.
+
+The quixote.servers package includes driver modules for cgi, fastcgi,
+scgi, medusa, twisted, and the simple_server. Each of these modules
+includes a ``run()`` function that you can use in a driver script that
+provides a function to create the publisher that you want. For an example
+of this pattern, see the __main__ part of demo/mini_demo.py. You could
+run the mini_demo.py with scgi by using the ``run()`` function imported
+from quixote.server.scgi_server instead of the one from
+quixote.server.simple_server. (You would also need your http server
+set up to use the scgi server.)
+
+That's almost the simplest possible case -- there's no
+application-specific configuration info apart from the root directory.
+
+Getting the driver script to actually run is between you and your web
+server. See the web-server.txt document for help.
+
+
+Configuration file
+------------------
+
+By default, the Publisher uses the configuration information from
+quixote/config.py. 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. If you want to
+customize any of the configuration variables, your driver script
+should provide your customized Config instance as an argument to the
+Publisher constructor.
+
+Logging
+-------
+
+The publisher also accepts an optional ``logger`` keyword argument,
+that should, if provided, support the same methods as the
+default value, an instance of ``DefaultLogger``. Even if you
+use the default logger, you can still customize the behavior
+by setting configuration values for ``access_log``, ``error_log``, and/or
+``error_email``. These configuration variables are described
+more fully in config.py.
+
+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 three purposes:
+
+* application output to ``sys.stdout`` and ``sys.stderr`` goes to
+ Quixote's error log
+* application tracebacks will be written to Quixote's error log
+
+If no error log is configured (with ``ERROR_LOG``), then all output is
+redirected 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.
+
+Having stdout redirected to the error log is useful for debugging. You
+can just sprinkle ``print`` statements into your application and the
+output will wind up in the error log.
+
+
+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. You may, optionally, wish to use PTL,
+which is simply Python with a novel way of generating function return
+values -- see PTL.txt for details.
+
+Quixote's Publisher constructs a request, splits the path into a list
+of components, and calls the root directory's _q_traverse() method,
+giving the component list as an argument. The _q_traverse() will either
+return a value that will become the content of the HTTPResponse, or
+else it may raise an Exception. Exceptions are caught by the Publisher
+and handled as needed, depending on configuration variables and
+whether or not the Exception is an instance of PublisherError.
+
+
diff --git a/pypers/europython05/Quixote-2.0/doc/session-mgmt.txt b/pypers/europython05/Quixote-2.0/doc/session-mgmt.txt
new file mode 100755
index 0000000..19df072
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/session-mgmt.txt
@@ -0,0 +1,323 @@
+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,
+the session management mechanism that comes with Quixote is
+cookie-based. (The most common alternative is to embed the session
+identifier in the URL. Since Quixote views the URL as a fundamental
+part of the web user interface, a URL-based session management scheme is
+considered 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.
+
+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. The get_session() function provides uniform
+ access to the current 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 made
+ available, through the get_session() function, as the application
+ code processes 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/altdemo.py.
+If the durus (http://www.mems-exchange.org/software/durus/) package is
+installed, the demo uses a durus database to store sessions, so sessions
+will be preserved, even if your are running it with plain cgi.
+
+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 altdemo.py::
+
+ def __init__ (self, id):
+ Session.__init__(self, id)
+ self.num_requests = 0
+
+ def start_request (self):
+ Session.start_request(self)
+ 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. If you want the
+value of the user attribute of the current session, just call
+get_user(). If you want some other attribute or method Use
+get_session() to get the current Session if you need access to other
+attributes (such as ``num_requests`` in the demo) or methods of the
+current Session instance.
+
+Note that the Session class initializes the user attribute to None,
+so get_user() will return None if no user has been identified for
+this session. Application code can use this to change behavior,
+as in the following::
+
+ if not get_user():
+ content += htmltext('<p>%s</p>' % href('login', 'login'))
+ else:
+ content += htmltext(
+ '<p>Hello, %s.</p>') % get_user()
+ content += htmltext('<p>%s</p>' % href('logout', 'logout'))
+
+
+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/altdemo.py``) ::
+
+ if get_field("name"):
+ session = get_session()
+ session.set_user(get_field("name")) # This is the important part.
+
+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 defense 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.
+
+The second hook (two hooks, really) apply if you use a transactional
+persistence mechanism to provide your SessionManager's mapping. The
+``altdemo.py`` script does this with Durus, if the durus package is
+installed, but you could also use ZODB or a relational database for
+this purpose. The hooks make sure that session (and other) changes
+get committed or aborted at the appropriate times. SessionManager
+provides two methods for you to override: ``forget_changes()`` and
+``commit_changes()``. ``forget_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. You'll have to
+use your own SessionManager subclass if you need to take advantage of
+these hooks for transactional session persistence.
+
+The third available hook is the Session's is_dirty() method. This is
+used when your mapping class uses a more primitive storage mechanism,
+as, for example, 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_manager = SessionManager(session_mapping=sessions)
+
+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, 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.
+
+
diff --git a/pypers/europython05/Quixote-2.0/doc/static-files.txt b/pypers/europython05/Quixote-2.0/doc/static-files.txt
new file mode 100755
index 0000000..64254f4
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/static-files.txt
@@ -0,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")
+
+
diff --git a/pypers/europython05/Quixote-2.0/doc/upgrading.txt b/pypers/europython05/Quixote-2.0/doc/upgrading.txt
new file mode 100755
index 0000000..4d002cb
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/upgrading.txt
@@ -0,0 +1,324 @@
+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 1.0 to 2.0
+-------------------------
+
+Change any imports you have from quixote.form to be from quixote.form1.
+
+Change any imports you have from quixote.form2 to be from quixote.form.
+
+Replace calls to HTTPRequest.get_form_var() with calls to get_field().
+
+Define a create_publisher() function to get the publisher you need
+and figure out how you want to connect it to web server.
+See files in demo and server for examples. Note that publish1.py
+contains a publisher that works more like the Quixote1 Publisher,
+and does not require the changes listed below.
+
+Make every namespace be an instance of quixote.directory.Directory.
+Update namespaces that are modules (or in the init.py of a package) by
+defining a new class in the module that inherits from Directory and
+moving your _q_exports and _q_* functions onto the class. Replace
+"request" parameters with "self" parameters on the new methods. If
+you have a _q_resolve method, include Resolving in the bases of your
+new class.
+
+Remove request from calls to _q_ functions. If request, session,
+user, path, or redirect is used in these new methods, replace as
+needed with calls to get_request(), get_session(), get_user(),
+get_path(), and/or redirect(), imported from quixote.
+
+In every namespace that formerly traversed into a module, import the
+new Directory class from the module and create an instance of the
+Directory in a variable whose name is the name of the module.
+
+In every namespace with a _q_exports and a _q_index, either add "" to
+_q_exports or make sure that _q_lookup handles "" by returning the result
+of a call to _q_index.
+
+If your code depends on the Publisher's namespace_stack attribute,
+try using quixote.util.get_directory_path() instead. If you need the
+namespace stack after the traversal, override Directory._q_traverse()
+to call get_directory_path() when the end of the path is reached, and
+record the result somewhere for later reference.
+
+If your code depends on _q_exception_handler, override the _q_traverse
+on your root namespace or on your own Directory class to catch exceptions
+and handle them the way you want. If you just want a general customization
+for exception responses, you can change or override
+Publisher.format_publish_error().
+
+If your code depended on _q_access, include the AccessControlled with
+the bases of your Directory classes as needed.
+
+Provide imports as needed to htmltext, TemplateIO, get_field,
+get_request, get_session, get_user, get_path, redirect, ?. You may
+find dulcinea/bin/unknown.py useful for identifying missing imports.
+
+Quixote 1's secure_errors configuration variable is not present in Quixote 2.
+
+Form.__init__ no longer has name or attrs keywords. If your existing
+code calls Form.__init__ with 'attrs=foo', you'll need to change it to
+'**foo'. Form instances no longer have a name attribute. If your code
+looks for form.name, you can find it with form.attrs.get('name').
+The Form.__init__ keyword parameter (and attribute) 'action_url' is now
+named 'action'.
+
+The SessionPublisher class is gone. Use the Publisher class instead.
+Also, the 'session_mgr' keyword has been renamed to 'session_manager'.
+
+
+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``.
+
+
+
+
diff --git a/pypers/europython05/Quixote-2.0/doc/web-server.txt b/pypers/europython05/Quixote-2.0/doc/web-server.txt
new file mode 100755
index 0000000..2abfe21
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/web-server.txt
@@ -0,0 +1,258 @@
+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
+
+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 favor 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 cgi driver script::
+
+ cp -p server/cgi_server.py /www/cgi-bin/demo.cgi
+
+(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.) Quixote includes support
+for mod_scgi and FastCGI.
+
+
+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/ .
+
+The quixote.server.scgi_server module is a script that
+publishes the demo quixote application via SCGI. You can use
+it for your application by importing it and calling the ``run()``
+function with arguments to run your application, on the port
+you choose. Here is an example::
+
+ #!/usr/bin/python
+ from quixote.server.scgi_server import run
+ from quixote.publish import Publisher
+ from mymodule import MyRootDirectory
+
+ def create_my_publisher():
+ return Publisher(MyRootDirectory())
+
+ run(create_my_publisher, port=3001)
+
+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>
+
+[Note: the mod_scgi module for Apache 2 requires a colon, instead of a
+space, between the host and port on the SCGIServer line.]
+
+
+SCGI through CGI
+----------------
+
+Recent releases of the scgi package include cgi2scgi.c, a small program
+that offers an extremely convenient way to take advantage of SCGI using
+Apache or any web server that supports CGI. To use it, compile the
+cgi2scgi.c and install the compiled program as usual for your
+webserver. The default SCGI port is 3000, but you can change that
+by adding ``-DPORT=3001`` (for example) to your compile command.
+
+Although this method requires a new process to be launched for each
+request, the process is small and fast, so the performance is
+acceptable for many applications.
+
+
+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 copy server/fastcgi_server.py 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 (in comparison to CGI).
+
+
diff --git a/pypers/europython05/Quixote-2.0/doc/web-services.txt b/pypers/europython05/Quixote-2.0/doc/web-services.txt
new file mode 100755
index 0000000..c89125c
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/web-services.txt
@@ -0,0 +1,169 @@
+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.directory import Directory
+ from quixote.util import xmlrpc
+ from quixote import get_request
+
+ class RPCDirectory(Directory):
+
+ _q_exports = ['rpc']
+
+ def rpc (self):
+ return xmlrpc(get_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 25695 2004-11-30 20:53:44Z dbinger $
diff --git a/pypers/europython05/Quixote-2.0/doc/widgets.txt b/pypers/europython05/Quixote-2.0/doc/widgets.txt
new file mode 100755
index 0000000..0dc3597
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/widgets.txt
@@ -0,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 $
diff --git a/pypers/europython05/Quixote-2.0/doc/win32.txt b/pypers/europython05/Quixote-2.0/doc/win32.txt
new file mode 100755
index 0000000..9204f10
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/doc/win32.txt
@@ -0,0 +1,14 @@
+Compiling Quixote c extensions for Windows using the mingw compiler:
+
+The variable build_extensions in setup.py must be set to True (instead
+of "sys.platform != win32"). Then, Quixote can be installed through
+
+ python setup.py build_ext --compiler=mingw32 install
+
+The mingw compiler can be downloaded from
+http://www.bloodshed.net/dev/devcpp.html. Probably you need to list
+the bin directory (C:\Program files\Dev-Cpp\bin) in the PATH.
+
+To permit gcc to link against Python library, you also need a
+libpython2x.a file. Instructions for its creation are provided at
+http://sebsauvage.net/python/mingw.html.
diff --git a/pypers/europython05/Quixote-2.0/errors.py b/pypers/europython05/Quixote-2.0/errors.py
new file mode 100755
index 0000000..f33dc41
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/errors.py
@@ -0,0 +1,141 @@
+"""quixote.errors
+$HeadURL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/errors.py $
+$Id: errors.py 26378 2005-03-17 14:04:45Z dbinger $
+
+Exception classes used by Quixote
+"""
+from quixote.html import htmltext, htmlescape
+
+__revision__ = "$Id: errors.py 26378 2005-03-17 14:04:45Z dbinger $"
+
+
+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 DISPLAY_EXCEPTIONS is
+ true; Quixote uses this to give you more detail about why the error
+ occurred. You might want to use it for similar, application-specific
+ information. (DISPLAY_EXCEPTIONS should always be false 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 DISPLAY_EXCEPTIONS is false
+
+ def __str__(self):
+ return self.private_msg or self.public_msg or "???"
+
+ def format(self):
+ msg = htmlescape(self.title)
+ 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):
+ msg = htmlescape(self.title) + ": " + self.path
+ 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.")
+
+
+
+def format_publish_error(exc):
+ """(exc : PublishError) -> string
+
+ Format a PublishError exception as a web page.
+ """
+ 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())
diff --git a/pypers/europython05/Quixote-2.0/form/__init__.py b/pypers/europython05/Quixote-2.0/form/__init__.py
new file mode 100755
index 0000000..5685c8a
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/form/__init__.py
@@ -0,0 +1,18 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/form/__init__.py $
+$Id: __init__.py 26469 2005-04-05 10:26:03Z dbinger $
+
+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.form.form import Form, FormTokenWidget
+from quixote.form.widget import Widget, StringWidget, FileWidget, \
+ PasswordWidget, TextWidget, CheckboxWidget, RadiobuttonsWidget, \
+ SingleSelectWidget, SelectWidget, OptionSelectWidget, \
+ MultipleSelectWidget, SubmitWidget, HiddenWidget, \
+ FloatWidget, IntWidget, subname, WidgetValueError, CompositeWidget, \
+ WidgetList, WidgetDict
diff --git a/pypers/europython05/Quixote-2.0/form/compatibility.py b/pypers/europython05/Quixote-2.0/form/compatibility.py
new file mode 100755
index 0000000..4ad0389
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/form/compatibility.py
@@ -0,0 +1,101 @@
+'''$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/form/compatibility.py $
+$Id: compatibility.py 26061 2005-02-11 02:48:16Z dbinger $
+
+A Form subclass that provides close to the same API as the old form
+class (useful for transitioning existing forms).
+'''
+
+from quixote import get_request, get_path, redirect
+from quixote.form 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, action_url=None, *args, **kwargs):
+ _Form.__init__(self, action=action_url, *args, **kwargs)
+ self.cancel_url = None
+ self.action_url = self.action
+
+ def add_widget(self, widget_class, name, value=None,
+ title=None, hint=None, required=False, **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):
+ action_url = url_quote(get_path())
+ query = get_request().get_query()
+ if query:
+ action_url += "?" + query
+ return action_url
+
+ def render(self, action_url=None):
+ if action_url:
+ self.action_url = action_url
+ return _Form.render(self)
+
+ def process(self):
+ values = {}
+ request = get_request()
+ for name, widget in self._names.items():
+ values[name] = widget.parse()
+ return values
+
+ def action(self, submit, values):
+ raise NotImplementedError, "sub-classes must implement 'action()'"
+
+ def handle(self):
+ """handle() -> 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.
+ """
+ request = get_request()
+ if not self.is_submitted():
+ return self.render(self.action_url)
+ submit = self.get_submit()
+ if submit == "cancel":
+ return redirect(self.cancel_url)
+ values = self.process()
+ 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(self.action_url)
+
+ if self.has_errors():
+ return self.render(self.action_url)
+ else:
+ return self.action(submit, values)
diff --git a/pypers/europython05/Quixote-2.0/form/css.py b/pypers/europython05/Quixote-2.0/form/css.py
new file mode 100755
index 0000000..c2849f8
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/form/css.py
@@ -0,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;
+}
+"""
diff --git a/pypers/europython05/Quixote-2.0/form/form.py b/pypers/europython05/Quixote-2.0/form/form.py
new file mode 100755
index 0000000..fd91fd4
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/form/form.py
@@ -0,0 +1,365 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/form/form.py $
+$Id: form.py 26200 2005-02-18 22:58:30Z dbinger $
+
+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 htmltag, htmltext, TemplateIO
+from quixote.form.widget import HiddenWidget, StringWidget, TextWidget, \
+ CheckboxWidget, SingleSelectWidget, RadiobuttonsWidget, \
+ MultipleSelectWidget, ResetWidget, SubmitWidget, FloatWidget, \
+ IntWidget, PasswordWidget
+
+
+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' # this error does not get displayed
+ 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(object):
+ """
+ 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')
+
+ TOKEN_NOTICE = 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>')
+
+ ERROR_NOTICE = htmltext('<div class="errornotice">'
+ 'There were errors processing your form. '
+ 'See below for details.'
+ '</div>')
+
+ def __init__(self,
+ method="post",
+ action=None,
+ enctype=None,
+ use_tokens=True,
+ **attrs):
+
+ if method not in ("post", "get"):
+ raise ValueError("Form method must be 'post' or 'get', "
+ "not %r" % method)
+ self.method = method
+ self.action = action or self._get_default_action()
+ if 'class' not in attrs:
+ 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(self):
+ query = get_request().get_query()
+ if query:
+ return "?" + query
+ else:
+ return ""
+
+ # -- 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_password(self, name, value=None, **kwargs):
+ self.add(PasswordWidget, 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', method=self.method,
+ enctype=self.enctype, action=self.action,
+ **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 self.TOKEN_NOTICE
+ else:
+ return self.ERROR_NOTICE
+
+ 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()
diff --git a/pypers/europython05/Quixote-2.0/form/widget.py b/pypers/europython05/Quixote-2.0/form/widget.py
new file mode 100755
index 0000000..b989766
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/form/widget.py
@@ -0,0 +1,951 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/form/widget.py $
+$Id: widget.py 26514 2005-04-07 13:29:50Z dbinger $
+
+Provides the basic web widget classes: Widget itself, plus StringWidget,
+TextWidget, CheckboxWidget, etc.
+"""
+
+import struct
+from quixote import get_request
+from quixote.html import htmltext, htmlescape, htmltag, TemplateIO, stringify
+from quixote.http_request import Upload
+
+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 stringify(self.msg)
+
+
+
+class Widget(object):
+ """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.
+ """
+
+ REQUIRED_ERROR = 'required'
+
+ 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(stringify(exc))
+ if (self.required and self.value is None and
+ not self.has_error()):
+ self.set_error(self.REQUIRED_ERROR)
+ 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 isinstance(value, basestring) 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()).
+ """
+
+ SELECTION_ERROR = "invalid value selected"
+
+ 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 isinstance(first, tuple):
+ 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(stringify(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 (stringify(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 = self.SELECTION_ERROR
+ 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"
+ MULTIPLE_SELECTION_ERROR = "cannot select multiple values"
+
+ def _parse(self, request):
+ parsed_key = request.form.get(self.name)
+ if parsed_key:
+ if isinstance(parsed_key, list):
+ self.error = self.MULTIPLE_SELECTION_ERROR
+ 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 isinstance(value, (list, tuple)):
+ 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 isinstance(parsed_keys, list):
+ 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
+
+ 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_OBJECT(self.value)
+ except ValueError:
+ self.error = self.TYPE_ERROR
+
+
+class FloatWidget(NumberWidget):
+ """
+ Instance attributes:
+ value : float
+ """
+ TYPE_OBJECT = float
+ TYPE_ERROR = "must be a number"
+
+
+class IntWidget(NumberWidget):
+ """
+ Instance attributes:
+ value : int
+ """
+ TYPE_OBJECT = 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 isinstance(parsed_key, list):
+ self.error = self.MULTIPLE_SELECTION_ERROR
+ 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
+ if self.attrs.get('disabled') and 'disabled' not in kwargs:
+ kwargs['disabled'] = True
+ 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={},
+ add_element_label="Add 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(add_element_label) in (str, htmltext), (
+ "value '%s'add_element_label not a string: "
+ "got %r" % (name, add_element_label))
+
+ 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_element_label)
+ 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 dict can have its own
+ error. You can also set an error on the WidgetDict 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={},
+ add_element_label='Add 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(add_element_label) in (str, htmltext), (
+ 'value %r element_name not a string: '
+ 'got %r' % (name, add_element_label))
+
+ 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_element_label)
+ 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()
diff --git a/pypers/europython05/Quixote-2.0/form1/__init__.py b/pypers/europython05/Quixote-2.0/form1/__init__.py
new file mode 100755
index 0000000..2989d6d
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/form1/__init__.py
@@ -0,0 +1,34 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/form1/__init__.py $
+$Id: __init__.py 25664 2004-11-22 20:35:07Z 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 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.
+"""
+
+from quixote.form1.form import Form, register_widget_class, FormTokenWidget
+from quixote.form1.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)
diff --git a/pypers/europython05/Quixote-2.0/form1/form.py b/pypers/europython05/Quixote-2.0/form1/form.py
new file mode 100755
index 0000000..c67598c
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/form1/form.py
@@ -0,0 +1,534 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/form1/form.py $
+$Id: form.py 25664 2004-11-22 20:35:07Z nascheme $
+
+Provides the Form class and bureaucracy for registering widget classes.
+(The standard widget classes are registered automatically.)
+"""
+
+from types import StringType
+from quixote import get_session, get_publisher, redirect
+from quixote.html import url_quote, htmltag, htmltext, nl2br, TemplateIO
+from quixote.form1.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 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)
diff --git a/pypers/europython05/Quixote-2.0/form1/widget.py b/pypers/europython05/Quixote-2.0/form1/widget.py
new file mode 100755
index 0000000..1ddc229
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/form1/widget.py
@@ -0,0 +1,842 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/form1/widget.py $
+$Id: widget.py 25664 2004-11-22 20:35:07Z nascheme $
+
+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
+from quixote.http_request 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 "checked" 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 = "multiple"
+ 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 = "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.
+ """
+
+ 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 = "checked"
+ 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)
diff --git a/pypers/europython05/Quixote-2.0/html/__init__.py b/pypers/europython05/Quixote-2.0/html/__init__.py
new file mode 100755
index 0000000..563e78b
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/html/__init__.py
@@ -0,0 +1,106 @@
+"""Various functions for dealing with HTML.
+$HeadURL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/html/__init__.py $
+$Id: __init__.py 26357 2005-03-16 14:56:23Z dbinger $
+
+These functions are fairly simple but it is critical that they be
+used correctly. Many security problems are caused by escaping 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.
+
+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 might need to have
+HTML special characters escaped. As an example of incorrect usage:
+
+ url = 'http://example.com/?a=1&copy=0' # INCORRECT
+ url = 'http://example.com/?a=1&amp;copy=0' # CORRECT
+ ...
+ '<a href="%s">do something</a>' % url
+
+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.
+"""
+
+import urllib
+
+try:
+ # faster C implementation
+ from quixote.html._c_htmltext import htmltext, htmlescape, \
+ stringify, TemplateIO
+except ImportError:
+ from quixote.html._py_htmltext import htmltext, htmlescape, \
+ stringify, TemplateIO
+
+ValuelessAttr = object() # magic singleton object
+
+def htmltag(tag, xml_end=False, 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, htmlescape(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 url_with_query(path, **attrs):
+ result = htmltext(url_quote(path))
+ if attrs:
+ result += "?" + "&".join([url_quote(key) + "=" + url_quote(value)
+ for key, value in attrs.items()])
+ return result
+
+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
+ return urllib.quote(stringify(value))
diff --git a/pypers/europython05/Quixote-2.0/html/_c_htmltext.c b/pypers/europython05/Quixote-2.0/html/_c_htmltext.c
new file mode 100755
index 0000000..1c6581a
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/html/_c_htmltext.c
@@ -0,0 +1,1019 @@
+/* htmltext type and the htmlescape function */
+
+#include "Python.h"
+#include "structmember.h"
+
+typedef struct {
+ PyObject_HEAD
+ PyObject *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 *data; /* PyList_Object */
+ int html;
+} 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 int
+string_check(PyObject *v)
+{
+ return PyUnicode_Check(v) || PyString_Check(v);
+}
+
+static PyObject *
+stringify(PyObject *obj)
+{
+ static PyObject *unicodestr = NULL;
+ PyObject *res, *func;
+ if (string_check(obj)) {
+ Py_INCREF(obj);
+ return obj;
+ }
+ if (unicodestr == NULL) {
+ unicodestr = PyString_InternFromString("__unicode__");
+ if (unicodestr == NULL)
+ return NULL;
+ }
+ func = PyObject_GetAttr(obj, unicodestr);
+ if (func != NULL) {
+ res = PyEval_CallObject(func, (PyObject *)NULL);
+ Py_DECREF(func);
+ }
+ else {
+ PyErr_Clear();
+ if (obj->ob_type->tp_str != NULL)
+ res = (*obj->ob_type->tp_str)(obj);
+ else
+ res = PyObject_Repr(obj);
+ }
+ if (res == NULL)
+ return NULL;
+ if (!string_check(res)) {
+ Py_DECREF(res);
+ return type_error("string object required");
+ }
+ return res;
+}
+
+static PyObject *
+escape_string(PyObject *obj)
+{
+ char *s;
+ PyObject *newobj;
+ size_t i, j, extra_space, size, new_size;
+ assert (PyString_Check(obj));
+ size = PyString_GET_SIZE(obj);
+ extra_space = 0;
+ for (i=0; i < size; i++) {
+ switch (PyString_AS_STRING(obj)[i]) {
+ case '&':
+ extra_space += 4;
+ break;
+ case '<':
+ case '>':
+ extra_space += 3;
+ break;
+ case '"':
+ extra_space += 5;
+ break;
+ }
+ }
+ if (extra_space == 0) {
+ Py_INCREF(obj);
+ return (PyObject *)obj;
+ }
+ new_size = size + extra_space;
+ newobj = PyString_FromStringAndSize(NULL, new_size);
+ if (newobj == NULL)
+ return NULL;
+ s = PyString_AS_STRING(newobj);
+ for (i=0, j=0; i < size; i++) {
+ switch (PyString_AS_STRING(obj)[i]) {
+ case '&':
+ s[j++] = '&';
+ s[j++] = 'a';
+ s[j++] = 'm';
+ s[j++] = 'p';
+ s[j++] = ';';
+ break;
+ case '<':
+ s[j++] = '&';
+ s[j++] = 'l';
+ s[j++] = 't';
+ s[j++] = ';';
+ break;
+ case '>':
+ s[j++] = '&';
+ s[j++] = 'g';
+ s[j++] = 't';
+ s[j++] = ';';
+ break;
+ case '"':
+ s[j++] = '&';
+ s[j++] = 'q';
+ s[j++] = 'u';
+ s[j++] = 'o';
+ s[j++] = 't';
+ s[j++] = ';';
+ break;
+ default:
+ s[j++] = PyString_AS_STRING(obj)[i];
+ break;
+ }
+ }
+ assert (j == new_size);
+ return (PyObject *)newobj;
+}
+
+static PyObject *
+escape_unicode(PyObject *obj)
+{
+ Py_UNICODE *u;
+ PyObject *newobj;
+ size_t i, j, extra_space, size, new_size;
+ assert (PyUnicode_Check(obj));
+ size = PyUnicode_GET_SIZE(obj);
+ extra_space = 0;
+ for (i=0; i < size; i++) {
+ switch (PyUnicode_AS_UNICODE(obj)[i]) {
+ case '&':
+ extra_space += 4;
+ break;
+ case '<':
+ case '>':
+ extra_space += 3;
+ break;
+ case '"':
+ extra_space += 5;
+ break;
+ }
+ }
+ if (extra_space == 0) {
+ Py_INCREF(obj);
+ return (PyObject *)obj;
+ }
+ new_size = size + extra_space;
+ newobj = PyUnicode_FromUnicode(NULL, new_size);
+ if (newobj == NULL) {
+ return NULL;
+ }
+ u = PyUnicode_AS_UNICODE(newobj);
+ for (i=0, j=0; i < size; i++) {
+ switch (PyUnicode_AS_UNICODE(obj)[i]) {
+ case '&':
+ u[j++] = '&';
+ u[j++] = 'a';
+ u[j++] = 'm';
+ u[j++] = 'p';
+ u[j++] = ';';
+ break;
+ case '<':
+ u[j++] = '&';
+ u[j++] = 'l';
+ u[j++] = 't';
+ u[j++] = ';';
+ break;
+ case '>':
+ u[j++] = '&';
+ u[j++] = 'g';
+ u[j++] = 't';
+ u[j++] = ';';
+ break;
+ case '"':
+ u[j++] = '&';
+ u[j++] = 'q';
+ u[j++] = 'u';
+ u[j++] = 'o';
+ u[j++] = 't';
+ u[j++] = ';';
+ break;
+ default:
+ u[j++] = PyUnicode_AS_UNICODE(obj)[i];
+ break;
+ }
+ }
+ assert (j == new_size);
+ return (PyObject *)newobj;
+}
+
+static PyObject *
+escape(PyObject *obj)
+{
+ if (PyString_Check(obj)) {
+ return escape_string(obj);
+ }
+ else if (PyUnicode_Check(obj)) {
+ return escape_unicode(obj);
+ }
+ else {
+ return type_error("string object required");
+ }
+}
+
+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(s);
+ Py_DECREF(s);
+ return qs;
+}
+
+static PyObject *
+quote_wrapper_str(QuoteWrapperObject *self)
+{
+ PyObject *qs;
+ PyObject *s = stringify(self->obj);
+ if (s == NULL)
+ return NULL;
+ qs = escape(s);
+ Py_DECREF(s);
+ return qs;
+}
+
+static PyObject *
+quote_wrapper_subscript(QuoteWrapperObject *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 steals a reference */
+ PyObject *self;
+ if (s == NULL)
+ return NULL;
+ assert (string_check(s));
+ self = PyType_GenericAlloc(&htmltext_Type, 0);
+ if (self == NULL) {
+ Py_DECREF(s);
+ return NULL;
+ }
+ ((htmltextObject *)self)->s = 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 = stringify(s);
+ if (s == NULL)
+ return NULL;
+ self = (htmltextObject *)type->tp_alloc(type, 0);
+ if (self == NULL) {
+ Py_DECREF(s);
+ return NULL;
+ }
+ self->s = 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)
+{
+ if (htmltextObject_Check(a)) {
+ a = htmltext_STR(a);
+ }
+ if (htmltextObject_Check(b)) {
+ b = htmltext_STR(b);
+ }
+ return PyObject_RichCompare(a, b, op);
+}
+
+static long
+htmltext_length(htmltextObject *self)
+{
+ return PyObject_Size(htmltext_STR(self));
+}
+
+
+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 is_unicode;
+ PyObject *rv, *wargs;
+ if (PyUnicode_Check(self->s)) {
+ is_unicode = 1;
+ }
+ else {
+ is_unicode = 0;
+ assert (PyString_Check(self->s));
+ }
+ 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;
+ }
+ if (is_unicode)
+ rv = PyUnicode_Format(self->s, wargs);
+ else
+ rv = PyString_Format(self->s, wargs);
+ Py_DECREF(wargs);
+ return htmltext_from_string(rv);
+}
+
+static PyObject *
+htmltext_add(PyObject *v, PyObject *w)
+{
+ PyObject *qv, *qw, *rv;
+ if (htmltextObject_Check(v) && htmltextObject_Check(w)) {
+ qv = htmltext_STR(v);
+ qw = htmltext_STR(w);
+ Py_INCREF(qv);
+ Py_INCREF(qw);
+ }
+ else if (string_check(w)) {
+ assert (htmltextObject_Check(v));
+ qv = htmltext_STR(v);
+ qw = escape(w);
+ if (qw == NULL)
+ return NULL;
+ Py_INCREF(qv);
+ }
+ else if (string_check(v)) {
+ assert (htmltextObject_Check(w));
+ qv = escape(v);
+ if (qv == NULL)
+ return NULL;
+ qw = htmltext_STR(w);
+ Py_INCREF(qw);
+ }
+ else {
+ Py_INCREF(Py_NotImplemented);
+ return Py_NotImplemented;
+ }
+ if (PyString_Check(qv)) {
+ PyString_ConcatAndDel(&qv, qw);
+ rv = qv;
+ }
+ else {
+ assert (PyUnicode_Check(qv));
+ rv = PyUnicode_Concat(qv, qw);
+ Py_DECREF(qv);
+ Py_DECREF(qw);
+ }
+ return htmltext_from_string(rv);
+}
+
+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)
+{
+ int i;
+ PyObject *quoted_args, *rv;
+
+ quoted_args = PySequence_List(args);
+ if (quoted_args == NULL)
+ return NULL;
+ for (i=0; i < PyList_Size(quoted_args); i++) {
+ PyObject *value, *qvalue;
+ value = PyList_GET_ITEM(quoted_args, i);
+ if (value == NULL) {
+ goto error;
+ }
+ if (htmltextObject_Check(value)) {
+ qvalue = htmltext_STR(value);
+ Py_INCREF(qvalue);
+ }
+ else {
+ if (!string_check(value)) {
+ type_error("join requires a list of strings");
+ goto error;
+ }
+ qvalue = escape(value);
+ if (qvalue == NULL)
+ goto error;
+ }
+ if (PyList_SetItem(quoted_args, i, qvalue) < 0) {
+ goto error;
+ }
+ }
+ if (PyUnicode_Check(htmltext_STR(self))) {
+ rv = PyUnicode_Join(htmltext_STR(self), quoted_args);
+ }
+ else {
+ rv = _PyString_Join(htmltext_STR(self), quoted_args);
+ }
+ Py_DECREF(quoted_args);
+ return htmltext_from_string(rv);
+
+error:
+ Py_DECREF(quoted_args);
+ return NULL;
+}
+
+static PyObject *
+quote_arg(PyObject *s)
+{
+ PyObject *ss;
+ if (string_check(s)) {
+ ss = escape(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->data = PyList_New(0);
+ if (self->data == NULL) {
+ Py_DECREF(self);
+ return NULL;
+ }
+ self->html = html != 0;
+ return (PyObject *)self;
+}
+
+static void
+template_io_dealloc(TemplateIO_Object *self)
+{
+ Py_DECREF(self->data);
+ self->ob_type->tp_free((PyObject *)self);
+}
+
+static PyObject *
+template_io_str(TemplateIO_Object *self)
+{
+ static PyObject *empty = NULL;
+ if (empty == NULL) {
+ empty = PyString_FromStringAndSize(NULL, 0);
+ if (empty == NULL)
+ return NULL;
+ }
+ return _PyString_Join(empty, self->data);
+}
+
+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_iadd(TemplateIO_Object *self, PyObject *other)
+{
+ 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 (htmltextObject_Check(other)) {
+ s = htmltext_STR(other);
+ Py_INCREF(s);
+ }
+ else {
+ if (self->html) {
+ PyObject *ss = stringify(other);
+ if (ss == NULL)
+ return NULL;
+ s = escape(ss);
+ Py_DECREF(ss);
+ }
+ else {
+ s = stringify(other);
+ }
+ if (s == NULL)
+ return NULL;
+ }
+ if (PyList_Append(self->data, s) != 0)
+ return NULL;
+ Py_DECREF(s);
+ Py_INCREF(self);
+ return (PyObject *)self;
+}
+
+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*/
+ 0, /*tp_getattro set to PyObject_GenericGetAttr by module init*/
+ 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*/
+ 0, /*tp_alloc set to PyType_GenericAlloc by module init*/
+ htmltext_new, /*tp_new*/
+ 0, /*tp_free set to _PyObject_Del by module init*/
+ 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 PyMappingMethods quote_wrapper_as_mapping = {
+ 0, /*mp_length*/
+ (binaryfunc)quote_wrapper_subscript, /*mp_subscript*/
+ 0, /*mp_ass_subscript*/
+};
+
+
+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*/
+ &quote_wrapper_as_mapping,/*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ (unaryfunc)quote_wrapper_str, /*tp_str*/
+};
+
+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*/
+ 0, /*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*/
+ 0, /*tp_getattro set to PyObject_GenericGetAttr by module init*/
+ 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*/
+ 0, /*tp_alloc set to PyType_GenericAlloc by module init*/
+ template_io_new, /*tp_new*/
+ 0, /*tp_free set to _PyObject_Del by module init*/
+ 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 = stringify(o);
+ if (s == NULL)
+ return NULL;
+ rv = escape(s);
+ Py_DECREF(s);
+ return htmltext_from_string(rv);
+ }
+}
+
+static PyObject *
+py_escape_string(PyObject *self, PyObject *o)
+{
+ return escape(o);
+}
+
+static PyObject *
+py_stringify(PyObject *self, PyObject *o)
+{
+ return stringify(o);
+}
+
+/* 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},
+ {"stringify", (PyCFunction)py_stringify, 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;
+
+ /* Fix not constant element initialization */
+ htmltext_Type.tp_getattro = PyObject_GenericGetAttr;
+ htmltext_Type.tp_alloc = PyType_GenericAlloc;
+ htmltext_Type.tp_free = _PyObject_Del;
+ TemplateIO_Type.tp_getattro = PyObject_GenericGetAttr;
+ TemplateIO_Type.tp_alloc = PyType_GenericAlloc;
+ TemplateIO_Type.tp_free = _PyObject_Del;
+
+ /* 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);
+}
diff --git a/pypers/europython05/Quixote-2.0/html/_py_htmltext.py b/pypers/europython05/Quixote-2.0/html/_py_htmltext.py
new file mode 100755
index 0000000..798944d
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/html/_py_htmltext.py
@@ -0,0 +1,213 @@
+"""Python implementation of the htmltext type, the htmlescape function and
+TemplateIO.
+"""
+
+#$HeadURL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/html/_py_htmltext.py $
+#$Id: _py_htmltext.py 26357 2005-03-16 14:56:23Z dbinger $
+
+def _escape_string(s):
+ if not isinstance(s, basestring):
+ raise TypeError, 'string object required'
+ s = s.replace("&", "&amp;")
+ s = s.replace("<", "&lt;")
+ s = s.replace(">", "&gt;")
+ s = s.replace('"', "&quot;")
+ return s
+
+def stringify(obj):
+ """Return 'obj' as a string or unicode object. Tries to prevent
+ turning strings into unicode objects.
+ """
+ tp = type(obj)
+ if issubclass(tp, basestring):
+ return obj
+ elif hasattr(tp, '__unicode__'):
+ s = tp.__unicode__(obj)
+ if not isinstance(s, basestring):
+ raise TypeError, '__unicode__ did not return a string'
+ return s
+ elif hasattr(tp, '__str__'):
+ s = tp.__str__(obj)
+ if not isinstance(s, basestring):
+ raise TypeError, '__str__ did not return a string'
+ return s
+ else:
+ return str(obj)
+
+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 = stringify(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):
+ if isinstance(args, tuple):
+ return htmltext(self.s % tuple(map(_wraparg, args)))
+ else:
+ return htmltext(self.s % _wraparg(args))
+
+ def __add__(self, other):
+ if isinstance(other, basestring):
+ return htmltext(self.s + _escape_string(other))
+ elif isinstance(other, htmltext):
+ return htmltext(self.s + other.s)
+ else:
+ return NotImplemented
+
+ def __radd__(self, other):
+ if isinstance(other, basestring):
+ return htmltext(_escape_string(other) + self.s)
+ else:
+ return NotImplemented
+
+ def __mul__(self, n):
+ return htmltext(self.s * n)
+
+ def join(self, items):
+ quoted_items = []
+ for item in items:
+ if isinstance(item, htmltext):
+ quoted_items.append(stringify(item))
+ elif isinstance(item, basestring):
+ quoted_items.append(_escape_string(item))
+ else:
+ raise TypeError(
+ 'join() requires string arguments (got %r)' % item)
+ return htmltext(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 htmltext(self.s.replace(old, new))
+
+ def lower(self):
+ return htmltext(self.s.lower())
+
+ def upper(self):
+ return htmltext(self.s.upper())
+
+ def capitalize(self):
+ return htmltext(self.s.capitalize())
+
+class _QuoteWrapper(object):
+ # helper for htmltext class __mod__
+
+ __slots__ = ['value']
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return _escape_string(stringify(self.value))
+
+ def __repr__(self):
+ return _escape_string(`self.value`)
+
+ def __getitem__(self, key):
+ return _wraparg(self.value[key])
+
+
+def _wraparg(arg):
+ if (isinstance(arg, htmltext) or
+ isinstance(arg, int) or
+ isinstance(arg, long) or
+ isinstance(arg, float)):
+ # ints, longs, floats, and htmltext are okay
+ return arg
+ else:
+ # everything is gets wrapped
+ return _QuoteWrapper(arg)
+
+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 isinstance(s, htmltext):
+ return s
+ else:
+ s = stringify(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=False):
+ 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 stringify(self.getvalue())
+
+ def getvalue(self):
+ if self.html:
+ return htmltext('').join(map(htmlescape, self.data))
+ else:
+ return ''.join(map(stringify, self.data))
diff --git a/pypers/europython05/Quixote-2.0/html/test/utest_html.py b/pypers/europython05/Quixote-2.0/html/test/utest_html.py
new file mode 100755
index 0000000..fa5450f
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/html/test/utest_html.py
@@ -0,0 +1,368 @@
+import sys
+from sancho.utest import UTest
+from quixote.html import _py_htmltext
+from quixote.html import href, url_with_query, url_quote, nl2br, htmltag
+
+markupchars = '<>&"'
+quotedchars = '&lt;&gt;&amp;&quot;'
+if sys.hexversion >= 0x20400a2:
+ unicodechars = u'\u1234'
+else:
+ unicodechars = 'x' # lie, Python <= 2.3 is broken
+
+class Wrapper:
+ def __init__(self, s):
+ self.s = s
+
+ def __repr__(self):
+ return self.s
+
+ def __str__(self):
+ return self.s
+
+class BrokenError(Exception):
+ pass
+
+class Broken:
+ def __str__(self):
+ raise BrokenError, 'eieee'
+
+ def __repr__(self):
+ raise BrokenError, 'eieee'
+
+htmltext = escape = htmlescape = TemplateIO = stringify = None
+
+class HTMLTest (UTest):
+
+ def check_href(self):
+ assert str(href('/foo/bar', 'bar')) == '<a href="/foo/bar">bar</a>'
+
+ def check_url_with_query(self):
+ assert str(url_with_query('/f/b', a='1')) == '/f/b?a=1'
+ assert str(url_with_query(
+ '/f/b', a='1', b='3 4')) == '/f/b?a=1&amp;b=3%204'
+
+ def check_nl2br(self):
+ assert str(nl2br('a\nb\nc')) == 'a<br />\nb<br />\nc'
+
+ def check_url_quote(self):
+ assert url_quote('abc') == 'abc'
+ assert url_quote('a b c') == 'a%20b%20c'
+ assert url_quote(None, fallback='abc') == 'abc'
+
+
+class HTMLTextTest (UTest):
+
+ def _pre(self):
+ global htmltext, escape, htmlescape, TemplateIO, stringify
+ htmltext = _py_htmltext.htmltext
+ escape = _py_htmltext._escape_string
+ stringify = _py_htmltext.stringify
+ htmlescape = _py_htmltext.htmlescape
+ TemplateIO = _py_htmltext.TemplateIO
+
+ def _post(self):
+ global htmltext, escape, htmlescape, TemplateIO, stringify
+ htmltext = escape = htmlescape = TemplateIO = stringify = None
+
+ def _check_init(self):
+ assert str(htmltext('foo')) == 'foo'
+ assert str(htmltext(markupchars)) == markupchars
+ assert unicode(htmltext(unicodechars)) == unicodechars
+ assert str(htmltext(unicode(markupchars))) == markupchars
+ assert str(htmltext(None)) == 'None'
+ assert str(htmltext(1)) == '1'
+ try:
+ htmltext(Broken())
+ assert 0
+ except BrokenError: pass
+
+ def check_stringify(self):
+ assert stringify(markupchars) is markupchars
+ assert stringify(unicodechars) is unicodechars
+ assert stringify(Wrapper(unicodechars)) is unicodechars
+ assert stringify(Wrapper(markupchars)) is markupchars
+ assert stringify(Wrapper) == str(Wrapper)
+ assert stringify(None) == str(None)
+
+ def check_escape(self):
+ assert htmlescape(markupchars) == quotedchars
+ assert isinstance(htmlescape(markupchars), htmltext)
+ assert escape(markupchars) == quotedchars
+ assert escape(unicodechars) == unicodechars
+ assert escape(unicode(markupchars)) == quotedchars
+ assert isinstance(escape(markupchars), basestring)
+ 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(u'\u1234') == u'\u1234'
+ 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)
+ assert markupchars + htmltext(u'') == quotedchars
+ try:
+ s + 1
+ assert 0
+ except TypeError: pass
+ try:
+ 1 + s
+ assert 0
+ except TypeError: pass
+ # mixing unicode and str
+ assert repr(htmltext('a') + htmltext('b')) == "<htmltext 'ab'>"
+ assert repr(htmltext(u'a') + htmltext('b')) == "<htmltext u'ab'>"
+ assert repr(htmltext('a') + htmltext(u'b')) == "<htmltext u'ab'>"
+
+ 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')
+ u_fmt = htmltext(u'%s')
+ assert s_fmt % 'foo' == "foo"
+ assert u_fmt % 'foo' == u"foo"
+ assert isinstance(s_fmt % 'foo', htmltext)
+ assert isinstance(u_fmt % 'foo', htmltext)
+ assert s_fmt % markupchars == quotedchars
+ assert u_fmt % markupchars == quotedchars
+ assert s_fmt % None == "None"
+ assert u_fmt % None == "None"
+ assert u_fmt % unicodechars == unicodechars
+ 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 BrokenError: pass
+ try:
+ htmltext('%r') % Broken()
+ assert 0
+ except BrokenError: pass
+ try:
+ s_fmt % (1, 2)
+ assert 0
+ except TypeError: pass
+ assert htmltext('%d') % 12300000000000000000L == "12300000000000000000"
+
+ def check_dict_format(self):
+ args = {'a': 'foo&', 'b': htmltext('bar&')}
+ result = "foo&amp; 'foo&amp;' bar&"
+ assert htmltext('%(a)s %(a)r %(b)s') % args == result
+ 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
+ assert htmltext('') % {} == ''
+ assert htmltext('%%') % {} == '%'
+
+ 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)
+ assert htmltext(u' ').join([unicodechars]) == unicodechars
+ assert htmltext(u' ').join(['']) == u''
+ 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)
+
+class TemplateTest (UTest):
+
+ def _pre(self):
+ global TemplateIO
+ TemplateIO = _py_htmltext.TemplateIO
+
+ def _post(self):
+ global TemplateIO
+ TemplateIO = None
+
+ def check_init(self):
+ TemplateIO()
+ TemplateIO(html=True)
+ TemplateIO(html=False)
+
+ def check_text_iadd(self):
+ t = TemplateIO()
+ assert t.getvalue() == ''
+ t += "abcd"
+ assert t.getvalue() == 'abcd'
+ t += None
+ assert t.getvalue() == 'abcd'
+ t += 123
+ assert t.getvalue() == 'abcd123'
+ t += u'\u1234'
+ assert t.getvalue() == u'abcd123\u1234'
+ try:
+ t += Broken(); t.getvalue()
+ assert 0
+ except BrokenError: pass
+
+ def check_html_iadd(self):
+ t = TemplateIO(html=1)
+ assert t.getvalue() == ''
+ t += "abcd"
+ assert t.getvalue() == 'abcd'
+ t += None
+ assert t.getvalue() == 'abcd'
+ t += 123
+ assert t.getvalue() == 'abcd123'
+ try:
+ t += Broken(); t.getvalue()
+ assert 0
+ except BrokenError: pass
+ t = TemplateIO(html=1)
+ t += markupchars
+ assert t.getvalue() == quotedchars
+
+ def check_repr(self):
+ t = TemplateIO()
+ t += "abcd"
+ assert "TemplateIO" in repr(t)
+
+ def check_str(self):
+ t = TemplateIO()
+ t += "abcd"
+ assert str(t) == "abcd"
+
+
+
+try:
+ from quixote.html 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, stringify
+ htmltext = _c_htmltext.htmltext
+ escape = _c_htmltext._escape_string
+ stringify = _py_htmltext.stringify
+ htmlescape = _c_htmltext.htmlescape
+
+ class CHTMLTextTest(HTMLTextTest):
+ def _pre(self):
+ global htmltext, escape, htmlescape, stringify
+ htmltext = _c_htmltext.htmltext
+ escape = _c_htmltext._escape_string
+ stringify = _py_htmltext.stringify
+ htmlescape = _c_htmltext.htmlescape
+
+ class CTemplateTest(TemplateTest):
+ def _pre(self):
+ global TemplateIO
+ TemplateIO = _c_htmltext.TemplateIO
+
+
+if __name__ == "__main__":
+ HTMLTest()
+ HTMLTextTest()
+ TemplateTest()
+ if _c_htmltext:
+ CHTMLTest()
+ CHTMLTextTest()
+ CTemplateTest()
diff --git a/pypers/europython05/Quixote-2.0/http_request.py b/pypers/europython05/Quixote-2.0/http_request.py
new file mode 100755
index 0000000..6a9602d
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/http_request.py
@@ -0,0 +1,759 @@
+"""quixote.http_request
+$HeadURL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/http_request.py $
+$Id: http_request.py 26337 2005-03-11 17:06:05Z dbinger $
+
+Provides the HTTPRequest class and related code for parsing HTTP
+requests, such as the Upload class.
+"""
+
+import re
+import string
+import tempfile
+import urllib
+import rfc822
+from cStringIO import StringIO
+
+from quixote.http_response import HTTPResponse
+from quixote.errors import RequestError
+
+
+# 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"(\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"[\w!#$%&'*+.^`|~-]+"
+_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
+
+def _decode_string(s, charset):
+ if charset == 'iso-8859-1':
+ return s
+ try:
+ return s.decode(charset)
+ except LookupError:
+ raise RequestError('unknown charset %r' % charset)
+ except UnicodeDecodeError:
+ raise RequestError('invalid %r encoded string' % charset)
+
+def parse_header(line):
+ """Parse a Content-type like header.
+
+ Return the main content-type and a dictionary of options.
+
+ """
+ plist = map(lambda x: x.strip(), line.split(';'))
+ key = plist.pop(0).lower()
+ pdict = {}
+ for p in plist:
+ i = p.find('=')
+ if i >= 0:
+ name = p[:i].strip().lower()
+ value = p[i+1:].strip()
+ if len(value) >= 2 and value[0] == value[-1] == '"':
+ value = value[1:-1]
+ pdict[name] = value
+ return key, pdict
+
+def parse_content_disposition(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 parse_query(qs, charset):
+ """(qs: string) -> {key:string, string|[string]}
+
+ Parse a query given as a string argument and return a dictionary.
+ """
+ fields = {}
+ for chunk in filter(None, qs.split('&')):
+ if '=' not in chunk:
+ name = chunk
+ value = ''
+ else:
+ name, value = chunk.split('=', 1)
+ name = urllib.unquote(name.replace('+', ' '))
+ value = urllib.unquote(value.replace('+', ' '))
+ name = _decode_string(name, charset)
+ value = _decode_string(value, charset)
+ _add_field_value(fields, name, value)
+ return fields
+
+def _add_field_value(fields, name, value):
+ if name in fields:
+ values = fields[name]
+ if not isinstance(values, list):
+ fields[name] = values = [values]
+ values.append(value)
+ else:
+ fields[name] = value
+
+
+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_field(), eg.
+ request.get_field("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.
+ """
+
+ DEFAULT_CHARSET = 'iso-8859-1'
+
+ def __init__(self, stdin, environ):
+ self.stdin = stdin
+ self.environ = environ
+ self.form = {}
+ self.session = None
+ self.response = HTTPResponse()
+
+ # 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_cookies(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 process_inputs(self):
+ query = self.get_query()
+ if query:
+ self.form.update(parse_query(query, self.DEFAULT_CHARSET))
+ length = self.environ.get('CONTENT_LENGTH') or "0"
+ try:
+ length = int(length)
+ except ValueError:
+ raise RequestError('invalid content-length header')
+ ctype = self.environ.get("CONTENT_TYPE")
+ if ctype:
+ ctype, ctype_params = parse_header(ctype)
+ if ctype == 'application/x-www-form-urlencoded':
+ self._process_urlencoded(length, ctype_params)
+ elif ctype == 'multipart/form-data':
+ self._process_multipart(length, ctype_params)
+
+ def _process_urlencoded(self, length, params):
+ query = self.stdin.read(length)
+ if len(query) != length:
+ raise RequestError('unexpected end of request body')
+ charset = params.get('charset', self.DEFAULT_CHARSET)
+ self.form.update(parse_query(query, charset))
+
+ def _process_multipart(self, length, params):
+ boundary = params.get('boundary')
+ if not boundary:
+ raise RequestError('multipart/form-data missing boundary')
+ charset = params.get('charset')
+ mimeinput = MIMEInput(self.stdin, boundary, length)
+ try:
+ for line in mimeinput.readpart():
+ pass # discard lines up to first boundary
+ while mimeinput.moreparts():
+ self._process_multipart_body(mimeinput, charset)
+ except EOFError:
+ raise RequestError('unexpected end of multipart/form-data')
+
+ def _process_multipart_body(self, mimeinput, charset):
+ headers = StringIO()
+ lines = mimeinput.readpart()
+ for line in lines:
+ headers.write(line)
+ if line == '\r\n':
+ break
+ headers.seek(0)
+ headers = rfc822.Message(headers)
+ ctype, ctype_params = parse_header(headers.get('content-type', ''))
+ if ctype and 'charset' in ctype_params:
+ charset = ctype_params['charset']
+ cdisp, cdisp_params = parse_header(headers.get('content-disposition',
+ ''))
+ if not cdisp:
+ raise RequestError('expected Content-Disposition header')
+ name = cdisp_params.get('name')
+ filename = cdisp_params.get('filename')
+ if not (cdisp == 'form-data' and name):
+ raise RequestError('expected Content-Disposition: form-data'
+ 'with a "name" parameter: got %r' %
+ headers.get('content-disposition', ''))
+ # FIXME: should really to handle Content-Transfer-Encoding and other
+ # MIME complexity here. See RFC2048 for the full horror story.
+ if filename:
+ # it might be large file upload so use a temporary file
+ upload = Upload(filename, ctype, charset)
+ upload.receive(lines)
+ _add_field_value(self.form, name, upload)
+ else:
+ value = _decode_string(''.join(lines),
+ charset or self.DEFAULT_CHARSET)
+ _add_field_value(self.form, name, 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_cookies(self):
+ return self.cookies
+
+ def get_field(self, name, default=None):
+ return self.form.get(name, default)
+
+ def get_fields(self):
+ return self.form
+
+ 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_query(self):
+ """() -> string
+
+ Return the query component of the URL.
+ """
+ return self.environ.get('QUERY_STRING', '')
+
+ 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(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 ()
+
+
+# See RFC 2109 for details. Note that this parser is more liberal.
+_COOKIE_RE = re.compile(r"""
+ \s*
+ (?P<name>[^=;,\s]+)
+ \s*
+ (
+ =
+ \s*
+ (
+ (?P<qvalue> "(\\[\x00-\x7f] | [^"])*")
+ |
+ (?P<value> [^";,\s]*)
+ )
+ )?
+ \s*
+ [;,]?
+ """, re.VERBOSE)
+
+def parse_cookies(text):
+ result = {}
+ for m in _COOKIE_RE.finditer(text):
+ name = m.group('name')
+ if name[0] == '$':
+ # discard, we don't handle per cookie attributes (e.g. $Path)
+ continue
+ qvalue = m.group('qvalue')
+ if qvalue:
+ value = re.sub(r'\\(.)', r'\1', qvalue)[1:-1]
+ else:
+ value = m.group('value') or ''
+ result[name] = value
+ return result
+
+SAFE_CHARS = string.letters + string.digits + "-@&+=_., "
+_safe_trans = None
+
+def make_safe_filename(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:
+ r"""
+ Represents a single uploaded file. Uploaded files live in the
+ filesystem, *not* in memory.
+
+ fp
+ an open file containing the content of the upload. The file pointer
+ points to the beginning of the file
+ 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_filename())
+ content_type
+ the content type provided by the user-agent in the request
+ that uploaded this file.
+ charset
+ the charset provide by the user-agent
+ """
+
+ def __init__(self, orig_filename, content_type=None, charset=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(filename)
+ else:
+ self.orig_filename = None
+ self.base_filename = None
+ self.content_type = content_type
+ self.charset = charset
+ self.fp = None
+
+ def receive(self, lines):
+ self.fp = tempfile.TemporaryFile("w+b")
+ for line in lines:
+ self.fp.write(line)
+ self.fp.seek(0)
+
+ def __str__(self):
+ return str(self.orig_filename)
+
+ def __repr__(self):
+ return "<%s at %x: %s>" % (self.__class__.__name__, id(self), self)
+
+ def read(self, n):
+ return self.fp.read(n)
+
+ def readline(self):
+ return self.fp.readlines()
+
+ def __iter__(self):
+ return iter(self.fp)
+
+ def close(self):
+ self.fp.close()
+
+
+class LineInput:
+ r"""
+ A wrapper for an input stream that has the following properties:
+
+ * lines are terminated by \r\n
+
+ * lines shorter than 'maxlength' are always returned unbroken
+
+ * lines longer than 'maxlength' are broken but the pair of
+ characters \r\n are never split
+
+ * no more than 'length' characters are read from the underlying
+ stream
+
+ * if the underlying stream does not produce at least 'length'
+ characters then EOFError is raised
+
+ """
+ def __init__(self, fp, length):
+ self.fp = fp
+ self.length = length
+ self.buf = ''
+
+ def readline(self, maxlength=4096):
+ # fill buffer
+ n = min(self.length, maxlength - len(self.buf))
+ if n > 0:
+ self.length -= n
+ assert self.length >= 0
+ chunk = self.fp.read(n)
+ if len(chunk) != n:
+ raise EOFError('unexpected end of input')
+ self.buf += chunk
+ # split into lines
+ buf = self.buf
+ i = buf.find('\r\n')
+ if i >= 0:
+ i += 2
+ self.buf = buf[i:]
+ return buf[:i]
+ elif buf.endswith('\r'):
+ # avoid splitting CR LF pairs
+ self.buf = '\r'
+ return buf[:-1]
+ else:
+ self.buf = ''
+ return buf
+
+class MIMEInput:
+ """
+ Split a MIME input stream into parts. Note that this class does not
+ handle headers, transfer encoding, etc.
+ """
+
+ def __init__(self, fp, boundary, length):
+ self.lineinput = LineInput(fp, length)
+ self.pat = re.compile(r'--%s(--)?[ \t]*\r\n' % re.escape(boundary))
+ self.done = False
+
+ def moreparts(self):
+ """Return true if there are more parts to be read."""
+ return not self.done
+
+ def readpart(self):
+ """Generate all the lines up to a MIME boundary. Note that you
+ must exhaust the generator before calling this function again."""
+ assert not self.done
+ last_line = ''
+ while 1:
+ line = self.lineinput.readline()
+ if not line:
+ # Hit EOF -- nothing more to read. This should *not* happen
+ # in a well-formed MIME message.
+ raise EOFError('MIME boundary not found (end of input)')
+ if last_line.endswith('\r\n') or last_line == '':
+ m = self.pat.match(line)
+ if m:
+ # If we hit the boundary line, return now. Forget
+ # the current line *and* the CRLF ending of the
+ # previous line.
+ if m.group(1):
+ # hit final boundary
+ self.done = True
+ yield last_line[:-2]
+ return
+ if last_line:
+ yield last_line
+ last_line = line
diff --git a/pypers/europython05/Quixote-2.0/http_response.py b/pypers/europython05/Quixote-2.0/http_response.py
new file mode 100755
index 0000000..435b9e8
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/http_response.py
@@ -0,0 +1,475 @@
+"""quixote.http_response
+$HeadURL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/http_response.py $
+$Id: http_response.py 26251 2005-02-25 16:17:06Z dbinger $
+
+Provides the HTTPResponse class.
+"""
+
+import time
+from sets import Set
+try:
+ import zlib
+except ImportError:
+ pass
+import struct
+from rfc822 import formatdate
+from quixote.html import stringify
+
+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',
+}
+
+_GZIP_HEADER = ("\037\213" # magic
+ "\010" # compression method
+ "\000" # flags
+ "\000\000\000\000" # time, who cares?
+ "\002"
+ "\377")
+
+_GZIP_EXCLUDE = Set(["application/pdf",
+ "application/zip",
+ "audio/mpeg",
+ "image/gif",
+ "image/jpeg",
+ "image/png",
+ "video/mpeg",
+ "video/quicktime",
+ "video/x-msvideo",
+ ])
+
+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:
+ content_type : string
+ the MIME content type of the response (does not include extra params
+ like charset)
+ charset : string
+ the character encoding of the the response
+ 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 : str | Stream
+ the response body, None by default. Note that if the body is not a
+ stream then it is already encoded using 'charset'.
+ 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.
+ """
+
+ DEFAULT_CONTENT_TYPE = 'text/html'
+ DEFAULT_CHARSET = 'iso-8859-1'
+
+ def __init__(self, status=200, body=None, content_type=None, charset=None):
+ """
+ Creates a new HTTP response.
+ """
+ self.content_type = content_type or self.DEFAULT_CONTENT_TYPE
+ self.charset = charset or self.DEFAULT_CHARSET
+ 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 = True
+ self.javascript_code = None
+
+ def set_content_type(self, content_type, charset='iso-8859-1'):
+ """(content_type : string, charset : string = 'iso-8859-1')
+
+ Set the content type of the response to the MIME type specified by
+ 'content_type'. Also sets the charset, defaulting to 'iso-8859-1'.
+ """
+ self.charset = charset
+ self.content_type = content_type
+
+ def set_charset(self, charset):
+ self.charset = str(charset).lower()
+
+ 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 not isinstance(status, int):
+ 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_expires(self, seconds=0, minutes=0, hours=0, days=0):
+ if seconds is None:
+ self.cache = None # don't generate 'Expires' header
+ else:
+ self.cache = seconds + 60*(minutes + 60*(hours + 24*days))
+
+ def _encode_chunk(self, chunk):
+ """(chunk : str | unicode) -> str
+ """
+ if self.charset == 'iso-8859-1' and isinstance(chunk, str):
+ return chunk # non-ASCII chars are okay
+ else:
+ return chunk.encode(self.charset)
+
+ def _compress_body(self, body):
+ """(body: str) -> str
+ """
+ n = len(body)
+ co = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS,
+ zlib.DEF_MEM_LEVEL, 0)
+ chunks = [_GZIP_HEADER,
+ co.compress(body),
+ co.flush(),
+ struct.pack("<ll", zlib.crc32(body), n)]
+ compressed_body = "".join(chunks)
+ ratio = float(n) / len(compressed_body)
+ #print "gzip original size %d, ratio %.1f" % (n, ratio)
+ if ratio > 1.0:
+ self.set_header("Content-Encoding", "gzip")
+ return compressed_body
+ else:
+ return body
+
+ def set_body(self, body, compress=False):
+ """(body : any, compress : bool = False)
+
+ Sets the response body equal to the argument 'body'. If 'compress'
+ is true then the body may be compressed using 'gzip'.
+ """
+ if not isinstance(body, Stream):
+ body = self._encode_chunk(stringify(body))
+ if compress and self.content_type not in _GZIP_EXCLUDE:
+ body = self._compress_body(body)
+ self.body = body
+
+ 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=False):
+ """Cause a redirection without raising an error"""
+ if not isinstance(location, str):
+ 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 get_status_code(self):
+ return self.status_code
+
+ def get_reason_phrase(self):
+ return self.reason_phrase
+
+ def get_content_type(self):
+ return self.content_type
+
+ def get_content_length(self):
+ if self.body is None:
+ return None
+ elif isinstance(self.body, Stream):
+ return self.body.length
+ else:
+ return len(self.body)
+
+ 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_headers = []
+ for name, attrs in self.cookies.items():
+ value = str(attrs['value'])
+ if '"' in value:
+ value = value.replace('"', '\\"')
+ chunks = ['%s="%s"' % (name, 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_headers.append(("Set-Cookie", '; '.join(chunks)))
+ return cookie_headers
+
+ def generate_headers(self):
+ """generate_headers() -> [(name:string, value:string)]
+
+ Generate a list of headers to be returned as part of the response.
+ """
+ headers = []
+
+ 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 "date" not in self.headers:
+ headers.append(("Date", formatdate(now)))
+
+ # Cache directives
+ if self.cache is None:
+ pass # don't mess with the expires header
+ elif "expires" not in self.headers:
+ 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))
+
+ # Content-type
+ if "content-type" not in self.headers:
+ headers.append(('Content-Type',
+ '%s; charset=%s' % (self.content_type,
+ self.charset)))
+
+ # Content-Length
+ if "content-length" not in self.headers:
+ length = self.get_content_length()
+ if length is not None:
+ headers.append(('Content-Length', length))
+
+ return headers
+
+ def generate_body_chunks(self):
+ """Return a sequence of body chunks, encoded using 'charset'.
+ """
+ if self.body is None:
+ pass
+ elif isinstance(self.body, Stream):
+ for chunk in self.body:
+ yield self._encode_chunk(chunk)
+ else:
+ yield self.body # already encoded
+
+ def write(self, output, include_status=True):
+ """(output : file, include_status : bool = True)
+
+ Write the HTTP response headers and body to 'output'. This is not
+ a complete HTTP response, as it doesn't start with a response
+ status line as specified by RFC 2616. By default, it does 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.
+ """
+ flush_output = not self.buffered and hasattr(output, 'flush')
+ if include_status:
+ # "Status" header must come first.
+ output.write("Status: %03d %s\r\n" % (self.status_code,
+ self.reason_phrase))
+ for name, value in self.generate_headers():
+ output.write("%s: %s\r\n" % (name, value))
+ output.write("\r\n")
+ if flush_output:
+ output.flush()
+ for chunk in self.generate_body_chunks():
+ output.write(chunk)
+ if flush_output:
+ output.flush()
+ if flush_output:
+ output.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)
diff --git a/pypers/europython05/Quixote-2.0/logger.py b/pypers/europython05/Quixote-2.0/logger.py
new file mode 100755
index 0000000..f631c57
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/logger.py
@@ -0,0 +1,92 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/logger.py $
+$Id: logger.py 25521 2004-11-04 18:16:18Z nascheme $
+"""
+import sys
+import os
+import time
+import socket
+from quixote.sendmail import sendmail
+
+class DefaultLogger:
+ """
+ This is the default logger object used by the Quixote publisher. It
+ controls access log and error log behavior. You may provide your own
+ object if you wish to have different behavior.
+
+ Instance attributes:
+
+ access_log : file | None
+ file to which every access will be logged. If None then access
+ is not logged.
+ 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 to sys.stderr by default.
+ error_email : string | None
+ if set then internal server errors will cause messages to be sent to
+ this address
+ """
+ def __init__(self, access_log=None, error_log=None, error_email=None):
+ if access_log:
+ self.access_log = open(access_log, 'a', 1)
+ else:
+ self.access_log = None
+ if error_log is None:
+ self.error_log = sys.stderr
+ else:
+ self.error_log = open(error_log, 'a', 1)
+ self.error_email = error_email
+ sys.stdout = self.error_log # print is handy for debugging
+
+ 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))
+
+ def log_internal_error(self, error_summary, error_msg):
+ """(error_summary: str, error_msg: str)
+
+ error_summary is a single line summary of the internal error, suitable
+ for an email subject. error_msg is a multi-line plaintext message
+ describing the error in detail.
+ """
+ self.log("exception caught")
+ self.error_log.write(error_msg)
+ if self.error_email:
+ sendmail('Quixote Traceback (%s)' % error_summary,
+ error_msg, [self.error_email],
+ from_addr=(self.error_email, socket.gethostname()))
+
+ def log_request(self, request, start_time):
+ """Log a request in the access_log file.
+ """
+ if self.access_log is None:
+ return
+ if request.session:
+ user = request.session.user or "-"
+ else:
+ user = "-"
+ now = time.time()
+ seconds = now - start_time
+ timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(now))
+
+ request_uri = request.get_path()
+ query = request.get_query()
+ if query:
+ request_uri += "?" + query
+ proto = request.get_environ('SERVER_PROTOCOL')
+ self.access_log.write('%s %s %s %d "%s %s %s" %s %r %0.2fsec\n' %
+ (request.get_environ('REMOTE_ADDR'),
+ user,
+ timestamp,
+ os.getpid(),
+ request.get_method(),
+ request_uri,
+ proto,
+ request.response.status_code,
+ request.get_environ('HTTP_USER_AGENT', ''),
+ seconds
+ ))
diff --git a/pypers/europython05/Quixote-2.0/ptl/__init__.py b/pypers/europython05/Quixote-2.0/ptl/__init__.py
new file mode 100755
index 0000000..3409414
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/ptl/__init__.py
@@ -0,0 +1,245 @@
+'''
+$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/ptl/__init__.py $
+$Id: __init__.py 26357 2005-03-16 14:56:23Z dbinger $
+
+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``.
+
+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 ``.pyc``. Since vanilla Python
+doesn't know anything about PTL, this package provides an import hook to let
+you import PTL files just like regular Python modules. The import
+hook is installed when you import *this* package.
+
+(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. A similar problem has been reported for
+BioPython and win32com.client imports.)
+'''
+
+
diff --git a/pypers/europython05/Quixote-2.0/ptl/cimport.c b/pypers/europython05/Quixote-2.0/ptl/cimport.c
new file mode 100755
index 0000000..6e37ca5
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/ptl/cimport.c
@@ -0,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);
+
+}
diff --git a/pypers/europython05/Quixote-2.0/ptl/install.py b/pypers/europython05/Quixote-2.0/ptl/install.py
new file mode 100755
index 0000000..642c69b
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/ptl/install.py
@@ -0,0 +1,2 @@
+import quixote.ptl.ptl_import
+quixote.ptl.ptl_import.install()
diff --git a/pypers/europython05/Quixote-2.0/ptl/ptl_compile.py b/pypers/europython05/Quixote-2.0/ptl/ptl_compile.py
new file mode 100755
index 0000000..47c0e32
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/ptl/ptl_compile.py
@@ -0,0 +1,314 @@
+#!/www/python/bin/python
+"""
+$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/ptl/ptl_compile.py $
+$Id: ptl_compile.py 26357 2005-03-16 14:56:23Z dbinger $
+
+Compile a PTL template.
+
+First template function names are mangled, noting the template type.
+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.
+"""
+
+import sys
+import os
+import stat
+import symbol
+import token
+import re
+import imp
+import stat
+import marshal
+import struct
+
+assert sys.hexversion >= 0x20300b1, 'PTL requires Python 2.3 or newer'
+
+from compiler import pycodegen, transformer
+from compiler import ast
+from compiler.consts import OP_ASSIGN
+from compiler import misc, syntax
+
+HTML_TEMPLATE_PREFIX = "_q_html_template_"
+PLAIN_TEMPLATE_PREFIX = "_q_plain_template_"
+
+class TemplateTransformer(transformer.Transformer):
+
+ def __init__(self, *args, **kwargs):
+ transformer.Transformer.__init__(self, *args, **kwargs)
+ # __template_type is a stack whose values are
+ # "html", "plain", or None
+ self.__template_type = []
+
+ def _get_template_type(self):
+ """Return the type of the function being compiled (
+ "html", "plain", or None)
+ """
+ if self.__template_type:
+ return self.__template_type[-1]
+ else:
+ return None
+
+ def file_input(self, nodelist):
+ doc = None # self.get_docstring(nodelist, symbol.file_input)
+
+ html_imp = ast.From('quixote.html', [('TemplateIO', '_q_TemplateIO'),
+ ('htmltext', '_q_htmltext')])
+ vars_imp = ast.From("__builtin__", [("vars", "_q_vars")])
+ stmts = [ vars_imp, html_imp ]
+
+ 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)_(dollar_)?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"
+ else:
+ raise RuntimeError, 'unknown prefix on %s' % name
+
+ self.__template_type.append(template_type)
+
+ 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])
+
+ # _q_output = _q_TemplateIO()
+ klass = ast.Name('_q_TemplateIO')
+ args = [ast.Const(template_type == "html")]
+ instance = ast.CallFunc(klass, args)
+ assign_name = ast.AssName('_q_output', OP_ASSIGN)
+ assign = ast.Assign([assign_name], instance)
+
+ # return _q_output.getvalue()
+ func = ast.Getattr(ast.Name('_q_output'), "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 self._get_template_type() is None:
+ return transformer.Transformer.expr_stmt(self, nodelist)
+
+ # Instead of discarding objects on the stack, call
+ # "_q_output += obj".
+ exprNode = self.com_node(nodelist[-1])
+ if len(nodelist) == 1:
+ lval = ast.Name('_q_output')
+ 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])
+ lineno = node[2]
+ return self._get_text_node(k)
+
+ def _get_text_node(self, k):
+ if self._get_template_type() == "html":
+ return ast.CallFunc(ast.Name('_q_htmltext'), [ast.Const(k)])
+ else:
+ return ast.Const(k)
+
+_template_re = re.compile(
+ r"^(?P<indent>[ \t]*) def (?:[ \t]+)"
+ r" (?P<name>[a-zA-Z_][a-zA-Z_0-9]*)"
+ r" (?:[ \t]*) \[(?P<type>plain|html)\] (?:[ \t]*)"
+ 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.
+
+ 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.
+ """
+ def replacement(match):
+ template_type = match.group('type')
+ return '%sdef _q_%s_template_%s(' % (match.group('indent'),
+ template_type,
+ match.group('name'))
+ return _template_re.sub(replacement, buf)
+
+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))
+
+
+PTL_EXT = ".ptl"
+
+class Template(pycodegen.Module):
+
+ def _get_tree(self):
+ tree = parse(self.source, self.filename)
+ misc.set_filename(self.filename, tree)
+ syntax.check(tree)
+ return tree
+
+ def dump(self, fp):
+ mtime = os.stat(self.filename)[stat.ST_MTIME]
+ fp.write('\0\0\0\0')
+ fp.write(struct.pack('<I', mtime))
+ marshal.dump(self.code, fp)
+ fp.flush()
+ fp.seek(0)
+ fp.write(imp.get_magic())
+
+
+def compile_template(input, filename, output=None):
+ """(input, filename, output=None) -> code
+
+ Compile an open file.
+ If output is not None then the code is written to output.
+ The code object is returned.
+ """
+ buf = input.read()
+ template = Template(buf, filename)
+ template.compile()
+ if output is not None:
+ template.dump(output)
+ return template.code
+
+def compile(inputname, outputname):
+ """(inputname, outputname)
+
+ Compile a template file. The new template is written to outputname.
+ """
+ input = open(inputname)
+ output = open(outputname, "wb")
+ try:
+ compile_template(input, inputname, output)
+ except:
+ # don't leave a corrupt .pyc 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_EXT:
+ cfile = fullname[:-4] + '.pyc'
+ 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 + ".pyc")
+
+if __name__ == "__main__":
+ main()
diff --git a/pypers/europython05/Quixote-2.0/ptl/ptl_import.py b/pypers/europython05/Quixote-2.0/ptl/ptl_import.py
new file mode 100755
index 0000000..d6ac2a0
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/ptl/ptl_import.py
@@ -0,0 +1,148 @@
+"""
+$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/ptl/ptl_import.py $
+$Id: ptl_import.py 26357 2005-03-16 14:56:23Z dbinger $
+
+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 PTL import hooks.
+"""
+
+import sys
+import os.path
+import imp, ihooks, new
+import struct
+import marshal
+import __builtin__
+
+from ptl_compile import compile_template, PTL_EXT
+
+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 _timestamp(filename):
+ try:
+ s = os.stat(filename)
+ except OSError:
+ return None
+ return s.st_mtime
+
+def _load_pyc(name, filename, pyc_filename):
+ try:
+ fp = open(pyc_filename, "rb")
+ except IOError:
+ return None
+ if fp.read(4) == imp.get_magic():
+ mtime = struct.unpack('<I', fp.read(4))[0]
+ ptl_mtime = _timestamp(filename)
+ if ptl_mtime is not None and mtime >= ptl_mtime:
+ code = marshal.load(fp)
+ return _exec_module_code(code, name, filename)
+ return None
+
+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)
+ pyc_filename = path + ".pyc"
+ module = _load_pyc(name, filename, pyc_filename)
+ if module is not None:
+ return module
+ try:
+ output = open(pyc_filename, "wb")
+ except IOError:
+ output = None
+ try:
+ code = compile_template(file, filename, output)
+ except:
+ if output:
+ output.close()
+ os.unlink(pyc_filename)
+ raise
+ else:
+ if output:
+ output.close()
+ return _exec_module_code(code, name, filename)
+
+
+# Constant used to signal a PTL files
+PTL_FILE = object()
+
+class PTLHooks(ihooks.Hooks):
+
+ def get_suffixes(self):
+ # add our suffixes
+ return [(PTL_EXT, 'r', PTL_FILE)] + imp.get_suffixes()
+
+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 is 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 = False
+
+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 = True
+
+
+if __name__ == '__main__':
+ install()
diff --git a/pypers/europython05/Quixote-2.0/ptl/ptlrun.py b/pypers/europython05/Quixote-2.0/ptl/ptlrun.py
new file mode 100755
index 0000000..490188a
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/ptl/ptlrun.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+import sys
+from quixote.ptl.ptl_compile import compile_template
+exec compile_template(open(sys.argv[1]), sys.argv[1])
+
diff --git a/pypers/europython05/Quixote-2.0/ptl/qx_distutils.py b/pypers/europython05/Quixote-2.0/ptl/qx_distutils.py
new file mode 100755
index 0000000..163545a
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/ptl/qx_distutils.py
@@ -0,0 +1,47 @@
+"""
+$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/ptl/qx_distutils.py $
+$Id: qx_distutils.py 26357 2005-03-16 14:56:23Z dbinger $
+
+Provides a version of the Distutils "build_py" command that knows about
+PTL files.
+"""
+
+import os, string
+from glob import glob
+from types import StringType, ListType, TupleType
+from distutils.command.build_py import build_py
+
+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)
diff --git a/pypers/europython05/Quixote-2.0/ptl/test/utest_ptl.py b/pypers/europython05/Quixote-2.0/ptl/test/utest_ptl.py
new file mode 100755
index 0000000..91b96ba
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/ptl/test/utest_ptl.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+from sancho.utest import UTest
+from quixote.ptl.ptl_compile import compile_template
+from cStringIO import StringIO
+from quixote.html import TemplateIO, htmltext
+
+def run_ptl(*source):
+ """
+ Compile the given lines of source code using the ptl compiler
+ and run the resulting compiled code.
+ """
+ # When the ptl compiler compiles a module, it places _q_TemplateIO
+ # and _q_htmltext into the globals of the module. Here, we don't
+ # have a module, but we provide these same globals for eval.
+ eval(compile_template(StringIO('\n'.join(source)), 'test'),
+ dict(_q_TemplateIO=TemplateIO, _q_htmltext=htmltext))
+
+class Test (UTest):
+
+ def check_html(self):
+ run_ptl(
+ 'from quixote.html import htmltext',
+ 'def f [html] (a):',
+ ' "&"',
+ ' a',
+ 'assert type(f(1)) == htmltext',
+ 'assert f("") == "&"',
+ 'assert f("&") == "&&amp;"',
+ 'assert f(htmltext("&")) == "&&"')
+
+ def check_plain(self):
+ run_ptl(
+ 'from quixote.html import htmltext',
+ 'def f [plain] (a):',
+ ' "&"',
+ ' a',
+ 'assert type(f(1)) == str',
+ 'assert f("") == "&"',
+ 'assert f("&") == "&&"',
+ 'assert f(htmltext("&")) == "&&"',
+ 'assert type(f(htmltext("&"))) == str')
+
+ def check_syntax(self):
+ run_ptl('def f(a):\n a')
+ try:
+ run_ptl('def f [] (a):\n a')
+ assert 0
+ except SyntaxError, e:
+ assert e.lineno == 1
+ try:
+ run_ptl('def f [HTML] (a):\n a')
+ assert 0
+ except SyntaxError, e:
+ assert e.lineno == 1
+
+if __name__ == "__main__":
+ Test()
+
diff --git a/pypers/europython05/Quixote-2.0/publish.py b/pypers/europython05/Quixote-2.0/publish.py
new file mode 100755
index 0000000..058875b
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/publish.py
@@ -0,0 +1,336 @@
+"""$HeadURL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/publish.py $
+$Id: publish.py 26333 2005-03-11 01:15:40Z dbinger $
+
+Logic for publishing modules and objects on the Web.
+"""
+
+import sys, traceback, StringIO
+import time
+import urlparse
+import cgitb
+
+from quixote.errors import PublishError, format_publish_error
+from quixote import util
+from quixote.config import Config
+from quixote.http_response import HTTPResponse
+from quixote.logger import DefaultLogger
+
+# 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 traversal of the
+ application's directory tree 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_directory : Directory
+ the root directory that will be searched for objects to fulfill
+ each request. This can be any object with a _q_traverse method
+ that acts like Directory._q_traverse.
+ logger : DefaultLogger
+ controls access log and error log behavior
+ session_manager : NullSessionManager
+ keeps track of sessions
+ config : Config
+ holds all configuration info for this application. If the
+ application doesn't provide values then default values
+ from the quixote.config module are used.
+ _request : HTTPRequest
+ the HTTP request currently being processed.
+ """
+
+ def __init__(self, root_directory, logger=None, session_manager=None,
+ config=None, **kwargs):
+ global _publisher
+ if config is None:
+ self.config = Config(**kwargs)
+ else:
+ if kwargs:
+ raise ValueError("cannot provide both 'config' object and"
+ " config arguments")
+ self.config = config
+ if logger is None:
+ self.logger = DefaultLogger(error_log=self.config.error_log,
+ access_log=self.config.access_log,
+ error_email=self.config.error_email)
+ else:
+ self.logger = logger
+ if session_manager is not None:
+ self.session_manager = session_manager
+ else:
+ from quixote.session import NullSessionManager
+ self.session_manager = NullSessionManager()
+
+ if _publisher is not None:
+ raise RuntimeError, "only one instance of Publisher allowed"
+ _publisher = self
+
+ if not callable(getattr(root_directory, '_q_traverse')):
+ raise TypeError(
+ 'Expected something with a _q_traverse method, got %r' %
+ root_directory)
+ self.root_directory = root_directory
+ self._request = None
+
+ def set_session_manager(self, session_manager):
+ self.session_manager = session_manager
+
+ def log(self, msg):
+ self.logger.log(msg)
+
+ def parse_request(self, request):
+ """Parse the request information waiting in 'request'.
+ """
+ request.process_inputs()
+
+ def start_request(self):
+ """Called at the start of each request.
+ """
+ self.session_manager.start_request()
+
+ 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 finish_successful_request(self):
+ """Called at the end of a successful request.
+ """
+ self.session_manager.finish_successful_request()
+
+ def format_publish_error(self, exc):
+ return format_publish_error(exc)
+
+ def finish_interrupted_request(self, 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.
+ """
+ if not self.config.display_exceptions and exc.private_msg:
+ exc.private_msg = None # hide it
+ request = get_request()
+ request.response = HTTPResponse(status=exc.status_code)
+ output = self.format_publish_error(exc)
+ self.session_manager.finish_successful_request()
+ return output
+
+ def finish_failed_request(self):
+ """
+ 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
+ request = get_request()
+ 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':
+ # 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.logger.log_internal_error(error_summary, plain_error_msg)
+ request.response.set_status(500)
+ self.session_manager.finish_failed_request()
+ return user_error_msg
+
+
+ def _generate_internal_error(self, request):
+ admin = request.get_environ('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 = StringIO.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 = StringIO.StringIO()
+ hook = cgitb.Hook(file=error_file)
+ hook(exc_type, exc_value, tb)
+ error_file.write('<h2>Original Request</h2>')
+ error_file.write(str(util.dump_request(request)))
+ error_file.write('<h2>Original Response</h2><pre>')
+ original_response.write(error_file)
+ error_file.write('</pre>')
+ return error_file.getvalue()
+
+
+ def try_publish(self, request):
+ """(request : HTTPRequest) -> object
+
+ The master method that does all the work for a single request.
+ Exceptions are handled by the caller.
+ """
+ self.start_request()
+ path = request.get_environ('PATH_INFO', '')
+ assert path[:1] == '/'
+ # split path into components
+ path = path[1:].split('/')
+ output = self.root_directory._q_traverse(path)
+ # The callable ran OK, commit any changes to the session
+ self.finish_successful_request()
+ return output
+
+ def filter_output(self, request, output):
+ """Hook for post processing the output. Subclasses may wish to
+ override (e.g. check HTML syntax).
+ """
+ return output
+
+ def process_request(self, request):
+ """(request : HTTPRequest) -> HTTPResponse
+
+ 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)
+ start_time = time.time()
+ try:
+ self.parse_request(request)
+ output = self.try_publish(request)
+ except PublishError, exc:
+ # Exit the publishing loop and return a result right away.
+ output = self.finish_interrupted_request(exc)
+ except:
+ # Some other exception, generate error messages to the logs, etc.
+ output = self.finish_failed_request()
+ output = self.filter_output(request, output)
+ self.logger.log_request(request, start_time)
+ if output:
+ if self.config.compress_pages and request.get_encoding(["gzip"]):
+ compress = True
+ else:
+ compress = False
+ request.response.set_body(output, compress)
+ self._clear_request()
+ return request.response
+
+
+# Publisher singleton, only one of these per process.
+_publisher = None
+
+def get_publisher():
+ return _publisher
+
+def get_request():
+ return _publisher.get_request()
+
+def get_response():
+ return _publisher.get_request().response
+
+def get_field(name, default=None):
+ return _publisher.get_request().get_field(name, default)
+
+def get_cookie(name, default=None):
+ return _publisher.get_request().get_cookie(name, default)
+
+def get_path(n=0):
+ return _publisher.get_request().get_path(n)
+
+def redirect(location, permanent=False):
+ """(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).
+ """
+ request = _publisher.get_request()
+ location = urlparse.urljoin(request.get_url(), str(location))
+ return request.response.redirect(location, permanent)
+
+def get_session():
+ return _publisher.get_request().session
+
+def get_session_manager():
+ return _publisher.session_manager
+
+def get_user():
+ session = _publisher.get_request().session
+ if session is None:
+ return None
+ else:
+ return session.user
diff --git a/pypers/europython05/Quixote-2.0/publish1.py b/pypers/europython05/Quixote-2.0/publish1.py
new file mode 100755
index 0000000..93bfaf3
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/publish1.py
@@ -0,0 +1,270 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/publish1.py $
+$Id: publish1.py 25664 2004-11-22 20:35:07Z nascheme $
+
+Provides a publisher object that behaves like the Quixote 1 Publisher.
+Specifically, arbitrary namespaces may be exported and the HTTPRequest
+object is passed as the first argument to exported functions. Also,
+the _q_lookup(), _q_resolve(), and _q_access() methods work as they did
+in Quixote 1.
+"""
+
+import sys
+import re
+import types
+import warnings
+from quixote import errors, get_request, redirect
+from quixote.publish import Publisher as _Publisher
+from quixote.directory import Directory
+from quixote.html import htmltext
+
+
+class Publisher(_Publisher):
+ """
+ Instance attributes:
+ namespace_stack : [ module | instance | class ]
+ """
+
+ def __init__(self, root_namespace, config=None):
+ from quixote.config import Config
+ if type(root_namespace) is types.StringType:
+ root_namespace = _get_module(root_namespace)
+ self.namespace_stack = [root_namespace]
+ if config is None:
+ config = Config()
+ directory = RootDirectory(root_namespace, self.namespace_stack)
+ _Publisher.__init__(self, directory, config=config)
+
+ def debug(self, msg):
+ self.log(msg)
+
+ def get_namespace_stack(self):
+ """get_namespace_stack() -> [ module | instance | class ]
+ """
+ return self.namespace_stack
+
+
+class RootDirectory(Directory):
+ def __init__(self, root_namespace, namespace_stack):
+ self.root_namespace = root_namespace
+ self.namespace_stack = namespace_stack
+
+ def _q_traverse(self, path):
+ # Initialize the publisher's namespace_stack
+ del self.namespace_stack[:]
+
+ request = get_request()
+
+ # Traverse package to a (hopefully-) callable object
+ object = _traverse_url(self.root_namespace, path, request,
+ 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):
+ output = object(request)
+ 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))
+
+ return output
+
+
+def _get_module(name):
+ """Get a module object by name."""
+ __import__(name)
+ module = sys.modules[name]
+ return module
+
+
+_slash_pat = re.compile("//*")
+
+def _traverse_url(root_namespace, path_components, request, namespace_stack):
+ """(root_namespace : any, path_components : [string],
+ request : HTTPRequest, 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.
+ """
+
+ path = '/' + '/'.join(path_components)
+
+ # 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:
+ return redirect(request.environ['SCRIPT_NAME'] + '/' , permanent=1)
+
+ # 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, 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:
+ # 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.
+ return redirect(request.get_path() + "/", permanent=1)
+
+ 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 _get_component(container, component, 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
+
+
+def isstring(x):
+ return isinstance(x, (str, unicode, htmltext))
diff --git a/pypers/europython05/Quixote-2.0/sendmail.py b/pypers/europython05/Quixote-2.0/sendmail.py
new file mode 100755
index 0000000..0a13884
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/sendmail.py
@@ -0,0 +1,273 @@
+"""quixote.sendmail
+$HeadURL: svn+ssh://svn.mems-exchange.org/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@example.com
+ Joe Luser <luser@example.com>
+ Paddy O'Reilly <paddy@example.ie>
+ "Smith, John" <smith@example.com>
+ Dick & Jane <dickjane@example.net>
+ "Tom, Dick, & Harry" <tdh@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@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@example.com"], ...)
+ sendmail(to_addrs=[("joe@example.com", "Joe User")], ...)
+ sendmail(to_addrs=[RFC822Mailbox("joe@example.com", "Joe User")], ...)
+ ...although the "To" header will be slightly different. In the
+ first case, it will be
+ To: joe@example.com
+ while in the other two, it will be:
+ To: Joe User <joe@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@example.com", "A Real User"),
+ smtp_sender="postmaster@example.com")
+
+ End users will see mail from "A Real User <realuser@example.com>" in
+ their inbox, but bounces will go to postmaster@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@example.com without filling
+ bigboss' inbox with dross, you might do this:
+ sendmail(to_addrs=["bigboss@example.com"],
+ ...,
+ smtp_recipients=["developers@example.com"])
+
+ This is so useful that it's a Quixote configuration option: just set
+ MAIL_DEBUG_ADDR to (eg.) "developers@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 ()
diff --git a/pypers/europython05/Quixote-2.0/server/__init__.py b/pypers/europython05/Quixote-2.0/server/__init__.py
new file mode 100755
index 0000000..6947382
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/server/__init__.py
@@ -0,0 +1,5 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/server/__init__.py $
+$Id: __init__.py 25579 2004-11-11 20:56:32Z nascheme $
+
+This package is for Quixote to server glue.
+"""
diff --git a/pypers/europython05/Quixote-2.0/server/_fcgi.py b/pypers/europython05/Quixote-2.0/server/_fcgi.py
new file mode 100755
index 0000000..7ac41d2
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/server/_fcgi.py
@@ -0,0 +1,466 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/server/_fcgi.py $
+$Id: _fcgi.py 25688 2004-11-30 20:05:33Z dbinger $
+Derived from Robin Dunn's FastCGI module,
+available at http://alldunn.com/python/#fcgi.
+"""
+#------------------------------------------------------------------------
+# 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 25688 2004-11-30 20:05:33Z dbinger $"
+
+
+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()
diff --git a/pypers/europython05/Quixote-2.0/server/cgi_server.py b/pypers/europython05/Quixote-2.0/server/cgi_server.py
new file mode 100755
index 0000000..c4e8ea2
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/server/cgi_server.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/server/cgi_server.py $
+$Id: cgi_server.py 25476 2004-10-27 21:54:59Z nascheme $
+"""
+
+import sys
+import os
+from quixote.http_request import HTTPRequest
+
+def run(create_publisher):
+ 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)
+ publisher = create_publisher()
+ request = HTTPRequest(sys.__stdin__, os.environ)
+ response = publisher.process_request(request)
+ try:
+ response.write(sys.__stdout__)
+ except IOError, err:
+ publisher.log("IOError while sending response ignored: %s" % err)
+
+
+if __name__ == '__main__':
+ from quixote.demo import create_publisher
+ run(create_publisher)
diff --git a/pypers/europython05/Quixote-2.0/server/fastcgi_server.py b/pypers/europython05/Quixote-2.0/server/fastcgi_server.py
new file mode 100755
index 0000000..4ba7530
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/server/fastcgi_server.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/server/fastcgi_server.py $
+$Id: fastcgi_server.py 25476 2004-10-27 21:54:59Z nascheme $
+
+Server for Quixote applications that use FastCGI. It should work
+for CGI too but the cgi_server module is preferred as it is more
+portable.
+"""
+
+from quixote.server import _fcgi
+from quixote.http_request import HTTPRequest
+
+def run(create_publisher):
+ publisher = create_publisher()
+ while _fcgi.isFCGI():
+ f = _fcgi.FCGI()
+ request = HTTPRequest(f.inp, f.env)
+ response = publisher.process_request(request)
+ try:
+ response.write(f.out)
+ except IOError, err:
+ publisher.log("IOError while sending response ignored: %s" % err)
+ f.Finish()
+
+
+if __name__ == '__main__':
+ from quixote.demo import create_publisher
+ run(create_publisher)
diff --git a/pypers/europython05/Quixote-2.0/server/medusa_server.py b/pypers/europython05/Quixote-2.0/server/medusa_server.py
new file mode 100755
index 0000000..239d95e
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/server/medusa_server.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/server/medusa_server.py $
+$Id: medusa_server.py 25579 2004-11-11 20:56:32Z nascheme $
+
+An HTTP handler for Medusa that publishes a Quixote application.
+"""
+
+import asyncore, rfc822, socket, urllib
+from StringIO import StringIO
+from medusa import http_server, xmlrpc_handler
+import quixote
+from quixote.http_request import HTTPRequest
+
+
+class StreamProducer:
+ def __init__(self, chunks):
+ self.chunks = chunks # a generator
+
+ def more(self):
+ try:
+ return self.chunks.next()
+ except StopIteration:
+ return ''
+
+
+class QuixoteHandler:
+ def __init__(self, publisher, server):
+ self.publisher = publisher
+ self.server = server
+
+ def match(self, request):
+ # Always match, since this is the only handler there is.
+ return True
+
+ 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': 'Quixote/%s' % quixote.__version__,
+ }
+ for title, header in msg.items():
+ envname = 'HTTP_' + title.replace('-', '_').upper()
+ environ[envname] = header
+
+ stdin = StringIO(data)
+ qrequest = HTTPRequest(stdin, environ)
+ qresponse = self.publisher.process_request(qrequest)
+
+ # 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)
+ request.push(StreamProducer(qresponse.generate_body_chunks()))
+ request.done()
+
+
+def run(create_publisher, host='', port=80):
+ """Runs a Medusa HTTP server that publishes a Quixote
+ application.
+ """
+ server = http_server.http_server(host, port)
+ publisher = create_publisher()
+ handler = QuixoteHandler(publisher, server)
+ server.install_handler(handler)
+ asyncore.loop()
+
+
+if __name__ == '__main__':
+ from quixote.server.util import main
+ main(run)
diff --git a/pypers/europython05/Quixote-2.0/server/mod_python_handler.py b/pypers/europython05/Quixote-2.0/server/mod_python_handler.py
new file mode 100755
index 0000000..17f32ec
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/server/mod_python_handler.py
@@ -0,0 +1,106 @@
+"""
+$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/server/mod_python_handler.py $
+$Id: mod_python_handler.py 25893 2005-01-19 14:26:25Z dbinger $
+
+This needs testing.
+
+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.server.mod_python_handler
+ PythonOption quixote-publisher-factory quixote.demo.create_publisher
+ 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 (1) by Erno Kuusela
+<erno@iki.fi> and the Quixote 2 port comes from Clint.
+"""
+
+import sys
+from mod_python import apache
+from quixote import enable_ptl
+from quixote.publish import Publisher
+from quixote.config import Config
+from quixote.util import import_object
+
+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, **kwargs):
+ Publisher.__init__(self, package, **kwargs)
+ # may be overwritten
+ self.logger.error_log = self.__error_log = ErrorLog(self)
+ self.__apache_request = None
+
+ def log(self, msg):
+ if self.logger.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
+
+name2publisher = {}
+
+def run(publisher, req):
+ from quixote.http_request import HTTPRequest
+ request = HTTPRequest(apache.CGIStdin(req), apache.build_cgi_env(req))
+ response = publisher.process_request(request)
+ try:
+ response.write(apache.CGIStdout(req))
+ except IOError, err:
+ publisher.log("IOError while sending response ignored: %s" % err)
+ return apache.OK
+
+def handler(req):
+ opts = req.get_options()
+ try:
+ factory = opts['quixote-publisher-factory']
+ except KeyError:
+ apache.log_error('quixote-publisher-factory setting required')
+ return apache.HTTP_INTERNAL_SERVER_ERROR
+ pub = name2publisher.get(factory)
+ if pub is None:
+ factory_fcn = import_object(factory)
+ pub = factory_fcn()
+ name2publisher[factory] = pub
+ return run(pub, req)
+
diff --git a/pypers/europython05/Quixote-2.0/server/scgi_server.py b/pypers/europython05/Quixote-2.0/server/scgi_server.py
new file mode 100755
index 0000000..1f99ede
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/server/scgi_server.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/server/scgi_server.py $
+$Id: scgi_server.py 25579 2004-11-11 20:56:32Z nascheme $
+
+A SCGI server that uses Quixote to publish dynamic content.
+"""
+
+from scgi import scgi_server
+from quixote.http_request import HTTPRequest
+
+class QuixoteHandler(scgi_server.SCGIHandler):
+ def __init__(self, parent_fd, create_publisher, script_name=None):
+ scgi_server.SCGIHandler.__init__(self, parent_fd)
+ self.publisher = create_publisher()
+ self.script_name = script_name
+
+ def handle_connection(self, conn):
+ input = conn.makefile("r")
+ output = conn.makefile("w")
+ env = self.read_env(input)
+
+ if self.script_name is not None:
+ # mod_scgi doesn't know SCRIPT_NAME :-(
+ prefix = self.script_name
+ path = env['SCRIPT_NAME']
+ assert path[:len(prefix)] == prefix, (
+ "path %r doesn't start with script_name %r" % (path, prefix))
+ env['SCRIPT_NAME'] = prefix
+ env['PATH_INFO'] = path[len(prefix):] + env.get('PATH_INFO', '')
+
+ request = HTTPRequest(input, env)
+ response = self.publisher.process_request(request)
+ try:
+ response.write(output)
+ input.close()
+ output.close()
+ conn.close()
+ except IOError, err:
+ self.publisher.log("IOError while sending response "
+ "ignored: %s" % err)
+
+
+def run(create_publisher, host='', port=3000, script_name=None, max_children=5):
+ def create_handler(parent_fd):
+ return QuixoteHandler(parent_fd, create_publisher, script_name)
+ s = scgi_server.SCGIServer(create_handler, host=host, port=port,
+ max_children=max_children)
+ s.serve()
+
+
+def main():
+ from optparse import OptionParser
+ from quixote.util import import_object
+ parser = OptionParser()
+ parser.set_description(run.__doc__)
+ default_host = 'localhost'
+ parser.add_option(
+ '--host', dest="host", default=default_host, type="string",
+ help="Host interface to listen on. (default=%s)" % default_host)
+ default_port = 3000
+ parser.add_option(
+ '--port', dest="port", default=default_port, type="int",
+ help="Port to listen on. (default=%s)" % default_port)
+ default_maxchild = 5
+ parser.add_option(
+ '--max-children', dest="maxchild", default=default_maxchild,
+ type="string",
+ help="Maximum number of children to spawn. (default=%s)" %
+ default_maxchild)
+ parser.add_option(
+ '--script-name', dest="script_name", default=None, type="string",
+ help="Value of SCRIPT_NAME (only needed if using mod_scgi)")
+ default_factory = 'quixote.demo.create_publisher'
+ parser.add_option(
+ '--factory', dest="factory",
+ default=default_factory,
+ help="Path to factory function to create the site Publisher. "
+ "(default=%s)" % default_factory)
+ (options, args) = parser.parse_args()
+ run(import_object(options.factory), host=options.host, port=options.port,
+ script_name=options.script_name, max_children=options.maxchild)
+
+if __name__ == '__main__':
+ main()
diff --git a/pypers/europython05/Quixote-2.0/server/simple_server.py b/pypers/europython05/Quixote-2.0/server/simple_server.py
new file mode 100755
index 0000000..08d5149
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/server/simple_server.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/server/simple_server.py $
+$Id: simple_server.py 26472 2005-04-05 12:40:24Z dbinger $
+
+A simple, single threaded, synchronous HTTP server.
+"""
+from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+import urllib
+import quixote
+from quixote import get_publisher
+from quixote.http_request import HTTPRequest
+from quixote.util import import_object
+
+class HTTPRequestHandler(BaseHTTPRequestHandler):
+
+ required_cgi_environment = {}
+
+ def get_cgi_env(self, method):
+ env = dict(
+ SERVER_SOFTWARE="Quixote/%s" % quixote.__version__,
+ SERVER_NAME=self.server.server_name,
+ GATEWAY_INTERFACE='CGI/1.1',
+ SERVER_PROTOCOL=self.protocol_version,
+ SERVER_PORT=str(self.server.server_port),
+ REQUEST_METHOD=method,
+ REMOTE_ADDR=self.client_address[0],
+ SCRIPT_NAME='')
+ if '?' in self.path:
+ env['PATH_INFO'], env['QUERY_STRING'] = self.path.split('?')
+ else:
+ env['PATH_INFO'] = self.path
+ env['PATH_INFO'] = urllib.unquote(env['PATH_INFO'])
+ if self.headers.typeheader is None:
+ env['CONTENT_TYPE'] = self.headers.type
+ else:
+ env['CONTENT_TYPE'] = self.headers.typeheader
+ env['CONTENT_LENGTH'] = self.headers.getheader('content-length') or "0"
+ for name, value in self.headers.items():
+ header_name = 'HTTP_' + name.upper().replace('-', '_')
+ env[header_name] = value
+ accept = []
+ for line in self.headers.getallmatchingheaders('accept'):
+ if line[:1] in "\t\n\r ":
+ accept.append(line.strip())
+ else:
+ accept = accept + line[7:].split(',')
+ env['HTTP_ACCEPT'] = ','.join(accept)
+ co = filter(None, self.headers.getheaders('cookie'))
+ if co:
+ env['HTTP_COOKIE'] = ', '.join(co)
+ env.update(self.required_cgi_environment)
+ return env
+
+ def process(self, env):
+ request = HTTPRequest(self.rfile, env)
+ response = get_publisher().process_request(request)
+ try:
+ self.send_response(response.get_status_code(),
+ response.get_reason_phrase())
+ response.write(self.wfile, include_status=False)
+ except IOError, err:
+ print "IOError while sending response ignored: %s" % err
+
+ def do_POST(self):
+ return self.process(self.get_cgi_env('POST'))
+
+ def do_GET(self):
+ return self.process(self.get_cgi_env('GET'))
+
+
+def run(create_publisher, host='', port=80, https=False):
+ """Runs a simple, single threaded, synchronous HTTP server that
+ publishes a Quixote application.
+ """
+ if https:
+ HTTPRequestHandler.required_cgi_environment['HTTPS'] = 'on'
+ httpd = HTTPServer((host, port), HTTPRequestHandler)
+ publisher = create_publisher()
+ httpd.serve_forever()
+
+
+if __name__ == '__main__':
+ from quixote.server.util import get_server_parser
+ parser = get_server_parser(run.__doc__)
+ parser.add_option(
+ '--https', dest="https", default=False, action="store_true",
+ help=("Force the scheme for all requests to be https. "
+ "Not that this is for running the simple server "
+ "through a proxy or tunnel that provides real SSL "
+ "support. The simple server itself does not. "))
+ (options, args) = parser.parse_args()
+ run(import_object(options.factory), host=options.host, port=options.port,
+ https=options.https)
diff --git a/pypers/europython05/Quixote-2.0/server/twisted_server.py b/pypers/europython05/Quixote-2.0/server/twisted_server.py
new file mode 100755
index 0000000..911ec50
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/server/twisted_server.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/server/twisted_server.py $
+$Id: twisted_server.py 25711 2004-12-07 22:52:41Z nascheme $
+
+An HTTP server for Twisted that publishes a Quixote application.
+"""
+
+import urllib
+from twisted.protocols import http
+from twisted.web import server
+from twisted.python import threadable
+from twisted.internet import reactor
+from quixote.http_request import HTTPRequest
+
+
+class QuixoteFactory(http.HTTPFactory):
+ def __init__(self, publisher):
+ self.publisher = publisher
+ http.HTTPFactory.__init__(self, None)
+
+ def buildProtocol(self, addr):
+ protocol = http.HTTPFactory.buildProtocol(self, addr)
+ protocol.requestFactory = QuixoteRequest
+ return protocol
+
+
+class QuixoteRequest(server.Request):
+ def process(self):
+ 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 = HTTPRequest(self.content, environ)
+ qxresponse = self.channel.factory.publisher.process_request(qxrequest)
+ self.setResponseCode(qxresponse.status_code)
+ for name, value in qxresponse.generate_headers():
+ if name != 'Set-Cookie':
+ self.setHeader(name, value)
+ # Cookies get special treatment since it seems Twisted cannot handle
+ # multiple Set-Cookie headers.
+ for name, attrs in qxresponse.cookies.items():
+ attrs = attrs.copy()
+ value = attrs.pop('value')
+ self.addCookie(name, value, **attrs)
+ QuixoteProducer(qxresponse, self)
+
+ def create_environment(self):
+ """
+ Borrowed heavily from twisted.web.twcgi
+ """
+ # Twisted doesn't decode the path for us, so let's do it here.
+ 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',
+ 'SERVER_PROTOCOL': 'HTTP/1.1',
+ }
+
+ for env_var, header in [('ACCEPT_ENCODING', 'Accept-encoding'),
+ ('CONTENT_TYPE', 'Content-type'),
+ ('HTTP_COOKIE', 'Cookie'),
+ ('HTTP_REFERER', 'Referer'),
+ ('HTTP_USER_AGENT', 'User-agent')]:
+ value = self.getHeader(header)
+ if value is not None:
+ env[env_var] = value
+
+ client = self.getClient()
+ if client is not None:
+ env['REMOTE_HOST'] = client
+ ip = self.getClientIP()
+ if ip is not None:
+ env['REMOTE_ADDR'] = ip
+ _, _, 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 QuixoteProducer:
+ """
+ Produce the Quixote response for twisted.
+ """
+ def __init__(self, qxresponse, request):
+ self.request = request
+ self.size = qxresponse.get_content_length()
+ self.stream = qxresponse.generate_body_chunks()
+ request.registerProducer(self, 0)
+
+ def resumeProducing(self):
+ if self.request:
+ try:
+ chunk = self.stream.next()
+ except StopIteration:
+ self.request.unregisterProducer()
+ self.request.finish()
+ self.request = None
+ else:
+ self.request.write(chunk)
+
+ def pauseProducing(self):
+ pass
+
+ def stopProducing(self):
+ self.request = None
+
+ synchronized = ['resumeProducing', 'stopProducing']
+
+threadable.synchronize(QuixoteProducer)
+
+
+def run(create_publisher, host='', port=80):
+ """Runs a Twisted HTTP server server that publishes a Quixote
+ application."""
+ publisher = create_publisher()
+ factory = QuixoteFactory(publisher)
+ reactor.listenTCP(port, factory, interface=host)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ from quixote.server.util import main
+ main(run)
diff --git a/pypers/europython05/Quixote-2.0/server/util.py b/pypers/europython05/Quixote-2.0/server/util.py
new file mode 100755
index 0000000..69ed675
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/server/util.py
@@ -0,0 +1,32 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/server/util.py $
+$Id: util.py 26427 2005-03-30 18:03:32Z dbinger $
+
+Miscellaneous utility functions shared by servers.
+"""
+
+from optparse import OptionParser
+from quixote.util import import_object
+
+def get_server_parser(doc):
+ parser = OptionParser()
+ parser.set_description(doc)
+ default_host = 'localhost'
+ parser.add_option(
+ '--host', dest="host", default=default_host, type="string",
+ help="Host interface to listen on. (default=%s)" % default_host)
+ default_port = 8080
+ parser.add_option(
+ '--port', dest="port", default=default_port, type="int",
+ help="Port to listen on. (default=%s)" % default_port)
+ default_factory = 'quixote.demo.create_publisher'
+ parser.add_option(
+ '--factory', dest="factory",
+ default=default_factory,
+ help="Path to factory function to create the site Publisher. "
+ "(default=%s)" % default_factory)
+ return parser
+
+def main(run):
+ parser = get_server_parser(run.__doc__)
+ (options, args) = parser.parse_args()
+ run(import_object(options.factory), host=options.host, port=options.port)
diff --git a/pypers/europython05/Quixote-2.0/session.py b/pypers/europython05/Quixote-2.0/session.py
new file mode 100755
index 0000000..0241b7f
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/session.py
@@ -0,0 +1,567 @@
+"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/session.py $
+$Id: session.py 26524 2005-04-08 10:22:34Z dbinger $
+
+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, get_cookie, get_response, get_request, \
+ get_session
+from quixote.util import randbytes
+
+class NullSessionManager:
+ """A session manager that does nothing. It is the default session manager.
+ """
+
+ def start_request(self):
+ """
+ Called near the beginning of each request: after the HTTPRequest
+ object has been built, but before we traverse the URL or call the
+ callable object found by URL traversal.
+ """
+
+ def finish_successful_request(self):
+ """Called near the end of each successful request. Not called if
+ there were any errors processing the request.
+ """
+
+ def finish_failed_request(self):
+ """Called near the end of a failed request (i.e. a exception that was
+ not a PublisherError was raised.
+ """
+
+
+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):
+ """(session_class : class = Session, session_mapping : mapping = None)
+
+ Create a new session manager. There should be one session
+ manager per publisher, ie. one per process
+
+ 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):
+ """() -> [string]
+
+ Return the list of session IDs of sessions in this session manager.
+ """
+ return self.sessions.keys()
+
+ def sorted_keys(self):
+ """() -> [string]
+
+ Return the same list as keys(), but sorted.
+ """
+ keys = self.keys()
+ keys.sort()
+ return keys
+
+ def values(self):
+ """() -> [Session]
+
+ Return the list of sessions in this session manager.
+ """
+ return self.sessions.values()
+
+ def items(self):
+ """() -> [(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):
+ """(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):
+ """(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):
+ """(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):
+ """(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):
+ """(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):
+ """(session : Session)
+
+ Placeholder for subclasses that implement transactional
+ persistence: forget about saving changes to the current
+ session. Called by the publisher when a request fails,
+ ie. when it catches an exception other than PublishError.
+ """
+ pass
+
+ def commit_changes(self, session):
+ """(session : Session)
+
+ Placeholder for subclasses that implement transactional
+ persistence: commit changes to the current session. Called by
+ the publisher 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, id):
+ """(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(id)
+
+ def _get_session_id(self, config):
+ """() -> string
+
+ Find the ID of the current session by looking for the session
+ cookie in the request. Return None if no such cookie or the
+ cookie has been expired, otherwise return the cookie's value.
+ """
+ id = get_cookie(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):
+ # Create a new session object, with no ID for now - one will
+ # be assigned later if we save the session.
+ return self.new_session(None)
+
+ def get_session(self):
+ """() -> 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, use it to look up and return an existing session object.
+ If no session cookie is found, create a new session.
+
+ 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(config)
+ session = self.get(id) or self._create_session()
+ session._set_access_time(self.ACCESS_TIME_RESOLUTION)
+ return session
+
+ def maintain_session(self, session):
+ """(session : Session)
+
+ Maintain session information. This method is called 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()
+ 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(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, value, **attrs):
+ config = get_publisher().config
+ name = config.session_cookie_name
+ if config.session_cookie_path:
+ path = config.session_cookie_path
+ else:
+ path = get_request().get_environ('SCRIPT_NAME')
+ if not path.endswith("/"):
+ path += "/"
+ domain = config.session_cookie_domain
+ get_response().set_cookie(name, value, domain=domain,
+ path=path, **attrs)
+ return name
+
+ def set_session_cookie(self, session_id):
+ """(session_id : string)
+
+ Ensure that a session cookie with value 'session_id' will be
+ returned to the client via the response object.
+ """
+ self._set_cookie(session_id)
+
+ def revoke_session_cookie(self):
+ """
+ Remove the session cookie from the remote user's session by
+ resetting the value and maximum age in the response object. Also
+ remove the cookie from the request so that further processing of
+ this request does not see the cookie's revoked value.
+ """
+ cookie_name = self._set_cookie("", max_age=0)
+ if get_cookie(cookie_name) is not None:
+ del get_request().cookies[cookie_name]
+
+ def expire_session(self):
+ """
+ Expire the current session, ie. revoke the session cookie from
+ the client and remove the session object from the session
+ manager and from the current request.
+ """
+ self.revoke_session_cookie()
+ request = get_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, must_exist=False):
+ """(must_exist : boolean = false) -> bool
+
+ Return true if the 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 = get_cookie(config.session_cookie_name)
+ if id is None:
+ return False
+ if must_exist:
+ return self.has_session(id)
+ else:
+ return True
+
+ # -- Hooks into the Quixote main loop ------------------------------
+
+ def start_request(self):
+ """
+ Called near the beginning of each request: after the HTTPRequest
+ object has been built, but before we traverse the URL or call the
+ callable object found by URL traversal.
+ """
+ session = self.get_session()
+ get_request().session = session
+ session.start_request()
+
+ def finish_successful_request(self):
+ """Called near the end of each successful request. Not called if
+ there were any errors processing the request.
+ """
+ session = get_session()
+ if session is not None:
+ self.maintain_session(session)
+ self.commit_changes(session)
+
+ def finish_failed_request(self):
+ """Called near the end of a failed request (i.e. a exception that was
+ not a PublisherError was raised.
+ """
+ self.abort_changes(get_session())
+
+
+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)
+ _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, id):
+ self.id = id
+ self.user = None
+ self._remote_address = get_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):
+ """() -> boolean
+
+ Return true if this session contains any information that must
+ be saved.
+ """
+ return self.user or self._form_tokens
+
+ def is_dirty(self):
+ """() -> 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 False
+
+ def dump(self, file=None, header=True, deep=True):
+ 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)
+
+ def start_request(self):
+ """
+ Called near the beginning of each request: after the HTTPRequest
+ object has been built, but before we traverse the URL or call the
+ callable object found by URL traversal.
+ """
+ if self.user is not None:
+ get_request().environ['REMOTE_USER'] = str(self.user)
+
+ # -- Simple accessors and modifiers --------------------------------
+
+ def set_user(self, user):
+ self.user = user
+
+ def get_user(self):
+ return self.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):
+ """() -> 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):
+ """(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):
+ """(token : string)
+
+ Remove 'token' from the queue of outstanding tokens.
+ """
+ self._form_tokens.remove(token)
diff --git a/pypers/europython05/Quixote-2.0/setup.py b/pypers/europython05/Quixote-2.0/setup.py
new file mode 100755
index 0000000..7498982
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/setup.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+#$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/setup.py $
+#$Id: setup.py 26539 2005-04-11 15:47:33Z dbinger $
+
+# Setup script for Quixote
+
+__revision__ = "$Id: setup.py 26539 2005-04-11 15:47:33Z dbinger $"
+
+import sys, os
+from distutils import core
+from distutils.extension import Extension
+from ptl.qx_distutils import qx_build_py
+
+# a fast htmltext type
+htmltext = Extension(name="quixote.html._c_htmltext",
+ sources=["html/_c_htmltext.c"])
+
+# faster import hook for PTL modules
+cimport = Extension(name="quixote.ptl.cimport",
+ sources=["ptl/cimport.c"])
+
+kw = {'name': "Quixote",
+ 'version': "2.0",
+ 'description': "A highly Pythonic Web application framework",
+ 'author': "MEMS Exchange",
+ 'author_email': "quixote@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.html', 'quixote.ptl',
+ '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)
diff --git a/pypers/europython05/Quixote-2.0/test/__init__.py b/pypers/europython05/Quixote-2.0/test/__init__.py
new file mode 100755
index 0000000..bcc196b
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/test/__init__.py
@@ -0,0 +1,2 @@
+
+# Empty file to make this directory a package
diff --git a/pypers/europython05/Quixote-2.0/test/ua_test.py b/pypers/europython05/Quixote-2.0/test/ua_test.py
new file mode 100755
index 0000000..d1de207
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/test/ua_test.py
@@ -0,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)
diff --git a/pypers/europython05/Quixote-2.0/test/utest_request.py b/pypers/europython05/Quixote-2.0/test/utest_request.py
new file mode 100755
index 0000000..ba3f053
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/test/utest_request.py
@@ -0,0 +1,43 @@
+from sancho.utest import UTest
+from quixote.http_request import parse_cookies
+
+
+class ParseCookiesTest (UTest):
+
+ def check_basic(self):
+ assert parse_cookies('a') == {'a': ''}
+ assert parse_cookies('a = ') == {'a': ''}
+ assert parse_cookies('a = ""') == {'a': ''}
+ assert parse_cookies(r'a = "\""') == {'a': '"'}
+ assert parse_cookies('a, b; c') == {'a': '', 'b': '', 'c': ''}
+ assert parse_cookies('a, b=1') == {'a': '', 'b': '1'}
+ assert parse_cookies('a = ";, \t";') == {'a': ';, \t'}
+
+ def check_rfc2109_example(self):
+ s = ('$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"; '
+ 'Part_Number="Rocket_Launcher_0001"; $Path="/acme"')
+ result = {'Customer': 'WILE_E_COYOTE',
+ 'Part_Number': 'Rocket_Launcher_0001',
+ }
+ assert parse_cookies(s) == result
+
+ def check_other(self):
+ s = 'PREF=ID=0a06b1:TM=108:LM=1069:C2COFF=1:S=ETXrcU'
+ result = {'PREF': 'ID=0a06b1:TM=108:LM=1069:C2COFF=1:S=ETXrcU'}
+ assert parse_cookies(s) == result
+ s = 'pageColor=White; pageWidth=990; fontSize=12; fontFace=1; E=E'
+ assert parse_cookies(s) == {'pageColor': 'White',
+ 'pageWidth': '990',
+ 'fontSize': '12',
+ 'fontFace': '1',
+ 'E': 'E'}
+ s = 'userid="joe"; QX_session="58a3ced39dcd0d"'
+ assert parse_cookies(s) == {'userid': 'joe',
+ 'QX_session': '58a3ced39dcd0d'}
+
+ def check_invalid(self):
+ parse_cookies('a="123')
+ parse_cookies('a=123"')
+
+if __name__ == "__main__":
+ ParseCookiesTest()
diff --git a/pypers/europython05/Quixote-2.0/util.py b/pypers/europython05/Quixote-2.0/util.py
new file mode 100755
index 0000000..1835be3
--- /dev/null
+++ b/pypers/europython05/Quixote-2.0/util.py
@@ -0,0 +1,390 @@
+"""quixote.util
+$HeadURL: svn+ssh://svn.mems-exchange.org/repos/trunk/quixote/util.py $
+$Id: util.py 26523 2005-04-08 10:20:19Z dbinger $
+
+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 directory.
+
+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 rfc822 import formatdate
+import quixote
+from quixote import errors
+from quixote.directory import Directory
+from quixote.html import htmltext, TemplateIO
+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 import_object(name):
+ i = name.rfind('.')
+ if i != -1:
+ module_name = name[:i]
+ object_name = name[i+1:]
+ __import__(module_name)
+ return getattr(sys.modules[module_name], object_name)
+ else:
+ __import__(name)
+ return sys.modules[name]
+
+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=False,
+ 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
+ # Decide the Content-Type of the file
+ guess_mime, guess_enc = mimetypes.guess_type(os.path.basename(path),
+ strict=False)
+ self.mime_type = mime_type or guess_mime or 'text/plain'
+ self.encoding = encoding or guess_enc or None
+ self.cache_time = cache_time
+ self.follow_symlinks = follow_symlinks
+
+ def __call__(self):
+ if not self.follow_symlinks and os.path.islink(self.path):
+ raise errors.TraversalError(private_msg="Path %r is a symlink"
+ % self.path)
+ request = quixote.get_request()
+ response = quixote.get_response()
+
+ if self.cache_time is None:
+ response.set_expires(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
+ response.set_expires(seconds=self.cache_time)
+
+ 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
+ response.set_status(304)
+ return ''
+
+ # Set the Content-Type for the response and return the file's contents.
+ response.set_content_type(self.mime_type)
+ if self.encoding:
+ response.set_header("Content-Encoding", self.encoding)
+
+ response.set_header('Last-Modified', last_modified)
+
+ return FileStream(open(self.path, 'rb'), stat.st_size)
+
+
+class StaticDirectory(Directory):
+
+ """
+ Wrap a filesystem directory containing static files as a Quixote directory.
+ """
+
+ _q_exports = ['']
+
+ FILE_CLASS = StaticFile
+
+ def __init__(self, path, use_cache=False, list_directory=False,
+ follow_symlinks=False, cache_time=None, file_class=None,
+ index_filenames=None):
+ """(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 _render_header(self, title):
+ r = TemplateIO(html=True)
+ r += htmltext('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 '
+ 'Transitional//EN" '
+ '"http://www.w3.org/TR/REC-html40/loose.dtd">')
+ r += htmltext('<html>')
+ r += htmltext('<head><title>%s</title></head>') % title
+ r += htmltext('<body>')
+ r += htmltext("<h1>%s</h1>") % title
+ return r.getvalue()
+
+ def _render_footer(self):
+ return htmltext('</body></html>')
+
+ def _q_index(self):
+ """
+ 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(name)
+ except errors.TraversalError:
+ continue
+ if not isinstance(obj, StaticDirectory) and callable(obj):
+ return obj()
+ r = TemplateIO(html=True)
+ if self.list_directory:
+ r += self._render_header('Index of %s' % quixote.get_path())
+ template = htmltext('<a href="%s">%s</a>%s\n')
+ r += htmltext('<pre>')
+ r += 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 ""
+ r += template % (urllib.quote(filename), filename, marker)
+ r += htmltext('</pre>')
+ r += self._render_footer()
+ else:
+ r += self._render_header('Directory listing denied')
+ r += htmltext('<p>This directory does not allow its contents '
+ 'to be listed.</p>')
+ r += self._render_footer()
+ return r.getvalue()
+
+ def _q_lookup(self, 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):
+ item = self.__class__(item_filepath, self.use_cache,
+ self.list_directory,
+ self.follow_symlinks, self.cache_time,
+ self.file_class, self.index_filenames)
+
+ 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=False):
+ self.location = location
+ self.permanent = permanent
+
+ def _q_lookup(self, component):
+ return self
+
+ def __call__(self):
+ return quixote.redirect(self.location, self.permanent)
+
+
+def dump_request(request=None):
+ if request is None:
+ request = quixote.get_request()
+ """Dump an HTTPRequest object as HTML."""
+ row_fmt = htmltext('<tr><th>%s</th><td>%s</td></tr>')
+ r = TemplateIO(html=True)
+ r += htmltext('<h3>form</h3>'
+ '<table>')
+ for k, v in request.form.items():
+ r += row_fmt % (k, v)
+ r += htmltext('</table>'
+ '<h3>cookies</h3>'
+ '<table>')
+ for k, v in request.cookies.items():
+ r += row_fmt % (k, v)
+ r += htmltext('</table>'
+ '<h3>environ</h3>'
+ '<table>')
+ for k, v in request.environ.items():
+ r += row_fmt % (k, v)
+ r += htmltext('</table>')
+ return r.getvalue()
+
+def get_directory_path():
+ """() -> [object]
+ Return the list of traversed instances.
+ """
+ path = []
+ frame = sys._getframe()
+ while frame:
+ if frame.f_code.co_name == '_q_traverse':
+ self = frame.f_locals.get('self', None)
+ if path[:1] != [self]:
+ path.insert(0, self)
+ frame = frame.f_back
+ return path