summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt12
-rw-r--r--CONTRIBUTORS.txt1
-rw-r--r--pyramid/config.py37
-rw-r--r--pyramid/tests/test_config.py40
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 = []