summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2012-11-11 12:35:25 -0500
committerChris McDonough <chrism@plope.com>2012-11-11 12:35:25 -0500
commita3adaeb87ceb2e7ae55bd9e56fcb0accee7c3d85 (patch)
tree9a38b9233de2b61d6c00542941b2b70896378868
parent267dbd261862c3d1e06ec49aad36e4bbae384eca (diff)
parentee0e41d020d3cc9f43a958a53528166e5d2293f7 (diff)
downloadpyramid-a3adaeb87ceb2e7ae55bd9e56fcb0accee7c3d85.tar.gz
pyramid-a3adaeb87ceb2e7ae55bd9e56fcb0accee7c3d85.tar.bz2
pyramid-a3adaeb87ceb2e7ae55bd9e56fcb0accee7c3d85.zip
Merge branch 'master' of github.com:Pylons/pyramid
-rw-r--r--CHANGES.txt5
-rw-r--r--pyramid/config/views.py51
-rw-r--r--pyramid/scaffolds/copydir.py2
-rw-r--r--pyramid/tests/test_config/test_views.py22
-rw-r--r--pyramid/tests/test_view.py4
5 files changed, 77 insertions, 7 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index b0bbd32a2..e40312c34 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -37,6 +37,11 @@ Features
permit limited composition reuse of the decorator by other software that
wants to provide custom decorators that are much like view_config.
+- Allow an iterable of decorators to be passed to
+ ``pyramid.config.Configurator.add_view``. This allows views to be wrapped
+ by more than one decorator without requiring combining the decorators
+ yourself.
+
Bug Fixes
---------
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index b01d17efd..8a4db149e 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -42,6 +42,7 @@ from pyramid.compat import (
url_quote,
WIN,
is_bound_method,
+ is_nonstr_iter
)
from pyramid.exceptions import (
@@ -837,14 +838,40 @@ class ViewsConfiguratorMixin(object):
decorator
- A :term:`dotted Python name` to function (or the function itself)
- 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. The view callable it is passed will
- accept ``(context, request)``. The decorator must return a
+ A :term:`dotted Python name` to function (or the function itself,
+ or an iterable of the aforementioned) which will be used to
+ decorate the registered :term:`view callable`. The decorator
+ function(s) will be called with the view callable as a single
+ argument. The view callable it is passed will accept
+ ``(context, request)``. The decorator(s) must return a
replacement view callable which also accepts ``(context,
request)``.
+ If decorator is an iterable, the callables will be combined and
+ used in the order provided as a decorator.
+ For example::
+
+ @view_config(...,
+ decorator=(decorator2,
+ decorator1))
+ def myview(request):
+ ....
+
+ Is similar to doing::
+
+ @view_config(...)
+ @decorator2
+ @decorator1
+ def myview(request):
+ ...
+
+ Except with the existing benefits of ``decorator=`` (having a common
+ decorator syntax for all view calling conventions and not having to
+ think about preserving function attributes such as ``__name__`` and
+ ``__module__`` within decorator logic).
+
+ Passing an iterable is only supported as of :app:`Pyramid` 1.4a4.
+
mapper
A Python object or :term:`dotted Python name` which refers to a
@@ -1071,7 +1098,19 @@ class ViewsConfiguratorMixin(object):
for_ = self.maybe_dotted(for_)
containment = self.maybe_dotted(containment)
mapper = self.maybe_dotted(mapper)
- decorator = self.maybe_dotted(decorator)
+
+ def combine(*decorators):
+ def decorated(view_callable):
+ # reversed() is allows a more natural ordering in the api
+ for decorator in reversed(decorators):
+ view_callable = decorator(view_callable)
+ return view_callable
+ return decorated
+
+ if is_nonstr_iter(decorator):
+ decorator = combine(*map(self.maybe_dotted, decorator))
+ else:
+ decorator = self.maybe_dotted(decorator)
if not view:
if renderer:
diff --git a/pyramid/scaffolds/copydir.py b/pyramid/scaffolds/copydir.py
index d55ea165a..ba0988523 100644
--- a/pyramid/scaffolds/copydir.py
+++ b/pyramid/scaffolds/copydir.py
@@ -245,7 +245,7 @@ Responses:
def makedirs(dir, verbosity, pad):
parent = os.path.dirname(os.path.abspath(dir))
if not os.path.exists(parent):
- makedirs(parent, verbosity, pad)
+ makedirs(parent, verbosity, pad) # pragma: no cover
os.mkdir(dir)
def substitute_filename(fn, vars):
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 575d8c738..8324eb2b9 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -185,6 +185,28 @@ class TestViewsConfigurationMixin(unittest.TestCase):
result = wrapper(None, None)
self.assertEqual(result, 'OK')
+ def test_add_view_with_decorator_tuple(self):
+ from pyramid.renderers import null_renderer
+ def view(request):
+ """ ABC """
+ return 'OK'
+ def view_wrapper1(fn):
+ def inner(context, request):
+ return 'wrapped1' + fn(context, request)
+ return inner
+ def view_wrapper2(fn):
+ def inner(context, request):
+ return 'wrapped2' + fn(context, request)
+ return inner
+ config = self._makeOne(autocommit=True)
+ config.add_view(view=view, decorator=(view_wrapper2, view_wrapper1),
+ renderer=null_renderer)
+ wrapper = self._getViewCallable(config)
+ self.assertFalse(wrapper is view)
+ self.assertEqual(wrapper.__doc__, view.__doc__)
+ result = wrapper(None, None)
+ self.assertEqual(result, 'wrapped2wrapped1OK')
+
def test_add_view_with_http_cache(self):
import datetime
from pyramid.response import Response
diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py
index df9d03490..0af941e0d 100644
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -372,6 +372,10 @@ class TestViewConfigDecorator(unittest.TestCase):
def test_create_with_other_predicates(self):
decorator = self._makeOne(foo=1)
self.assertEqual(decorator.foo, 1)
+
+ def test_create_decorator_tuple(self):
+ decorator = self._makeOne(decorator=('decorator1', 'decorator2'))
+ self.assertEqual(decorator.decorator, ('decorator1', 'decorator2'))
def test_call_function(self):
decorator = self._makeOne()