summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2010-07-23 07:15:05 +0000
committerChris McDonough <chrism@agendaless.com>2010-07-23 07:15:05 +0000
commitef8a8c8c04a53d3913141e1bf85c11728721e2a3 (patch)
treea369ea8fc50dacc581fabe7119f7f84616692958
parentb4c212546023e41243ea30886f9afb8625e89c93 (diff)
downloadpyramid-ef8a8c8c04a53d3913141e1bf85c11728721e2a3.tar.gz
pyramid-ef8a8c8c04a53d3913141e1bf85c11728721e2a3.tar.bz2
pyramid-ef8a8c8c04a53d3913141e1bf85c11728721e2a3.zip
- 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.
-rw-r--r--CHANGES.txt37
-rw-r--r--docs/narr/hybrid.rst2
-rw-r--r--docs/narr/urldispatch.rst35
-rw-r--r--docs/whatsnew-1.3.rst12
-rw-r--r--docs/zcml/route.rst39
-rw-r--r--repoze/bfg/configuration.py66
-rw-r--r--repoze/bfg/tests/test_configuration.py20
-rw-r--r--repoze/bfg/traversal.py2
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 '/'