summaryrefslogtreecommitdiff
path: root/repoze
diff options
context:
space:
mode:
Diffstat (limited to 'repoze')
-rw-r--r--repoze/bfg/configuration.py59
-rw-r--r--repoze/bfg/tests/test_configuration.py34
-rw-r--r--repoze/bfg/tests/test_view.py7
-rw-r--r--repoze/bfg/tests/test_zcml.py43
-rw-r--r--repoze/bfg/view.py10
-rw-r--r--repoze/bfg/zcml.py42
6 files changed, 175 insertions, 20 deletions
diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py
index a741c1564..f135b2ee2 100644
--- a/repoze/bfg/configuration.py
+++ b/repoze/bfg/configuration.py
@@ -352,7 +352,7 @@ class Configurator(object):
request_type=None, route_name=None, request_method=None,
request_param=None, containment=None, attr=None,
renderer=None, wrapper=None, xhr=False, accept=None,
- header=None, path_info=None, _info=u''):
+ header=None, path_info=None, custom_predicates=(), _info=u''):
""" Add a :term:`view configuration` to the current
configuration state. Arguments to ``add_view`` are broken
down below into *predicate* arguments and *non-predicate*
@@ -568,7 +568,20 @@ class Configurator(object):
be tested against the ``PATH_INFO`` WSGI environment
variable. If the regex matches, this predicate will be
``True``.
- """
+
+ custom_predicates
+
+ This value should be a sequence of references to custom
+ predicate callables. Use custom predicates when no set of
+ predefined predicates do what you need. Custom predicates
+ can be combined with predefined predicates as necessary.
+ Each custom predicate callable should accept two arguments:
+ ``context`` and ``request`` and should return either
+ ``True`` or ``False`` after doing arbitrary evaluation of
+ the context and/or the request. If all callables return
+ ``True``, the associated view callable will be considered
+ viable for a given request.
+ """
if not view:
if renderer:
@@ -603,7 +616,7 @@ class Configurator(object):
score, predicates = _make_predicates(
xhr=xhr, request_method=request_method, path_info=path_info,
request_param=request_param, header=header, accept=accept,
- containment=containment)
+ containment=containment, custom=custom_predicates)
derived_view = self._derive_view(view, permission, predicates, attr,
renderer, wrapper, name, accept)
@@ -675,7 +688,8 @@ class Configurator(object):
def add_route(self, name, path, view=None, view_for=None,
permission=None, factory=None, for_=None,
header=None, xhr=False, accept=None, path_info=None,
- request_method=None, request_param=None,
+ request_method=None, request_param=None,
+ custom_predicates=(),
view_permission=None, view_request_method=None,
view_request_param=None,
view_containment=None, view_attr=None,
@@ -788,6 +802,20 @@ class Configurator(object):
request, this predicate will be true. If this predicate
returns ``False``, route matching continues.
+ custom_predicates
+
+ This value should be a sequence of references to custom
+ predicate callables. Use custom predicates when no set of
+ predefined predicates does what you need. Custom predicates
+ can be combined with predefined predicates as necessary.
+ Each custom predicate callable should accept two arguments:
+ ``context`` and ``request`` and should return either
+ ``True`` or ``False`` after doing arbitrary evaluation of
+ the context and/or the request. If all callables return
+ ``True``, the associated route will be considered viable for
+ a given request. If any custom predicate returns ``False``,
+ route matching continues.
+
View-Related Arguments
view
@@ -892,7 +920,9 @@ class Configurator(object):
path_info=path_info,
request_param=request_param,
header=header,
- accept=accept)
+ accept=accept,
+ custom=custom_predicates)
+
request_iface = self.registry.queryUtility(IRouteRequest, name=name)
if request_iface is None:
@@ -1093,7 +1123,7 @@ class Configurator(object):
def _make_predicates(xhr=None, request_method=None, path_info=None,
request_param=None, header=None, accept=None,
- containment=None):
+ containment=None, custom=()):
# Predicates are added to the predicate list in (presumed)
# computation expense order. All predicates associated with a
# view must evaluate true for the view to "match" a request.
@@ -1123,16 +1153,21 @@ def _make_predicates(xhr=None, request_method=None, path_info=None,
predicates = []
weight = sys.maxint
+ if custom:
+ for predicate in custom:
+ weight = weight - 1
+ predicates.append(predicate)
+
if xhr:
def xhr_predicate(context, request):
return request.is_xhr
- weight = weight - 10
+ weight = weight - 20
predicates.append(xhr_predicate)
if request_method is not None:
def request_method_predicate(context, request):
return request.method == request_method
- weight = weight - 20
+ weight = weight - 30
predicates.append(request_method_predicate)
if path_info is not None:
@@ -1142,7 +1177,7 @@ def _make_predicates(xhr=None, request_method=None, path_info=None,
raise ConfigurationError(why[0])
def path_info_predicate(context, request):
return path_info_val.match(request.path_info) is not None
- weight = weight - 30
+ weight = weight - 40
predicates.append(path_info_predicate)
if request_param is not None:
@@ -1153,7 +1188,7 @@ def _make_predicates(xhr=None, request_method=None, path_info=None,
if request_param_val is None:
return request_param in request.params
return request.params.get(request_param) == request_param_val
- weight = weight - 40
+ weight = weight - 50
predicates.append(request_param_predicate)
if header is not None:
@@ -1170,13 +1205,13 @@ def _make_predicates(xhr=None, request_method=None, path_info=None,
return header_name in request.headers
val = request.headers.get(header_name)
return header_val.match(val) is not None
- weight = weight - 50
+ weight = weight - 60
predicates.append(header_predicate)
if accept is not None:
def accept_predicate(context, request):
return accept in request.accept
- weight = weight - 60
+ weight = weight - 70
predicates.append(accept_predicate)
if containment is not None:
diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py
index a42e230f1..7d714f42a 100644
--- a/repoze/bfg/tests/test_configuration.py
+++ b/repoze/bfg/tests/test_configuration.py
@@ -926,6 +926,32 @@ class ConfiguratorTests(unittest.TestCase):
request.path_info = '/'
self._assertNotFound(wrapper, None, request)
+ def test_add_view_with_custom_predicates_match(self):
+ view = lambda *arg: 'OK'
+ config = self._makeOne()
+ def pred1(context, request):
+ return True
+ def pred2(context, request):
+ return True
+ predicates = (pred1, pred2)
+ config.add_view(view=view, custom_predicates=predicates)
+ wrapper = self._getViewCallable(config)
+ request = self._makeRequest(config)
+ self.assertEqual(wrapper(None, request), 'OK')
+
+ def test_add_view_with_custom_predicates_nomatch(self):
+ view = lambda *arg: 'OK'
+ config = self._makeOne()
+ def pred1(context, request):
+ return True
+ def pred2(context, request):
+ return False
+ predicates = (pred1, pred2)
+ config.add_view(view=view, custom_predicates=predicates)
+ wrapper = self._getViewCallable(config)
+ request = self._makeRequest(config)
+ self._assertNotFound(wrapper, None, request)
+
def _assertRoute(self, config, name, path, num_predicates=0):
from repoze.bfg.interfaces import IRoutesMapper
mapper = config.registry.getUtility(IRoutesMapper)
@@ -990,6 +1016,14 @@ class ConfiguratorTests(unittest.TestCase):
request.params = {}
self.assertEqual(predicate(None, request), False)
+ def test_add_route_with_custom_predicates(self):
+ config = self._makeOne()
+ def pred1(context, request): pass
+ def pred2(context, request): pass
+ config.add_route('name', 'path', custom_predicates=(pred1, pred2))
+ route = self._assertRoute(config, 'name', 'path', 2)
+ self.assertEqual(route.predicates, [pred1, pred2])
+
def test_add_route_with_header(self):
config = self._makeOne()
config.add_route('name', 'path', header='Host')
diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py
index 6fbedb934..677c15bc8 100644
--- a/repoze/bfg/tests/test_view.py
+++ b/repoze/bfg/tests/test_view.py
@@ -361,6 +361,13 @@ class TestBFGViewDecorator(unittest.TestCase):
self.assertEqual(settings[0]['attr'], 'foo')
self.assertEqual(settings[1]['attr'], 'bar')
+ def test_with_custom_predicates(self):
+ decorator = self._makeOne(custom_predicates=(1,))
+ def foo(context, request): return 'OK'
+ decorated = decorator(foo)
+ settings = decorated.__bfg_view_settings__
+ self.assertEqual(settings[0]['custom_predicates'], (1,))
+
class TestDefaultForbiddenView(BaseTest, unittest.TestCase):
def _callFUT(self, context, request):
from repoze.bfg.view import default_forbidden_view
diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py
index f3e0ed80f..cbbbe664c 100644
--- a/repoze/bfg/tests/test_zcml.py
+++ b/repoze/bfg/tests/test_zcml.py
@@ -92,6 +92,31 @@ class TestViewDirective(unittest.TestCase):
regview = reg.adapters.lookup((IDummy, IRequest), IView, name='')
self.assertEqual(regview(None, None).body, 'OK')
+ def test_with_custom_predicates(self):
+ from repoze.bfg.threadlocal import get_current_registry
+ from repoze.bfg.interfaces import IView
+ from repoze.bfg.interfaces import IRequest
+ context = DummyContext()
+ reg = get_current_registry()
+ view = lambda *arg: 'OK'
+ def pred1(context, request):
+ return True
+ def pred2(context, request):
+ return True
+ preds = (pred1, pred2)
+ self._callFUT(context, 'repoze.view', IDummy, view=view,
+ custom_predicates=preds)
+ actions = context.actions
+ self.assertEqual(len(actions), 1)
+ discrim = ('view', IDummy, '', None, IView, None, None, None, None,
+ None, False, None, None, None)
+ discrim = discrim + tuple(sorted(preds))
+ self.assertEqual(actions[0]['discriminator'], discrim)
+ register = actions[0]['callable']
+ register()
+ regview = reg.adapters.lookup((IDummy, IRequest), IView, name='')
+ self.assertEqual(regview(None, None), 'OK')
+
class TestNotFoundDirective(unittest.TestCase):
def setUp(self):
testing.setUp()
@@ -490,6 +515,24 @@ class TestRouteDirective(unittest.TestCase):
result = wrapped(None, request)
self.assertEqual(result.body, 'OK')
+ def test_with_custom_predicates(self):
+ def pred1(context, request): pass
+ def pred2(context, request): pass
+ preds = tuple(sorted([pred1, pred2]))
+
+ context = DummyContext()
+ self._callFUT(context, 'name', 'path', custom_predicates=(pred1, pred2))
+ actions = context.actions
+ self.assertEqual(len(actions), 1)
+
+ route_action = actions[0]
+ route_action['callable']()
+ route_discriminator = route_action['discriminator']
+ self.assertEqual(
+ route_discriminator,
+ ('route', 'name', False, None, None, None, None,None) + preds)
+ self._assertRoute('name', 'path', 2)
+
class TestStaticDirective(unittest.TestCase):
def setUp(self):
testing.setUp()
diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py
index e5b271262..4aebe8832 100644
--- a/repoze/bfg/view.py
+++ b/repoze/bfg/view.py
@@ -322,6 +322,12 @@ class bfg_view(object):
expression. The view will only be invoked if the ``PATH_INFO``
WSGI environment variable matches the expression.
+ If ``custom_predicates`` is specified, it must be a sequence of
+ :term:`predicate` callables (a predicate callable accepts two
+ arguments: ``context`` and ``request`` and returns ``True`` or
+ ``False``). The view will only be invoked if all custom
+ predicates return ``True``.
+
Any individual or all parameters can be omitted. The simplest
bfg_view declaration then becomes::
@@ -427,7 +433,8 @@ class bfg_view(object):
def __init__(self, name='', request_type=None, for_=None, permission=None,
route_name=None, request_method=None, request_param=None,
containment=None, attr=None, renderer=None, wrapper=None,
- xhr=False, accept=None, header=None, path_info=None):
+ xhr=False, accept=None, header=None, path_info=None,
+ custom_predicates=()):
self.name = name
self.request_type = request_type
self.for_ = for_
@@ -443,6 +450,7 @@ class bfg_view(object):
self.accept = accept
self.header = header
self.path_info = path_info
+ self.custom_predicates = custom_predicates
def __call__(self, wrapped):
setting = self.__dict__.copy()
diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py
index 1d3ab2540..7ac32fae2 100644
--- a/repoze/bfg/zcml.py
+++ b/repoze/bfg/zcml.py
@@ -134,6 +134,15 @@ class IViewDirective(Interface):
description=(u'Accepts a regular expression.'),
required = False)
+ custom_predicates = Tokens(
+ title=u"One or more custom dotted names to custom predicate callables",
+ description=(u"A list of dotted name references to callables that "
+ "will be used as predicates for this view configuration"),
+ required=False,
+ value_type=GlobalObject()
+ )
+
+
def view(
_context,
permission=None,
@@ -152,6 +161,7 @@ def view(
accept=None,
header=None,
path_info=None,
+ custom_predicates=(),
cacheable=True, # not used, here for b/w compat < 0.8
):
@@ -176,12 +186,18 @@ def view(
request_method=request_method, request_param=request_param,
containment=containment, attr=attr, renderer=renderer,
wrapper=wrapper, xhr=xhr, accept=accept, header=header,
- path_info=path_info, _info=_context.info)
+ path_info=path_info, custom_predicates=custom_predicates,
+ _info=_context.info)
+
+ discriminator = ['view', for_, name, request_type, IView, containment,
+ request_param, request_method, route_name, attr,
+ xhr, accept, header, path_info]
+
+ discriminator.extend(sorted(custom_predicates))
+ discriminator = tuple(discriminator)
_context.action(
- discriminator = ('view', for_, name, request_type, IView, containment,
- request_param, request_method, route_name, attr,
- xhr, accept, header, path_info),
+ discriminator = discriminator,
callable = register,
)
@@ -223,11 +239,18 @@ class IRouteDirective(Interface):
accept = TextLine(title=u'accept', required=False)
xhr = Bool(title=u'xhr', required=False)
path_info = TextLine(title=u'path_info', required=False)
+ custom_predicates = Tokens(
+ title=u"One or more custom dotted names to custom predicate callables",
+ description=(u"A list of dotted name references to callables that "
+ "will be used as predicates for this view configuration"),
+ required=False,
+ value_type=GlobalObject()
+ )
def route(_context, name, path, view=None, view_for=None,
permission=None, factory=None, for_=None,
header=None, xhr=False, accept=None, path_info=None,
- request_method=None, request_param=None,
+ request_method=None, request_param=None, custom_predicates=(),
view_permission=None, view_request_method=None,
view_request_param=None, view_containment=None, view_attr=None,
renderer=None, view_renderer=None, view_header=None,
@@ -259,6 +282,7 @@ def route(_context, name, path, view=None, view_for=None,
path_info=path_info,
request_method=request_method,
request_param=request_param,
+ custom_predicates=custom_predicates,
view=view,
view_for=view_for,
view_permission=view_permission,
@@ -273,10 +297,14 @@ def route(_context, name, path, view=None, view_for=None,
view_path_info=view_path_info,
_info=_context.info
)
+
+ discriminator = ['route', name, xhr, request_method, path_info,
+ request_param, header, accept]
+ discriminator.extend(sorted(custom_predicates))
+ discriminator = tuple(discriminator)
_context.action(
- discriminator = ('route', name, xhr, request_method, path_info,
- request_param, header, accept),
+ discriminator=discriminator,
callable = register,
)