diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-12-19 18:40:19 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-12-19 18:40:19 +0000 |
| commit | 6225a24982dfaeffbc53f85d214159c08a0dbfc2 (patch) | |
| tree | f6939169df6a212c1a95972e55007b285930c8e1 /repoze | |
| parent | 3809abb4c55ef98541158e4f58593c6c6011034f (diff) | |
| download | pyramid-6225a24982dfaeffbc53f85d214159c08a0dbfc2.tar.gz pyramid-6225a24982dfaeffbc53f85d214159c08a0dbfc2.tar.bz2 pyramid-6225a24982dfaeffbc53f85d214159c08a0dbfc2.zip | |
- Add a ``custom_predicates`` argument to the ``Configurator``
``add_view`` method, the ``bfg_view`` decorator and the attribute
list of the ZCML ``view`` directive. If ``custom_predicates`` is
specified, it must be a sequence of predicate callables (a predicate
callable accepts two arguments: ``context`` and ``request`` and
returns ``True`` or ``False``). The associated view callable will
only be invoked if all custom predicates return ``True``. Use one
or more custom predicates when no existing predefined predicate is
useful. Predefined and custom predicates can be mixed freely.
- Add a ``custom_predicates`` argument to the ``Configurator``
``add_route`` and the attribute list of the ZCML ``route``
directive. If ``custom_predicates`` is specified, it must be a
sequence of predicate callables (a predicate callable accepts two
arguments: ``context`` and ``request`` and returns ``True`` or
``False``). The associated route will match will only be invoked if
all custom predicates return ``True``, else route matching
continues. Use one or more custom predicates when no existing
predefined predicate is useful. Predefined and custom predicates
can be mixed freely.
Diffstat (limited to 'repoze')
| -rw-r--r-- | repoze/bfg/configuration.py | 59 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_configuration.py | 34 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_view.py | 7 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_zcml.py | 43 | ||||
| -rw-r--r-- | repoze/bfg/view.py | 10 | ||||
| -rw-r--r-- | repoze/bfg/zcml.py | 42 |
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, ) |
