summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2008-07-20 19:13:42 +0000
committerChris McDonough <chrism@agendaless.com>2008-07-20 19:13:42 +0000
commit0bc787d4999460d4219c621f83c619ca7c4552c2 (patch)
tree1f6c47221ffede2b68ef6c288c61ae6027a1cfa1 /docs
parentb54cdb6d0951a28b7d7bf4f585a4059cc5e6b18a (diff)
downloadpyramid-0bc787d4999460d4219c621f83c619ca7c4552c2.tar.gz
pyramid-0bc787d4999460d4219c621f83c619ca7c4552c2.tar.bz2
pyramid-0bc787d4999460d4219c621f83c619ca7c4552c2.zip
More docs; fix autogen app model root creation.
Diffstat (limited to 'docs')
-rw-r--r--docs/index.rst1
-rw-r--r--docs/narr/introduction.rst457
-rw-r--r--docs/narr/project.rst199
-rw-r--r--docs/narr/traversal.rst269
4 files changed, 460 insertions, 466 deletions
diff --git a/docs/index.rst b/docs/index.rst
index 28b2d91d4..f7bf25bd0 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -14,6 +14,7 @@ Narrative documentation in chapter form explaining how to use
narr/introduction
narr/project
+ narr/traversal
narr/views
narr/templates
narr/models
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index 0782af2fb..438e4008b 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -1,83 +1,34 @@
repoze.bfg Introduction
=======================
-``repoze.bfg`` is a system for routing web requests to applications
-based on graph traversal. It is inspired by Zope's publisher, and
-uses Zope libraries to do much of its work. However, it is less
-ambitious and less featureful than any released version of Zope's
-publisher.
+``repoze.bfg`` is a web application framework based on graph
+traversal. It is inspired by Zope's publisher, and uses Zope
+libraries to do much of its work. However, it is less ambitious and
+less featureful than any released version of Zope's publisher.
``repoze.bfg`` uses the WSGI protocol to handle requests and
responses, and integrates Zope, Paste, and WebOb libraries to form the
basis for a simple web object publishing framework.
-Graph Traversal
----------------
-
-In many popular web frameworks, a "URL dispatcher" is used to
-associate a particular URL with a bit of code (known somewhat
-ambiguously as a "controller" or "view" depending upon the particular
-vocabulary religion to which you subscribe). These systems allow the
-developer to create "urlconfs" or "routes" to controller/view Python
-code using pattern matching against URL components. Examples:
-`Django's URL dispatcher
-<http://www.djangoproject.com/documentation/url_dispatch/>`_ and the
-`Routes URL mapping system <http://routes.groovie.org/>`_ .
-
-It is however possible to map URLs to code differently, using object
-graph traversal. The venerable Zope and CherryPy web frameworks offer
-graph-traversal-based URL dispatch. ``repoze.bfg`` also provides
-graph-traversal-based dispatch of URLs to code. Graph-traversal based
-dispatching is useful if you like the URL to be representative of an
-arbitrary hierarchy of potentially heterogeneous items.
-
-Non-graph traversal based URL dispatch can easily handle URLs such as
-``http://example.com/members/Chris``, where it's assumed that each
-item "below" ``members`` in the URL represents a member in the system.
-You just match everything "below" ``members`` to a particular view.
-They are not very good, however, at inferring the difference between
-sets of URLs such as ``http://example.com/members/Chris/document`` vs.
-``http://example.com/members/Chris/stuff/page`` wherein you'd like the
-``document`` in the first URL to represent, e.g. a PDF document, and
-``/stuff/page`` in the second to represent, e.g. an OpenOffice
-document in a "stuff" folder. It takes more pattern matching
-assertions to be able to make URLs like these work in URL-dispatch
-based systems, and some assertions just aren't possible. For example,
-URL-dispatch based systems don't deal very well with URLs that
-represent arbitrary-depth hierarchies.
-
-Graph traversal works well if you need to divine meaning out of these
-types of "ambiguous" URLs and URLs that represent arbitrary-depth
-hierarchies. Each URL segment represents a single traversal through
-an edge of the graph. So a URL like ``http://example.com/a/b/c`` can
-be thought of as a graph traversal on the example.com site through the
-edges "a", "b", and "c".
-
-Finally, if you're willing to treat your application models as a graph
-that can be traversed, it also becomes trivial to provide "row-level
-security" (in common relational parlance): you just attach a security
-declaration to each instance in the graph. This is not as easy in
-frameworks that use URL-based dispatch.
-
-Graph traversal is materially more complex than URL-based dispatch,
-however, if only because it requires the construction and maintenance
-of a graph, and it requires the developer to think about mapping URLs
-to code in terms of traversing the graph. (How's *that* for
-self-referential! ;-) That said, for developers comfortable with Zope,
-in particular, and comfortable with hierarchical data stores like
-ZODB, mapping a URL to a graph traversal it's a natural way to think
-about creating a web application.
-
-In essence, the choice to use graph traversal vs. URL dispatch is
-largely religious in some sense. Graph traversal dispatch probably
-just doesn't make any sense when you possess completely "square" data
-stored in a relational database. However, when you have a
-hierarchical data store, it can provide advantages over using
-URL-based dispatch.
-
Similarities with Other Frameworks
----------------------------------
+``repoze.bfg`` was inspired by Zope, Django, and Pylons.
+
+``repoze.bfg``'s traversal is inspired by Zope. ``repoze.bfg`` uses
+the Zope Component Architecture ("CA") internally like Zope 2, Zope3,
+and Grok. Developers don't interact with the CA very much during
+typical development, however; it's mostly used by the framework
+developer rather than the application developer.
+
+Like Pylons, ``repoze.bfg`` is mostly policy-free. It makes no
+assertions about which database you should use, and its built-in
+templating facilities are only for convenience. It is essentially
+only supplies a mechanism to map URLs to view code and convention for
+calling those views. You are free to use third-party components in
+your application that fit your needs. Also like Pylons,
+``repoze.bfg`` is heavily dependent on WSGI.
+
The Django docs state that Django is an "MTV" framework in their `FAQ
<http://www.djangoproject.com/documentation/faq/>`_. This also
happens to be true for ``repoze.bfg``::
@@ -110,23 +61,6 @@ happens to be true for ``repoze.bfg``::
framework - that is, "model", "template", and "view." That breakdown
makes much more sense.
-How ``repoze.bfg`` is Configured
---------------------------------
-
-Users interact with your ``repoze.bfg``-based application via a
-"router", which is itself a WSGI application. At system startup time,
-the router must be configured with a root object from which all
-traversal will begin. The root object is a mapping object, such as a
-Python dictionary. In fact, all items contained in the graph are
-either leaf nodes (these have no __getitem__) or container nodes
-(these do have a __getitem__).
-
-Items contained within the graph are analogous to the concept of
-``model`` objects used by many other frameworks. They are typically
-instances of classes. Each containerish instance is willing to return
-a child or raise a KeyError based on a name passed to its __getitem__.
-No leaf-level instance is required to have a __getitem__.
-
Jargon
------
@@ -163,12 +97,13 @@ model
context
- A model in the system that is the subject of a view.
+ A model in the system that is found during traversal; it becomes the
+ subject of a view.
-view registry (aka application registry)
+application registry
- A registry which maps a context and view name to a view callable and
- optionally a permission.
+ A registry which maps model types to views, as well as performing
+ other application-specific component registrations.
template
@@ -201,345 +136,3 @@ permission
to which principal to answer the question "is this user allowed to
do this".
-How ``repoze.bfg`` Processes a Request
---------------------------------------
-
-When a user requests a page from your ``repoze.bfg`` -powered
-application, the system uses this algorithm to determine which Python
-code to execute:
-
- 1. The request for the page is presented to ``repoze.bfg``'s
- "router" in terms of a standard WSGI request, which is
- represented by a WSGI environment and a start_response callable.
-
- 2. The router creates a `WebOb <http://pythonpaste.org/webob/>`_
- request object based on the WSGI environment.
-
- 3. The router uses the WSGI environment's ``PATH_INFO`` variable to
- determine the path segments to traverse. The leading slash is
- stripped off `PATH_INFO``, and the remaining path segments are
- split on the slash character to form a traversal sequence, so a
- request with a ``PATH_INFO`` variable of ``/a/b/c`` maps to the
- traversal sequence ``['a', 'b', 'c']``.
-
- 4. Traversal begins at the root object. For the traversal sequence
- ``['a', 'b', 'c']``, the root object's __getitem__ is called with
- the name ``a``. Traversal continues through the sequence. In
- our example, if the root object's __getitem__ called with the
- name ``a`` returns an object (aka "object A"), that object's
- __getitem__ is called with the name ``b``. If object A returns
- an object when asked for ``b``, object B's __getitem__ is then
- asked for the name ``c``, and may return object C.
-
- 5. Traversal ends when a) the entire path is exhausted or b) when
- any graph element raises a KeyError from its __getitem__ or c)
- when any non-final path element traversal does not have a
- __getitem__ method (resulting in a NameError) or d) when any path
- element is prefixed with the set of characters ``@@`` (indicating
- that the characters following the ``@@`` token should be treated
- as a "view name").
-
- 6. When traversal ends for any of the reasons in the previous step,
- the the last object found during traversal is deemed to be the
- "context". If the path has been exhausted when traversal ends,
- the "view name" is deemed to be the empty string (``''``).
- However, if the path was not exhausted before traversal
- terminated, the first remaining path element is treated as the
- view name. Any subseqent path elements after the view name are
- deemed the "subpath". For instance, if ``PATH_INFO`` was
- ``/a/b`` and the root returned an "A" object, and the "A" object
- returned a "B" object, the router deems that the context is
- "object B", the view name is the empty string, and the subpath is
- the empty sequence. On the other hand, if ``PATH_INFO`` was
- ``/a/b/c`` and "object A" was found but raised a KeyError for the
- name ``b``, the router deems that the context is object A, the
- view name is ``b`` and the subpath is ``['c']``.
-
- 7. If a security policy is configured, the router performs a
- permission lookup. If a permission declaration is found for the
- view name and context implied by the current request, the
- security policy is consulted to see if the "current user" (also
- determined by the security policy) can perform the action. If he
- can, processing continues. If he cannot, an HTTPUnauthorized
- error is raised.
-
- 8. Armed with the context, the view name, and the subpath, the
- router performs a view lookup. It attemtps to look up a view
- from the ``repoze.bfg`` view registry using the view name and the
- context. If a view factory is found, it is called with the
- context and the request. It returns a response, which is fed
- back upstream. If a view is not found, a generic WSGI
- ``NotFound`` application is constructed.
-
-In either case, the result is returned upstream via the WSGI protocol.
-
-A Traversal Example
--------------------
-
-Let's pretend the user asks for
-``http://example.com/foo/bar/baz/biz/buz.txt``. Let's pretend that the
-request's ``PATH_INFO`` in that case is ``/foo/bar/baz/biz/buz.txt``.
-Let's further pretend that when this request comes in that we're
-traversing the follwing graph::
-
- /--
- |
- |-- foo
- |
- ----bar
-
-Here's what happens:
-
- - bfg traverses the root, and attempts to find foo, which it finds.
-
- - bfg traverses foo, and attempts to find bar, which it finds.
-
- - bfg traverses bar, and attempts to find baz, which it does not
- find ('bar' raises a ``KeyError`` when asked for baz).
-
-The fact that it does not find "baz" at this point does not signify an
-error condition. It signifies that:
-
- - the "context" is bar (the context is the last item found during
- traversal).
-
- - the "view name" is ``baz``
-
- - the "subpath" is ``['biz', 'buz.txt']``
-
-Because it's the "context", bfg examimes "baz" to find out what "type"
-it is. Let's say it finds that the context an ``IBar`` type (because
-"bar" happens to have an attribute attached to it that indicates it's
-an ``IBar``).
-
-Using the "view name" ("baz") and the type, it asks the "view
-registry" (configured separately, in our case via "configure.zcml")
-this question:
-
- - Please find me a "view" (controller in some religions) with the
- name "baz" that can be used for the type ``IBar``.
-
-Let's say it finds no matching view type. It then returns a NotFound.
-The request ends. Everyone is sad.
-
-But! For this graph::
-
- /--
- |
- |-- foo
- |
- ----bar
- |
- ----baz
- |
- biz
-
-The user asks for ``http://example.com/foo/bar/baz/biz/buz.txt``
-
- - bfg traverses foo, and attempts to find bar, which it finds.
-
- - bfg traverses bar, and attempts to find baz, which it finds.
-
- - bfg traverses baz, and attempts to find biz, which it finds.
-
- - bfg traverses biz, and attemtps to find "buz.txt" which it does
- not find.
-
-The fact that it does not find "biz.txt" at this point does not
-signify an error condition. It signifies that:
-
- - the "context" is biz (the context is the last item found during traversal).
-
- - the "view name" is "buz.txt"
-
- - the "subpath" is the empty list []
-
-Because it's the "context", bfg examimes "biz" to find out what "type"
-it is. Let's say it finds that the context an ``IBiz`` type (because
-"biz" happens to have an attribute attached to it that happens
-indicates it's an ``IBiz``).
-
-Using the "view name" ("buz.txt") and the type, it asks the "view
-registry" (configured separately, in our case in "configure.zcml")
-this question:
-
- - Please find me a "view" (controller in some religions) with the name
- "buz.txt" that can be used for type ``IBiz``.
-
-Let's say that question is answered "here you go, here'a a bit of code
-that is willing to deal with that case", and returns a view. It is
-passed the "biz" object as the "context" and the current WebOb request
-as the "request". It returns a response.
-
-There are two special cases:
-
-- During traversal you will often end up with a "view name" that is
- the empty string. This indicates that ``repoze.bfg`` should look up
- the *default view*. The default view is a view that is registered
- with no name or a view which is registered with a name that equals
- the empty string.
-
-- If any path segment element begins with the special characters
- ``@@`` (think of them as goggles), that element is considered the
- view name immediately and traversal stops there. This allows you to
- address views that may have the same names as model instance names
- without conflict.
-
-A Sample Application
---------------------
-
-A typical simple ``repoze.bfg`` application consists of four things:
-
- 1. A ``views.py`` module, which contains view code.
-
- 2. A ``models.py`` module, which contains model code.
-
- 3. A ``configure.zcml`` file which maps view names to model types.
- This is also known as the "view registry", although it also
- often contains non-view-related declarations.
-
- 4. A "templates" directory, which is full of zc3.pt templates.
-
-An application must be a Python package (meaning it must have an
-__init__.py and it must be findable on the PYTHONPATH).
-
-We don't describe any security in our very simple sample application.
-Security is optional in a repoze.bfg application; it needn't be used
-until necessary.
-
-views.py
-~~~~~~~~
-
-A views.py module might look like so::
-
- from webob import Response
- from repoze.bfg.template import render_template_to_response
-
- def my_hello_view(context, request):
- response = Response('Hello from %s @ %s' % (
- context.__name__,
- request.environ['PATH_INFO']))
- return response
-
- def my_template_view(context, request):
- return render_template_to_response('templates/my.pt', name=context.__name__)
-
-models.py
-~~~~~~~~~
-
-A models.py might look like so::
-
- from UserDict import UserDict
-
- from zope.interface import implements
- from zope.interface import Attribute
- from zope.interface import Interface
-
- class IMyModel(Interface):
- __name__ = Attribute('Name of the model instance')
-
- class MyModel(UserDict):
- implements(IMyModel)
- def __init__(self, name):
- self.__name__ = name
- UserDict.__init__(self, {})
-
- # model instance info would typically be stored in a database of some
- # kind; here we put it at module scope for demonstration purposes.
-
- root = MyModel('root')
- root['a'] = MyModel('a')
- root['b'] = MyModel('b')
-
- def get_root(environ):
- return root
-
-configure.zcml
-~~~~~~~~~~~~~~
-
-A view registry might look like so::
-
- <configure xmlns="http://namespaces.zope.org/zope"
- xmlns:bfg="http://namespaces.repoze.org/bfg"
- i18n_domain="repoze.bfg">
-
- <!-- this must be included for the view declarations to work -->
- <include package="repoze.bfg" />
-
- <!-- the default view for a MyModel -->
- <bfg:view
- for=".models.IMyModel"
- view=".views.my_hello_view"
- permission="read"
- />
-
- <!-- the templated.html view for a MyModel -->
- <bfg:view
- for=".models.IMyModel"
- view=".views.my_template_view"
- name="templated.html"
- permission="read"
- />
-
- </configure>
-
-templates/my.pt
-~~~~~~~~~~~~~~~
-
-A template that is used by a view might look like so::
-
- <html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
- <head></head>
- <body>
- <h1>My template viewing ${name}</h1>
- </body>
- </html>
-
-Running the Application
------------------------
-
-To run the application above, the simplest method is to run it
-directly from a starter script (although you might also use Paste to
-perform this task)::
-
- from paste import httpserver
-
- from repoze.bfg import make_app
- from myapp.models import get_root
- import myapp
-
- app = make_app(get_root, myapp)
- httpserver.serve(app, host='0.0.0.0', port='5432')
-
-Viewing the Application
------------------------
-
-Visit http://localhost:5432/ in your browser. You will see::
-
- Hello from root @ /
-
-Visit http://localhost:5432/a in your browser. You will see::
-
- Hello from a @ /a
-
-Visit http://localhost:5432/b in your browser. You will see::
-
- Hello from b @ /b
-
-Visit http://localhost:5432/templated.html in your browser. You will
-see::
-
- My template viewing root
-
-
-Visit http://localhost:5432/a/templated.html in your browser. You
-will see::
-
- My template viewing a
-
-Visit http://localhost:5432/b/templated.html in your browser. You
-will see::
-
- My template viewing b
-
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 7c48874b7..c12e0db71 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -1,6 +1,12 @@
Starting a ``repoze.bfg`` Project
=================================
+You can use ``repoze.bfg`` 's sample application generator to get
+started.
+
+Creating the Project
+--------------------
+
To start a ``repoze.bfg`` project, use the ``paster create``
facility::
@@ -33,12 +39,12 @@ facility::
Running /Users/chrism/projects/repoze-devel/bfg/bin/python setup.py egg_info
The project will be created in a directory named ``myproject``. That
-directory is a directory from which a Python setuptools *distribution*
-can be created. THe ``setup.py`` file in that directory can be used
-to distribute your application, or install your application for
-deployment or development. A sample PasteDeploy ``.ini`` file named
-``myproject.ini`` will also be created in the project directory. You
-can use this to run your application.
+directory is a setuptools *project* directory from which a Python
+setuptools *distribution* can be created. The ``setup.py`` file in
+that directory can be used to distribute your application, or install
+your application for deployment or development. A sample PasteDeploy
+``.ini`` file named ``myproject.ini`` will also be created in the
+project directory. You can use this to run your application.
The main ``myproject`` contains an additional subdirectory (also named
``myproject``) representing a Python pakckage which holds very simple
@@ -48,9 +54,11 @@ code and templates.
Installing your Newly Created Project for Development
-----------------------------------------------------
-Using your favorite Python interpreter (using a virtualenv suggested
-in order to get isolation), invoke the following command when inside
-the project directory against the generated ``setup.py``::
+Using your favorite Python interpreter (using a `virtualenv
+<http://pypi.python.org/pypi/virtualenv>`_ is suggested in order to
+isolate your application from your system Python's packages), invoke
+the following command when inside the project directory against the
+generated ``setup.py``::
$ python setup.py develop
...
@@ -59,29 +67,6 @@ the project directory against the generated ``setup.py``::
This will install your application 's package into the interpreter so
it can be found and run under a webserver.
-Runnning The Project Application
---------------------------------
-
-Once the project is installed for development, you can run it using
-the ``paster serve`` command against the generated ``myproject.ini``
-configuration file::
-
- $ paster serve myproject/myproject.ini
- Starting server in PID 16601.
- serving on 0.0.0.0:5432 view at http://127.0.0.1:5432
-
-It will listen on port 5432. If you visit the unchanged sample
-application using a browser (e.g. http://localhost:5432/), you will
-see the following::
-
- Welcome to myproject
-
-.. note:: During development, it's often useful to run ``paster serve``
- using its ``--reload`` option. When any Python module your project
- uses, changes, it will restart the server, which makes development
- easier, as changes to Python code under ``repoze.bfg`` is not put
- into effect until the server restarts.
-
Running The Tests For Your Application
--------------------------------------
@@ -104,5 +89,151 @@ To run unit tests for your application, you should invoke them like so::
OK
-The tests are found in the ``tests.py`` module in your generated
-project. One sample test exists.
+The tests are found in the ``tests.py`` module in your
+paster-create-generated project. One sample test exists.
+
+Runnning The Project Application
+--------------------------------
+
+Once the project is installed for development, you can run the
+application it represents using the ``paster serve`` command against
+the generated ``myproject.ini`` configuration file::
+
+ $ paster serve myproject/myproject.ini
+ Starting server in PID 16601.
+ serving on 0.0.0.0:5432 view at http://127.0.0.1:5432
+
+It will listen on port 5432.
+
+.. note:: During development, it's often useful to run ``paster serve``
+ using its ``--reload`` option. When any Python module your project
+ uses, changes, it will restart the server, which makes development
+ easier, as changes to Python code under ``repoze.bfg`` is not put
+ into effect until the server restarts.
+
+Viewing the Application
+-----------------------
+
+Visit http://localhost:5432/ in your browser. You will see::
+
+ Welcome to myproject
+
+The Project Structure
+---------------------
+
+Our generated ``repoze.bfg`` application is a setuptools *project*
+(named ``myproject``), which contains a Python package (also named
+``myproject``).
+
+The ``myproject`` package has the following files and directories:
+
+ 1. A ``views.py`` module, which contains view code.
+
+ 2. A ``models.py`` module, which contains model code.
+
+ 3. A ``run.py`` module, which contains code that helps users run the
+ application.
+
+ 4. A ``configure.zcml`` file which maps view names to model types.
+ This is also known as the "application registry", although it
+ also often contains non-view-related declarations.
+
+ 5. A ``templates`` directory, which is full of zc3.pt and/or XSL
+ templates.
+
+This is purely by convention: ``repoze.bfg`` doesn't insist that you
+name things in any particular way.
+
+We don't describe any security in our sample application. Security is
+optional in a repoze.bfg application; it needn't be used until
+necessary.
+
+``views.py``
+------------
+
+The code in the views.py project looks like this::
+
+ from repoze.bfg.template import render_template_to_response
+
+ def my_view(context, request):
+ return render_template_to_response('templates/mytemplate.pt',
+ project = 'myproject')
+
+``models.py``
+-------------
+
+The code in the models.py looks like this::
+
+ from zope.interface import Interface
+ from zope.interface import implements
+
+ class IMyModel(Interface):
+ pass
+
+ class MyModel(object):
+ implements(IMyModel)
+ pass
+
+ root = MyModel()
+
+ def get_root(environ):
+ return root
+
+In a "real" application, the root object would not be such a simple
+object. Instead, it would be an object that could access some
+persistent data store, such as a database. ``repoze.bfg`` doesn't
+make any assumption about which sort of datastore you'll want to use,
+so the sample application uses an instance of ``MyModel`` to represent
+the root.
+
+``configure.zcml``
+------------------
+
+The ``configure.zcml`` (representing the application registry) looks
+like so::
+
+ <configure xmlns="http://namespaces.zope.org/zope"
+ xmlns:bfg="http://namespaces.repoze.org/bfg"
+ i18n_domain="repoze.bfg">
+
+ <!-- this must be included for the view declarations to work -->
+ <include package="repoze.bfg" />
+
+ <bfg:view
+ for=".models.IMyModel"
+ view=".views.my_view"
+ />
+
+ </configure>
+
+``templates/my.pt``
+-------------------
+
+The single template in the project looks like so::
+
+ <html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:tal="http://xml.zope.org/namespaces/tal">
+ <head></head>
+ <body>
+ <h1>Welcome to ${project}</h1>
+ </body>
+ </html>
+
+``run.py``
+----------
+
+The run.py file looks like so::
+
+ def make_app(global_config, **kw):
+ # paster app config callback
+ from repoze.bfg import make_app
+ from myproject.models import get_root
+ import myproject
+ app = make_app(get_root, myproject)
+ return app
+
+ if __name__ == '__main__':
+ from paste import httpserver
+ app = make_app(None)
+ httpserver.serve(app, host='0.0.0.0', port='5432')
+
diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst
new file mode 100644
index 000000000..3e7c6125b
--- /dev/null
+++ b/docs/narr/traversal.rst
@@ -0,0 +1,269 @@
+Traversal
+=========
+
+In many popular web frameworks, a "URL dispatcher" is used to
+associate a particular URL with a bit of code (known somewhat
+ambiguously as a "controller" or "view" depending upon the particular
+vocabulary religion to which you subscribe). These systems allow the
+developer to create "urlconfs" or "routes" to controller/view Python
+code using pattern matching against URL components. Examples:
+`Django's URL dispatcher
+<http://www.djangoproject.com/documentation/url_dispatch/>`_ and the
+`Routes URL mapping system <http://routes.groovie.org/>`_ .
+
+It is however possible to map URLs to code differently, using object
+graph traversal. The venerable Zope and CherryPy web frameworks offer
+graph-traversal-based URL dispatch. ``repoze.bfg`` also provides
+graph-traversal-based dispatch of URLs to code. Graph-traversal based
+dispatching is useful if you like the URL to be representative of an
+arbitrary hierarchy of potentially heterogeneous items.
+
+Non-graph traversal based URL dispatch can easily handle URLs such as
+``http://example.com/members/Chris``, where it's assumed that each
+item "below" ``members`` in the URL represents a member in the system.
+You just match everything "below" ``members`` to a particular view.
+They are not very good, however, at inferring the difference between
+sets of URLs such as ``http://example.com/members/Chris/document`` vs.
+``http://example.com/members/Chris/stuff/page`` wherein you'd like the
+``document`` in the first URL to represent, e.g. a PDF document, and
+``/stuff/page`` in the second to represent, e.g. an OpenOffice
+document in a "stuff" folder. It takes more pattern matching
+assertions to be able to make URLs like these work in URL-dispatch
+based systems, and some assertions just aren't possible. For example,
+URL-dispatch based systems don't deal very well with URLs that
+represent arbitrary-depth hierarchies.
+
+Graph traversal works well if you need to divine meaning out of these
+types of "ambiguous" URLs and URLs that represent arbitrary-depth
+hierarchies. Each URL segment represents a single traversal through
+an edge of the graph. So a URL like ``http://example.com/a/b/c`` can
+be thought of as a graph traversal on the example.com site through the
+edges "a", "b", and "c".
+
+Finally, if you're willing to treat your application models as a graph
+that can be traversed, it also becomes trivial to provide "row-level
+security" (in common relational parlance): you just attach a security
+declaration to each instance in the graph. This is not as easy in
+frameworks that use URL-based dispatch.
+
+Graph traversal is materially more complex than URL-based dispatch,
+however, if only because it requires the construction and maintenance
+of a graph, and it requires the developer to think about mapping URLs
+to code in terms of traversing the graph. (How's *that* for
+self-referential! ;-) That said, for developers comfortable with Zope,
+in particular, and comfortable with hierarchical data stores like
+ZODB, mapping a URL to a graph traversal it's a natural way to think
+about creating a web application.
+
+In essence, the choice to use graph traversal vs. URL dispatch is
+largely religious in some sense. Graph traversal dispatch probably
+just doesn't make any sense when you possess completely "square" data
+stored in a relational database. However, when you have a
+hierarchical data store, it can provide advantages over using
+URL-based dispatch.
+
+The Model Graph
+---------------
+
+Users interact with your ``repoze.bfg``-based application via a
+"router", which is itself a WSGI application. At system startup time,
+the router is configured with a root object from which all traversal
+will begin. The root object is a mapping object, such as a Python
+dictionary. In fact, all items contained in the graph are either leaf
+nodes (these have no ``__getitem__``) or container nodes (these do
+have a ``__getitem__``).
+
+Items contained within the graph are analogous to the concept of
+``model`` objects used by many other frameworks (and ``repoze.bfg``
+refers to them as models, as well). They are typically instances of
+classes. Each containerish instance is willing to return a child or
+raise a KeyError based on a name passed to its ``__getitem__``. No
+leaf-level instance is required to have a ``__getitem__``.
+
+``repoze.bfg`` traverses the model graph in order to find a *context*.
+It then attempts to find a *view* based on the type of the context.
+
+How ``repoze.bfg`` Processes a Request Using Traversal
+------------------------------------------------------
+
+When a user requests a page from your ``repoze.bfg`` -powered
+application, the system uses this algorithm to determine which Python
+code to execute:
+
+ 1. The request for the page is presented to ``repoze.bfg``'s
+ "router" in terms of a standard WSGI request, which is
+ represented by a WSGI environment and a start_response callable.
+
+ 2. The router creates a `WebOb <http://pythonpaste.org/webob/>`_
+ request object based on the WSGI environment.
+
+ 3. The router uses the WSGI environment's ``PATH_INFO`` variable to
+ determine the path segments to traverse. The leading slash is
+ stripped off ``PATH_INFO``, and the remaining path segments are
+ split on the slash character to form a traversal sequence, so a
+ request with a ``PATH_INFO`` variable of ``/a/b/c`` maps to the
+ traversal sequence ``['a', 'b', 'c']``.
+
+ 4. Traversal begins at the root object. For the traversal sequence
+ ``['a', 'b', 'c']``, the root object's ``__getitem__`` is called
+ with the name ``a``. Traversal continues through the sequence.
+ In our example, if the root object's ``__getitem__`` called with
+ the name ``a`` returns an object (aka "object A"), that object's
+ ``__getitem__`` is called with the name ``b``. If object A
+ returns an object when asked for ``b``, object B's
+ ``__getitem__`` is then asked for the name ``c``, and may return
+ object C.
+
+ 5. Traversal ends when a) the entire path is exhausted or b) when
+ any graph element raises a KeyError from its ``__getitem__`` or
+ c) when any non-final path element traversal does not have a
+ ``__getitem__`` method (resulting in a NameError) or d) when any
+ path element is prefixed with the set of characters ``@@``
+ (indicating that the characters following the ``@@`` token should
+ be treated as a "view name").
+
+ 6. When traversal ends for any of the reasons in the previous step,
+ the the last object found during traversal is deemed to be the
+ "context". If the path has been exhausted when traversal ends,
+ the "view name" is deemed to be the empty string (``''``).
+ However, if the path was not exhausted before traversal
+ terminated, the first remaining path element is treated as the
+ view name. Any subseqent path elements after the view name are
+ deemed the "subpath". For instance, if ``PATH_INFO`` was
+ ``/a/b`` and the root returned an "A" object, and the "A" object
+ returned a "B" object, the router deems that the context is
+ "object B", the view name is the empty string, and the subpath is
+ the empty sequence. On the other hand, if ``PATH_INFO`` was
+ ``/a/b/c`` and "object A" was found but raised a KeyError for the
+ name ``b``, the router deems that the context is object A, the
+ view name is ``b`` and the subpath is ``['c']``.
+
+ 7. If a security policy is configured, the router performs a
+ permission lookup. If a permission declaration is found for the
+ view name and context implied by the current request, the
+ security policy is consulted to see if the "current user" (also
+ determined by the security policy) can perform the action. If he
+ can, processing continues. If he cannot, an HTTPUnauthorized
+ error is raised.
+
+ 8. Armed with the context, the view name, and the subpath, the
+ router performs a view lookup. It attemtps to look up a view
+ from the ``repoze.bfg`` application registry using the view name
+ and the context. If a view factory is found, it is called with
+ the context and the request. It returns a response, which is fed
+ back upstream. If a view is not found, a generic WSGI
+ ``NotFound`` application is constructed.
+
+In either case, the result is returned upstream via the WSGI protocol.
+
+A Traversal Example
+-------------------
+
+Let's pretend the user asks for
+``http://example.com/foo/bar/baz/biz/buz.txt``. Let's pretend that the
+request's ``PATH_INFO`` in that case is ``/foo/bar/baz/biz/buz.txt``.
+Let's further pretend that when this request comes in that we're
+traversing the follwing graph::
+
+ /--
+ |
+ |-- foo
+ |
+ ----bar
+
+Here's what happens:
+
+ - bfg traverses the root, and attempts to find foo, which it finds.
+
+ - bfg traverses foo, and attempts to find bar, which it finds.
+
+ - bfg traverses bar, and attempts to find baz, which it does not
+ find ('bar' raises a ``KeyError`` when asked for baz).
+
+The fact that it does not find "baz" at this point does not signify an
+error condition. It signifies that:
+
+ - the "context" is bar (the context is the last item found during
+ traversal).
+
+ - the "view name" is ``baz``
+
+ - the "subpath" is ``['biz', 'buz.txt']``
+
+Because it's the "context", bfg examimes "baz" to find out what "type"
+it is. Let's say it finds that the context an ``IBar`` type (because
+"bar" happens to have an attribute attached to it that indicates it's
+an ``IBar``).
+
+Using the "view name" ("baz") and the type, it asks the "application
+registry" (configured separately, via "configure.zcml") this question:
+
+ - Please find me a "view" (controller in some religions) with the
+ name "baz" that can be used for the type ``IBar``.
+
+Let's say it finds no matching view type. It then returns a NotFound.
+The request ends. Everyone is sad.
+
+But! For this graph::
+
+ /--
+ |
+ |-- foo
+ |
+ ----bar
+ |
+ ----baz
+ |
+ biz
+
+The user asks for ``http://example.com/foo/bar/baz/biz/buz.txt``
+
+ - bfg traverses foo, and attempts to find bar, which it finds.
+
+ - bfg traverses bar, and attempts to find baz, which it finds.
+
+ - bfg traverses baz, and attempts to find biz, which it finds.
+
+ - bfg traverses biz, and attemtps to find "buz.txt" which it does
+ not find.
+
+The fact that it does not find "biz.txt" at this point does not
+signify an error condition. It signifies that:
+
+ - the "context" is biz (the context is the last item found during traversal).
+
+ - the "view name" is "buz.txt"
+
+ - the "subpath" is the empty list []
+
+Because it's the "context", bfg examimes "biz" to find out what "type"
+it is. Let's say it finds that the context an ``IBiz`` type (because
+"biz" happens to have an attribute attached to it that happens
+indicates it's an ``IBiz``).
+
+Using the "view name" ("buz.txt") and the type, it asks the
+"application registry" (configured separately, in "configure.zcml")
+this question:
+
+ - Please find me a "view" (controller in some religions) with the name
+ "buz.txt" that can be used for type ``IBiz``.
+
+Let's say that question is answered "here you go, here'a a bit of code
+that is willing to deal with that case", and returns a view. It is
+passed the "biz" object as the "context" and the current WebOb request
+as the "request". It returns a response.
+
+There are two special cases:
+
+- During traversal you will often end up with a "view name" that is
+ the empty string. This indicates that ``repoze.bfg`` should look up
+ the *default view*. The default view is a view that is registered
+ with no name or a view which is registered with a name that equals
+ the empty string.
+
+- If any path segment element begins with the special characters
+ ``@@`` (think of them as goggles), that segment is considered the
+ "view name" immediately and traversal stops there. This allows you
+ to address views that may have the same names as model instance
+ names in the graph umambiguously.
+