summaryrefslogtreecommitdiff
path: root/docs/narr/firstapp.rst
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2010-01-11 18:58:50 +0000
committerChris McDonough <chrism@agendaless.com>2010-01-11 18:58:50 +0000
commit9ec2d646eb23b88e4ef8119b0b46240081953daf (patch)
tree80296b92edb8310bfb7699d165e92a1ea9e36256 /docs/narr/firstapp.rst
parent96d63068ba033809ed73834e095f274800e4d4e8 (diff)
downloadpyramid-9ec2d646eb23b88e4ef8119b0b46240081953daf.tar.gz
pyramid-9ec2d646eb23b88e4ef8119b0b46240081953daf.tar.bz2
pyramid-9ec2d646eb23b88e4ef8119b0b46240081953daf.zip
Merge of andrew-docs branch.
Diffstat (limited to 'docs/narr/firstapp.rst')
-rw-r--r--docs/narr/firstapp.rst767
1 files changed, 767 insertions, 0 deletions
diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst
new file mode 100644
index 000000000..e533fdcb2
--- /dev/null
+++ b/docs/narr/firstapp.rst
@@ -0,0 +1,767 @@
+.. _configuration_narr:
+
+Creating Your First :mod:`repoze.bfg` Application
+=================================================
+
+We'll walk through the creation of a tiny :mod:`repoze.bfg`
+application in this chapter, and explain in more detail how the
+application works.
+
+.. index::
+ single: helloworld
+
+.. _helloworld_imperative:
+
+Hello World, Goodbye World (Imperative)
+---------------------------------------
+
+Here's one of the simplest :mod:`repoze.bfg` applications, configured
+imperatively:
+
+.. code-block:: python
+ :linenos:
+
+ from webob import Response
+ from paste.httpserver import serve
+ from repoze.bfg.configuration import Configurator
+
+ def hello_world(request):
+ return Response('Hello world!')
+
+ def goodbye_world(request):
+ return Response('Goodbye world!')
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.begin()
+ config.add_view(hello_world)
+ config.add_view(goodbye_world, name='goodbye')
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+When this code is inserted into a Python script named
+``helloworld.py`` and executed by a Python interpreter which has the
+:mod:`repoze.bfg` software installed, an HTTP server is started on
+port 8080. When port 8080 is visited by a user agent on the root URL
+(``/``), the server will simply serve up the text "Hello world!" with
+the HTTP response values ``200 OK`` as a response code and a
+``Content-Type`` header value of ``text/plain``. But for reasons
+we'll better understand shortly, when visited by a user agent on the
+URL ``/goodbye``, the server will serve up "Goodbye world!".
+
+Let's examine this program piece-by-piece.
+
+Imports
+~~~~~~~
+
+The above script defines the following set of imports:
+
+.. code-block:: python
+ :linenos:
+
+ from webob import Response
+ from paste.httpserver import serve
+ from repoze.bfg.configuration import Configurator
+
+:mod:`repoze.bfg` uses the :term:`WebOb` library as the basis for its
+:term:`request` and :term:`response` objects. The script uses the
+:class:`webob.Response` class later in the script to create a
+:term:`response` object.
+
+Like many other Python web frameworks, :mod:`repoze.bfg` uses the
+:term:`WSGI` protocol to connect an application and a web server
+together. The :mod:`paste.httpserver` server is used in this example
+as a WSGI server for convenience, as ``Paste`` is a dependency of
+:mod:`repoze.bfg` itself. However, :mod:`repoze.bfg` applications can
+be served by any WSGI server.
+
+The script also imports the ``Configurator`` class from the
+``repoze.bfg.configuration`` module. This class is used to configure
+:mod:`repoze.bfg` for a particular application. An instance of this
+class provides methods which help configure various parts of
+:mod:`repoze.bfg` for a given application deployment.
+
+View Declaration
+~~~~~~~~~~~~~~~~
+
+The above script, beneath its set of imports, defines two functions:
+one named ``hello_world`` and one named ``goodbye_world``.
+
+.. code-block:: python
+ :linenos:
+
+ def hello_world(request):
+ return Response('Hello world!')
+
+ def goodbye_world(request):
+ return Response('Goodbye world!')
+
+Each function accepts a single argument (``request``) and returns an
+instance of the :class:`webob.Response` class. In the ``hello_world``
+function, the string ``'Hello world!'`` is passed to the ``Response``
+constructor as the *body* of the response. In the ``goodbye_world``
+function, the string ``'Goodbye world!'`` is passed.
+
+Each of these functions is known as a :term:`view callable`. View
+callables in a "real" :mod:`repoze.bfg` application are often
+functions which accept a :term:`request` and return a
+:term:`response`. A view callable can be represented via another type
+of object, like a class or an instance, but for our purposes here, a
+function serves us well.
+
+A view callable is called with a :term:`request` object, which is a
+representation of an HTTP request sent by a remote user agent. A view
+callable is required to return a :term:`response` object because a
+response object has all the information necessary to formulate an
+actual HTTP response; this object is then converted to text and sent
+back to the requesting user agent.
+
+The ``hello_world`` view callable defined by the script does nothing
+but return a response with the body ``Hello world!``; the
+``goodbye_world`` view callable returns a response with the body
+``Goodbye world!``.
+
+.. index::
+ pair: traversal; introduction
+
+.. _traversal_intro:
+
+An Introduction to Traversal
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you've run the code listed in :ref`helloworld_imperative` already,
+you've unwittingly configured :mod:`repoze.bfg` to serve an
+application that relies on :term:`traversal`. A full explanation of
+how :mod:`repoze.bfg` locates "the right" :term:`view callable` for a
+given request requires some explanation of :term:`traversal`.
+
+Traversal is part of a mechanism used by :mod:`repoze.bfg` to map the
+URL of some request to a particular :term:`view callable`. It is not
+the only mechanism made available by :mod:`repoze.bfg` that allows the
+mapping a URL to a view callable. Another distinct mode known as
+:term:`URL dispatch` can alternately be used to find a view callable
+based on a URL. However, our sample application uses only
+:term:`traversal`.
+
+In :mod:`repoze.bfg` terms, :term:`traversal` is the act of walking
+over an object graph starting from a :term:`root` object in order to
+find a :term:`context` object and a :term:`view name`. Once a context
+and a view name are found, these two bits of information, plus other
+information from the request are used to look up a :term:`view
+callable`. :mod:`repoze.bfg` bothers to do traversal only because the
+information returned from traversal allows a view callable to be
+found.
+
+The individual path segments of the "path info" portion of a URL (the
+data following the hostname and port number, but before any query
+string elements or fragments, for example the ``/a/b/c`` portion of
+the URL ``http://example.com/a/b/c?foo=1``) are used as "steps" during
+traversal.
+
+.. note:: A useful analogy of how :mod:`repoze.bfg` :term:`traversal`
+ works is available within the chapter section entitled
+ :ref:`traversal_behavior`.
+
+The results of a :term:`traversal` include a :term:`context` and a
+:term:`view name`. The :term:`view name` is the *first* URL path
+segment in the set of path segments "left over" in the results of
+:term:`traversal`. This will either be the empty string (``''``) or a
+non-empty string (one of the path segment strings). The empty string
+represents the :term:`default view` of a context object.
+
+The :term:`default view` is found when all path elements in the URL
+are exhausted before :term:`traversal` returns a :term:`context`
+object, causing the :term:`view name` to be ``''`` (the empty string).
+When no path segments are "left over" after traversal, the
+:term:`default view` for the context found is invoked.
+
+If traversal returns a non-empty :term:`view name`, it means that
+traversal "ran out" of nodes in the graph before it finished
+exhausting all the path segments implied by the path info of the URL:
+no segments are "left over". In this case, because the :term:`view
+name` is non-empty, a *non-default* view callable will be invoked.
+
+The combination of the :term:`context` object and the :term:`view
+name` (and, in more complex configurations, other :term:`predicate`
+values) are used to find "the right" :term:`view callable`, which will
+be invoked after traversal.
+
+The object graph of our hello world application is very simple:
+there's exactly one object in our graph; the default :term:`root`
+object.
+
+Relating Traversal to the Hello World Application
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Our application's :term:`root` object is the *default* root object
+used when one isn't otherwise specified in application configuration.
+This root object does not have a ``__getitem__`` method, thus it has
+no children. Although in a more complex system there can be many
+contexts which URLs resolve to in our application, effectively there
+is only ever one context: the root object.
+
+We have only a single default view registered (the registration for
+the ``hello_world`` view callable). Due to this set of circumstances,
+you can consider the sole possible URL that will resolve to a
+:term:`default view` in this application the root URL ``'/'``. It is
+the only URL that will resolve to the :term:`view name` of ``''`` (the
+empty string).
+
+We have only a single view registered for the :term:`view name`
+``goodbye`` (the registration for the ``goodbye_world`` view
+callable). Due to this set of circumstances, you can consider the
+sole possible URL that will resolve to the ``goodbye_world`` in this
+application the URL ``'/goodbye'`` because it is the only URL that
+will resolve to the :term:`view name` of ``goodbye``.
+
+.. index::
+ pair: imperative; configuration
+ single: Configurator
+
+.. _helloworld_imperative_appconfig:
+
+Application Configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In the above script, the following code, representing the
+*configuration* of an application which uses the previously defined
+imports and function definitions is placed within the confines of an
+``if`` statement:
+
+.. code-block:: python
+ :linenos:
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.begin()
+ config.add_view(hello_world)
+ config.add_view(goodbye_world, name='goodbye')
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+Let's break this down this piece-by-piece.
+
+Configurator Construction
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+ :linenos:
+
+ if __name__ == '__main__':
+ config = Configurator()
+
+The ``if __name__ == '__main__':`` line above represents a Python
+idiom: the code inside this if clause is not invoked unless the script
+is run directly from the command line via, for example, ``python
+helloworld.py`` where the file named ``helloworld.py`` contains the
+entire script body. ``helloworld.py`` in this case is a Python
+*module*. Using the ``if`` clause is necessary (or at least "best
+practice") because code in any Python module may be imported by
+another Python module. By using this idiom, the script is indicating
+that it does not want the code within the ``if`` statement to execute
+if this module is imported; the code within the ``if`` block should
+only be run during a direct script execution.
+
+The ``config = Configurator()`` line above creates an instance of the
+:class:`repoze.bfg.configuration.Configurator` class. The resulting
+``config`` object represents an API which the script uses to configure
+this particular :mod:`repoze.bfg` application.
+
+.. note::
+
+ An instance of the :class:`repoze.bfg.configuration.Configurator`
+ class is a *wrapper* object which mutates an :term:`application
+ registry` as its methods are called. An application registry
+ represents the configuration state of a :mod:`repoze.bfg`
+ application. The ``Configurator`` is not itself an
+ :term:`application registry`, it is a mechanism used to configure
+ an application registry. The underlying application registry
+ object being configured by a ``Configurator`` is available as its
+ ``registry`` attribute.
+
+Beginning Configuration
+~~~~~~~~~~~~~~~~~~~~~~~
+
+.. ignore-next-block
+.. code-block:: python
+
+ config.begin()
+
+The :meth:`repoze.bfg.configuration.Configurator.begin` method tells
+the the system that application configuration has begun. In
+particular, this causes the :term:`application registry` associated
+with this configurator to become the "current" application registry,
+meaning that code which attempts to use the application registry
+:term:`thread local` will obtain the registry associated with the
+configurator. This is an explicit step because it's sometimes
+convenient to use a configurator without causing the registry
+associated with the configurator to become "current".
+
+Adding Configuration
+~~~~~~~~~~~~~~~~~~~~
+
+.. ignore-next-block
+.. code-block:: python
+ :linenos:
+
+ config.add_view(hello_world)
+ config.add_view(goodbye_world, name='goodbye')
+
+Each of these lines calls the
+:meth:`repoze.bfg.configuration.Configurator.add_view` method. The
+``add_view`` method of a configurator registers a :term:`view
+configuration` within the :term:`application registry`. A :term:`view
+configuration` represents a :term:`view callable` which must be
+invoked when a set of circumstances related to the :term:`request` is
+true. This "set of circumstances" is provided as one or more keyword
+arguments to the ``add_view`` method, otherwise known as
+:term:`predicate` arguments.
+
+The line ``config.add_view(hello_world)`` registers the
+``hello_world`` function as a view callable. The ``add_view`` method
+of a Configurator must be called with a view callable object as its
+first argument, so the first argument passed is ``hello_world``
+function we'd like to use as a :term:`view callable`. However, this
+line calls ``add_view`` with a single default :term:`predicate`
+argument, the ``name`` predicate with a value of ``''``, meaning that
+we'd like :mod:`repoze.bfg` to invoke the ``hello_world`` view
+callable for any request for the :term:`default view` of an object.
+
+Our ``hello_world`` :term:`view callable` returns a Response instance
+with a body of ``Hello world!`` in the configuration implied by this
+script. It is configured as a :term:`default view`. Therefore, a
+user agent contacting a server running this application will receive
+the greeting ``Hello world!`` when any :term:`default view` is
+invoked.
+
+.. sidebar:: View Dispatch and Ordering
+
+ When :term:`traversal` is used, :mod:`repoze.bfg` chooses the most
+ specific view callable based *only* on view :term:`predicate`
+ applicability. This is unlike :term:`URL dispatch`, another
+ dispatch mode of :mod:`repoze.bfg` (and other frameworks, like
+ :term:`Pylons` and :term:`Django`) which first uses an ordered
+ routing lookup to resolve the request to a view callable by running
+ it through a relatively-ordered series of URL path matches. We're
+ not really concerned about the finer details of :term:`URL
+ dispatch` right now. It's just useful to use for demonstrative
+ purposes: the ordering of calls to
+ :meth:`repoze.bfg.configuration.Configurator.add_view`` is never
+ very important. We can register ``goodbye_world`` first and
+ ``hello_world`` second; :mod:`repoze.bfg` will still give us the
+ most specific callable when a request is dispatched to it.
+
+The line ``config.add_view(goodbye_world, name='goodbye')`` registers
+the ``goodbye_world`` function as a view callable. The line calls
+``add_view`` with the view callable as the first required positional
+argument, and a :term:`predicate` keyword argument ``name`` with the
+value ``'goodbye'``. This :term:`view configuration` implies that a
+request with a :term:`view name` of ``goodbye`` should cause the
+``goodbye_world`` view callable to be invoked. For the purposes of
+this discussion, the :term:`view name` can be considered the first
+non-empty path segment in the URL: in particular, this view
+configuration will match when the URL is ``/goodbye``.
+
+Our ``goodbye_world`` :term:`view callable` returns a Response
+instance with a body of ``Goodbye world!`` in the configuration
+implied by this script. It is configured as with a :term:`view name`
+predicate of ``goodbye``. Therefore, a user agent contacting a server
+running this application will receive the greeting ``Goodbye world!``
+when the path info part of the request is ``/goodbye``.
+
+Each invocation of the ``add_view`` method implies a :term:`view
+configuration` registration. Each :term:`predicate` provided as a
+keyword argument to the ``add_view`` method narrows the set of
+circumstances which would cause the view configuration's callable to
+be invoked. In general, a greater number of predicates supplied along
+with a view configuration will more strictly limit the applicability
+of its associated view callable. When :mod:`repoze.bfg` processes a
+request, however, the view callable with the *most specific* view
+configuration (the view configuration that matches the largest number
+of predicates) is always invoked.
+
+Earlier we explained that the server would return ``Hello world!`` if
+you visited the *root* (``/``) URL. However, actually, because the
+view configuration registration for the ``hello_world`` view callable
+has no :term:`predicate` arguments, the ``hello_world`` view callable
+is applicable for the :term:`default view` of any :term:`context`
+resulting from a request. This isn't all that interesting in this
+application, because we always only have *one* potential context (the
+root object): it is the only object in the graph.
+
+We've also registered a view configuration for another circumstance:
+the ``goodbye_world`` view callable has a ``name`` predicate of
+``goodbye``, meaning that it will match for requests that have the
+:term:`view name` ``goodbye`` unlike the ``hello_world`` view
+configuration registration, which will only match the default view
+(view name ``''``) of a request. Because :mod:`repoze.bfg` chooses
+the best view configuration for any request, the ``goodbye_world``
+view callable will be used when the URL contains path information that
+ends with ``/goodbye``.
+
+Ending Configuration
+~~~~~~~~~~~~~~~~~~~~
+
+.. ignore-next-block
+.. code-block:: python
+
+ config.end()
+
+The :meth:`repoze.bfg.configuration.Configurator.end` method tells the
+the system that application configuration has ended. It is the
+inverse of :meth:`repoze.bfg.configuration.Configurator.begin`. In
+particular, this causes the :term:`application registry` associated
+with this configurator to no longer be the "current" application
+registry, meaning that code which attempts to use the application
+registry :term:`thread local` will no longer obtain the registry
+associated with the configurator.
+
+.. index::
+ single: make_wsgi_app
+ pair: WSGI; application
+ triple: WSGI; application; creation
+
+WSGI Application Creation
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. ignore-next-block
+.. code-block:: python
+
+ app = config.make_wsgi_app()
+
+After configuring views and ending configuration, the script creates a
+WSGI *application* via the
+:meth:`repoze.bfg.configuration.Configurator.make_wsgi_app` method. A
+call to ``make_wsgi_app`` implies that all configuration is finished
+(meaning all method calls to the configurator which set up views, and
+various other configuration settings have been performed). The
+``make_wsgi_app`` method returns a :term:`WSGI` application object
+that can be used by any WSGI server to present an application to a
+requestor.
+
+The :mod:`repoze.bfg` application object, in particular, is an
+instance of the :class:`repoze.bfg.router.Router` class. It has a
+reference to the :term:`application registry` which resulted from
+method calls to the configurator used to configure it. The Router
+consults the registry to obey the policy choices made by a single
+application. These policy choices were informed by method calls to
+the ``Configurator`` made earlier; in our case, the only policy
+choices made were implied by two calls to the ``add_view`` method,
+telling our application that it should effectively serve up the
+``hello_world`` view callable to any user agent when it visits the
+root URL, and the ``goodbye_world`` view callable to any user agent
+when it visits the URL with the path info ``/goodbye``.
+
+WSGI Application Serving
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. ignore-next-block
+.. code-block:: python
+
+ serve(app)
+
+Finally, we actually serve the application to requestors by starting
+up a WSGI server. We happen to use the :func:`paste.httpserver.serve`
+WSGI server runner, using the default TCP port of 8080, and we pass it
+the ``app`` object (an instance of the
+:class:`repoze.bfg.router.Router` class) as the application we wish to
+serve. This causes the server to start listening on the TCP port. It
+will serve requests forever, or at least until we stop it by killing
+the process which runs it.
+
+Conclusion
+~~~~~~~~~~
+
+Our hello world application is one of the simplest possible
+:mod:`repoze.bfg` applications, configured "imperatively". We can see
+a good deal of what's going on "under the hood" when we configure a
+:mod:`repoze.bfg` application imperatively. However, another mode of
+configuration exists named *declarative* configuration.
+
+.. index::
+ pair: helloworld; declarative
+ single: helloworld
+
+.. _helloworld_declarative:
+
+Hello World, Goodbye World (Declarative)
+----------------------------------------
+
+:mod:`repoze.bfg` can be configured for the same "hello world"
+application "declaratively", if so desired, as described in
+:ref:`declarative_configuration`.
+
+Create a file named ``helloworld.py``:
+
+.. code-block:: python
+ :linenos:
+
+ from webob import Response
+ from paste.httpserver import serve
+ from repoze.bfg.configuration import Configurator
+
+ def hello_world(request):
+ return Response('Hello world!')
+
+ def goodbye_world(request):
+ return Response('Goodbye world!')
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.begin()
+ config.load_zcml('configure.zcml')
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+Create a file named ``configure.zcml`` in the same directory as the
+previously created ``helloworld.py``:
+
+.. code-block:: xml
+ :linenos:
+
+ <configure xmlns="http://namespaces.repoze.org/bfg">
+
+ <include package="repoze.bfg.includes" />
+
+ <view
+ view="helloworld.hello_world"
+ />
+
+ <view
+ name="goodbye"
+ view="helloworld.goodbye_world"
+ />
+
+ </configure>
+
+This pair of files forms an application functionally equivalent to the
+application we created earlier. Let's examine the differences between
+the code described in :ref:`helloworld_imperative` and the code above.
+
+In :ref:`helloworld_imperative_appconfig`, we had the following lines
+within the ``if __name__ == '__main__'`` section of ``helloworld.py``:
+
+.. code-block:: python
+ :linenos:
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.begin()
+ config.add_view(hello_world)
+ config.add_view(goodbye_world, name='goodbye')
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+In our "declarative" code, we've added a call to the
+:meth:`repoze.bfg.configuration.Configurator.load_zcml` method with
+the value ``configure.zcml``, and we've removed the lines which read
+``config.add_view(hello_world)`` and ``config.add_view(goodbye_world,
+name='goodbye')``, so that it now reads as:
+
+.. code-block:: python
+ :linenos:
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.begin()
+ config.load_zcml('configure.zcml')
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+Everything else is much the same.
+
+The ``config.load_zcml('configure.zcml')`` line tells the configurator
+to load configuration declarations from the ``configure.zcml`` file
+which sits next to ``helloworld.py``. Let's take a look at the
+``configure.zcml`` file now:
+
+.. code-block:: xml
+ :linenos:
+
+ <configure xmlns="http://namespaces.repoze.org/bfg">
+
+ <include package="repoze.bfg.includes" />
+
+ <view
+ view="helloworld.hello_world"
+ />
+
+ <view
+ name="goodbye"
+ view="helloworld.goodbye_world"
+ />
+
+ </configure>
+
+Let's deconstruct what this actually does.
+
+The ``<configure>`` Tag
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``configure.zcml`` ZCML file contains this bit of XML:
+
+.. code-block:: xml
+ :linenos:
+
+ <configure xmlns="http://namespaces.repoze.org/bfg">
+
+ <!-- other directives -->
+
+ </configure>
+
+Because :term:`ZCML` is XML, and because XML requires a single root
+tag for each document, every ZCML file used by :mod:`repoze.bfg` must
+contain a ``configure`` container directive, which acts as the root
+XML tag. It is a "container" directive because its only job is to
+contain other directives.
+
+See also :ref:`configure_directive` and :ref:`word_on_xml_namespaces`.
+
+The ``<include>`` Tag
+~~~~~~~~~~~~~~~~~~~~~
+
+The ``configure.zcml`` ZCML file contains this bit of XML within the
+``<configure>`` root tag:
+
+.. code-block:: xml
+
+ <include package="repoze.bfg.includes" />
+
+This singleton (self-closing) tag instructs ZCML to load a ZCML file
+from the Python package with the :term:`dotted Python name`
+:mod:`repoze.bfg.includes`, as specified by its ``package`` attribute.
+This particular ``<include>`` declaration is required because it
+actually allows subsequent declaration tags (such as ``<view>``, which
+we'll see shortly) to be recognized. The ``<include>`` tag
+effectively just includes another ZCML file; this causes its
+declarations to be executed. In this case, we want to load the
+declarations from the file named ``configure.zcml`` within the
+:mod:`repoze.bfg.includes` Python package. We know we want to load
+the ``configure.zcml`` from this package because ``configure.zcml`` is
+the default value for another attribute of the ``<include>`` tag named
+``file``. We could have spelled the include tag more verbosely, but
+equivalently as:
+
+.. code-block:: xml
+ :linenos:
+
+ <include package="repoze.bfg.includes"
+ file="configure.zcml"/>
+
+The ``<include>`` tag that includes the ZCML statements implied by the
+``configure.zcml`` file from the Python package named
+:mod:`repoze.bfg.includes` is basically required to come before any
+other named declaration in an application's ``configure.zcml``. If it
+is not included, subsequent declaration tags will fail to be
+recognized, and the configuration system will generate a traceback.
+However, the ``<include package="repoze.bfg.includes"/>`` tag needs to
+exist only in a "top-level" ZCML file, it needn't also exist in ZCML
+files *included by* a top-level ZCML file.
+
+See also :ref:`include_directive`.
+
+The ``<view>`` Tag
+~~~~~~~~~~~~~~~~~~
+
+The ``configure.zcml`` ZCML file contains these bits of XML *after* the
+``<include>`` tag, but *within* the ``<configure>`` root tag:
+
+.. code-block:: xml
+ :linenos:
+
+ <view
+ view="helloworld.hello_world"
+ />
+
+ <view
+ name="goodbye"
+ view="helloworld.goodbye_world"
+ />
+
+These ``<view>`` declaration tags direct :mod:`repoze.bfg` to create
+two :term:`view configuration` registrations. The first ``<view>``
+tag has an attribute (the attribute is also named ``view``), which
+points at a :term:`dotted Python name`, referencing the
+``hello_world`` function defined within the ``helloworld`` package.
+The second ``<view>`` tag has a ``view`` attribute which points at a
+:term:`dotted Python name`, referencing the ``goodbye_world`` function
+defined within the ``helloworld`` package. The second ``<view>`` tag
+also has an attribute called ``name`` with a value of ``goodbye``.
+
+These effect of the ``<view>`` tag declarations we've put into our
+``configure.zcml`` is functionally equivalent to the effect of lines
+we've already seen in an imperatively-configured application. We're
+just spelling things differently, using XML instead of Python.
+
+In our previously defined application, in which we added view
+configurations imperatively, we saw this code:
+
+.. ignore-next-block
+.. code-block:: python
+ :linenos:
+
+ config.add_view(hello_world)
+ config.add_view(goodbye_world, name='goodbye')
+
+Each ``<view>`` declaration tag encountered in a ZCML file effectively
+invokes the :meth:`repoze.bfg.configuration.Configurator.add_view`
+method on the behalf of the developer. Various attributes can be
+specified on the ``<view>`` tag which influence the :term:`view
+configuration` it creates.
+
+Since the relative ordering of calls to
+:meth:`repoze.bfg.configuration.Configurator.add_view` doesn't matter
+(see the sidebar entitled *View Dispatch and Ordering*, the relative
+order of ``<view>`` tags in ZCML doesn't matter either. The following
+ZCML orderings are completely equivalent:
+
+.. topic:: Hello Before Goodbye
+
+ .. code-block:: xml
+ :linenos:
+
+ <view
+ view="helloworld.hello_world"
+ />
+
+ <view
+ name="goodbye"
+ view="helloworld.goodbye_world"
+ />
+
+.. topic:: Goodbye Before Hello
+
+ .. code-block:: xml
+ :linenos:
+
+ <view
+ name="goodbye"
+ view="helloworld.goodbye_world"
+ />
+
+ <view
+ view="helloworld.hello_world"
+ />
+
+We've now configured a :mod:`repoze.bfg` helloworld application
+declaratively.
+
+References
+----------
+
+For more information about the API of a ``Configurator`` object, see
+:class:`repoze.bfg.configuration.Configurator` . The equivalent ZCML
+declaration tags are introduced in narrative documentation chapters as
+necessary.
+
+For more information about :term:`traversal`, see
+:ref:`traversal_chapter`.
+
+For more information about :term:`view configuration`, see
+:ref:`views_chapter`.
+