diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-11-01 00:48:04 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-11-01 00:48:04 +0000 |
| commit | 65476e8d2cef6b3ce105c4786645a88151a09a6c (patch) | |
| tree | 40d0c77df2fa1ba54c6bdb9df7effcc7a70049a9 | |
| parent | 3052d0b41bd06314b0b1f1c78e9977b8174f637a (diff) | |
| download | pyramid-65476e8d2cef6b3ce105c4786645a88151a09a6c.tar.gz pyramid-65476e8d2cef6b3ce105c4786645a88151a09a6c.tar.bz2 pyramid-65476e8d2cef6b3ce105c4786645a88151a09a6c.zip | |
- The ZCML ``route`` directive's attributes ``xhr``,
``request_method``, ``path_info``, ``request_param``, ``header`` and
``accept`` are now *route* predicates rather than *view* predicates.
If one or more of these predicates is specified in the route
configuration, all of the predicates must return true for the route
to match a request. If one or more of the route predicates
associated with a route returns ``False`` when checked during a
request, the route match fails, and the next match in the routelist
is tried. This differs from the previous behavior, where no route
predicates existed and all predicates were considered view
predicates, because in that scenario, the next route was not tried.
| -rw-r--r-- | CHANGES.txt | 12 | ||||
| -rw-r--r-- | docs/narr/urldispatch.rst | 132 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_urldispatch.py | 39 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 317 | ||||
| -rw-r--r-- | repoze/bfg/urldispatch.py | 13 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 110 |
6 files changed, 411 insertions, 212 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 27467dcc7..6b5f56020 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -30,6 +30,18 @@ Features dictionary-like methods in such a way that existing traverser code which expects to be passed an environ will continue to work. +- The ZCML ``route`` directive's attributes ``xhr``, + ``request_method``, ``path_info``, ``request_param``, ``header`` and + ``accept`` are now *route* predicates rather than *view* predicates. + If one or more of these predicates is specified in the route + configuration, all of the predicates must return true for the route + to match a request. If one or more of the route predicates + associated with a route returns ``False`` when checked during a + request, the route match fails, and the next match in the routelist + is tried. This differs from the previous behavior, where no route + predicates existed and all predicates were considered view + predicates, because in that scenario, the next route was not tried. + Documentation ------------- diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 26d4b4a93..968f1f0a6 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -85,6 +85,84 @@ view view callable when this route matches. e.g. ``mypackage.views.my_view``. +xhr + + Thie value should be either ``True`` or ``False``. If this value is + specified and is ``True``, the :term:`request` must possess an + ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header for this + route to match. This is useful for detecting AJAX requests issued + from jQuery, Prototype and other Javascript libraries. If this + predicate returns false, route matching continues. + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + +request_method + + A string representing an HTTP method name, e.g. ``GET``, ``POST``, + ``HEAD``, ``DELETE``, ``PUT``. If this argument is not specified, + this route will match if the request has *any* request method. If + this predicate returns false, route matching continues. + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + +path_info + + The value of this attribute represents a regular expression pattern + that will be tested against the ``PATH_INFO`` WSGI environment + variable. If the regex matches, this predicate will be true. If + this predicate returns false, route matching continues. + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + +request_param + + This value can be any string. A view declaration with this + attribute ensures that the associated route will only match when the + request has a key in the ``request.params`` dictionary (an HTTP + ``GET`` or ``POST`` variable) that has a name which matches the + supplied value. If the value supplied to the attribute has a ``=`` + sign in it, e.g. ``request_params="foo=123"``, then the key + (``foo``) must both exist in the ``request.params`` dictionary, and + the value must match the right hand side of the expression (``123``) + for the route to "match" the current request. If this predicate + returns false, route matching continues. + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + +header + + The value of this attribute represents an HTTP header name or a + header name/value pair. If the value contains a ``:`` (colon), it + will be considered a name/value pair (e.g. ``User-Agent:Mozilla/.*`` + or ``Host:localhost``). The *value* of an attribute that represent + a name/value pair should be a regular expression. If the value does + not contain a colon, the entire value will be considered to be the + header name (e.g. ``If-Modified-Since``). If the value evaluates to + a header name only without a value, the header specified by the name + must be present in the request for this predicate to be true. If + the value evaluates to a header name/value pair, the header + specified by the name must be present in the request *and* the + regular expression specified as the value must match the header + value. Whether or not the value represents a header name or a + header name/value pair, the case of the header name is not + significant. If this predicate returns false, route matching + continues. + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + +accept + + The value of this attribute represents a match query for one or more + mimetypes in the ``Accept`` HTTP request header. If this value is + specified, it must be in one of the following forms: a mimetype + match token in the form ``text/plain``, a wildcard mimetype match + token in the form ``text/*`` or a match-all wildcard mimetype match + token in the form ``*/*``. If any of the forms matches the + ``Accept`` header of the request, this predicate will be true. If + this predicate returns false, route matching continues. + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + view_for The Python dotted-path name to a class or an interface that the @@ -112,18 +190,28 @@ view_permission view_request_type A dotted Python name to an interface representing a :term:`request - type`. For backwards compatibility with :mod:`repoze.bfg` 1.0 and - before, this may also be a string naming an HTTP ``REQUEST_METHOD`` - (any of ``GET``, ``POST``, ``HEAD``, ``DELETE``, ``PUT``). However, - these values should really be specified in ``request_method``. If - this argument is not specified, any request type will be considered - a match for the view associated with this route. + type`. If this argument is not specified, any request type will be + considered a match for the view associated with this route. If the ``view`` attribute is not provided, this attribute has no effect. This attribute can also be spelled as ``request_type``. +view_containment + + This value should be a Python dotted-path string representing the + class that a graph traversal parent object of the :term:`context` + must be an instance of (or :term:`interface` that a parent object + must provide) in order for this view to be found and called. Your + models must be "location-aware" to use this feature. See + :ref:`location_aware` for more information about location-awareness. + + If the ``view`` attribute is not provided, this attribute has no + effect. + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + view_request_method A string representing an HTTP method name, e.g. ``GET``, ``POST``, @@ -134,8 +222,6 @@ view_request_method If the ``view`` attribute is not provided, this attribute has no effect. - This attribute can also be spelled as ``request_method``. - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. view_request_param @@ -153,24 +239,6 @@ view_request_param If the ``view`` attribute is not provided, this attribute has no effect. - This attribute can also be spelled as ``request_param``. - - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. - -view_containment - - This value should be a Python dotted-path string representing the - class that a graph traversal parent object of the :term:`context` - must be an instance of (or :term:`interface` that a parent object - must provide) in order for this view to be found and called. Your - models must be "location-aware" to use this feature. See - :ref:`location_aware` for more information about location-awareness. - - If the ``view`` attribute is not provided, this attribute has no - effect. - - This attribute can also be spelled as ``containment``. - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. view_attr @@ -188,8 +256,6 @@ view_attr If the ``view`` attribute is not provided, this attribute has no effect. - This attribute can also be spelled as ``attr``. - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. view_renderer @@ -211,8 +277,6 @@ view_renderer If the ``view`` attribute is not provided, this attribute has no effect. - This attribute can also be spelled as ``renderer``. - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. view_xhr @@ -224,7 +288,8 @@ view_xhr requests issued from jQuery, Prototype and other Javascript libraries. - This attribute can also be spelled as ``xhr``. + If the ``view`` attribute is not provided, this attribute has no + effect. .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. @@ -238,8 +303,6 @@ view_accept token in the form ``*/*``. If any of the forms matches the ``Accept`` header of the request, this predicate will be true. - This attribute can also be spelled as ``accept``. - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. view_header @@ -260,11 +323,8 @@ view_header header name/value pair, the case of the header name is not significant. - This attribute can also be spelled as ``header``. - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. - The Matchdict ------------- diff --git a/repoze/bfg/tests/test_urldispatch.py b/repoze/bfg/tests/test_urldispatch.py index 99d5d1a98..c7dfdf196 100644 --- a/repoze/bfg/tests/test_urldispatch.py +++ b/repoze/bfg/tests/test_urldispatch.py @@ -105,6 +105,45 @@ class RoutesRootFactoryTests(unittest.TestCase): self.assertEqual(request.matchdict, routing_args) self.failUnless(req_iface.providedBy(request)) + def test_route_matches_with_predicates(self): + root_factory = DummyRootFactory(123) + req_iface = self._registerRouteRequest('foo') + mapper = self._makeOne(root_factory) + mapper.connect('archives/:action/:article', 'foo', + predicates=[lambda *arg: True]) + request = self._getRequest(PATH_INFO='/archives/action1/article1') + result = mapper(request) + self.assertEqual(result, 123) + environ = request.environ + routing_args = environ['wsgiorg.routing_args'][1] + self.assertEqual(routing_args['action'], 'action1') + self.assertEqual(routing_args['article'], 'article1') + self.assertEqual(environ['bfg.routes.matchdict'], routing_args) + self.assertEqual(environ['bfg.routes.route'].name, 'foo') + self.assertEqual(request.matchdict, routing_args) + self.failUnless(req_iface.providedBy(request)) + + def test_route_fails_to_match_with_predicates(self): + root_factory = DummyRootFactory(123) + foo_iface = self._registerRouteRequest('foo') + bar_iface = self._registerRouteRequest('bar') + mapper = self._makeOne(root_factory) + mapper.connect('archives/:action/article1', 'foo', + predicates=[lambda *arg: True, lambda *arg: False]) + mapper.connect('archives/:action/:article', 'bar') + request = self._getRequest(PATH_INFO='/archives/action1/article1') + result = mapper(request) + self.assertEqual(result, 123) + environ = request.environ + routing_args = environ['wsgiorg.routing_args'][1] + self.assertEqual(routing_args['action'], 'action1') + self.assertEqual(routing_args['article'], 'article1') + self.assertEqual(environ['bfg.routes.matchdict'], routing_args) + self.assertEqual(environ['bfg.routes.route'].name, 'bar') + self.assertEqual(request.matchdict, routing_args) + self.failUnless(bar_iface.providedBy(request)) + self.failIf(foo_iface.providedBy(request)) + def test_root_route_matches(self): root_factory = DummyRootFactory(123) req_iface = self._registerRouteRequest('root') diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py index 8106baa03..d534559f6 100644 --- a/repoze/bfg/tests/test_zcml.py +++ b/repoze/bfg/tests/test_zcml.py @@ -1542,9 +1542,9 @@ class TestConnectRouteFunction(unittest.TestCase): def tearDown(self): cleanUp() - def _callFUT(self, path, name, factory): + def _callFUT(self, path, name, factory, predicates): from repoze.bfg.zcml import connect_route - return connect_route(path, name, factory) + return connect_route(path, name, factory, predicates) def _registerRoutesMapper(self): from zope.component import getSiteManager @@ -1556,8 +1556,9 @@ class TestConnectRouteFunction(unittest.TestCase): def test_defaults(self): mapper = self._registerRoutesMapper() - self._callFUT('path', 'name', 'factory') - self.assertEqual(mapper.connections, [('path', 'name', 'factory')]) + self._callFUT('path', 'name', 'factory', 'predicates') + self.assertEqual(mapper.connections, [('path', 'name', 'factory', + 'predicates')]) class TestRouteDirective(unittest.TestCase): def setUp(self): @@ -1582,10 +1583,12 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual( + route_discriminator, + ('route', 'name', False, None, None, None, None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 0) def test_with_view(self): from zope.interface import Interface @@ -1622,10 +1625,12 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual( + route_discriminator, + ('route', 'name', False, None, None, None, None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 0) def test_with_view_and_view_for(self): from zope.component import getSiteManager @@ -1658,10 +1663,11 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None,)) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None, None, None,None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 0) def test_without_view(self): from repoze.bfg.zcml import connect_route @@ -1675,10 +1681,9 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None, None, None, None)) + self.assertEqual(route_args, ('path', 'name', None, [])) def test_with_view_request_type(self): from zope.component import getSiteManager @@ -1711,12 +1716,13 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None, None, None,None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 0) - def test_with_view_request_type_alias(self): + def test_with_view_request_method(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView @@ -1725,7 +1731,8 @@ class TestRouteDirective(unittest.TestCase): context = DummyContext() def view(context, request): """ """ - self._callFUT(context, 'name', 'path', view=view, request_type="GET") + self._callFUT(context, 'name', 'path', view=view, + view_request_method="GET") actions = context.actions self.assertEqual(len(actions), 2) @@ -1746,12 +1753,13 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None, None, None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 0) - def test_with_view_request_method(self): + def test_with_view_containment(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView @@ -1760,8 +1768,7 @@ class TestRouteDirective(unittest.TestCase): context = DummyContext() def view(context, request): """ """ - self._callFUT(context, 'name', 'path', view=view, - view_request_method="GET") + self._callFUT(context, 'name', 'path', view=view, view_containment=True) actions = context.actions self.assertEqual(len(actions), 2) @@ -1771,7 +1778,7 @@ class TestRouteDirective(unittest.TestCase): sm = getSiteManager() request_type = sm.getUtility(IRouteRequest, 'name') view_discriminator = view_action['discriminator'] - discrim = ('view', None, '', request_type, IView, None, None, 'GET', + discrim = ('view', None, '', request_type, IView, True, None, None, 'name', None, False, None, None, None) self.assertEqual(view_discriminator, discrim) wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') @@ -1782,12 +1789,13 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None,None, None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 0) - def test_with_view_request_method_alias(self): + def test_with_view_header(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView @@ -1796,7 +1804,7 @@ class TestRouteDirective(unittest.TestCase): context = DummyContext() def view(context, request): """ """ - self._callFUT(context, 'name', 'path', view=view, request_method="GET") + self._callFUT(context, 'name', 'path', view=view, view_header='Host') actions = context.actions self.assertEqual(len(actions), 2) @@ -1805,10 +1813,45 @@ class TestRouteDirective(unittest.TestCase): register() sm = getSiteManager() request_type = sm.getUtility(IRouteRequest, 'name') + view_discriminator = view_action['discriminator'] + discrim = ('view', None, '', request_type, IView, None, None, None, + 'name', None, False, None, 'Host', None) + self.assertEqual(view_discriminator, discrim) + wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') + self.failUnless(wrapped) + + route_action = actions[1] + route_callable = route_action['callable'] + route_discriminator = route_action['discriminator'] + route_args = route_action['args'] + self.assertEqual(route_callable, connect_route) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None,None, None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 0) + def test_with_view_path_info(self): + from zope.component import getSiteManager + from repoze.bfg.zcml import connect_route + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest + + context = DummyContext() + def view(context, request): + """ """ + self._callFUT(context, 'name', 'path', view=view, view_path_info='/foo') + actions = context.actions + self.assertEqual(len(actions), 2) + + view_action = actions[0] + register = view_action['callable'] + register() + sm = getSiteManager() + request_type = sm.getUtility(IRouteRequest, 'name') view_discriminator = view_action['discriminator'] - discrim = ('view', None, '', request_type, IView, None, None, 'GET', - 'name', None, False, None, None, None) + discrim = ('view', None, '', request_type, IView, None, None, None, + 'name', None, False, None, None, '/foo') self.assertEqual(view_discriminator, discrim) wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') self.failUnless(wrapped) @@ -1818,12 +1861,13 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None, None, None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 0) - def test_with_view_containment(self): + def test_with_view_xhr(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView @@ -1832,7 +1876,7 @@ class TestRouteDirective(unittest.TestCase): context = DummyContext() def view(context, request): """ """ - self._callFUT(context, 'name', 'path', view=view, view_containment=True) + self._callFUT(context, 'name', 'path', view=view, view_xhr=True) actions = context.actions self.assertEqual(len(actions), 2) @@ -1842,8 +1886,8 @@ class TestRouteDirective(unittest.TestCase): sm = getSiteManager() request_type = sm.getUtility(IRouteRequest, 'name') view_discriminator = view_action['discriminator'] - discrim = ('view', None, '', request_type, IView, True, None, None, - 'name', None, False, None, None, None) + discrim = ('view', None, '', request_type, IView, None, None, None, + 'name', None, True, None, None, None) self.assertEqual(view_discriminator, discrim) wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') self.failUnless(wrapped) @@ -1853,12 +1897,13 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None, None, None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 0) - def test_with_view_containment_alias(self): + def test_with_view_accept(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView @@ -1867,7 +1912,8 @@ class TestRouteDirective(unittest.TestCase): context = DummyContext() def view(context, request): """ """ - self._callFUT(context, 'name', 'path', view=view, containment=True) + self._callFUT(context, 'name', 'path', view=view, + view_accept='text/xml') actions = context.actions self.assertEqual(len(actions), 2) @@ -1877,8 +1923,8 @@ class TestRouteDirective(unittest.TestCase): sm = getSiteManager() request_type = sm.getUtility(IRouteRequest, 'name') view_discriminator = view_action['discriminator'] - discrim = ('view', None, '', request_type, IView, True, None, None, - 'name', None, False, None, None, None) + discrim = ('view', None, '', request_type, IView, None, None, None, + 'name', None, False, 'text/xml', None, None) self.assertEqual(view_discriminator, discrim) wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') self.failUnless(wrapped) @@ -1888,12 +1934,14 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual( + route_discriminator, + ('route', 'name', False, None, None, None, None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 0) - def test_with_view_header(self): + def test_with_request_type_GET(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView @@ -1902,7 +1950,7 @@ class TestRouteDirective(unittest.TestCase): context = DummyContext() def view(context, request): """ """ - self._callFUT(context, 'name', 'path', view=view, view_header='Host') + self._callFUT(context, 'name', 'path', view=view, request_type="GET") actions = context.actions self.assertEqual(len(actions), 2) @@ -1912,8 +1960,8 @@ class TestRouteDirective(unittest.TestCase): sm = getSiteManager() request_type = sm.getUtility(IRouteRequest, 'name') view_discriminator = view_action['discriminator'] - discrim = ('view', None, '', request_type, IView, None, None, None, - 'name', None, False, None, 'Host', None) + discrim = ('view', None, '', request_type, IView, None, None, 'GET', + 'name', None, False, None, None, None) self.assertEqual(view_discriminator, discrim) wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') self.failUnless(wrapped) @@ -1923,12 +1971,15 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None, None, None,None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 0) + + # route predicates - def test_with_view_header_alias(self): + def test_with_xhr(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView @@ -1937,7 +1988,7 @@ class TestRouteDirective(unittest.TestCase): context = DummyContext() def view(context, request): """ """ - self._callFUT(context, 'name', 'path', view=view, header='Host') + self._callFUT(context, 'name', 'path', view=view, xhr=True) actions = context.actions self.assertEqual(len(actions), 2) @@ -1948,7 +1999,7 @@ class TestRouteDirective(unittest.TestCase): request_type = sm.getUtility(IRouteRequest, 'name') view_discriminator = view_action['discriminator'] discrim = ('view', None, '', request_type, IView, None, None, None, - 'name', None, False, None, 'Host', None) + 'name', None, False, None, None, None) self.assertEqual(view_discriminator, discrim) wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') self.failUnless(wrapped) @@ -1958,21 +2009,25 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', True, None, None, None, None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 1) + request = DummyRequest() + request.is_xhr = True + self.assertEqual(predicates[0](None, request), True) - def test_with_view_path_info(self): + def test_with_request_method(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IRouteRequest - + context = DummyContext() def view(context, request): """ """ - self._callFUT(context, 'name', 'path', view=view, view_path_info='/foo') + self._callFUT(context, 'name', 'path', view=view, request_method="GET") actions = context.actions self.assertEqual(len(actions), 2) @@ -1981,9 +2036,10 @@ class TestRouteDirective(unittest.TestCase): register() sm = getSiteManager() request_type = sm.getUtility(IRouteRequest, 'name') + view_discriminator = view_action['discriminator'] discrim = ('view', None, '', request_type, IView, None, None, None, - 'name', None, False, None, None, '/foo') + 'name', None, False, None, None, None) self.assertEqual(view_discriminator, discrim) wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') self.failUnless(wrapped) @@ -1993,21 +2049,25 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', False, 'GET',None, None, None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 1) + request = DummyRequest() + request.method = 'GET' + self.assertEqual(predicates[0](None, request), True) - def test_with_view_xhr(self): + def test_with_path_info(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IRouteRequest - + context = DummyContext() def view(context, request): """ """ - self._callFUT(context, 'name', 'path', view=view, view_xhr=True) + self._callFUT(context, 'name', 'path', view=view, path_info='/foo') actions = context.actions self.assertEqual(len(actions), 2) @@ -2018,7 +2078,7 @@ class TestRouteDirective(unittest.TestCase): request_type = sm.getUtility(IRouteRequest, 'name') view_discriminator = view_action['discriminator'] discrim = ('view', None, '', request_type, IView, None, None, None, - 'name', None, True, None, None, None) + 'name', None, False, None, None, None) self.assertEqual(view_discriminator, discrim) wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') self.failUnless(wrapped) @@ -2028,12 +2088,16 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, '/foo',None,None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 1) + request = DummyRequest() + request.path_info = '/foo' + self.assertEqual(predicates[0](None, request), True) - def test_with_view_xhr_alias(self): + def test_with_request_param(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView @@ -2042,7 +2106,7 @@ class TestRouteDirective(unittest.TestCase): context = DummyContext() def view(context, request): """ """ - self._callFUT(context, 'name', 'path', view=view, xhr=True) + self._callFUT(context, 'name', 'path', view=view, request_param='abc') actions = context.actions self.assertEqual(len(actions), 2) @@ -2053,7 +2117,7 @@ class TestRouteDirective(unittest.TestCase): request_type = sm.getUtility(IRouteRequest, 'name') view_discriminator = view_action['discriminator'] discrim = ('view', None, '', request_type, IView, None, None, None, - 'name', None, True, None, None, None) + 'name', None, False, None, None, None) self.assertEqual(view_discriminator, discrim) wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') self.failUnless(wrapped) @@ -2063,12 +2127,16 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None,'abc', None, None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 1) + request = DummyRequest() + request.params = {'abc':'123'} + self.assertEqual(predicates[0](None, request), True) - def test_with_view_accept(self): + def test_with_header(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView @@ -2077,8 +2145,7 @@ class TestRouteDirective(unittest.TestCase): context = DummyContext() def view(context, request): """ """ - self._callFUT(context, 'name', 'path', view=view, - view_accept='text/xml') + self._callFUT(context, 'name', 'path', view=view, header='Host') actions = context.actions self.assertEqual(len(actions), 2) @@ -2089,7 +2156,7 @@ class TestRouteDirective(unittest.TestCase): request_type = sm.getUtility(IRouteRequest, 'name') view_discriminator = view_action['discriminator'] discrim = ('view', None, '', request_type, IView, None, None, None, - 'name', None, False, 'text/xml', None, None) + 'name', None, False, None, None, None) self.assertEqual(view_discriminator, discrim) wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') self.failUnless(wrapped) @@ -2099,12 +2166,16 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None,None,'Host', None)) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 1) + request = DummyRequest() + request.headers = {'Host':'example.com'} + self.assertEqual(predicates[0](None, request), True) - def test_with_view_accept_alias(self): + def test_with_accept(self): from zope.component import getSiteManager from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView @@ -2125,7 +2196,7 @@ class TestRouteDirective(unittest.TestCase): view_discriminator = view_action['discriminator'] discrim = ('view', None, '', request_type, IView, None, None, None, - 'name', None, False, 'text/xml', None, None) + 'name', None, False, None, None, None) self.assertEqual(view_discriminator, discrim) wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') self.failUnless(wrapped) @@ -2135,10 +2206,15 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 2) - self.assertEqual(route_discriminator[0], 'route') - self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_args, ('path', 'name', None)) + self.assertEqual( + route_discriminator, + ('route', 'name', False, None, None, None, None, 'text/xml')) + self.assertEqual(route_args[:3], ('path', 'name', None)) + predicates = route_args[3] + self.assertEqual(len(predicates), 1) + request = DummyRequest() + request.accept = ['text/xml'] + self.assertEqual(predicates[0](None, request), True) class TestStaticDirective(unittest.TestCase): def setUp(self): @@ -2186,7 +2262,8 @@ class TestStaticDirective(unittest.TestCase): discriminator = action['discriminator'] args = action['args'] self.assertEqual(callable, connect_route) - self.assertEqual(discriminator, ('route', 'name')) + self.assertEqual(discriminator, + ('route', 'name', False, None, None, None, None, None)) self.assertEqual(args[0], 'name*subpath') def test_package_relative(self): @@ -2220,7 +2297,8 @@ class TestStaticDirective(unittest.TestCase): discriminator = action['discriminator'] args = action['args'] self.assertEqual(callable, connect_route) - self.assertEqual(discriminator, ('route', 'name')) + self.assertEqual(discriminator, + ('route', 'name', False, None, None, None, None, None)) self.assertEqual(args[0], 'name*subpath') def test_here_relative(self): @@ -2255,7 +2333,8 @@ class TestStaticDirective(unittest.TestCase): discriminator = action['discriminator'] args = action['args'] self.assertEqual(callable, connect_route) - self.assertEqual(discriminator, ('route', 'name')) + self.assertEqual(discriminator, + ('route', 'name', False, None, None, None, None, None)) self.assertEqual(args[0], 'name*subpath') class TestResourceDirective(unittest.TestCase): @@ -2564,8 +2643,8 @@ class DummyMapper: def __init__(self): self.connections = [] - def connect(self, *args): - self.connections.append(args) + def connect(self, path, name, factory, predicates=()): + self.connections.append((path, name, factory, predicates)) class DummyRoute: pass diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py index 4088cef12..185baa1a1 100644 --- a/repoze/bfg/urldispatch.py +++ b/repoze/bfg/urldispatch.py @@ -5,18 +5,20 @@ from zope.interface import directlyProvides from repoze.bfg.interfaces import IRouteRequest +from repoze.bfg.compat import all +from repoze.bfg.encode import url_quote from repoze.bfg.traversal import traversal_path from repoze.bfg.traversal import quote_path_segment -from repoze.bfg.encode import url_quote _marker = object() class Route(object): - def __init__(self, path, name=None, factory=None): + def __init__(self, path, name=None, factory=None, predicates=()): self.path = path self.match, self.generate = _compile_route(path) self.name = name self.factory = factory + self.predicates = predicates class RoutesRootFactory(object): def __init__(self, default_root_factory): @@ -30,8 +32,8 @@ class RoutesRootFactory(object): def get_routes(self): return self.routelist - def connect(self, path, name, factory=None): - route = Route(path, name, factory) + def connect(self, path, name, factory=None, predicates=()): + route = Route(path, name, factory, predicates) self.routelist.append(route) self.routes[name] = route return route @@ -69,6 +71,9 @@ class RoutesRootFactory(object): for route in self.routelist: match = route.match(path) if match is not None: + preds = route.predicates + if preds and not all((p(None, request) for p in preds)): + continue environ['wsgiorg.routing_args'] = ((), match) environ['bfg.routes.route'] = route environ['bfg.routes.matchdict'] = match diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index c39f06bb3..97af9112a 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -197,7 +197,7 @@ def _make_predicates(xhr=None, request_method=None, path_info=None, except re.error, why: raise ConfigurationError(why[0]) def path_info_predicate(context, request): - return path_info_val.match(request.path_info) + return path_info_val.match(request.path_info) is not None weight = weight - 30 predicates.append(path_info_predicate) @@ -553,9 +553,19 @@ class IRouteDirective(Interface): path = TextLine(title=u'path', required=True) factory = GlobalObject(title=u'context factory', required=False) view = GlobalObject(title=u'view', required=False) + view_for = GlobalObject(title=u'view_for', required=False) + # alias for view_for + for_ = GlobalObject(title=u'for', required=False) + view_permission = TextLine(title=u'view_permission', required=False) + # alias for view_permission + permission = TextLine(title=u'permission', required=False) + view_request_type = TextLine(title=u'view_request_type', required=False) + # alias for view_request_type + request_type = TextLine(title=u'request_type', required=False) + view_request_method = TextLine(title=u'view_request_method', required=False) view_containment = GlobalObject( title = u'Dotted name of a containment class or interface', @@ -566,31 +576,12 @@ class IRouteDirective(Interface): view_accept = TextLine(title=u'view_accept', required=False) view_xhr = Bool(title=u'view_xhr', required=False) view_path_info = TextLine(title=u'view_path_info', required=False) - # alias for "view_for" - for_ = GlobalObject(title=u'for', required=False) - # alias for "view_permission" - permission = TextLine(title=u'permission', required=False) - # alias for "view_request_type" - request_type = TextLine(title=u'request_type', required=False) - # alias for "view_request_method" + request_method = TextLine(title=u'request_method', required=False) - # alias for "view_request_param" request_param = TextLine(title=u'request_param', required=False) - # alias for "view_containment" - containment = GlobalObject( - title = u'Dotted name of a containment class or interface', - required=False) - # alias for "view_attr" - attr = TextLine(title=u'attr', required=False) - # alias for "view_renderer" - renderer = TextLine(title=u'renderer', required=False) - # alias for "view_header" header = TextLine(title=u'header', required=False) - # alias for "view_accept" accept = TextLine(title=u'accept', required=False) - # alias for "view_xhr" xhr = Bool(title=u'xhr', required=False) - # alias for "view_path_info" path_info = TextLine(title=u'path_info', required=False) class IRouteRequirementDirective(Interface): @@ -601,59 +592,72 @@ class IRouteRequirementDirective(Interface): def route(_context, name, path, view=None, view_for=None, permission=None, factory=None, request_type=None, for_=None, + header=None, xhr=False, accept=None, path_info=None, view_permission=None, view_request_type=None, request_method=None, view_request_method=None, - request_param=None, view_request_param=None, containment=None, - view_containment=None, attr=None, view_attr=None, renderer=None, - view_renderer=None, header=None, view_header=None, accept=None, - view_accept=None, xhr=False, view_xhr=False, - path_info=None, view_path_info=None): + request_param=None, view_request_param=None, + view_containment=None, view_attr=None, + view_renderer=None, view_header=None, + view_accept=None, view_xhr=False, + view_path_info=None): """ Handle ``route`` ZCML directives """ # the strange ordering of the request kw args above is for b/w # compatibility purposes. - for_ = view_for or for_ - request_type = view_request_type or request_type - permission = view_permission or permission - request_method = view_request_method or request_method - request_param = view_request_param or request_param - containment = view_containment or containment - attr = view_attr or attr - renderer = view_renderer or renderer - header = view_header or header - accept = view_accept or accept - xhr = view_xhr or xhr - path_info = view_path_info or path_info + # these are route predicates; if they do not match, the next route + # in the routelist will be tried + _, predicates = _make_predicates(xhr=xhr, + request_method=request_method, + path_info=path_info, + request_param=request_param, + header=header, + accept=accept) sm = getSiteManager() if request_type in ('GET', 'HEAD', 'PUT', 'POST', 'DELETE'): # b/w compat for 1.0 - request_method = request_type + view_request_method = request_type request_type = None - if request_type is None: - request_type = queryUtility(IRouteRequest, name=name) - if request_type is None: - request_type = route_request_iface(name) - sm.registerUtility(request_type, IRouteRequest, name=name) + request_iface = queryUtility(IRouteRequest, name=name) + if request_iface is None: + request_iface = route_request_iface(name) + sm.registerUtility(request_iface, IRouteRequest, name=name) if view: - _view(_context, permission=permission, for_=for_, view=view, name='', - request_type=request_type, route_name=name, - request_method=request_method, request_param=request_param, - containment=containment, attr=attr, renderer=renderer, - header=header, accept=accept, xhr=xhr, path_info=path_info) + view_for = view_for or for_ + view_request_type = view_request_type or request_type + view_permission = view_permission or permission + _view( + _context, + permission=view_permission, + for_=view_for, + view=view, + name='', + request_type=view_request_type, + route_name=name, + request_method=view_request_method, + request_param=view_request_param, + containment=view_containment, + attr=view_attr, + renderer=view_renderer, + header=view_header, + accept=view_accept, + xhr=view_xhr, + path_info=view_path_info, + ) _context.action( - discriminator = ('route', name), + discriminator = ('route', name, xhr, request_method, path_info, + request_param, header, accept), callable = connect_route, - args = (path, name, factory), + args = (path, name, factory, predicates), ) -def connect_route(path, name, factory): +def connect_route(path, name, factory, predicates): mapper = getUtility(IRoutesMapper) - mapper.connect(path, name, factory) + mapper.connect(path, name, factory, predicates=predicates) class IRendererDirective(Interface): factory = GlobalObject( |
