diff options
| author | Chris McDonough <chrism@agendaless.com> | 2010-01-13 14:51:08 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2010-01-13 14:51:08 +0000 |
| commit | de449d91fafc0fd4bec6d552dab44845b0948784 (patch) | |
| tree | 43c8738eaabf603cd07c6b396ba5e4e58bba944a /docs | |
| parent | c16e4d88a60f13d8bc10aee4d7b3ac3950daa317 (diff) | |
| download | pyramid-de449d91fafc0fd4bec6d552dab44845b0948784.tar.gz pyramid-de449d91fafc0fd4bec6d552dab44845b0948784.tar.bz2 pyramid-de449d91fafc0fd4bec6d552dab44845b0948784.zip | |
Merge andrew-docs branch.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/index.rst | 13 | ||||
| -rw-r--r-- | docs/narr/configuration.rst | 52 | ||||
| -rw-r--r-- | docs/narr/firstapp.rst | 338 | ||||
| -rw-r--r-- | docs/narr/urlmapping.rst | 119 |
4 files changed, 281 insertions, 241 deletions
diff --git a/docs/index.rst b/docs/index.rst index 7e727d410..28b2667a1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,19 +4,14 @@ The repoze.bfg Web Application Framework ======================================== -:mod:`repoze.bfg` is a Python web application framework. It is -inspired by :term:`Zope`, :term:`Pylons`, and :term:`Django`. It uses -various Zope-related libraries internally to do much of its work. -:mod:`repoze.bfg` uses the WSGI protocol to handle request and -responses. - -:mod:`repoze.bfg` is developed as part of the `Repoze +:mod:`repoze.bfg` is a small, fast, down-to-earth Python web +application framework. It is developed as part of the `Repoze <http://repoze.org>`_ project by `Agendaless Consulting <http://agendaless.com>`_ and other contributors. It is licensed under a `BSD-like license <http://repoze.org/license.html>`_. -Fore-Matter -=========== +Front Matter +============ .. toctree:: :maxdepth: 1 diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index 08ca49710..9d62593ef 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -15,11 +15,11 @@ accounting information. :mod:`repoze.bfg` refers to the way in which code is plugged in to it for a specific application as "configuration". -Most people understand "configuration" as coarse knobs that inform the -high-level operation of a specific application deployment. For +Most people understand "configuration" as coarse settings that inform +the high-level operation of a specific application deployment. For instance, it's easy to think of the values implied by a ``.ini`` file which is parsed at application startup time as "configuration". -:mod:`repoze.bfg` extends this pattern out to application development, +:mod:`repoze.bfg` extends this pattern to application development, using the term "configuration" to express standardized ways that code gets plugged into a deployment of the framework itself. When you plug code into the :mod:`repoze.bfg` framework, you are "configuring" @@ -44,7 +44,7 @@ configuration "imperatively" fits their brain best. This is the configuration mode in which a developer cedes the least amount of control to the framework; it's "imperative" because you express the configuration directly in Python code, and you have the full power of -Python at your disposal as you perform configuration statements. +Python at your disposal as you issue configuration statements. Here's one of the simplest :mod:`repoze.bfg` applications, configured imperatively: @@ -65,7 +65,7 @@ imperatively: config.add_view(hello_world) config.end() app = config.make_wsgi_app() - serve(app) + serve(app, host='0.0.0.0') We won't talk much about what this application does yet. Just note that the "configuration' statements take place underneath the ``if @@ -90,12 +90,6 @@ A :mod:`repoze.bfg` application can be alternatively be configured format named :term:`ZCML` (Zope Configuration Markup Language), an XML dialect. -Declarative configuration mode is the configuration mode in which -developers cede the most amount of control to the framework itself. -Because application developers cede more control to the framework, it -is also sometimes harder to understand than purely imperative -configuration. - A :mod:`repoze.bfg` application configured declaratively requires not one, but two files: a Python file and a :term:`ZCML` file. @@ -117,7 +111,7 @@ In a file named ``helloworld.py``: config.load_zcml('configure.zcml') config.end() app = config.make_wsgi_app() - serve(app) + serve(app, host='0.0.0.0') In a file named ``configure.zcml`` in the same directory as the previously created ``helloworld.py``: @@ -137,9 +131,8 @@ previously created ``helloworld.py``: This pair of files forms an application functionally equivalent to the application we created earlier in :ref:`imperative_configuration`. - -Let's examine the differences between the code listing in -:ref:`imperative_configuration` and the code above. +Let's examine the differences between that code listing and the code +above. In :ref:`imperative_configuration`, we had the following lines within the ``if __name__ == '__main__'`` section of ``helloworld.py``: @@ -153,7 +146,7 @@ the ``if __name__ == '__main__'`` section of ``helloworld.py``: config.add_view(hello_world) config.end() app = config.make_wsgi_app() - serve(app) + serve(app, host='0.0.0.0') In our "declarative" code, we've removed the call to ``add_view`` and replaced it with a call to the @@ -169,7 +162,7 @@ it now reads as: config.load_zcml('configure.zcml') config.end() app = config.make_wsgi_app() - serve(app) + serve(app, host='0.0.0.0') Everything else is much the same. @@ -219,15 +212,18 @@ and imperative configuration are functionally equivalent. Using declarative configuration has a number of benefits, the primary benefit being that applications configured declaratively can be *overridden* and *extended* by third parties without requiring the -third party to change application code. +third party to change application code. If you want to build a +framework or an extensible application, using declarative +configuration is a good idea. Declarative configuration has a down +side: you can't use plain-old-Python syntax you probably already know +and understand to configure your application; instead you need to use +:term:`ZCML`. .. note:: See :ref:`extending_chapter` for a discussion of extending and overriding :mod:`repoze.bfg` applications. -If you want to build a framework or an extensible application, using -ZCML is a good idea. .. index:: pair: ZCML; conflict detection @@ -294,15 +290,15 @@ to code that is referred to by the declaration itself. For example: def hello(request): return Response('Hello') -The :class:`repoze.bfg.view.bfg_view` decorator above adds an -attribute to the ``hello`` function, making it available for a -:term:`scan` to find it later. - The mere existence of configuration decoration doesn't cause any configuration registration to be made. Before they have any effect on the configuration of a :mod:`repoze.bfg` application, a configuration decoration within application code must be found through a process -known as *scanning*. +known as a :term:`scan`. + +The :class:`repoze.bfg.view.bfg_view` decorator above adds an +attribute to the ``hello`` function, making it available for a +:term:`scan` to find it later. :mod:`repoze.bfg` is willing to :term:`scan` a module or a package and its subpackages for decorations when the @@ -330,7 +326,7 @@ and its subpackages. For example: config.scan() config.end() app = config.make_wsgi_app() - serve(app) + serve(app, host='0.0.0.0') :term:`ZCML` can also invoke a :term:`scan` via its ``<scan>`` directive. If a ZCML file is processed that contains a scan @@ -358,7 +354,7 @@ directive, the package the ZCML file points to is scanned. config.load_zcml('configure.zcml') config.end() app = config.make_wsgi_app() - serve(app) + serve(app, host='0.0.0.0') .. code-block:: xml :linenos: @@ -377,7 +373,7 @@ or module recursively, looking for special attributes attached to objects defined within a module. These special attributes are typically attached to code via the use of a :term:`decorator`. For example, the :class:`repoze.bfg.view.bfg_view` decorator can be -attached to a function or instance method: +attached to a function or instance method. Once scanning is invoked, and :term:`configuration decoration` is found by the scanner, a set of calls are made to a diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index e533fdcb2..1df082003 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -3,10 +3,92 @@ 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 +We will walk through the creation of a tiny :mod:`repoze.bfg` +application in this chapter and explain in more detail how the application works. +But before we dive into the code, a short introduction to +:term:`traversal` is required. + +.. index:: + pair: traversal; introduction + +.. _traversal_intro: + +An Introduction to Traversal +---------------------------- + +In order for a web application to perform any useful action, it needs +some way of finding and invoking code based on parameters present in +the :term:`request`. :term:`traversal` is a mechanism that plays a +part in finding code when a request enters the system. + +:term:`traversal` is the act of finding a :term:`context` and a +:term:`view name` by walking over a graph of objects starting from a +:term:`root` object, using the :term:`request` object as a source of +path information. + +:term:`view` code is the code in an application that responds to a +request. Traversal doesn't actually locate :term:`view` code by +itself: it only locates a :term:`context` and a :term:`view name`. +But the combination of the :term:`context` object and the :term:`view +name` found via traversal is used by a separate :mod:`repoze.bfg` +subsystem (the "view lookup" subsystem) to find a :term:`view +callable` later in the same request. A view callable is a specific +bit of code that receives a :term:`request` and which returns a +:term:`response`. + +.. note:: + + Another distinct mode known as :term:`URL dispatch` can alternately + be used to find a view callable based on a URL. However, the + application we're going to write uses only :term:`traversal`. + +The ``PATH_INFO`` portion of a URL is 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``. + +Traversal treats the ``PATH_INFO`` segment of a URL as a sequence of +path segments. For example, the ``PATH_INFO`` string ``/a/b/c`` is +treated as the sequence ``['a', 'b', 'c']``. Traversal pops the first +element (``a``) from the segment sequence and attempts to use it as a +lookup key into an *object graph* supplied by our application. If +that succeeeds, the :term:`context` temporarily becomes the object +found via that lookup. Then the next segment (``b``) is popped from +the sequence, and the object graph is queried for that segment; if +that lookup succeeds, the :term:`context` becomes that object. This +process continues until the path segment sequence is exhausted or any +lookup for a name in the sequence fails. + +As we previously mentioned, 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_INFO`` segments +"left over" in the path segment list popped by :term:`traversal`. It +will be the empty string (``''``) if no segments remain. The +circumstance where the :term:`view name` is the empty string +represents that the :term:`default view` for a :term:`context` should +be invoked. + +If the :term:`view name` is *not* the empty string, it means that +traversal "ran out" of nodes in the *object graph* before it finished +exhausting all the path segments implied by the URL path segments. In +this case, because the :term:`view name` is non-empty, a *non-default* +view callable will be invoked. + +This description of traversal is not comprehensive: it's tailored +towards understand the sample application we're about to create; we'll +cover traversal in more detail in the :term:`traversal_chapter`. + +.. note:: + + A detailed analogy of how :mod:`repoze.bfg` :term:`traversal` works + is available within the chapter section entitled + :ref:`traversal_behavior`. If you're a "theory-first" person, you + might choose to read this to augment your understanding of + traversal while diving into the code that follows, but it's not + necessary if you're willing to "go with the flow". + .. index:: single: helloworld @@ -38,7 +120,7 @@ imperatively: config.add_view(goodbye_world, name='goodbye') config.end() app = config.make_wsgi_app() - serve(app) + serve(app, host='0.0.0.0') When this code is inserted into a Python script named ``helloworld.py`` and executed by a Python interpreter which has the @@ -48,9 +130,45 @@ port 8080. When port 8080 is visited by a user agent on the root URL 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!". +URL ``/goodbye``, the server will serve up "Goodbye world!" + +Our application's :term:`root` object is the *default* root object +used when one isn't otherwise specified in application configuration. +The default root object has no children. -Let's examine this program piece-by-piece. +.. note:: + + In a "real" traversal-based :mod:`repoze.bfg` application, we'd + pass a ``root_factory`` to the ``Configurator`` object's + constructor, which would provide our application with a custom root + object instead of using the :mod:`repoze.bfg` default root object. + Supplying a custom ``root_factory`` is how you provide a custom + *object graph* to :mod:`repoze.bfg`. However, because our + application is so simple, we don't need a custom root object here. + +In a more complex :mod:`repoze.bfg` application there will be many +:term:`context` objects to which URLs might resolve. However, in this +toy application, effectively there is only ever one context: the +:term:`root` object. This is because the object graph of our hello +world application is very simple: there's exactly one object in our +graph; the default root object. + +We have only a single :term:`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 result in the :term:`view name` of ``goodbye`` after traversal. + +Now that we have a rudimentary understanding of what the application +does, let's examine it piece-by-piece. Imports ~~~~~~~ @@ -73,8 +191,7 @@ 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. +:mod:`repoze.bfg` itself. The script also imports the ``Configurator`` class from the ``repoze.bfg.configuration`` module. This class is used to configure @@ -123,99 +240,6 @@ but return a response with the body ``Hello world!``; the ``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 @@ -239,7 +263,7 @@ imports and function definitions is placed within the confines of an config.add_view(goodbye_world, name='goodbye') config.end() app = config.make_wsgi_app() - serve(app) + serve(app, host='0.0.0.0') Let's break this down this piece-by-piece. @@ -299,6 +323,13 @@ 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". +.. note:: + + See :ref:`threadlocals_chapter` for a discussion about what it + means for an application registry to be "current". + +.. _adding_configuration: + Adding Configuration ~~~~~~~~~~~~~~~~~~~~ @@ -323,11 +354,12 @@ 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. +function we'd like to use as a view callable. However, this line +calls ``add_view`` with a single default :term:`predicate` argument, +the ``name`` predicate with a value of the empty string (``''``), +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 @@ -341,14 +373,14 @@ invoked. 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 + dispatch mode of :mod:`repoze.bfg` (and similar schemes used by + 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 comparative 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. @@ -379,8 +411,8 @@ 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. +configuration (the view configuration that matches the most specific +set 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 @@ -418,6 +450,11 @@ registry, meaning that code which attempts to use the application registry :term:`thread local` will no longer obtain the registry associated with the configurator. +.. note:: + + See :ref:`threadlocals_chapter` for a discussion about what it + means for an application registry to be "current". + .. index:: single: make_wsgi_app pair: WSGI; application @@ -442,15 +479,15 @@ 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 +instance of a class representing a :mod:`repoze.bfg` :term:`router`. +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``. @@ -460,16 +497,22 @@ WSGI Application Serving .. ignore-next-block .. code-block:: python - serve(app) + serve(app, host='0.0.0.0') 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. +WSGI server runner, passing it the ``app`` object (a :term:`router`) +as the application we wish to serve. We also pass in an argument +``host=='0.0.0.0'``, meaning "listen on all TCP interfaces." By +default, the Paste HTTP server listens only on the ``127.0.0.1`` +interface, which is problematic if you're running the server on a +remote system and you wish to access it with a web browser from a +local system. We don't specify a TCP port number to listen on; this +means we want to use the default TCP port, which is 8080. + +When this line is invoked, it causes the server to start listening on +TCP port 8080. It will serve requests forever, or at least until we +stop it by killing the process which runs it. Conclusion ~~~~~~~~~~ @@ -514,7 +557,7 @@ Create a file named ``helloworld.py``: config.load_zcml('configure.zcml') config.end() app = config.make_wsgi_app() - serve(app) + serve(app, host='0.0.0.0') Create a file named ``configure.zcml`` in the same directory as the previously created ``helloworld.py``: @@ -538,8 +581,9 @@ previously created ``helloworld.py``: </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. +application we created earlier in :ref:`helloworld_imperative`. +Let's examine the differences between the code in that section and the +code above. In :ref:`helloworld_imperative_appconfig`, we had the following lines within the ``if __name__ == '__main__'`` section of ``helloworld.py``: @@ -554,7 +598,7 @@ within the ``if __name__ == '__main__'`` section of ``helloworld.py``: config.add_view(goodbye_world, name='goodbye') config.end() app = config.make_wsgi_app() - serve(app) + serve(app, host='0.0.0.0') In our "declarative" code, we've added a call to the :meth:`repoze.bfg.configuration.Configurator.load_zcml` method with @@ -571,7 +615,7 @@ name='goodbye')``, so that it now reads as: config.load_zcml('configure.zcml') config.end() app = config.make_wsgi_app() - serve(app) + serve(app, host='0.0.0.0') Everything else is much the same. @@ -598,7 +642,10 @@ which sits next to ``helloworld.py``. Let's take a look at the </configure> -Let's deconstruct what this actually does. +We already understand what the view code does, because the application +is functionally equivalent to the application described in +:ref:`helloworld_imperative`, but use of :term:`ZCML` is new. Let's +break that down tag-by-tag. The ``<configure>`` Tag ~~~~~~~~~~~~~~~~~~~~~~~ @@ -658,10 +705,10 @@ The ``<include>`` tag that includes the ZCML statements implied by the :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. +recognized, and the configuration system will generate an error at +startup. 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`. @@ -716,9 +763,10 @@ 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: +(see the sidebar entitled *View Dispatch and Ordering* within +:ref:`adding_configuration), the relative order of ``<view>`` tags in +ZCML doesn't matter either. The following ZCML orderings are +completely equivalent: .. topic:: Hello Before Goodbye diff --git a/docs/narr/urlmapping.rst b/docs/narr/urlmapping.rst index 8d755bba3..6f111dcfd 100644 --- a/docs/narr/urlmapping.rst +++ b/docs/narr/urlmapping.rst @@ -7,88 +7,89 @@ Mapping URLs to Code -------------------- -:mod:`repoze.bfg` supports :term:`URL dispatch` via a subsystem that -was inspired by the :term:`Routes` system used by :term:`Pylons`. +:mod:`repoze.bfg` supports two methods by which a URL can be mapped to +code: :term:`URL dispatch` and :term:`traversal`. + +.. note:: + + The :mod:`repoze.bfg` support for :term:`URL dispatch` was inspired + by the :term:`Routes` system used by :term:`Pylons`. + :mod:`repoze.bfg` support for :term:`traversal` was inspired by + :term:`Zope`. + :term:`URL dispatch` is convenient and straightforward: an incoming URL is checked against a list of potential matches in a predefined -order. Each potential match often maps directly to a :term:`view -callable`. When a match is found, it usually means that a particular -:term:`view callable` is invoked. - -Like :term:`Zope`, :mod:`repoze.bfg` can also map URLs to code -slightly differently, by using using object graph :term:`traversal`. -Graph-traversal based dispatching is useful if you like your URLs to -represent an arbitrary hierarchy of potentially heterogeneous items, -or if you need to attach "instance-level" security (akin to -"row-level" security in relational parlance) declarations to -:term:`model` instances. Traversal is slightly more complex than URL -dispatch, but it is also a bit more powerful. +order. When a match is found, it means that a particular :term:`view +callable` will be invoked. :term:`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. - -For example, you might configure a :term:`route` to match against the -following URL patterns: +You just match everything "below" ``members`` to a particular +:term:`view callable`. For example, you might configure URL dispatch +within :mod:`repoze.bfg` to match against the following URL patterns: .. code-block:: text - archives/:year/:month/:day members/:membername + archives/:year/:month/:day -In this configuration, there are exactly two types of URLs that will -match views in your application: ones that start with ``/archives`` -and have subsequent path elements that represent a year, month, and -day. And ones that start with ``/members`` which are followed by a -path segment containing a member's name. This is very simple. When -you limit your application to using URL dispatch, you know every URL -that your application might generate or respond to, and all the URL -matching elements are listed in a single place. +In this configuration, there will be exactly two types of URLs that +will be meaningful to your application: URLs that start with +``/members`` which are followed by a path segment containing a +member's name. And URLs that start with ``/archives`` and have +subsequent path elements that represent a year, month, and day. Each +route pattern will be mapped to a specific :term:`view callable`. -:term:`URL dispatch` is not very good, however, at inferring the -difference between sets of URLs such as: +URL dispatch is very straightforward. When you limit your application +to using URL dispatch, you know every URL that your application might +generate or respond to, and all the URL matching elements are listed +in a single place. + +URL dispatch is not very good, however, at inferring the difference +between sets of URLs such as these: .. code-block:: text http://example.com/members/Chris/document http://example.com/members/Chris/stuff/page -...wherein you'd like the ``document`` in the first URL to represent a +If you'd like the ``document`` in the first URL above to represent a PDF document, and ``/stuff/page`` in the second to represent 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. - -:term:`traversal` works well for these types of "ambiguous" URLs and -for URLs that represent arbitrary-depth hierarchies. When traversal -is used, each URL segment represents a single traversal step through -an edge of a 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``. - -If you're willing to treat your application models as a graph that can -be traversed, it also becomes easy 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! ;-) ) +OpenOffice document in a "stuff" folder, it's hard to express this +using URL dispatch. It takes more pattern matching assertions to be +able to make hierarchies like these work in URL-dispatch based +systems, and some assertions just aren't possible. Essentially, +URL-dispatch based systems just don't deal very well with URLs that +represent arbitrary-depth hierarchies. + +However, the other URL mapping mode supported by :mod:`repoze.bfg`, +named :term:`traversal`, *does* work well for URLs that represent +arbitrary-depth hierarchies. When traversal is used, each URL segment +represents a single traversal step through an edge of a 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``. Since the path segments that compose a URL are addressed +separately, it becomes very easy to form URLs that represent arbitrary +depth hierarchies in a system that uses traversal. + +When you're willing to treat your application models as a graph that +can be traversed, it also becomes easy to provide "instance-level +security": you just attach a security declaration to each instance in +the graph. This is not nearly as easy to do when using URL dispatch. In essence, the choice to use graph traversal vs. URL dispatch is largely religious. 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. - -:mod:`repoze.bfg` provides support for both approaches. You can use -either as you see fit. +relational database because it requires the construction and +maintenance of a graph and requires that the developer think about +mapping URLs to code in terms of traversing that graph. However, when +you have a hierarchical data store, using traversal can provide +significant advantages over using URL-based dispatch. + +Since :mod:`repoze.bfg` provides support for both approaches, you can +use either as you see fit; you can even combine them together if +necessary. .. toctree:: :maxdepth: 2 |
