SVN: r25812 - trunk/quixote/doc

David Binger <dbinger-fVcApmY9cLvQ3/1i3zOLAti2O/[email protected]> Tue, 21 Dec 2004 11:40:36 -0500
Newsgroups gmane.comp.web.quixote.cvs
Message-ID <[email protected]>
Author: dbinger
Date: 2004-12-21 09:10:38 -0500 (Tue, 21 Dec 2004)
New Revision: 25812

Modified:
   trunk/quixote/doc/programming.txt
Log:
Update programming.txt to agree with Quixote 2.


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