Skip to content

Commit

Permalink
- The fix for issue Pylons#461 (which made
Browse files Browse the repository at this point in the history
  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 Pylons#503

Fixes Pylons#503
  • Loading branch information
mcdonc committed Mar 20, 2012
1 parent dd08cce commit 6c15971
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 15 deletions.
5 changes: 5 additions & 0 deletions CHANGES.txt
Expand Up @@ -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)
==================

Expand Down
7 changes: 7 additions & 0 deletions pyramid/compat.py
@@ -1,3 +1,4 @@
import inspect
import platform
import sys
import types
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

27 changes: 12 additions & 15 deletions pyramid/config/views.py
Expand Up @@ -41,6 +41,7 @@
im_func,
url_quote,
WIN,
is_bound_method,
)

from pyramid.exceptions import (
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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))
Expand Down
12 changes: 12 additions & 0 deletions pyramid/tests/test_config/test_views.py
Expand Up @@ -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:
Expand Down

0 comments on commit 6c15971

Please sign in to comment.