Permalink
Browse files

- The fix for issue #461 (which made

  it possible for instance methods to be used as view callables) introduced a
  backwards incompatibility when methods that declared only a request
  argument were used.  See #503

Fixes #503
  • Loading branch information...
1 parent dd08cce commit 6c15971a0006ed944e19a8f93b5897dccda028c0 @mcdonc mcdonc committed Mar 20, 2012
Showing with 36 additions and 15 deletions.
  1. +5 −0 CHANGES.txt
  2. +7 −0 pyramid/compat.py
  3. +12 −15 pyramid/config/views.py
  4. +12 −0 pyramid/tests/test_config/test_views.py
View
@@ -11,6 +11,11 @@ Bug Fixes
because it mistakenly detects that a route was matched when, in fact, it
was not.
+- The fix for issue https://github.com/Pylons/pyramid/issues/461 (which made
+ it possible for instance methods to be used as view callables) introduced a
+ backwards incompatibility when methods that declared only a request
+ argument were used. See https://github.com/Pylons/pyramid/issues/503
+
1.3b3 (2012-03-17)
==================
View
@@ -1,3 +1,4 @@
+import inspect
import platform
import sys
import types
@@ -185,8 +186,10 @@ def is_nonstr_iter(v):
if PY3: # pragma: no cover
im_func = '__func__'
+ im_self = '__self__'
else:
im_func = 'im_func'
+ im_self = 'im_self'
try: # pragma: no cover
import configparser
@@ -237,3 +240,7 @@ def unquote_bytes_to_wsgi(bytestring):
from urlparse import unquote as unquote_to_bytes
def unquote_bytes_to_wsgi(bytestring):
return unquote_to_bytes(bytestring)
+
+def is_bound_method(ob):
+ return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None
+
@@ -41,6 +41,7 @@
im_func,
url_quote,
WIN,
+ is_bound_method,
)
from pyramid.exceptions import (
@@ -140,18 +141,7 @@ def __call__(self, view):
self.decorated_view(
self.rendered_view(
self.mapped_view(
- self.text_wrapped_view(
- view))))))))))
-
- @wraps_view
- def text_wrapped_view(self, view):
- # if the method is an instance method, we need to wrap it in order
- # to be able to assign a __text__ value to it later. see #461.
- if inspect.ismethod(view):
- def text_wrapper(context, request):
- return view(context, request)
- return text_wrapper
- return view
+ view)))))))))
@wraps_view
def mapped_view(self, view):
@@ -428,9 +418,16 @@ def map_nonclass(self, view):
elif self.attr:
mapped_view = self.map_nonclass_attr(view)
if inspect.isroutine(mapped_view):
- # we potentially mutate an unwrapped view here if it's a function;
- # we do this to avoid function call overhead of injecting another
- # wrapper
+ # This branch will be true if the view is a function or a method.
+ # We potentially mutate an unwrapped object here if it's a
+ # function. We do this to avoid function call overhead of
+ # injecting another wrapper. However, we must wrap if the
+ # function is a bound method because we can't set attributes on a
+ # bound method.
+ if is_bound_method(view):
+ _mapped_view = mapped_view
+ def mapped_view(context, request):
+ return _mapped_view(context, request)
if self.attr is not None:
mapped_view.__text__ = 'attr %s of %s' % (
self.attr, object_description(view))
@@ -230,6 +230,18 @@ def index(self, context, request):
result = wrapper(None, None)
self.assertEqual(result, 'OK')
+ def test_add_view_as_instancemethod_requestonly(self):
+ from pyramid.renderers import null_renderer
+ class View:
+ def index(self, request):
+ return 'OK'
+ view = View()
+ config=self._makeOne(autocommit=True)
+ config.add_view(view=view.index, renderer=null_renderer)
+ wrapper = self._getViewCallable(config)
+ result = wrapper(None, None)
+ self.assertEqual(result, 'OK')
+
def test_add_view_as_instance_requestonly(self):
from pyramid.renderers import null_renderer
class AView:

0 comments on commit 6c15971

Please sign in to comment.