summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2012-03-20 00:35:33 -0400
committerChris McDonough <chrism@plope.com>2012-03-20 00:35:33 -0400
commit676131b34addf444f58a3dec011dd5f05329b0cf (patch)
treee618bf0f4aaf7e1dc35b74f851160b8c9b64079c
parentdd08cceff6c650f211c4e3fd3c7c73e37616ea7c (diff)
parent6c15971a0006ed944e19a8f93b5897dccda028c0 (diff)
downloadpyramid-676131b34addf444f58a3dec011dd5f05329b0cf.tar.gz
pyramid-676131b34addf444f58a3dec011dd5f05329b0cf.tar.bz2
pyramid-676131b34addf444f58a3dec011dd5f05329b0cf.zip
Merge branch '1.3-branch'
-rw-r--r--CHANGES.txt5
-rw-r--r--pyramid/compat.py7
-rw-r--r--pyramid/config/views.py27
-rw-r--r--pyramid/tests/test_config/test_views.py12
4 files changed, 36 insertions, 15 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 4befd2ecc..916deb009 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -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)
==================
diff --git a/pyramid/compat.py b/pyramid/compat.py
index 3459e0b02..222810b3b 100644
--- a/pyramid/compat.py
+++ b/pyramid/compat.py
@@ -1,3 +1,4 @@
+import inspect
import platform
import sys
import types
@@ -185,8 +186,10 @@ else:
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 @@ else:
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
+
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 432db79cf..a79d20d7d 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -41,6 +41,7 @@ from pyramid.compat import (
im_func,
url_quote,
WIN,
+ is_bound_method,
)
from pyramid.exceptions import (
@@ -140,18 +141,7 @@ class ViewDeriver(object):
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 @@ class DefaultViewMapper(object):
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))
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 8877c011e..d44bec691 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -230,6 +230,18 @@ class TestViewsConfigurationMixin(unittest.TestCase):
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: