Commits

Anonymous committed 43ece70

Refactored and culled documentation

  • Participants
  • Parent commits 847f554
  • Branches mainline-refactor

Comments (0)

Files changed (7)

docs/Paste.txt

-Python Paste
-============
-
-This is a WSGI_ version of WebKit.  In addition to supporting WSGI, it
-also is a simplification and refactoring of Webware.
-
-.. _WSGI: http://www.python.org/peps/pep-0333.html
-
-License
--------
-
-Paste is distributed under the `Python Software Foundation`__
-license.  This is a BSD/MIT-style license.
-
-.. __: http://www.python.org/psf/license.html
-
-Quick Start
------------
-
-First, grab WSGIUtils, at http://www.owlfish.com/software/wsgiutils/,
-or download it directly from
-http://www.owlfish.com/software/wsgiutils/downloads/WSGI%20Utils-0.5.tar.gz
-
-You can use other servers with Paste, but WSGIUtils is pretty easy
-and built on SimpleHTTPServer.  Run ``python setup.py install`` to
-install it.
-
-Right now it's best NOT to install Paste with ``setup.py``, but just
-to run it out of the checkout.  An easy way to do that::
-
-    ./scripts/app-setup create webkit_zpt /path/to/put/files
-    cd /path/to/put/files
-    /path/to/paste/scripts/server -v
-
-And it will be running a server on http://localhost:8080/
-
-Compatibility
--------------
-
-This system is intended to be largely compatible with current Webware
-applications.  This is only an intention; use with real-world
-applications will help us refine this compatibility.
-
-Some parts that aren't being brought over:
-
-* Configuration.  Right now configuration is a little inconsistent.
-
-* Contexts.  These were half-formed in Webware, and didn't really
-  provide much benefit.  There are much more general and useful ways
-  to deal with URLs.
-
-* URL introspection.  I tried to get some of it, but there's way too
-  many ways of refactoring the URL in Webware, and I didn't deal with
-  all of them.  The environment (``request.environ()``) may look
-  different than in Webware -- it matches the WSGI expectations.  It's
-  certainly possible -- and hopefully clearer -- to do introspection
-  in Paste, but it might not be backward compatible (but still file
-  bugs if you find problems).
-
-Discussion
-----------
-
-Discussion should take place on the Webware mailing list,
-webware-discuss@lists.sf.net and webware-devel@list.sf.net
-
-Configuration
--------------
-
-Configuration is done with normal Python files.  Global variables
-become configuration values.  So a configuration file might look
-like::
-
-    host = 'localhost'
-    port = 8080
-    publish_dir = '/path/to/dir'
-    database_name = 'app_data'
-
-And so on.  There's a default configuration file in
-``paste/default_config.conf`` which will also serve as
-documentation.  Your configuration overrides those values.  The
-extension is arbitrary at this point.
-
-Some important configuration values:
-
-``config_file``:
-    Load the given configuration file.
-
-``server``: 
-    The (string) name of the server you want to use.  Right now there
-    is ``"wsgiutils"`` and ``"twisted"``.
-
-``publish_dir``:
-    The base directory to find Webware servlets.  (In the future other
-    frameworks will also be supported.)
-
-``debug``:
-    If true, then errors will be displayed in the browser.
-
-``reload``:
-    If true, then we'll monitor for any changed modules and restart
-    the server if those are found.
-
-``verbose``:
-    If true, talk a lot.
-
-Use
----
-
-These are some manual ways to set up a WSGI server, but
-``scripts/server`` is the easiest.  
-
-This script takes takes options that turn into configuration
-parameters.  If there's a ``port`` configuration value you can also
-use the ``--port=X`` command-line option.  ``-`` is also replaced with
-``_``.
-
-Another option is ``-f config_file.conf`` which loads the given
-configuration file.  This might be the only option you need, if you
-put all the other settings in the configuration file.
-
-Note that ``--verbose``, ``--reload`` and ``--debug`` do not require
-an argument.
-
-Multiple Applications / Manual Setup
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Right now integration of multiple applications into a single server is
-kind of an unsolved problem, but manual application setup would be the
-way to go about it...
-
-CGI
-~~~
-
-The ``cgiserver`` script is included.  It's not particularly good,
-but it's there for you.  To use it, you'll set up your own CGI script,
-like::
-
-    #!/usr/bin/env python
-    # maybe import sys and modify sys.path
-    from paste import cgiserver
-    from paste.webkit import wsgiwebkit
-    app = wsgiwebkit.webkit('/path/to/app')
-    cgiserver.run_with_cgi(app)
-
-Twisted
-~~~~~~~
-
-This package includes ``twisted_wsgi``, which is a WSGI adapter
-written by Peter Hunt.  The canonical location for this file is:
-http://st0rm.hopto.org:8080/wsgi/ (note -- Twisted 2.0 will include
-native WSGI support; there's been reports of bugs in the version we
-are currently shipping).
-
-This is closer to the normal Webware environment than CGI.  To set up
-this server use::
-
-    #!/usr/bin/env python
-    # maybe import sys and modify sys.path
-    from paste import twisted_wsgi
-    from paste.webkit import wsgiwebkit
-    app = wsgiwebkit.webkit('/path/to/app')
-    twisted_wsgi.serve_application(app, 8080)
-
-Note, the twisted server is dependent on wsgiref which can be found at
-http://cvs.eby-sarna.com/wsgiref
-
-
-Multi-threaded
-~~~~~~~~~~~~~~
-
-WSGIUtils_ includes a SimpleHTTPServer_-based multi-threaded server.
-This is an architecture very similar to Webware's AppServer.  Twisted
-may be the better approach, but who knows?
-
-.. _WSGIUtils: http://www.owlfish.com/software/wsgiutils/
-.. _SimpleHTTPServer: http://python.org/doc/current/lib/module-SimpleHTTPServer.html
-
-To set up this server use::
-
-    #!/usr/bin/env python
-    from wsgiutils import wsgiServer
-    from paste.webkit import wsgiwebkit
-    app_root='/path/to/app'
-    app = wsgiwebkit.webkit(app_root)
-    server = wsgiServer.WSGIServer(('127.0.0.1', 8080), {'/': app})
-    server.serve_forever()
-
-Interactive
-~~~~~~~~~~~
-
-This allows you to test an application without running any web server.
-While you can use this several ways (e.g., for acceptance tests), this
-is a simple way::
-
-    #!/usr/bin/env python
-    # maybe import sys and modify sys.path
-    from paste import wsgilib
-    from paste.webkit import wsgiwebkit
-    app = wsgiwebkit.webkit('/path/to/app')
-    def run(url):
-        print wsgilib.interactive(application, url)
-    if __name__ == '__main__':
-        import sys
-        url = sys.argv[1]
-        run(url)
-
-Then you run this script with the URL you want to access; the headers
-and page will be printed out.  This can also be used at the
-interactive prompt, e.g., ``python -i interactiveapp.py``, and you can
-use pdb_ or other debugging tools with this.
-
-.. _pdb: http://python.org/doc/current/lib/module-pdb.html
-
-Comparison of architectures
----------------------------
-
-WSGI WebKit's implementation of ``Page`` (and ``HTTPServlet``)
-implements a ``__call__`` method, which is all that is required to
-make a servlet into a WSGI application.  The __call__ method creates
-the transaction (``wktransaction.Transaction``), which in turn creates
-the request (``wkrequest.HTTPRequest``) and response
-(``wkresponse.HTTPResponse``).  Then there's a bit of back-and-forth 
-
-There are also session and application objects, which are largely
-dummy objects that provide some of the interface that was previously
-implemented by those objects.  In WSGI WebKit these features
-(forwarding and sessions) are implemented in middleware.
-
-This is in contrast to normal Webware, where the ``AppServer`` starts
-the connection, hands it off to ``Application``, which creates
-``Transaction``, ``HTTPRequest``, and ``HTTPResponse``, then finds the
-servlet and runs it with those objects.

docs/TodoTutorial.txt

-+++++++++++++++++
-To-Do: A Tutorial
-+++++++++++++++++
-
-:author: Ian Bicking <ianb@colorstudy.com>
-:revision: $Rev$
-:date: $LastChangedDate$
-
-.. contents::
-
-.. comment (about this document)
-
-    This document is meant to be processed with
-    paste/tests/doctest_webapp.py, which assembles the file and
-    provides a degree of testing.  The pages inlined aren't current
-    tested, and so must be inspected by eye after the document is
-    assembled.
-
-Introduction and audience
-=========================
-
-This tutorial is intended for people interested in developing
-applications in Paste.
-
-This is a tutorial for building a simple to-do list application using
-`Python Paste <http://pythonpaste.org>`_, SQLObject_, and `Zope Page Templates`_.  You can view the
-completed application in the repository at ``examples/todo_sql`` or
-view the repository online at
-http://svn.w4py.org/Paste/trunk/examples/todo_sql/
-
-.. _SQLObject: http://sqlobject.org
-.. _Zope Page Templates: http://www.zope.org/DevHome/Wikis/DevSite/Projects/ZPT/FrontPage
-
-.. include:: include/contact.txt
-
-Setting up the files
-====================
-
-First, get the latest version of Paste with::
-
-    svn co http://svn.pythonpaste.org/Paste/trunk Paste
-    cd Paste
-    python build-pkg.py
-
-That last line downloads a lot of dependencies and installs them
-locally to Paste.  You can re-run this command to update these
-libraries as well.
-
-.. note::
-
-    We're doing all this in the Python interpreter, even though you'd normally do
-    some of this in the shell.  This way the authors of this tutorial
-    can use something called doctest_, which allows this tutorial to
-    be tested Python in an automated way.
-
-    .. _doctest: http://python.org/doc/current/lib/module-doctest.html
-
-.. comment (setup doctests)
-
-    >>> from paste.tests.doctest_webapp import *
-
-Let's start out quickly.  We'll be installing the application in
-``/var/www/example-builds/todo_sql``:
-
-.. comment (setup)
-
-    >>> BASE = '/var/www/example-builds/todo_sql'
-    >>> import sys
-    >>> sys.path.append('/path/to/Paste')
-    >>> clear_dir(BASE)
-    >>> run("paster create --template=webkit_zpt %s" % BASE)
-    >>> os.chdir(BASE)
-    >>> ls(recurse=True)
-    __init__.py
-    server.conf
-    sitepage.py
-    templates/
-      generic_error.pt
-      index.pt
-      standard_template.pt
-    web/
-      __init__.py
-      index.py
-      static/
-        stylesheet.css
-
-::
-
-    $ export PYTHONPATH=/path/to/Paste:$PYTHONPATH
-    $ BASE=/var/www/example-builds/todo_sql
-    $ paster create --template=webkit_zpt $BASE
-    $ cd $BASE
-    $ ls -R
-    .:
-    __init__.py
-    server.conf
-    sitepage.py
-    templates/
-    web/
-
-    ./templates:
-    generic_error.pt
-    index.pt
-    standard_template.pt
-    
-    ./web:
-    index.py
-    __init__.py
-    static/
-
-    ./web/static:
-    stylesheet.css
-
-I want to give a quick explanation of the file structure and the
-nature of the files.
-
-``__init__.py``:
-    This is a special file that Python looks for in a "package".  It
-    needs to exist for Python to allow you to import modules from the
-    directory.  This module is also the first thing imported in our
-    application, so any setup code can go here.
-
-``server.conf``:
-    This contains the main configuration of our application.  This is
-    a python-format file, and all the variables assigned in it are
-    configuration keys.
-
-``sitepage.py``:
-    This module contains an abstract class that all our servlets
-    subclass from.  Each servlet is a class, and a page in our web
-    application; by making them all subclass from a single class
-    (``SitePage``) we can provide functionality that is global to our
-    application.
-
-``templates/``:
-    This contains all the "templates" -- there is a template for every
-    page in our application.
-
-``templates/standard_template.pt``:
-    This defines the look of our application -- changes we make here
-    affect all pages in the application.
-
-``templates/index.pt``:
-    This is a template for a single servlet (the ``index`` servlet, in
-    ``web/index.py``).
-
-``templates/generic_error.pt``:
-    This template is used when we just want to output a simple
-    message to the user, wrapped in the site look.
-
-``web/``:
-    This directory contains your servlets.  Actually, any file in this
-    directory will be served up -- ``.py`` files are expected to
-    contain servlets, and most other files are served up as
-    themselves.
-
-``web/__init__.py``:
-    Unlike the other ``.py`` files, this one is special, and can
-    contain "hooks" called during the URL parsing.
-
-``web/index.py``:
-    This is a simple example servlet.  Anything named ``index`` is
-    also used as the default page (like ``index.html``).  Paste
-    mostly ignores extensions when finding pages, so ``/index`` can
-    refer to ``index.py``, ``index.html``, or any other page named
-    ``index`` regardless of extension.
-
-``web/static/``:
-    This contains files that don't have any dynamic content, like
-    images and Javascript.  Based on deployment, these files could be
-    served up by Apache or another web server without Paste being
-    involved at all (with some CPU savings), so we keep them
-    separated.
-
-``web/static/stylesheet.css``:
-    A simple stylesheet for your application.
-
-
-Running the application
------------------------
-
-It's just the barest example application, but we can still run it and
-get some basic output.  Change into the ``$BASE`` directory and run::
-
-    $ path/to/paste/scripts/paster serve
-
-This will run a server on http://localhost:8080
-
-.. comment (a psuedo-server for doctest)
-
-    >>> set_default_app(make_app(BASE), 'http://localhost:8080')
-    >>> show('/', 'default-frontpage')
-
-.. raw:: html
-   :file: resources/TodoTutorial/default-frontpage.html
-
-You can run the server in different ways by passing command-line
-options to ``paster serve``, or by editing ``server.conf``.  The
-default setup only serves connections from the local computer
-(use ``host='0.0.0.0'`` to allow connections from anywhere), and
-serves on port 8080 (use ``port = 80`` to make it the default
-server).  However, this default server (WSGIUtils) is not suggested
-for production use.  Twisted includes a better WSGI server, and there
-are a variety of ways to connect your application to Apache or
-IIS.  We'll ignore those deployment issues now, though, and just use
-localhost:8080.
-
-
-Looking at servlets
--------------------
-
-The idea of a servlet in Paste is taken from Java, but the
-similarity isn't that great.  Let's look at the ``index.py`` servlet
-we showed you:
-
-.. comment (create index.py highlighted)
-
-   >>> show_file('web/index.py', 'before_editing')
-
-.. raw:: html
-   :file: resources/TodoTutorial/web/index.py.before_editing.gen.html
-
-A few things to note:
-
-``from todo_sql.sitepage import SitePage``
-    ``todo_sql`` is the package you just created, and ``sitepage`` is
-    the ``sitepage.py`` file, and ``SitePage`` is a class.
-    ``SitePage`` is where you'll put all your global customizations, a
-    kind of mini-framework for your application.
-
-``class index(SitePage):``
-    Every servlet must have a class with the same name as the file that
-    contains it.  This file gets created for every request, so you
-    can assign attributes (like ``self.list`` or whatever) and they'll
-    only last as long as one request.
-
-``def setup(self):``
-    The ``setup`` method is called at the beginning of every request.
-    This is where you should put together any objects that are
-    required to process the request, and possibly perform actions
-    (say, in response to a form submittal).  The actual content will
-    be rendered later.
-
-``self.options.vars``
-    ``self.options`` is an object where you store data for use by your
-    template.  The template can access any attributes of the servlet,
-    but assigning to ``self.options`` makes it explicit that the value
-    is intended for the template.
-
-``self.request()``
-    This is how, in the servlet, you access the request object.  The
-    request object has several methods we'll look at later -- the most
-    useful being ``req.field(name, default)``, which retrieves the
-    given field.
-
-``req.environ()``
-    This is the WSGI environment.  You won't know what that is, except
-    that it's much like the environment passed to a CGI script.  It
-    contains things such as ``SCRIPT_NAME``, ``REMOTE_ADDR``, etc.
-    It's a dictionary, so we're just getting a sorted list of the keys
-    and values from that dictionary.
-
-The rest should be self-explanatory by now.  
-
-Looking at templates
---------------------
-
-Now let's look at the template that goes with the servlet.  Every
-template is in ``template/servlet_name.pt``. The ``.pt`` stands for
-"Page Template".
-
-.. comment (create index.py highlighted)
-
-   >>> show_file('templates/index.pt', 'before_editing')
-
-.. raw:: html
-   :file: resources/TodoTutorial/templates/index.pt.before_editing.gen.html
-
-OK, so a bit of explanation...
-
-``<html metal:use-macro="here/standard_template.pt/macros/page">``
-    ``metal:use-macro`` says that this page will define slots that
-    will be inserted into the ``page`` macro in
-    ``standard_template.pt``. This is another way of saying that
-    ``standard_template.pt`` gives the look and layout of the page.
-    This is different than many templating systems, where you include a
-    header and footer -- ``standard_template.pt`` can rearrange all
-    your slots in whichever way it chooses, and is itself a complete
-    page.  We'll talk about slots next...
-
-    Notice the ``metal:`` namespace.  METAL is the Page Template
-    "macro" system; TAL is the substitution system.  Each uses special
-    tags with the given namespace.
-
-``<metal:body fill-slot="body">``
-    We could have any text we wanted before this tag and after
-    ``<html>``, but for brevity we've left those out.  If you were
-    using a WYSIWYG tool it would *require* a valid page, so this is
-    all quite compatible with WYSIWYG tools.
-
-    ``fill-slot`` says that everything inside this tag will be
-    inserted into the ``body`` slot of ``standard_template.pt``.  
-    Notice that we call the tag ``<metal:body>`` -- by putting it in
-    the ``metal:`` namespace it will be eliminated from the final
-    page, and we don't have to specify ``metal:fill-slot``.  This is
-    because the ``<body>`` tag is actually located in
-    ``standard_template.pt``.
-
-``<tr tal:repeat="var options/vars">``
-    ``tal:repeat`` is like a Python ``for`` loop.  It means the
-    ``<tr>`` tag will be repeated, looping over ``options/vars`` (and
-    assigning to ``var``).
-
-    ``options/vars`` isn't really a proper Python expression -- TAL
-    has its own kind of shortened expressions.  A ``/`` is a kind of
-    generic accessor -- it can mean ``options.vars``,
-    ``options['vars']`` or ``options.vars()``, depending on what kind
-    of object ``options`` is.  This is a way of hiding some of the
-    complexity of accessing objects from template authors.  
-
-    Note also that each directive operates on the tag itself.  So
-    ``tal:repeat`` operates on the ``<tr>`` tag and all its contents.
-
-``<td tal:content="python: var[0]">Var Name</td>``
-    This is a substitution -- ``tal:content`` replaces the contents of
-    the tag with the given expression.  The current contents (``Var
-    Name``) are simply thrown away; they are there for documentation
-    at most.  If you wanted to leave them out, you could use the XHTML
-    notation of ``<td tal:content="python: var[0]" />``
-
-    Here we need a Python expression, because ``var/0`` would be like
-    ``var['0']``, and we need to access the index zero, not ``"0"``.
-    We indicate that it's a Python expression with the ``python:``
-    prefix.
-
-That's a quick introduction to Page Templates.  We'll wait to look at
-``standard_template.pt``.
-
-
-The sample application
-======================
-
-We'll be making a simple to-do list application.  The application will
-have multiple lists, and each list has multiple items.
-
-
-Using a database
-----------------
-
-.. note::
-
-    These SQLObject classes could be considered your "model", but
-    really your model is whatever you want it to be. There's no
-    formal concept of a model in this tutorial.
-
-The first thing we'll set up is a database connection.  This example
-uses SQLObject_, which is a object-relational mapper. Basically, it
-makes your database tables look like Python classes, with each row in
-those tables as an instance of those classes.
-
-We'll be creating two tables:
-
-+-------------------------------+
-| todo_list                     |
-+=============+=================+
-| id          | INT PRIMARY KEY |
-+-------------+-----------------+
-| description | TEXT            |
-+-------------+-----------------+
-
-+---------------------------------+
-| todo_item                       |
-+==============+==================+
-| id           | INT PRIMARY KEY  |
-+--------------+------------------+
-| todo_list_id | INT NOT NULL     |
-+--------------+------------------+
-| description  | TEXT NOT NULL    |
-+--------------+------------------+
-| done         | BOOLEAN NOT NULL |
-+--------------+------------------+
-
-The actual types will differ somewhat depending on what database we'll
-be using.  PostgreSQL, for instance, has a ``BOOLEAN`` data type, but
-on MySQL we'll just use ``INT``.  SQLObject, however, handles all this
-for us.  We just define the classes:
-
-.. _SQLObject: http://sqlobject.org
-
-.. comment (create db.py)
-
-    >>> create_file('db.py', 'v1', r"""
-    ... from sqlobject import *
-    ...
-    ... class TodoList(SQLObject):
-    ...
-    ...     description = StringCol(notNull=True)
-    ...     items = MultipleJoin('TodoItem')
-    ... 
-    ... class TodoItem(SQLObject):
-    ... 
-    ...     todo_list = ForeignKey('TodoList')
-    ...     description = StringCol(notNull=True)
-    ...     done = BoolCol(notNull=True, default=False)
-    ... """)
-
-.. raw:: html
-   :file: resources/TodoTutorial/db.py.v1.gen.html
-
-Some things to notice.  First, all the symbols and classes you see in
-this example were imported with ``from sqlobject import *``.
-We create two new classes -- ``TodoList`` and ``TodoItem``.  
-
-Each table is a class (that subclasses from ``SQLObject``).  We use
-the normal naming style of classes (StudlyCaps), but SQLObject
-translates this to an underscore style for the database.  
-
-Each column is an attribute of the class, using special classes to
-indicate the type (``StringCol``, ``BoolCol``, etc).  Keyword
-arguments are used to indicate options such as whether ``NULL`` is
-allowed (by default it is), and whether there's a default value
-(SQLObject doesn't treat NULL as a default). You could also give the
-size of the text fields (by default they are all ``TEXT`` -- in these
-modern days it's not necessary to specify the length of your fields).
-
-SQLObject knows about several databases -- PostgreSQL, MySQL, and
-SQLite are especially well supported.  It can hide much of the
-specifics of the database, including generating the ``CREATE``
-statement.  We'll use SQLite in this example, but it's pretty trivial
-to use another backend.
-
-First, we have to add configuration to our ``server.conf`` file.
-We'll add these lines::
-
-    import os
-    database = 'sqlite:%s/data.db' % os.path.dirname(__file__)
-
-We use ``os.path.dirname(__file__)`` to put the database file in the
-same directory as ``server.conf``.  You could also use something
-like::
-
-    database = 'mysql://user:passwd@localhost/dbname'
-    # or:
-    database = 'postgresql://user:password@localhost/dbname'
-
-.. comment (change server.conf)
-
-    >>> change_file('server.conf', [('insert', 2, r"""import os
-    ... database = 'sqlite:%s/data.db' % os.path.dirname(__file__)
-    ...
-    ... """)])
-
-.. comment (put sqlobject-admin in path)
-
-Now we have to actually create the tables; we'll use the
-``sqlobject-admin`` script to help us with this.  First::
-
-.. note::
-
-   SQLObject is, among other things, a database abstraction layer.  So
-   it tries to use as many of the capabilities as it can of the
-   underlying database, but it glosses over other issues.  In this case,
-   the ``todo_list_id`` column is a foreign key, but the SQL we show
-   is for SQLite, and SQLite doesn't have foreign key constraints.  On
-   PostgreSQL the ``CREATE`` statement would look different.
-
-.. comment (Run it)
-
-    >>> run('sqlobject-admin sql -f server.conf -m todo_sql.db')
-    CREATE TABLE todo_item (
-        id INTEGER PRIMARY KEY,
-        done TINYINT NOT NULL,
-        todo_list_id INT,
-        description TEXT NOT NULL
-    );
-    CREATE TABLE todo_list (
-        id INTEGER PRIMARY KEY,
-        description TEXT NOT NULL
-    );
-
-::
-
-    $ sqlobject-admin sql -f server.conf -m todo_sql.db
-    CREATE TABLE todo_item (
-        id INTEGER PRIMARY KEY,
-        done TINYINT NOT NULL,
-        todo_list_id INT,
-        description TEXT NOT NULL
-    );
-    CREATE TABLE todo_list (
-        id INTEGER PRIMARY KEY,
-        description TEXT NOT NULL
-    );
-
-
-You have to be sure ``/var/www/example-builds`` is in your ``$PYTHONPATH``
-so that your ``todo_sql/`` directory is a module that Python can load.  ``-m
-todo_sql.db`` tells ``sqlobject-admin`` to load that module, look for
-SQLObject classes, and use them for its command -- in this case
-(``sqlobject-admin sql``) showing the ``CREATE`` statements.  Now, let's
-actually create the tables::
-
-    $ sqlobject-admin create -f server.conf -m todo_sql.db
-
-.. comment (do it)
-
-    >>> run('sqlobject-admin create -f server.conf -m todo_sql.db')
-
-It prints nothing on success. Or, use ``-v``, or even ``-vv``, to get
-more messages.
-
-Now, let's put in just a little data for later::
-
-    $ sqlobject-admin execute -f server.conf "INSERT INTO
-      todo_list (description) VALUES ('test 1')"
-
-.. comment (do it)
-
-    >>> run('''sqlobject-admin execute -f server.conf "INSERT INTO
-    ...        todo_list (description) VALUES ('test 1')" ''')
-
-
-Creating a servlet
-------------------
-
-For now, we'll reuse the ``index.py`` servlet, adding this code:
-
-.. comment (make code)
-
-    >>> create_file('web/index.py', 'v1', r"""
-    ... from todo_sql.sitepage import SitePage
-    ... from todo_sql.db import *
-    ...
-    ... class index(SitePage):
-    ...
-    ...     def setup(self):
-    ...         self.options.title = 'List of Lists'
-    ...         self.options.lists = list(TodoList.select())
-    ... """)
-
-.. raw:: html
-   :file: resources/TodoTutorial/web/index.py.v1.gen.html
-
-There's one new item here::
-
-    self.options.list = list(TodoList.select())
-
-``TodoList.select()`` creates a select query -- it *doesn't* actually
-access the database, but it will when we first iterate over it (like
-in a ``for`` loop).  In this case, we use ``list()`` to turn it into a
-list.  We could give arguments to ``.select()`` to add a ``WHERE``
-clause to the ``SELECT`` statement.  Here's the servlet that goes with it:
-
-.. comment (make code)
-
-    >>> create_file('templates/index.pt', 'v1', r"""
-    ... <html metal:use-macro="here/standard_template.pt/macros/page">
-    ... <metal:body fill-slot="body">
-    ...
-    ... <ul>
-    ...  <li tal:repeat="list options/lists">
-    ...   <a tal:attributes="href python: '%s/view_list?id=%s' % (servlet.baseURL, list.id)"
-    ...    tal:content="list/description">list</a></li>
-    ... </ul>
-    ...
-    ... <p tal:condition="not: options/lists">
-    ...  There are no lists to display
-    ... </p>
-    ...
-    ... <form action="./edit_list" method="POST">
-    ...  Create a new list:<br>
-    ...  <input type="hidden" name="id" value="new">
-    ...  <input type="hidden" name="_action_" value="save">
-    ...  <input type="text" name="description">
-    ...  <input type="submit" value="Create!!!!">
-    ... </form>
-    ... </metal:body>
-    ... </html>
-    ... """)
-
-.. raw:: html
-   :file: resources/TodoTutorial/templates/index.pt.v1.gen.html
-
-We also have to add a little magic to ``web/__init__.py`` to configure
-SQLObject. (This will be improved in the future.)
-
-.. comment (make config)
-
-    >>> create_file('web/__init__.py', 'v1', r"""
-    ... import os
-    ... from paste import wsgilib
-    ... from paste.util.thirdparty import add_package
-    ... add_package('sqlobject')
-    ... import sqlobject
-    ...
-    ... sql_set = False
-    ...
-    ... def urlparser_hook(environ):
-    ...     global sql_set
-    ...     if not environ.has_key('todo_sql.base_url'):
-    ...         environ['todo_sql.base_url'] = environ['SCRIPT_NAME']
-    ...     if not sql_set:
-    ...         sql_set = True
-    ...         db_uri = environ['paste.config']['database']
-    ...         sqlobject.sqlhub.processConnection = sqlobject.connectionForURI(
-    ...             db_uri)
-    ... """)
-
-.. raw:: html
-   :file: resources/TodoTutorial/web/__init__.py.v1.gen.html
-
-And this is what we get:
-
-.. comment (show it)
-
-    >>> change_file('__init__.py', 
-    ...             [('insert', 5, "add_package('sqlobject')\n")])
-    >>> show('/', 'v1-frontpage')
-
-.. raw:: html
-   :file: resources/TodoTutorial/v1-frontpage.html
-
-edit_list page
---------------
-
-Now we have to create an ``edit_list`` page to create new lists.
-Here's what that looks like:
-
-.. comment (expanded)
-
-    >>> create_file('web/edit_list.py', 'v1', r"""
-    ... from todo_sql.sitepage import SitePage
-    ... from todo_sql.db import *
-    ... 
-    ... class edit_list(SitePage):
-    ... 
-    ...     def setup(self):
-    ...         self.list_id = self.request().field('id')
-    ...         if self.list_id != 'new':
-    ...             self.list = TodoList.get(int(self.list_id))
-    ...         self.options.title = 'Edit list '
-    ... 
-    ...     def actions(self):
-    ...         return ['save', 'destroy']
-    ... 
-    ...     def save(self):
-    ...         desc = self.request().field('description')
-    ...         if self.list_id == 'new':
-    ...             self.list = TodoList(description=desc)
-    ...             self.message('List created')
-    ...         else:
-    ...             self.list.description = desc
-    ...             self.message('List updated')
-    ...         self.sendRedirectAndEnd('./view_list?id=%s' % self.list.id)
-    ... 
-    ...     def destroy(self):
-    ...         desc = self.list.description
-    ...         self.list.destroySelf()
-    ...         self.message('List %s deleted' % desc)
-    ...         self.sendRedirectAndEnd('./')
-    ... """)
-
-.. raw:: html
-   :file: resources/TodoTutorial/web/edit_list.py.v1.gen.html
-
-A few things to point about out this.  First, note we're using the
-edit form as a creation form, too, with the special id of ``"new"`` for
-this case.  You don't have to do that, but I find it convenient.
-
-If there was an id passed in, we use it to fetch an instance of
-``TodoList`` with ``TodoList.get(int(self.list_id))``.
-
-Second, actions.  When there's a field named ``_action_actionName`` or
-``_action_=actionName``, that tells the servlet to call the named
-action.  (The first form is useful when you have a button where you
-can't necessarily give the action name the same value as the text of
-the button.)  As a security measure, the ``.actions()`` method returns
-a list of possible actions -- so a user couldn't change the form and
-call an arbitrary method.
-
-``.setup()`` is called regardless of the action, and
-``.defaultAction()`` is later called if no action is defined. (By
-default, ``defaultAction`` doesn't do anything.)  In the form that
-submits to ``edit_list``, we used an action of ``save``, so that's what
-gets called.
-
-In ``.save()`` there are two kinds of actions -- one inserts a row, and
-one updates a row.  Insertion is like instance creation -- you call
-the class::
-
-    self.list = TodoList(description=desc)
-
-Updating is like attribute assignment. (Remember, we already assigned
-``self.list`` in ``setup``.)
-
-    self.list.description = desc
-
-Next, you'll see we call ``self.message(...)`` -- this stores a message
-in the user's session object.  It's useful in cases like this, where
-you want to redirect the user someplace useful, but you also want to
-give them some indication of what happened.  When they go to the next
-page, that message will be displayed at the top of the page (and then
-removed from the session).  Even if you don't redirect, this is an easy
-way of adding messages to the top of the screen.
-
-Lastly, we do a redirect -- ``self.sendRedirectAndEnd()`` aborts the
-rest of the transaction and immediately redirects the user.
-
-You can also see we have a list deletion method (``destroy``).  To
-delete a row with SQLObject, call
-``sqlobjectInstance.destroySelf()``.  Then we just redirect back
-to the main page.
-
-Here's what happens when you create a list:
-
-.. comment (do it)
-
-    >>> show('/edit_list?_action_=save&'
-    ...      'id=new&description=another%20list', 'v1-edit')
-
-.. raw:: html
-   :file: resources/TodoTutorial/v1-edit.html
-
-view_list page
---------------
-
-Hmm... Well, that last page isn't very interesting, is it... We'd better
-write that ``view_list`` servlet.  Viewing a list means displaying all
-its items (``TodoItem``) and allowing items to be added, removed, and
-marked done or not done.
-
-.. comment (expanded)
-
-    >>> create_file('web/view_list.py', 'v1', r"""
-    ... from todo_sql.sitepage import SitePage
-    ... from todo_sql.db import *
-    ... 
-    ... class view_list(SitePage):
-    ... 
-    ...     def setup(self):
-    ...         self.options.list = TodoList.get(int(self.request().field('id')))
-    ...         self.options.list_items = list(self.options.list.items)
-    ...         self.options.title = 'List: %s' % self.options.list.description
-    ... 
-    ...     def actions(self):
-    ...         return ['check', 'add', 'destroy']
-    ... 
-    ...     def check(self):
-    ...         field = self.request().field
-    ...         for item in self.options.list.items:
-    ...             checked = field('item_%s' % item.id, False)
-    ...             if not checked and item.done:
-    ...                 self.message('Item %s marked not done'
-    ...                              % item.description)
-    ...                 item.done = False
-    ...             if checked and not item.done:
-    ...                 self.message('Item %s marked done'
-    ...                              % item.description)
-    ...                 item.done = True
-    ...         self.sendRedirectAndEnd(
-    ...             './view_list?id=%s' % self.options.list.id)
-    ...     
-    ...     def add(self):
-    ...         desc = self.request().field('description')
-    ...         if not desc:
-    ...             self.message('You must give a description')
-    ...         else:
-    ...             TodoItem(todo_list=self.options.list,
-    ...                      description=desc)
-    ...             self.message('Item added')
-    ...         self.sendRedirectAndEnd(
-    ...             './view_list?id=%s' % self.options.list.id)
-    ...
-    ...     def destroy(self):
-    ...         id = int(self.request.field('item_id'))
-    ...         item = TodoItem.get(id)
-    ...         assert item.todo_list.id == self.options.list.id, (
-    ...             "You are trying to delete %s, which does not "
-    ...             "belong to the list %s" % (item, self.options.list))
-    ...         desc = item.description
-    ...         item.destroySelf()
-    ...         self.message("Item %s removed" % desc)
-    ...         self.sendRedirectAndEnd(
-    ...             './view_list?id=%s' % self.options.list.id)
-    ... """)
-
-.. raw:: html
-   :file: resources/TodoTutorial/web/view_list.py.v1.gen.html
-
-Hopefully this looks familiar.  In ``setup`` we load up the
-objects and set a title based on those options.  We also fetch the
-list's items and define several actions: ``check`` marks items done
-or not done, ``add`` adds new items, and ``destroy`` removes a single
-item.
-
-Next we need the template:
-
-.. comment (template)
-
-    >>> create_file('templates/view_list.pt', 'v1', r"""
-    ... <html metal:use-macro="here/standard_template.pt/macros/page">
-    ... <metal:body fill-slot="body">
-    ... 
-    ... <form action="view_list?_action_=check" method="POST">
-    ... <input type="hidden" name="id"
-    ...  tal:attributes="value options/list/id">
-    ... 
-    ... <table>
-    ... <tr>
-    ...   <th>Done?</th>
-    ...   <th>Description</th>
-    ... </tr>
-    ... 
-    ... <tr tal:repeat="item options/list_items">
-    ...  <tal:let define="name python: 'item_%s' % item.id">
-    ...  <td>
-    ...   <input type="checkbox"
-    ...    tal:attributes="name name;
-    ...                    id name;
-    ...                    checked item/done">
-    ...  </td>
-    ...  <td>
-    ...   <label tal:attributes="for name"
-    ...    tal:content="item/description">description</label>
-    ...  </td>
-    ...  <td><a tal:attributes="
-    ...         href python: 'view_list?_action_=destroy&item_id=%s'
-    ...         % item.id">[delete]</a></td>
-    ...  </tal:let>
-    ... </tr>
-    ... 
-    ... <tr tal:condition="not: options/list_items">
-    ...  <td colspan=2>No items to display</td>
-    ... </tr>
-    ... 
-    ... </table>
-    ... 
-    ... <input type="submit" value="Update item status">
-    ... </form>
-    ... 
-    ... <form action="view_list?_action_=add" method="POST">
-    ... <input type="hidden" name="id"
-    ...  tal:attributes="value options/list/id">
-    ... Add item:<br>
-    ... <input type="text" style="width: 100%" name="description"><br>
-    ... <input type="submit" value="Add">
-    ... </form>
-    ... 
-    ... <form action="edit_list?_action_=destroy" method="POST">
-    ... <input type="hidden" name="id"
-    ...  tal:attributes="value options/list/id">
-    ... <input type="submit" value="Delete this list"
-    ...  onclick="return window.confirm('Really delete this list?')">
-    ... </form>
-    ... 
-    ... </metal:body>
-    ... </html>
-    ... """)
-
-.. raw:: html
-   :file: resources/TodoTutorial/templates/view_list.pt.v1.gen.html
-
-Now we can view that list:
-
-    >>> show('/view_list?id=2', 'v1-view')
-
-.. raw:: html
-   :file: resources/TodoTutorial/v1-view.html
-
-You can play with the app, but we're pretty much done.
-
-
-Changing standard_template.pt
------------------------------
-
-The one thing missing is navigation, which we want to apply globally
-to our application.  To fix this, we'll be changing ``SitePage`` and
-``standard_template.pt``.
-
-``SitePage`` is the class that all our other servlets inherit from.
-By adding things to ``SitePage.awake()`` we make them available to all
-of our application.  You can also hang utility methods off this class,
-or anything else that should be globally available, or have some
-default (but overrideable) implementation.
-
-For navigation we're going to put all the lists in a sidebar.  So
-we'll have to load all the lists in ``SitePage.awake()``. Then we'll
-use that data in ``standard_template.pt``.  We'll add one line to
-``SitePage.awake``, before ``self.setup()``::
-
-    self.options.lists = list(TodoList.select())
-
-We can get rid of this line in ``web/index.py`` now as well, since it
-will be redundant.
-
-.. comment (change files)
-
-    >>> change_file('sitepage.py', [
-    ... ('insert', 4, 'from todo_sql.db import *\n'),
-    ... ('insert', 20, 
-    ...  "        self.options.lists = list(TodoList.select())\n")])
-    >>> change_file('web/index.py', [
-    ... ('delete', 7, 8)])
-
-Now we'll change ``standard_template.pt`` to create navigation based
-on this:
-
-.. comment (make it)
-
-    >>> create_file('templates/standard_template.pt', 'nav', r"""
-    ... <metal:tpl define-macro="page">
-    ... <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
-    ...         "http://www.w3.org/TR/REC-html40/loose.dtd">
-    ... <html lang="en-US">
-    ... <head>
-    ... <title tal:content="servlet/title">title</title>
-    ... <link rel="stylesheet" type="text/css"
-    ...       tal:attributes="href python: servlet.baseStaticURL + '/stylesheet.css'">
-    ... 
-    ... <metal:slot define-slot="extra_head"></metal:slot>
-    ... 
-    ... </head>
-    ... <body>
-    ... 
-    ... <h1 id="header"><tal:mark replace="servlet/title"/></h1>
-    ... 
-    ... <table width="100%">
-    ... <tr>
-    ... <td id="sidenav" width="20%">
-    ... <a tal:attributes="href servlet/baseURL">Home</a><br>
-    ... <tal:for repeat="list options/lists">
-    ...  <a tal:attributes="href python: '%s/view_list?id=%s' %
-    ...                     (servlet.baseURL, list.id)"
-    ...     tal:content="list/description">link</a><br>
-    ... </tal:for>
-    ... </td>
-    ... 
-    ... <td>
-    ... <span tal:replace="structure servlet/messageText">
-    ... This is where the notification messages go.
-    ... </span>
-    ... 
-    ... <div id="content">
-    ... <metal:tpl define-slot="body">
-    ... [This page has not customized its "body" slot]
-    ... </metal:tpl>
-    ... </div>
-    ... 
-    ... </td></tr></table>
-    ... 
-    ... </body>
-    ... </html>
-    ... </metal:tpl>
-    ... """)
-
-.. raw:: html
-   :file: resources/TodoTutorial/templates/standard_template.pt.nav.gen.html
-
-Let's look at that view page again:
-
-.. comment (show)
-
-    >>> show('/view_list?id=2', 'v2-view')
-
-.. raw:: html
-   :file: resources/TodoTutorial/v2-view.html
-
-Conclusion
-==========
-
-Now that we're finished, you might want to play with the result of the
-tutorial and extend it.  You'll find the application in
-``examples/todo_sql``.
-
-I also want to note a few aspects of what we've created:
-
-* Style is separate from logic, by way of templates (style) and
-  servlets (controller logic).  This is similar to a Controller-View
-  separation, but it's also intended to assist a separation of roles,
-  typically Programmer-Designer.
-
-* The web interface logic is separated from the domain (or business)
-  logic.  We actually don't have much domain logic in this program --
-  just the stuff in ``db.py`` -- but larger applications would have
-  more.  None of this logic needs to be specific to the web at all, or
-  the framework we are using.
-
-* These distinctions between web logic, style decisions, and domain
-  logic are represented (a bit casually) by the file layout.
-
-* All the templates are valid HTML (and could be valid XHTML if you
-  want to write them like that -- but it's not required).  They can be
-  viewed, unrendered, in a browser.  The templates can be almost
-  arbitrarily complicated, depending on how complicated your view
-  logic is -- but all the logic is kept together in a relatively small
-  number of files.  If you want to share logic across pages, METAL can
-  be used for more than just ``standard_template.pt``.
-

docs/blog-tutorial.txt

-+++++++++++++
-Blog Tutorial
-+++++++++++++
-
-:author: Ian Bicking <ianb@colorstudy.com>
-:revision: $Rev$
-:date: $LastChangedDate$
-
-.. contents::
-
-.. note::
-
-    This tutorial is not yet finished.  What you see is what you get,
-    and yeah that's not a whole lot.
-
-Introduction
-============
-
-This tutorial will go through the process of creating a blog using
-`Python Paste <http://pythonpaste.org>`_, SQLObject_, and `Zope Page
-Templates`_.  This blog will rely heavily on static publishing -- that
-is, when at all possible flat HTML pages will be written to disk.  For
-some parts (e.g., posting a new item) this will of course be
-infeasible, but for most of the site this should work fine.
-
-.. _SQLObject: http://sqlobject.org
-.. _Zope Page Templates: http://www.zope.org/DevHome/Wikis/DevSite/Projects/ZPT/FrontPage
-
-As much as possible, this code will be accompanied by unit tests, and
-test-driven methodologies.  Doing test-driven documenting of the
-incremental process of creating test-driven software may get a little
-hairy, but wish me luck!
-
-This tutorial presupposes you are somewhat comfortable with the basic
-stack -- the `To-Do Tutorial`_ is a better place to start for a
-beginner.
-
-.. _To-Do Tutorial: TodoTutorial.html
-
-Setting Up The App
-==================
-
-.. note::
-
-    We're doing all this in the Python interpreter, even though you'd normally do
-    some of this in the shell.  This way the authors of this tutorial
-    can use something called doctest_, which allows this tutorial to
-    be tested Python in an automated way.
-
-    .. _doctest: http://python.org/doc/current/lib/module-doctest.html
-
-.. comment:
-
-    >>> from paste.tests.doctest_webapp import *
-    >>> BASE = '/var/www/example-builds/wwblog'
-    >>> import sys
-    >>> clear_dir(BASE)
-    >>> run("paster create --template=webkit_zpt %s" % BASE)
-    >>> os.chdir(BASE)
-
-::
-
-    $ export PYTHONPATH=/path/to/Paste:$PYTHONPATH
-    $ BASE=/var/www/example-builds/wwblog
-    $ paster create --template=webkit_zpt $BASE
-    $ cd $BASE
-
-The Model
-=========
-
-Since we're using SQLObject, we'll be doing the complete model in
-that.  The predecessor of this blog used flat files, custom-written
-indexes, and simple rfc822_ based files for structure.  It did not
-scale well at all.
-
-.. _rfc822: http://python.org/doc/current/lib/module-rfc822.html
-
-Here's the model:
-
-.. run:
-
-    create_file('db.py', 'v1', r"""
-    from sqlobject import *
-    
-    class Article(SQLObject):
-        url = StringCol(notNull=True)
-        title = StringCol()
-        content = StringCol(notNull=True)
-        content_mime_type = StringCol(notNull=True)
-        author = ForeignKey('User')
-        parent = ForeignKey('Article', default=None)
-	created = DateTimeCol(notNull=True, default=DateTimeCol.now)
-        last_updated = DateTimeCol(default=None)
-        atom_id = StringCol()
-        hidden = BoolCol(notNull=True, default=False)
-        article_type = StringCol(notNull=True, default='article')
-        categories = RelatedJoin('Category')
-    
-    class Category(SQLObject):
-        name = StringCol(alternateID=True)
-        articles = RelatedJoin('Article')
-    
-    class User(SQLObject):
-        class sqlmeta:
-            table = 'user_info'
-        username = StringCol(alternateID=True)
-        email = StringCol()
-        name = StringCol()
-        homepage = StringCol()
-        password_encoded = StringCol()
-        role = StringCol(notNull=True, default='user')
-    """)
-
-.. raw:: html
-   :file: resources/blog-tutorial/db.py.v1.gen.html
-
-A few things to note:
-
-* All the columns allow ``NULL`` by default, unless we say
-  ``notNull=True``.
-
-* ``ForeignKey('User')`` is a join to another table (the ``User``
-  table, of course).  We have to use strings to refer to other class,
-  because in this case the ``User`` class hasn't even been created.
-  Generally all references between classes are by name.
-
-* ``created`` has a default.  You can give a fixed default (like
-  ``True`` or ``3``), or you can pass in a function that is called.
-  In this case, if you don't indicate ``Article(...,
-  created=something)`` then ``created`` will be the current date and
-  time.  Unless a default is explicitly given, it is an error to leave
-  a column out of the constructor.  ``NULL`` (which is ``None`` in
-  Python) is *not* considered a default.
-
-* Some column types don't relate directly to database types.  For
-  instance, though PostgreSQL has a ``BOOLEAN`` type, most databases
-  don't, so ``BoolCol`` translates to some kind of ``INT`` column on
-  those database.
-
-* ``RelatedJoin('Category')`` creates a mapping table
-  (``article_category``) and is a many-to-many join between articles
-  and categories.
-
-* ``user`` isn't a valid table name in many databases, so while the
-  class is named ``User``, the table actually is ``user_info``.  This
-  kind of extra information about a class is typically passed in
-  through the ``sqlmeta`` inner class.
-
-These classes have lots of other *behavior*, but this should be a good
-list of actual information.  We'll add more behavior later.
-
-Now we'll create the database.  First we configure it, adding these
-lines to ``server.conf``::
-
-    import os
-    database = 'sqlite:%s/data.db' % os.path.dirname(__file__)
-
-You could also use::
-
-    database = 'mysql://user:passwd@localhost/dbname'
-    database = 'postgresql://user:password@localhost/dbname'
-
-.. comment (change server.conf)
-
-    >>> change_file('server.conf', [('insert', 2, r"""import os
-    ... database = 'sqlite:%s/data.db' % os.path.dirname(__file__)
-    ...
-    ... """)])
-
-Now we'll use ``sqlobject-admin`` to set up the tables:
-
-.. comment (do it)
-
-    >>> run_command('sqlobject-admin create -f server.conf '
-    ...             '-m wwblog.db', 'create', and_print=True)
-
-
-.. raw:: html
-   :file: resources/blog-tutorial/shell-command.create.gen.html
-
-Fixture Data
-------------
-
-To test things later we'll need a bit of data to make the tests
-interesting.  It's best if we write code to clear any data and put
-known data in -- that way we can restore the database at any time to a
-known state, and can write our tests against that data.
-
-We'll add some code to the end of ``db.py``:
-
-.. comment (change)
-
-    >>> append_to_file('db.py', 'append-fixture', r"""
-    ... 
-    ... from paste import CONFIG
-    ... def reset_data():
-    ...     sqlhub.processConnection = connectionForURI(CONFIG['database'])
-    ...     for soClass in (User, Category, Article):
-    ...         soClass.clearTable()
-    ...     auth = User(username='author', email='author@example.com',
-    ...                 name='Author Person', password_encoded=None,
-    ...                 role='author', homepage=None)
-    ...     user = User(username='commentor', email='comment@example.com',
-    ...                 name='Comment Person', password_encoded=None,
-    ...                 role='user', homepage='http://yahoo.com')
-    ...     programming = Category(name='Programming')
-    ...     family = Category(name='family')
-    ...     a1 = Article(url='/2004/05/01/article1.html',
-    ...                  title='First article',
-    ...                  content='This is an article',
-    ...                  content_mime_type='text/html',
-    ...                  author=auth, parent=None,
-    ...                  last_updated=None, atom_id=None)
-    ...     a2 = Article(url='/2004/05/10/article2.html',
-    ...                  title='Second article',
-    ...                  content='Another\narticle',
-    ...                  content_mime_type='text/plain',
-    ...                  author=auth, parent=None,
-    ...                  last_updated=None, atom_id=None)
-    ...     c1 = Article(url='/2004/05/01/article1-comment1.html',
-    ...                  title=None, content='Nice article!',
-    ...                  content_mime_type='text/x-untrusted-html',
-    ...                  author=user, parent=a1, last_updated=None,
-    ...                  atom_id=None, article_type='comment')
-    ...     a1.addCategory(programming)
-    ...     a1.addCategory(family)
-    ... """)
-
-.. raw:: html
-   :file: resources/blog-tutorial/db.py.append-fixture.gen.html
-
-Test Fixture
-------------
-
-See `Testing Applications With Paste <testing-applications.html>`_ for
-more on the details of how we set up testing.  We'll be using `py.test
-<http://codespeak.net/py/current/doc/test.html>`_ for the testing
-framework.
-
-First, lets create our own test fixture.  We'll create a directory
-``tests/`` and add a file ``fixture.py``.
-
-.. comment (create files):
-
-    >>> create_file('tests/__init__.py', 'v1', '#\n')
-    >>> create_file('tests/fixture.py', 'v1', r"""
-    ... from paste.tests.fixture import setup_module as paste_setup
-    ... from wwblog import db
-    ...
-    ... def setup_module(module):
-    ...     paste_setup(module)
-    ...     db.reset_data()
-    ... """)
-
-.. raw:: html
-   :file: resources/blog-tutorial/tests/fixture.py.v1.gen.html
-
-Now in each test we'll do::
-
-    from wwblog.tests.fixture import setup_module
-
-And that will give us a consistent state for the module (note that
-data isn't reset between each test in the module, just once for the
-module, so we'll have to be aware of that).
-
-Let's write a first test:
-
-.. comment (create test):
-
-    >>> create_file('tests/test_db.py', 'v1', r"""
-    ... from fixture import setup_module
-    ... from wwblog.db import *
-    ...
-    ... def test_data():
-    ...     # make sure we have the two users we set up
-    ...     assert len(list(User.select())) == 2
-    ...     # and get the first article for testing
-    ...     a1 = list(Article.selectBy(title='First article'))[0]
-    ...     # make sure it has categories, then make sure the
-    ...     # categories contain this article as well
-    ...     assert len(list(a1.categories)) == 2
-    ...     for cat in a1.categories:
-    ...         assert a1 in cat.articles
-    ... """)
-
-.. raw:: html
-   :file: resources/blog-tutorial/tests/test_db.py.v1.gen.html
-
-For the most part, this stuff is already tested by SQLObject, but this
-is a basic sanity check, and a test that we have set up the classes
-properly.  One problem, though, is that we have to make sure that
-``sys.path`` is set up properly.  We could set ``$PYTHONPATH``, but
-that can be a bit annoying; we'll put it in a special file
-``conftest.py`` that py.test loads up:
-
-.. comment (create):
-
-    >>> create_file('conftest.py', 'v1', r"""
-    ... import sys, os
-    ... sys.path.append('/path/to/Paste')
-    ... sys.path.append(os.path.dirname(os.path.dirname(__file__)))
-    ... from paste.util.thirdparty import add_package
-    ... add_package('sqlobject')
-    ... """)
-
-.. raw:: html
-   :file: resources/blog-tutorial/conftest.py.v1.gen.html
-    
-Now we should be able to run py.test:
-
-.. comment (do):
-
-    >>> run_command('py.test', 't1', and_print=True)
-    inserting into sys.path: ...
-    =...= test process starts =...=
-    testing-mode: inprocess
-    executable:   .../python  (...)
-    using py lib: .../py <rev ...>
-    .../test_db.py[1] .
-    =...= tests finished: 1 passed in ... seconds =...=
-
-.. raw:: html
-   :file: resources/blog-tutorial/shell-command.t1.gen.html
-
-Very good!  Alright then, on to making an application...
-
-Static Publishing
------------------
-
-Remember I said something about static publishing?  So... what does
-that mean?
-
-Well, it means that when possible we should write files out to disk.
-These files might be in their final form, though in some environments
-it might be nice to write out files that are interpreted by `server
-side includes <http://httpd.apache.org/docs/mod/mod_include.html>`_ or
-PHP.  
-
-By generating what is effectively code, the "static" files can contain
-dynamic portions, e.g., a running list of recently-updated external
-blogs.  But more importantly, many changes can be made without
-generating the entire site; changes to the look of the site, of
-course, but also smaller things, like up-to-date archive links on the
-sides of pages and other little bits of code.
-
-This tutorial will work with server-side includes, because they are
-dumb enough that we won't be tempted to push too much functionality
-into them (and we might be able to extract their functionality into
-fully-formed pages); but they'll also save us a lot of work early on.
-If you haven't used server-side includes you can read the document I
-linked too, but you can also probably pick it up easily enough from
-the examples.
-
-URL Layout
-----------
-
-The blog "application" will be available through some URL (which we
-can figure out at runtime).  But everything else gets written onto
-disk with some URL equivalent, so that path and URL will have to be
-configurable.
-
-The Front Page
---------------
-
-First we'll set up a simple front page.  We'll write a new
-``index.py``:
-
-.. comment (do so):
-
-    >>> create_file('web/index.py', 'v1', r"""
-    ... """)
-
-.. raw:: html
-   :file: resources/blog-tutorial/web/index.py.v1.gen.html
 Documentation
 =============
 
-Reference
----------
+Tutorials and Introduction
+==========================
 
-* `Paste Reference Documentation 
-  <http://pythonpaste.org/docs/reference.html>`_
-* `paste.deploy <http://pythonpaste.org/deploy/paste-deploy.html>`_
-* `Configuration <configuration.html>`_
-* `Integrating Frameworks Info Paste <integration.html>`_
-* `Roadmap for Paste <roadmap.html>`_
+* `To-Do Tutorial <http://pythonpaste.org/todo/TodoTutorial.html>`_
+
+The core Paste package is a set of tools that use `WSGI
+<http://www.python.org/peps/pep-0333.html>`_ heavily as the means of
+communication.  WSGI is an in-process CGI-like communication protocol.
+
+Right now the best way to see the various tools available in Paste is
+to `look through the source <module-index.html>`_.
+
+Related Projects
+================
+
+Closely related packages:
+
+* `Paste Deploy <http://pythonpaste.org/deploy/>`_
+* `Paste Script <http://pythonpaste.org/script/>`_
+* `Paste WebKit <http://pythonpaste.org/webkit/>`_
+* `Wareweb <http://pythonpaste.org/wareweb/>`_
+
+Developer Documentation
+=======================
+
+* `Developer Guidelines <DeveloperGuidelines.html>`_
+* `Style Guide <StyleGuide.html>`_
+
+Reference Documentation
+=======================
+
 * `Testing Applications With Paste <testing-applications.html>`_
 * `URL Parsing with WSGI and Paste <url-parsing-with-wsgi.html>`_
 
-Tutorials and Introduction
---------------------------
 
-* `To-Do Tutorial <TodoTutorial.html>`_
-* `What Is Paste? <what-is-paste.html>`_; older related documents:
+License
+=======
 
-  * `What is WSGIKit <http://blog.ianbicking.org/what-is-wsgikit.html>`_
-  * `What can WSGIKit do for you?
-    <http://blog.ianbicking.org/what-can-wsgikit-do-for-you.html>`_
-  * Ian Bicking presented at `PyCon 2005 <http://pycon.org/dc2005>`_ on 
-    `WSGI Middleware and WSGIKit
-    <http://www.imagescape.com/software/docs/wsgikit-pycon2005/>`_: 
-    *Using WSGI Middleware to build a foundation for Python web
-    programming*
+Paste is distributed under the `Python Software Foundation`__
+license.  This is a BSD/MIT-style license.
 
-Developers
-----------
+.. __: http://www.python.org/psf/license.html
 
-* `Developer Guidelines <DeveloperGuidelines.html>`_
-* `Style Guide <StyleGuide.html>`_
+Overview
+========
+
+If you don't want to look through source, here's a quick overview of
+what there is here:
+
+Testing
+-------
+
+* A fixture for testing WSGI applications conveniently and in-process,
+  in ``paste.fixture``
+
+* A fixture for testing command-line applications, also in
+  ``paste.fixture``
+
+* Check components for WSGI-compliance in ``paste.lint``
+
+Dispatching
+-----------
+
+* Chain and cascade WSGI applications (returning the first non-error
+  response) in ``paste.cascade``
+
+* Dispatch to several WSGI applications based on URL prefixes, in
+  ``paste.urlmap``
+
+* Allow applications to make subrequests and forward requests
+  internally, in ``paste.recursive``
+
+Web Application
+---------------
+
+* Run CGI programs as WSGI applications in ``paste.cgiapp`` (and
+  Python-sepcific CGI programs with ``paste.pycgiwrapper``)
+
+* Traverse files and load WSGI applications from ``.py`` files (or
+  static files), in ``paste.urlparser``
+
+* Serve static directories of files, also in ``paste.urlparser``
+
+Tools
+-----
+
+* Catch HTTP-related exceptions (e.g., ``HTTPNotFound``) and turn them
+  into proper responses in ``paste.httpexceptions``
+
+* Check for signed cookies for authentication, setting ``REMOTE_USER``
+  in ``paste.login``
+
+* Create sessions in ``paste.session`` and ``paste.flup_session``
+
+* Gzip responses in ``paste.gzip``
+
+* A wide variety of routines for manipulating WSGI requests and
+  producing responses, in ``paste.wsgilib``
+
+Debugging Filters
+-----------------
+
+* Catch (optionally email) errors with extended tracebacks (using
+  Zope/ZPT conventions) in ``paste.exceptions``
+
+* Catch errors presenting a `cgitb
+  <http://python.org/doc/current/lib/module-cgitb.html>`_-based
+  output, in ``paste.cgitb_catcher``.
+
+* Profile each request and append profiling information to the HTML,
+  in ``paste.profilemiddleware``
+
+* Capture ``print`` output and present it in the browser for
+  debugging, in ``paste.printdebug``
+
+* Validate all HTML output from applications using the ``WDG Validator
+  <http://www.htmlhelp.com/tools/validator/>`_, appending any errors
+  or warnings to the page, in ``paste.wdg_validator``
+
+Other Tools
+-----------
+
+* A file monitor to allow restarting the server when files have been
+  updated (for automatic restarting when editing code) in
+  ``paste.reloader``
+
+* A class for generating and traversing URLs, and creating associated
+  HTML code, in ``paste.url``

docs/roadmap.txt

-Paste Roadmap
-+++++++++++++
-
-:revision: $Rev$
-:date: $LastChangedDate$
-
-This is a document describing Paste's future and some of the things
-planned.  This may change as events continue.  Discussion is welcome;
-please direct discussion to paste-users@pythonpaste.org
-
-Bold items are particularly important.  The sections are in no
-particular order.
-
-.. contents::
-
-To-Do
-=====
-
-paster
-------
-
-* **Package installation and upgrading.** (partially done)
-
-  * Package installation should allow for Paste-specific hooks.
-
-* Allow applications to easily add their own commands, e.g.,
-  ``add-user``.  
-
-* Better PID management when in daemon mode (don't clobber a PID file
-  of a running process)
-
-* Add options to manage the daemon process, like force
-  reloading/restarting (HUP?) and stopping; make init.d scripts as
-  minimal as possible.
-
-paste.docsupport
-----------------
-
-* Add indexing for the various objects (middleware, configuration
-  options, WSGI environment keys).
-
-* Extract some packages into separate files.  (e.g., ``paste.webkit``,
-  ``paste.wareweb`` -- but *not* ``paste.exceptions``)
-
-paste.tests.doctest_webapp
---------------------------
-
-* Maybe move out of ``tests`` package.
-
-* Make it fail hard on certain errors (e.g., when ``paster`` fails)
-
-paste.tests.fixture
--------------------
-
-* Maybe move out of ``tests`` package.
-
-* Get rid of fake_request (convert current tests to TestApp).
-
-* Fill out or remove other mock objects.  (Maybe make a package for
-  other pieces?)
-
-* ``setup_module`` really only applies to application acceptance
-  testing, but can be enabled unwittingly when doing ``import *`` --
-  figure something out there.
-
-paste.pycgiwrapper
-------------------
-
-* Document, test, or throw away.
-
-* Allow for non-Python CGI scripts (no clever tricks, just run it in a
-  subprocess).
-
-paste.pyconfig
---------------
-
-* Put in notion of "profiles," which involve different sets of
-  configuration files.
-
-* Add ini-file parser/loader (probably taking from the old
-  ``paste.conf`` module), so some parts of configuration can be
-  explicitly handed off to a simpler configuration file.
-
-* Clean up ``use_package`` and move to another module.
-
-paste.reloader
---------------
-
-* Make it optionally use FAM or iNotify, instead of polling files.
-
-* Make sure it uses HUP, etc.
-
-paste.wdg_validate
-------------------
-
-* Allow other validators to be plugged in.
-
-* Validate CSS (embedded CSS with report in parent page?).
-
-Blue-sky
-~~~~~~~~
-
-* Validate Javascript...?  An anal Javascript parser would be really
-  cool.  Maybe use http://www.crockford.com/javascript/lint.html --
-  the interesting part is including the output of the lint in pages
-  that include the Javascript.  Since the lint program itself is
-  written in Javascript, this is mostly a figure-it-out task, not a
-  server infrastructure task.
-
-paste.exceptions
-----------------
-
-* Display local variables in exception reports, like ``cgitb``.
-  Except values should be inlined into the source line itself in HTML
-  ``title`` attributes, and the ``class`` should represent the rough
-  type (e.g., module, None, string, number, function).
-
-* Keep extra data from being doubly displayed.
-
-* Add object and protocol so HTML can be included in extra data.
-
-* Include "serial number" in non-exception output.
-
-* Add hooks to suppress reporters.  E.g., if a "developer" is logged
-  in, the error report need not mailed.  Maybe just based on IP
-  address (a rough feature is all that is needed).
-
-Blue-sky
-~~~~~~~~
-
-* Allow evaluation in local stacks, interactively, keeping the
-  traceback in a session or similar location.
-
-paste.urlparser
----------------
-
-* Static-file-only serving, for serving directories full of Javascript
-  and whatnot without security concern.
-
-* Middleware for various compositions of applications, like nesting
-  several applications under the same URL space, using the first one
-  that doesn't return 404.
-
-paste.session
--------------
-
-* **Use flup session middleware**
-
-* Add DB-API-based session storage.
-
-* Add URI->DB-API connection parser (maybe strip from SQLObject).
-
-paste.login
------------
-
-* Really document it, and test it.
-
-* Provide some useful backends (htpasswd, database/DB-API,
-  SQLObject).
-
-paste.gzipper
--------------
-
-* Probably should just be removed.
-
-Things to Add
-=============
-
-No module yet, so these are listed generally.
-
-* Really good static-file WSGI application, that knows about
-  If-Modified-Since, ETag, etc.  Should have caching of gzipped
-  versions and cache some metadata (like etags themselves).
-
-* Authorization middleware.
-
-* Something to manage collections of static files, like a Javascript
-  library.
-
-* Something to copy static files to a known location, based on
-  configuration.  E.g., copy static files into a location served by
-  Apache.  
-
-* Bring some Javascript library in, mostly for use in examples, but
-  also as a kind of suggestion.
-
-* Upload-progress-tracking WSGI application.
-
-* Something like Webware's TaskKit, but more abstract.  And
-  multiprocess-friendly.
-
-Done
-====
-
-25 May 2005: 
-    **``--daemon`` option for running as background process**
-
-29 May 2005:
-    * Template files should have optional ``.ext_tmpl`` extension, where
-      ``_tmpl`` gets stripped.  This way the currently-invalid ``.py``
-      template files wouldn't be recognized as Pythohn files.
-
-30 May 2005:
-
-    * **Deployment of multiple applications using configuration files**
-
-      * An application could be mapped to a particular URL, given a
-        configuration file that describes the application.

docs/what-is-paste.txt

-What Is Paste?
-==============
-
-:author: Ian Bicking
-:revision: $Rev$
-:date: $LastChangedDate$
-
-Introduction
-------------
-
-It has come up several times that people don't understand quite what
-Paste is and what it is intended to be.  This document is an attempt
-to respond to that.  
-
-In part the confusion has been because Paste has is really several
-things.  It is an attempt to fill in some of the gaps in web
-frameworks, and to identify places where things can be shared; as such
-it is a reaction to the current state of frameworks, and a direct
-attempt to be complimentary to those frameworks.  As a result it can
-be somewhat eclectic.
-
-Server/Application Glue
------------------------
-
-`WSGI <http://www.python.org/peps/pep-0333.html>`_ defines how servers
-invoke applications, and how application respond.  However, it does
-not define how servers or applications come into existance, or how
-they are passed to each other.
-
-Paste is meant to bridge that, but providing a single entry point that
-can create and configure a server, create an WSGI application, and
-hook the two together.
-
-This generally involves distinct code for each server supported, since
-there isn't any standard.
-
-Also, Paste is expected to create the applications that get served.
-This is typically done through at least somewhat-custom code that
-is driven by the configuration.  Which leads us to...
-
-Configuration
--------------
-
-In order to set up servers and application, some kind of configuration
-is needed.  Paste loads up configuration files and makes these
-available to all parts of the system.
-
-One goal of Paste is to support small pieces of decoupled code that
-work together.  This is part of its WSGI-driven architecture.
-However, exactly how that code is split up is an implementation detail
-that really shouldn't be exposed to end users.  Because of this, each
-component can't have its own configuration without resulting in a mess
-of configuration files and formats that are fragile and difficult to
-understand.
-
-This configuration is accessible from all portions of the system, so
-your application configuration can go in beside server and middleware
-configuration.
-
-Reusable Middleware
--------------------
-
-WSGI allows for the idea of "middleware" -- something that is both a
-server and an application.  This is similar to a filter or a wrapper.
-By building these on WSGI, they are neutral with respect to any
-particular framework.
-
-Use of the middleware is generally optional, but they serve as a way
-to share work, and tend to be a fairly good architecture for many
-problems.
-
-Some of the middleware included:
-
-* Adding configuration information to the request
-  (``paste.configmiddleware``)
-
-* Catching and reporting errors (``paste.error_middleware``)
-
-* Catching HTTP-related exceptions and producing HTTP responses
-  (``paste.httpexceptions``)
-
-* Testing for WSGI compliance (``paste.lint``)
-
-* Identifying and authenticating user logins (``paste.login``)
-
-* Facilitating internal redirects and recursive calls
-  (``paste.recursive``)
-
-* Adding sessions to the request (``paste.session``)
-
-* Validating HTML output from applications (``paste.wdg_validate``)
-
-Another kind of middleware is one which finds and constructs
-applications.  At the moment, just one such middleware is in the
-library: ``paste.urlparser``.  This looks on disk for files and Python
-modules, and creates WSGI applications from them.  Other URL resolvers
-are also possible, e.g., one that traverses objects, or uses explicit
-URL->application maps.
-
-Otherwise Homeless Code
------------------------
-
-All code has to go somewhere.  Sometimes there's not a good location
-for that code.  So it can go in Paste.
-
-An Implementation Webware
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-``paste.webkit`` is a reimplementation of Webware built on the Paste
-middleware.  This is a fairly thin implementation, mostly mapping the
-middleware APIs to Webware APIs.
-
-In this system Webware Servlet instances become WSGI applications
-(``paste.webkit.wkservlet.Servlet.__call__`` implements a WSGI
-application).
-
-Reloader
-~~~~~~~~
-
-This module (``paste.reloader``) checks in the background for modified
-files (typically modules in ``sys.modules``) and restarts the entire
-server when that happens.
-
-This avoids the stale-code issue, where code in memory gets out of
-sync with code on disk.  When that happens confusion can ensue.
-Manually restarting is also somewhat annoying, so this does the
-restarting for you.  It's not really appropriate for a live
-deployment, but it works well in development.
-
-Documentation System
-~~~~~~~~~~~~~~~~~~~~
-
-This is still young and not well defined, but there's some work on
-using `doctest
-<http://python.org/doc/current/lib/module-doctest.html>`_ to generate
-and test documentation.  These can turn into a kind of acceptance
-test.
-
-Application Templates
----------------------
-
-One facility in Paste is ``paster`` a script to create
-application "templates".  Basically an empty application, with a
-little structure.  For instance, the Webware/Zope Page Template
-(webkit_zpt) application template sets up these files::
-
-    __init__.py
-    server.conf
-    sitepage.py
-    templates/standard_template.pt
-    templates/index.pt
-    web/__init__.py
-    web/index.py
-    web/static/stylesheet.css
-
-This is a kind of a minimal set up for a typical web application using
-these systems.  After the application is set up, ``paster`` can
-provide other commands.  For instance in a webkit_zpt application
-``paster servlet edit`` will create ``web/edit.py`` and
-``web/edit.pt`` files.  Each template has control implement any
-commands how it sees fit, but some convenient functions and classes
-are provided to make implementation easier.
-
-Distribution
-------------
-
-This is still an open issue, but I hope Paste will facilitate
-installation of multiple frameworks quickly.  Some of this is handled
-already: ``paste-server`` starts a server easily and quickly, and
-``paster`` gives a user the basis for an application quickly.
-Actual software installation is a little harder.  Right now the plan
-is to use `Python Eggs
-<http://peak.telecommunity.com/DevCenter/PythonEggs>`_, but it's just
-a plan.  Python Eggs are still in development (though usable), and it
-requires creating packages for each project (which is feasible, but
-requires a fair amount of grunt work).
-
 tag_svn_revision = true
 
 [pudge]
-docs = docs/index.txt