diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-10-30 19:38:41 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-10-30 19:38:41 +0000 |
| commit | acc7765a037983907d3275312396ee10b6b9ad2e (patch) | |
| tree | dc91f45c5b638f6c86ec9c74b627e37251707d7c | |
| parent | 11644e705834ff65cb8963333855a1db6272ae1e (diff) | |
| download | pyramid-acc7765a037983907d3275312396ee10b6b9ad2e.tar.gz pyramid-acc7765a037983907d3275312396ee10b6b9ad2e.tar.bz2 pyramid-acc7765a037983907d3275312396ee10b6b9ad2e.zip | |
- The ``__call__`` of a plugin "traverser" implementation (registered
as an adapter for ``ITraverser`` or ``ITraverserFactory``) will now
receive a *request* as the single argument to its ``__call__``
method. In previous versions it was passed a WSGI ``environ``
object. The request object passed to the factory implements
dictionary-like methods in such a way that existing traverser code
which expects to be passed an environ will continue to work.
- Fix docs.
| -rw-r--r-- | CHANGES.txt | 8 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 12 | ||||
| -rw-r--r-- | docs/narr/hybrid.rst | 2 | ||||
| -rw-r--r-- | docs/narr/router.rst | 42 | ||||
| -rw-r--r-- | docs/narr/security.rst | 2 | ||||
| -rw-r--r-- | docs/narr/traversal.rst | 21 | ||||
| -rw-r--r-- | docs/narr/urldispatch.rst | 12 | ||||
| -rw-r--r-- | repoze/bfg/router.py | 6 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_router.py | 2 | ||||
| -rw-r--r-- | repoze/bfg/traversal.py | 19 |
10 files changed, 79 insertions, 47 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index ca6c501fb..b664ab092 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -22,6 +22,14 @@ Features factory code which expects to be passed an environ will continue to work. +- The ``__call__`` of a plugin "traverser" implementation (registered + as an adapter for ``ITraverser`` or ``ITraverserFactory``) will now + receive a *request* as the single argument to its ``__call__`` + method. In previous versions it was passed a WSGI ``environ`` + object. The request object passed to the factory implements + dictionary-like methods in such a way that existing traverser code + which expects to be passed an environ will continue to work. + Internal -------- diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index d2020af0d..0fea7c64e 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -231,7 +231,7 @@ a class that implements the following interface: def __init__(self, root): """ Accept the root object returned from the root factory """ - def __call__(self, environ): + def __call__(self, request): """ Return a dictionary with (at least) the keys ``root``, ``context``, ``view_name``, ``subpath``, ``traversed``, ``virtual_root``, and ``virtual_root_path``. These values are @@ -255,6 +255,16 @@ a class that implements the following interface: as attributes of the ``request`` object. """ +.. warning:: In :mod:`repoze.bfg.` 1.0 and previous versions, the + traverser ``__call__`` method accepted a WSGI *environment* + dictionary rather than a :term:`request` object. The request + object passed to the traverser implements a dictionary-like API + which mutates and queries the environment, as a backwards + compatibility shim, in order to allow older code to work. + However, for maximum forward compatibility, traverser code + targeting :mod:`repoze.bfg` 1.1 and higher should expect a + request object directly. + More than one traversal algorithm can be active at the same time. For instance, if your :term:`root factory` returns more than one type of object conditionally, you could claim that an alternate traverser is diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index 34f606a97..4ff5df493 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -138,7 +138,7 @@ looks like so: root = Traversable( {'a':Traversable({'b':Traversable({'c':Traversable({})})})}) - def root_factory(environ): + def root_factory(request): return root We've defined a bogus graph here that can be traversed, and a diff --git a/docs/narr/router.rst b/docs/narr/router.rst index 55df2936c..7382c07ae 100644 --- a/docs/narr/router.rst +++ b/docs/narr/router.rst @@ -21,18 +21,22 @@ processing? the WSGI environment to the ``__call__`` method of the :mod:`repoze.bfg` :term:`router` object. +#. A :term:`request` object is created based on the WSGI environment. + #. To service :term:`url dispatch`, the :mod:`repoze.bfg` :term:`router` calls a :term:`URL dispatch` "root factory wrapper" callable, which acts as a :term:`root factory`. The job of the - mapper is to examine the ``PATH_INFO`` and other various keys in - the environment to determine whether any user-defined :term:`route` - matches the current WSGI environment. The :term:`router` passes - the WSGI environment as an argument to the mapper. + mapper is to examine the ``PATH_INFO`` implied by the request to + determine whether any user-defined :term:`route` matches the + current WSGI environment. The :term:`router` passes the request as + an argument to the mapper. #. If any route matches, the WSGI environment is mutated; a ``bfg.routes.route`` key and a ``bfg.routes.matchdict`` are added - to the WSGI environment. If a route *doesn't* match, neither of - these keys is added to the WSGI environment. + to the WSGI environment, and an attribute named ``matchdict`` is + added to the request. If a route *doesn't* match, neither of these + keys is added to the WSGI environment and the request object is not + mutated. #. Regardless of whether any route matched or not, the :term:`URL dispatch` mapper returns a root object. If a particular @@ -44,27 +48,17 @@ processing? ``make_app`` is ``None``, a default root factory is used to generate a root. -#. A :term:`WebOb` :term:`request` is generated using the WSGI - environment. The request type (its class and any :term:`interface` - attached to it) is dependent upon a combination of the - ``REQUEST_METHOD`` of the WSGI environment as well as any - :term:`route` match. For example, a very particular kind of - request object is generated when the request has a - ``REQUEST_METHOD`` of ``POST`` and a :term:`route` named "home" - matches. We use the request type to determine exactly which - :term:`view` to call later. - #. A ``NewRequest`` :term:`event` is sent to any subscribers. #. The :mod:`repoze.bfg` router calls a "traverser" function with the - root object and the WSGI environment. The traverser function - attempts to traverse the root object (using any existing - ``__getitem__`` on the root object and subobjects) to find a - :term:`context`. If the root object has no ``__getitem__`` method, - the root itself is assumed to be the context. The exact traversal - algorithm is described in :ref:`traversal_chapter`. The traverser - function returns a dictionary, which contains a :term:`context` and - a :term:`view name` as well as other ancillary information. + root object and the request. The traverser function attempts to + traverse the root object (using any existing ``__getitem__`` on the + root object and subobjects) to find a :term:`context`. If the root + object has no ``__getitem__`` method, the root itself is assumed to + be the context. The exact traversal algorithm is described in + :ref:`traversal_chapter`. The traverser function returns a + dictionary, which contains a :term:`context` and a :term:`view + name` as well as other ancillary information. #. The request is decorated with various names returned from the traverser (such as ``context``, ``view_name``, and so forth), so diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 90ead339c..773ab3a4f 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -522,7 +522,7 @@ An example of its usage, with all attributes fully expanded: The ``identifier_name`` controls the name used to look up the :term:`repoze.who` "identifier" plugin within -``environ['repoze.who.plugins']`` which is used by this policy to +``request.environ['repoze.who.plugins']`` which is used by this policy to "remember" and "forget" credentials. It defaults to ``auth_tkt``. The ``callback`` is a Python dotted name to a function passed the diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index 340f8c277..5ed3db7cb 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -23,11 +23,10 @@ Users interact with your :mod:`repoze.bfg` -based application via a *router*, which is just a fancy :term:`WSGI` application. At system startup time, the router is configured with a callback known as a :term:`root factory`, supplied by the application developer. The root -factory is passed the WSGI "environment" (a dictionary) and it is -expected to return an object which represents the root of the model -graph. All :term:`traversal` will begin at this root object. The -root object is usually a *mapping* object (such as a Python -dictionary). +factory is passed a :term:`request` object and it is expected to +return an object which represents the root of the model graph. All +:term:`traversal` will begin at this root object. The root object is +usually a *mapping* object (such as a Python dictionary). .. note:: If a :term:`root factory` is passed to the :mod:`repoze.bfg` "make_app" function as the value ``None``, a default root factory @@ -43,6 +42,14 @@ dictionary). matched, it is also possible to do traversal *after* a route has been matched. See :ref:`hybrid_chapter` for more information. +.. warning:: In BFG 1.0 and prior versions, the root factory was + passed a term WSGI *environment* object (a dictionary) while in + BFG 1.1+ it is passed a request object. For backwards + compatibility purposes, the request object passed to the root + factory has a dictionary-like interface that emulates the WSGI + environment, so code expecting the argument to be a dictionary + will continue to work. + Items contained within the object graph are analogous to the concept of :term:`model` objects used by many other frameworks (and :mod:`repoze.bfg` refers to them as models, as well). They are @@ -93,10 +100,10 @@ code to execute: #. The router creates a :term:`WebOb` request object based on the WSGI environment. -#. The :term:`root factory` is called with the WSGI environment. It +#. The :term:`root factory` is called with the :term:`request`. It returns a :term:`root` object. -#. The router uses the WSGI environment's ``PATH_INFO`` variable to +#. The router uses the request's ``PATH_INFO`` information 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 diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 7a2a184f7..26d4b4a93 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -472,7 +472,7 @@ dispatch will by default be an instance of the object returned by the default :term:`root factory`. You can override this behavior by passing in a ``factory`` argument to the ZCML directive for a particular route. The ``factory`` should be a callable that accepts a -WSGI environment and returns an instance of a class that will be the +:term:`request` and returns an instance of a class that will be the context used by the view. An example of using a route with a factory: @@ -489,7 +489,7 @@ An example of using a route with a factory: The above route will manufacture an ``Idea`` model as a context, assuming that ``mypackage.models.Idea`` resolves to a class that -accepts a WSGI environment in its ``__init__``. +accepts a request in its ``__init__``. .. note:: Values prefixed with a period (``.``) for the ``factory`` and ``view`` attributes of a ``route`` (such as ``.models.Idea`` @@ -536,8 +536,8 @@ The ``.models`` module referred to above might look like so: :linenos: class Article(object): - def __init__(self, environ): - self.__dict__.update(environ['bfg.routes.matchdict']) + def __init__(self, request): + self.__dict__.update(request.matchdict) def is_root(self): return self.article == 'root' @@ -739,8 +739,8 @@ Such a ``factory`` might look like so: :linenos: class Article(object): - def __init__(self, environ): - matchdict = environ['bfg.routes.matchdict'] + def __init__(self, request): + matchdict = request.matchdict article = matchdict.get('article', None) if article == '1': self.__acl__ = [ (Allow, 'editor', 'view') ] diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index 3916d6627..c48a96664 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -71,7 +71,7 @@ class Router(object): traverser = registry.queryAdapter(root, ITraverser) if traverser is None: traverser = ModelGraphTraverser(root) - tdict = traverser(environ) + tdict = traverser(request) context, view_name, subpath, traversed, vroot, vroot_path = ( tdict['context'], tdict['view_name'], tdict['subpath'], tdict['traversed'], tdict['virtual_root'], @@ -136,8 +136,8 @@ def make_app(root_factory, package=None, filename='configure.zcml', """ Return a Router object, representing a fully configured ``repoze.bfg`` WSGI application. - ``root_factory`` must be a callable that accepts a WSGI - environment and returns a traversal root object. The traversal + ``root_factory`` must be a callable that accepts a :term:`request` + object and which returns a traversal root object. The traversal root returned by the root factory is the *default* traversal root; it can be overridden on a per-view basis. ``root_factory`` may be ``None``, in which case a 'default default' traversal root is diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index d2fbf4b2d..ac77508ef 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -46,7 +46,7 @@ class TestRouter(unittest.TestCase): def __init__(self, root): self.root = root - def __call__(self, path): + def __call__(self, request): values = {'root':self.root, 'context':context, 'view_name':view_name, diff --git a/repoze/bfg/traversal.py b/repoze/bfg/traversal.py index 43adcdebb..d48627e7e 100644 --- a/repoze/bfg/traversal.py +++ b/repoze/bfg/traversal.py @@ -268,12 +268,12 @@ def traverse(model, path): if path and path[0] == '/': model = find_root(model) - environ = {'PATH_INFO':path} + request = FakeRequest({'PATH_INFO':path}) traverser = queryAdapter(model, ITraverser) if traverser is None: traverser = ModelGraphTraverser(model) - return traverser(environ) + return traverser(request) def model_path_tuple(model, *elements): """ @@ -474,7 +474,15 @@ class ModelGraphTraverser(object): def __init__(self, root): self.root = root - def __call__(self, environ): + def __call__(self, request): + try: + environ = request.environ + except AttributeError: + # In BFG 1.0 and before, this API expected an environ + # rather than a request; some bit of code may still be + # passing us an environ. If so, deal. + environ = request + if 'bfg.routes.matchdict' in environ: matchdict = environ['bfg.routes.matchdict'] @@ -623,3 +631,8 @@ class TraversalContextURL(object): def _join_path_tuple(tuple): return tuple and '/'.join([quote_path_segment(x) for x in tuple]) or '/' +class FakeRequest(dict): + def __init__(self, environ): + self.update(environ) + self.environ = self # XXX circref? + |
