diff options
| -rw-r--r-- | CHANGES.txt | 37 | ||||
| -rw-r--r-- | docs/narr/hybrid.rst | 2 | ||||
| -rw-r--r-- | docs/narr/urldispatch.rst | 35 | ||||
| -rw-r--r-- | docs/whatsnew-1.3.rst | 12 | ||||
| -rw-r--r-- | docs/zcml/route.rst | 39 | ||||
| -rw-r--r-- | repoze/bfg/configuration.py | 66 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_configuration.py | 20 | ||||
| -rw-r--r-- | repoze/bfg/traversal.py | 2 |
8 files changed, 211 insertions, 2 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 9fadd1beb..dfdec3ec6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,6 +13,43 @@ Features ``bfg_view`` API, the Configurator ``add_view`` API, and the ZCML ``view`` directive. +- New argument to ``repoze.bfg.configuration.Configurator.add_route`` + and the ``route`` ZCML directive: ``traverse``. If you would like + to cause the ``context`` to be something other than the ``root`` + object when this route matches, you can spell a traversal pattern as + the ``traverse`` argument. This traversal pattern will be used as + the traversal path: traversal will begin at the root object implied + by this route (either the global root, or the object returned by the + ``factory`` associated with this route). + + The syntax of the ``traverse`` argument is the same as it is for + ``path``. For example, if the ``path`` provided is + ``articles/:article/edit``, and the ``traverse`` argument provided + is ``/:article``, when a request comes in that causes the route to + match in such a way that the ``article`` match value is '1' (when + the request URI is ``/articles/1/edit``), the traversal path will be + generated as ``/1``. This means that the root object's + ``__getitem__`` will be called with the name ``1`` during the + traversal phase. If the ``1`` object exists, it will become the + ``context`` of the request. The Traversal narrative has more + information about traversal. + + If the traversal path contains segment marker names which are not + present in the path argument, a runtime error will occur. The + ``traverse`` pattern should not contain segment markers that do not + exist in the ``path``. + + A similar combining of routing and traversal is available when a + route is matched which contains a ``*traverse`` remainder marker in + its path. The ``traverse`` argument allows you to associate route + patterns with an arbitrary traversal path without using a a + ``*traverse`` remainder marker; instead you can use other match + information. + + Note that the ``traverse`` argument is ignored when attached to a + route that has a ``*traverse`` remainder marker in its path. + + Documentation ------------- diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index 75592572c..368d44626 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -188,6 +188,8 @@ match is straightforward. When a route is matched: root factory were explained previously within :ref:`the_object_graph`. +.. _using_traverse_in_a_route_path: + Using ``*traverse`` In a Route Path ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 4e49ac4b3..59bcde13f 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -422,6 +422,41 @@ represent neither predicates nor view configuration information. this argument is not specified, the traversal root factory will be used. +``traverse`` + If you would like to cause the :term:`context` to be something other + than the :term:`root` object when this route matches, you can spell + a traversal pattern as the ``traverse`` argument. This traversal + pattern will be used as the traversal path: traversal will begin at + the root object implied by this route (either the global root, or + the object returned by the ``factory`` associated with this route). + + The syntax of the ``traverse`` argument is the same as it is for + ``path``. For example, if the ``path`` provided is + ``articles/:article/edit``, and the ``traverse`` argument provided + is ``/:article``, when a request comes in that causes the route to + match in such a way that the ``article`` match value is '1' (when + the request URI is ``/articles/1/edit``), the traversal path will be + generated as ``/1``. This means that the root object's + ``__getitem__`` will be called with the name ``1`` during the + traversal phase. If the ``1`` object exists, it will become the + :term:`context` of the request. :ref:`traversal_chapter` has more + information about traversal. + + If the traversal path contains segment marker names which are not + present in the path argument, a runtime error will occur. The + ``traverse`` pattern should not contain segment markers that do not + exist in the ``path``. + + A similar combining of routing and traversal is available when a + route is matched which contains a ``*traverse`` remainder marker in + its path (see :ref:`using_traverse_in_a_route_path`). The + ``traverse`` argument allows you to associate route patterns with an + arbitrary traversal path without using a a ``*traverse`` remainder + marker; instead you can use other match information. + + Note that the ``traverse`` argument is ignored when attached to a + route that has a ``*traverse`` remainder marker in its path. + **Predicate Arguments** ``path`` diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst index fc6785f0a..f55ac88a6 100644 --- a/docs/whatsnew-1.3.rst +++ b/docs/whatsnew-1.3.rst @@ -170,6 +170,18 @@ Minor Feature Additions ZCML :ref:`view_directive` directive. See the documentation for those APIs for more inforamtion. +- New argument to + :class:`repoze.bfg.configuration.Configurator.add_route` and the + ZCML ``route`` directive: ``traverse``. If you would like to cause + the :term:`context` to be something other than the :term:`root` + object when this route matches, you can spell a traversal pattern as + the ``traverse`` argument. This traversal pattern will be used as + the traversal path: traversal will begin at the root object implied + by this route (either the global root, or the object returned by the + ``factory`` associated with this route). See + :class:`repoze.bfg.configuration.Configurator.add_route` for more + information (the ``traverse`` argument). + Backwards Incompatibilities --------------------------- diff --git a/docs/zcml/route.rst b/docs/zcml/route.rst index cf765e04f..b4a318043 100644 --- a/docs/zcml/route.rst +++ b/docs/zcml/route.rst @@ -40,6 +40,45 @@ Attributes .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. +``traverse`` + If you would like to cause the :term:`context` to be something other + than the :term:`root` object when this route matches, you can spell + a traversal pattern as the ``traverse`` argument. This traversal + pattern will be used as the traversal path: traversal will begin at + the root object implied by this route (either the global root, or + the object returned by the ``factory`` associated with this route). + + The syntax of the ``traverse`` argument is the same as it is for + ``path``. For example, if the ``path`` provided to the ``route`` + directive is ``articles/:article/edit``, and the ``traverse`` + argument provided to the ``route`` directive is ``/:article``, when + a request comes in that causes the route to match in such a way that + the ``article`` match value is '1' (when the request URI is + ``/articles/1/edit``), the traversal path will be generated as + ``/1``. This means that the root object's ``__getitem__`` will be + called with the name ``1`` during the traversal phase. If the ``1`` + object exists, it will become the :term:`context` of the request. + :ref:`traversal_chapter` has more information about traversal. + + If the traversal path contains segment marker names which are not + present in the path argument, a runtime error will occur. The + ``traverse`` pattern should not contain segment markers that do not + exist in the ``path``. + + A similar combining of routing and traversal is available when a + route is matched which contains a ``*traverse`` remainder marker in + its path (see :ref:`using_traverse_in_a_route_path`). The + ``traverse`` argument to the ``route`` directive allows you to + associate route patterns with an arbitrary traversal path without + using a a ``*traverse`` remainder marker; instead you can use other + match information. + + Note that the ``traverse`` argument to the ``route`` directive is + ignored when attached to a route that has a ``*traverse`` remainder + marker in its path. + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.3. + ``request_method`` A string representing an HTTP method name, e.g. ``GET``, ``POST``, ``HEAD``, ``DELETE``, ``PUT``. If this argument is not specified, diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py index e5e8bc8f6..3b03817bb 100644 --- a/repoze/bfg/configuration.py +++ b/repoze/bfg/configuration.py @@ -957,6 +957,7 @@ class Configurator(object): path_info=None, request_method=None, request_param=None, + traverse=None, custom_predicates=(), view_permission=None, renderer=None, @@ -990,6 +991,49 @@ class Configurator(object): ``mypackage.models.MyFactoryClass``. If this argument is not specified, a default root factory will be used. + traverse + + If you would like to cause the :term:`context` to be + something other than the :term:`root` object when this route + matches, you can spell a traversal pattern as the + ``traverse`` argument. This traversal pattern will be used + as the traversal path: traversal will begin at the root + object implied by this route (either the global root, or the + object returned by the ``factory`` associated with this + route). + + The syntax of the ``traverse`` argument is the same as it is + for ``path``. For example, if the ``path`` provided to + ``add_route`` is ``articles/:article/edit``, and the + ``traverse`` argument provided to ``add_route`` is + ``/:article``, when a request comes in that causes the route + to match in such a way that the ``article`` match value is + '1' (when the request URI is ``/articles/1/edit``), the + traversal path will be generated as ``/1``. This means that + the root object's ``__getitem__`` will be called with the + name ``1`` during the traversal phase. If the ``1`` object + exists, it will become the :term:`context` of the request. + :ref:`traversal_chapter` has more information about + traversal. + + If the traversal path contains segment marker names which + are not present in the path argument, a runtime error will + occur. The ``traverse`` pattern should not contain segment + markers that do not exist in the ``path``. + + A similar combining of routing and traversal is available + when a route is matched which contains a ``*traverse`` + remainder marker in its path (see + :ref:`using_traverse_in_a_route_path`). The ``traverse`` + argument to add_route allows you to associate route patterns + with an arbitrary traversal path without using a a + ``*traverse`` remainder marker; instead you can use other + match information. + + Note that the ``traverse`` argument to ``add_route`` is + ignored when attached to a route that has a ``*traverse`` + remainder marker in its path. + Predicate Arguments path @@ -1683,7 +1727,7 @@ class Configurator(object): def _make_predicates(xhr=None, request_method=None, path_info=None, request_param=None, header=None, accept=None, containment=None, request_type=None, - view_match_val=None, custom=()): + view_match_val=None, traverse=None, custom=()): # PREDICATES # ---------- @@ -1834,6 +1878,26 @@ def _make_predicates(xhr=None, request_method=None, path_info=None, predicates.append(view_match_val_predicate) h.update('view_match_val:%r=%r' % (match_name, match_val)) + if traverse is not None: + # ``traverse`` can only be used as a *route* "predicate"; it + # adds 'traverse' to the matchdict if it's specified in the + # routing args. This causes the ModelGraphTraverser to use + # the resolved traverse pattern as the traversal path. + from repoze.bfg.urldispatch import _compile_route + _, tgenerate = _compile_route(traverse) + def traverse_predicate(context, request): + if 'traverse' in context: + return True + tvalue = tgenerate(context) + context['traverse'] = traversal_path(tvalue) + return True + # This isn't actually a predicate, it's just a infodict + # modifier that injects ``traverse`` into the matchdict. As a + # result, the ``traverse_predicate`` function above always + # returns True, and we don't need to update the hash or attach + # a weight to it + predicates.append(traverse_predicate) + if custom: for num, predicate in enumerate(custom): predicates.append(predicate) diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py index 17708bf63..d5b212303 100644 --- a/repoze/bfg/tests/test_configuration.py +++ b/repoze/bfg/tests/test_configuration.py @@ -3162,6 +3162,26 @@ class Test__make_predicates(unittest.TestCase): ) self.failUnless(order1 > order2) + def test_traverse_has_remainder_already(self): + order, predicates, phash = self._callFUT(traverse='/1/:a/:b') + self.assertEqual(len(predicates), 1) + pred = predicates[0] + info = {'traverse':'abc'} + request = DummyRequest() + result = pred(info, request) + self.assertEqual(result, True) + self.assertEqual(info, {'traverse':'abc'}) + + def test_traverse_matches(self): + order, predicates, phash = self._callFUT(traverse='/1/:a/:b') + self.assertEqual(len(predicates), 1) + pred = predicates[0] + info = {'a':'a', 'b':'b'} + request = DummyRequest() + result = pred(info, request) + self.assertEqual(result, True) + self.assertEqual(info, {'a':'a', 'b':'b', 'traverse':('1', 'a', 'b')}) + class TestMultiView(unittest.TestCase): def _getTargetClass(self): from repoze.bfg.configuration import MultiView diff --git a/repoze/bfg/traversal.py b/repoze/bfg/traversal.py index d45881a3b..ce5b3225d 100644 --- a/repoze/bfg/traversal.py +++ b/repoze/bfg/traversal.py @@ -515,7 +515,7 @@ class ModelGraphTraverser(object): subpath = traversal_path(subpath) else: - # this request did not match a Routes route + # this request did not match a route subpath = () try: path = environ['PATH_INFO'] or '/' |
