diff options
| -rw-r--r-- | CHANGES.txt | 12 | ||||
| -rw-r--r-- | CONTRIBUTORS.txt | 1 | ||||
| -rw-r--r-- | pyramid/config.py | 37 | ||||
| -rw-r--r-- | pyramid/tests/test_config.py | 40 |
4 files changed, 85 insertions, 5 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index e7ecad31a..fabb882f7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,4 @@ +======= Next release ============ @@ -9,6 +10,16 @@ Bug Fixes Instead of trying to resolve the view, if it cannot, it will now just print ``<unknown>``. +Features +-------- + +- config.add_view now accepts a 'decorator' keyword argument, a + callable which will decorate the view callable before it is added to + the registry + +- If a handler class provides an _action_decorator classmethod, use that + as the decorator for each view registration for that handler. + Documentation ------------- @@ -16,6 +27,7 @@ Documentation removed from the tutorials section. It was moved to the ``pyramid_tutorials`` Github repository. + 1.0a8 (2010-12-27) ================== diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index b48e769a1..443503914 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -117,3 +117,4 @@ Contributors - Casey Duncan, 2010/12/27 +- Rob Miller, 2010/12/28 diff --git a/pyramid/config.py b/pyramid/config.py index d3c197008..e1005102b 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -4,6 +4,7 @@ import re import sys import threading import traceback +from types import FunctionType import venusian @@ -937,6 +938,17 @@ class Configurator(object): pattern = route.pattern + action_decorator = getattr(handler, '__action_decorator__', None) + if action_decorator is not None: + class_or_static = getattr(action_decorator, 'im_self', + None) is not None + if not class_or_static: + class_or_static = isinstance(action_decorator, FunctionType) + if not class_or_static: + raise ConfigurationError( + 'The "__action_decorator__" callable on a handler class ' + 'MUST be defined as a classmethod or a staticmethod.') + path_has_action = ':action' in pattern or '{action}' in pattern if action and path_has_action: @@ -966,7 +978,8 @@ class Configurator(object): preds.append(ActionPredicate(action)) view_args['custom_predicates'] = preds self.add_view(view=handler, attr=method_name, - route_name=route_name, **view_args) + route_name=route_name, + decorator=action_decorator, **view_args) else: method_name = action if method_name is None: @@ -989,14 +1002,15 @@ class Configurator(object): view_args = expose_config.copy() del view_args['name'] self.add_view(view=handler, attr=meth_name, - route_name=route_name, **view_args) + route_name=route_name, + decorator=action_decorator, **view_args) # Now register the method itself method = getattr(handler, method_name, None) configs = getattr(method, '__exposed__', [{}]) for expose_config in configs: self.add_view(view=handler, attr=action, route_name=route_name, - **expose_config) + decorator=action_decorator, **expose_config) return route @@ -1006,7 +1020,7 @@ class Configurator(object): request_param=None, containment=None, attr=None, renderer=None, wrapper=None, xhr=False, accept=None, header=None, path_info=None, custom_predicates=(), - context=None, view_mapper=None): + context=None, decorator=None, view_mapper=None): """ Add a :term:`view configuration` to the current configuration state. Arguments to ``add_view`` are broken down below into *predicate* arguments and *non-predicate* @@ -1115,6 +1129,15 @@ class Configurator(object): view is the same context and request of the inner view. If this attribute is unspecified, no view wrapping is done. + decorator + + A function which will be used to decorate the registered + :term:`view callable`. The decorator function will be + called with the view callable as a single argument, and it + must return a replacement view callable which accepts the + same arguments and returns the same type of values as the + original function. + Predicate Arguments name @@ -1342,8 +1365,9 @@ class Configurator(object): accept=accept, order=order, phash=phash, + decorator=decorator, view_mapper=view_mapper)(view) - + registered = self.registry.adapters.registered # A multiviews is a set of views which are registered for @@ -2884,6 +2908,7 @@ class DefaultViewMapper(object): @preserve_attrs def __call__(self, view): attr = self.kw.get('attr') + decorator = self.kw.get('decorator') isclass = inspect.isclass(view) ronly = self.requestonly(view) if isclass and ronly: @@ -2896,6 +2921,8 @@ class DefaultViewMapper(object): view = self.map_attr(view) elif self.helper is not None: view = self.map_rendered(view) + if decorator is not None: + view = decorator(view) return view def map_requestonly_class(self, view): diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 8ba4a4520..b094caae9 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -739,6 +739,19 @@ class ConfiguratorTests(unittest.TestCase): result = wrapper(None, None) self.assertEqual(result, 'OK') + def test_add_view_with_decorator(self): + def view(request): + return 'OK' + def view_wrapper(fn): + fn.__assert_wrapped__ = True + return fn + config = self._makeOne(autocommit=True) + config.add_view(view=view, decorator=view_wrapper) + wrapper = self._getViewCallable(config) + self.assertTrue(getattr(wrapper, '__assert_wrapped__', False)) + result = wrapper(None, None) + self.assertEqual(result, 'OK') + def test_add_view_as_instance(self): class AView: def __call__(self, context, request): @@ -1964,6 +1977,33 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(view['attr'], 'action') self.assertEqual(view['view'], MyView) + def test_add_handler_with_action_decorator(self): + config = self._makeOne(autocommit=True) + views = [] + def dummy_add_view(**kw): + views.append(kw) + config.add_view = dummy_add_view + class MyHandler(object): + @classmethod + def _action_decorator(cls, fn): # pragma: no cover + return fn + def action(self): # pragma: no cover + return 'response' + config.add_handler('name', '/{action}', MyHandler) + self.assertEqual(len(views), 1) + self.assertEqual(views[0]['decorator'], MyHandler._action_decorator) + + def test_add_handler_with_action_decorator_no_classmethod(self): + config = self._makeOne(autocommit=True) + class MyHandler(object): + def _action_decorator(self, fn): # pragma: no cover + return fn + def action(self): # pragma: no cover + return 'response' + from pyramid.exceptions import ConfigurationError + self.assertRaises(ConfigurationError, config.add_handler, + 'name', '/{action}', MyHandler) + def test_add_handler_doesnt_mutate_expose_dict(self): config = self._makeOne(autocommit=True) views = [] |
