From 6454a73a9ea0b00f3881e003efda3048e8407a5f Mon Sep 17 00:00:00 2001 From: rforkel Date: Tue, 26 Jun 2012 08:40:34 +0200 Subject: Added support for passing multiple decorators to add_view. --- pyramid/config/views.py | 20 ++++++++++++++++---- pyramid/scaffolds/copydir.py | 2 +- pyramid/tests/test_config/test_views.py | 22 ++++++++++++++++++++++ pyramid/tests/test_view.py | 4 ++++ 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 9e9b5321b..b00c4b9f2 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -807,11 +807,12 @@ class ViewsConfiguratorMixin(object): decorator - A :term:`dotted Python name` to function (or the function itself) + A :term:`dotted Python name` to function (or the function itself, + or a list or tuple of the aforementioned) which will be used to decorate the registered :term:`view - callable`. The decorator function will be called with the 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 must return a + accept ``(context, request)``. The decorator(s) must return a replacement view callable which also accepts ``(context, request)``. @@ -979,7 +980,18 @@ 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): + for decorator in decorators: + view_callable = decorator(view_callable) + return view_callable + return decorated + + if isinstance(decorator, (tuple, list)): + 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 9b46f83c9..4435d341e 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -184,6 +184,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_wrapper1, view_wrapper2), + 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 a105adb70..35fa132a8 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -371,6 +371,10 @@ class TestViewConfigDecorator(unittest.TestCase): self.assertEqual(decorator.mapper, 'mapper') self.assertEqual(decorator.decorator, 'decorator') self.assertEqual(decorator.match_param, 'match_param') + + 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() -- cgit v1.2.3 From 6b2c15e8187b072343cb162d731b5026cc0f940a Mon Sep 17 00:00:00 2001 From: rforkel Date: Tue, 26 Jun 2012 10:07:59 +0200 Subject: Added explanations to docstring. --- pyramid/config/views.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index b00c4b9f2..e8169e958 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -816,6 +816,27 @@ class ViewsConfiguratorMixin(object): replacement view callable which also accepts ``(context, request)``. + If decorator is a tuple or list of callables, the callables will be + combined and used in the order provided as a decorator. + For example:: + + @view_config(..., decorator=[decorator1, decorator2]) + 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). + mapper A Python object or :term:`dotted Python name` which refers to a -- cgit v1.2.3 From 76c9c20478f53c36d5ded59191e335dba1d47da6 Mon Sep 17 00:00:00 2001 From: rforkel Date: Tue, 26 Jun 2012 08:40:34 +0200 Subject: Added support for passing multiple decorators to add_view. --- pyramid/config/views.py | 20 ++++++++++++++++---- pyramid/scaffolds/copydir.py | 2 +- pyramid/tests/test_config/test_views.py | 22 ++++++++++++++++++++++ pyramid/tests/test_view.py | 4 ++++ 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index b01d17efd..b17619356 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -837,11 +837,12 @@ class ViewsConfiguratorMixin(object): decorator - A :term:`dotted Python name` to function (or the function itself) + A :term:`dotted Python name` to function (or the function itself, + or a list or tuple of the aforementioned) which will be used to decorate the registered :term:`view - callable`. The decorator function will be called with the 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 must return a + accept ``(context, request)``. The decorator(s) must return a replacement view callable which also accepts ``(context, request)``. @@ -1071,7 +1072,18 @@ 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): + for decorator in decorators: + view_callable = decorator(view_callable) + return view_callable + return decorated + + if isinstance(decorator, (tuple, list)): + 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..a62e5f2ea 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_wrapper1, view_wrapper2), + 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() -- cgit v1.2.3 From 9aac76aad6d8e40278f0aab16322bd1dbe6803bc Mon Sep 17 00:00:00 2001 From: rforkel Date: Tue, 26 Jun 2012 10:07:59 +0200 Subject: Added explanations to docstring. --- pyramid/config/views.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index b17619356..02d5b52bf 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -846,6 +846,27 @@ class ViewsConfiguratorMixin(object): replacement view callable which also accepts ``(context, request)``. + If decorator is a tuple or list of callables, the callables will be + combined and used in the order provided as a decorator. + For example:: + + @view_config(..., decorator=[decorator1, decorator2]) + 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). + mapper A Python object or :term:`dotted Python name` which refers to a -- cgit v1.2.3 From f194db1b3020e1c3610092f292c16a2f07219269 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 4 Nov 2012 19:10:16 -0600 Subject: reversed the ordering of decorator arg to add_view --- pyramid/config/views.py | 22 ++++++++++++---------- pyramid/tests/test_config/test_views.py | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 02d5b52bf..0cd61cba5 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 ( @@ -838,19 +839,19 @@ class ViewsConfiguratorMixin(object): decorator A :term:`dotted Python name` to function (or the function itself, - or a list or tuple 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 + 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 a tuple or list of callables, the callables will be - combined and used in the order provided as a decorator. + If decorator is an iterable, the callables will be combined and + used in the order provided as a decorator. For example:: - @view_config(..., decorator=[decorator1, decorator2]) + @view_config(..., decorator=[decorator2, decorator1]) def myview(request): .... @@ -1096,12 +1097,13 @@ class ViewsConfiguratorMixin(object): def combine(*decorators): def decorated(view_callable): - for decorator in decorators: + # 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 isinstance(decorator, (tuple, list)): + if is_nonstr_iter(decorator): decorator = combine(*map(self.maybe_dotted, decorator)) else: decorator = self.maybe_dotted(decorator) diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index a62e5f2ea..8324eb2b9 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -199,7 +199,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): return 'wrapped2' + fn(context, request) return inner config = self._makeOne(autocommit=True) - config.add_view(view=view, decorator=(view_wrapper1, view_wrapper2), + config.add_view(view=view, decorator=(view_wrapper2, view_wrapper1), renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertFalse(wrapper is view) -- cgit v1.2.3