summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Anderson <sontek@gmail.com>2014-12-26 22:48:41 -0800
committerJohn Anderson <sontek@gmail.com>2014-12-26 22:48:41 -0800
commit4a7029f6b313b65ba94d0726042ea3adbad38e81 (patch)
tree79827131ef2a6cbb16c824584d5c04e744f5eb21
parentbc26debd9ed2a46fca1b0931c78b4054bd37841d (diff)
downloadpyramid-4a7029f6b313b65ba94d0726042ea3adbad38e81.tar.gz
pyramid-4a7029f6b313b65ba94d0726042ea3adbad38e81.tar.bz2
pyramid-4a7029f6b313b65ba94d0726042ea3adbad38e81.zip
Raise errors if unbound methods are passed in
-rw-r--r--pyramid/compat.py17
-rw-r--r--pyramid/config/views.py15
-rw-r--r--pyramid/tests/test_compat.py46
-rw-r--r--pyramid/tests/test_config/test_views.py21
4 files changed, 76 insertions, 23 deletions
diff --git a/pyramid/compat.py b/pyramid/compat.py
index bfa345b88..749435ebc 100644
--- a/pyramid/compat.py
+++ b/pyramid/compat.py
@@ -244,3 +244,20 @@ else:
def is_bound_method(ob):
return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None
+def is_unbound_method(fn):
+ """
+ This consistently verifies that the callable is bound to a
+ class.
+ """
+ is_bound = is_bound_method(fn)
+
+ if not is_bound and inspect.isroutine(fn):
+ spec = inspect.getargspec(fn)
+ has_self = len(spec.args) > 0 and spec.args[0] == 'self'
+
+ if PY3 and inspect.isfunction(fn) and has_self: # pragma: no cover
+ return True
+ elif inspect.ismethod(fn):
+ return True
+
+ return False
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 3e305055f..d498395e1 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_unbound_method,
is_nonstr_iter,
im_self,
)
@@ -419,15 +420,11 @@ class DefaultViewMapper(object):
self.attr = kw.get('attr')
def __call__(self, view):
- # Map the attr directly if the passed in view is method and a
- # constructor is defined and must be unbound (for backwards
- # compatibility)
- if inspect.ismethod(view):
- is_bound = getattr(view, im_self, None) is not None
-
- if not is_bound:
- self.attr = view.__name__
- view = view.im_class
+ if is_unbound_method(view) and self.attr is None:
+ raise ConfigurationError((
+ 'Unbound method calls are not supported, please set the class '
+ 'as your `view` and the method as your `attr`'
+ ))
if inspect.isclass(view):
view = self.map_class(view)
diff --git a/pyramid/tests/test_compat.py b/pyramid/tests/test_compat.py
new file mode 100644
index 000000000..2f80100dd
--- /dev/null
+++ b/pyramid/tests/test_compat.py
@@ -0,0 +1,46 @@
+import unittest
+
+class TestUnboundMethods(unittest.TestCase):
+ def test_old_style_bound(self):
+ from pyramid.compat import is_unbound_method
+
+ class OldStyle:
+ def run(self):
+ return 'OK'
+
+ self.assertFalse(is_unbound_method(OldStyle().run))
+
+ def test_new_style_bound(self):
+ from pyramid.compat import is_unbound_method
+
+ class NewStyle(object):
+ def run(self):
+ return 'OK'
+
+ self.assertFalse(is_unbound_method(NewStyle().run))
+
+ def test_old_style_unbound(self):
+ from pyramid.compat import is_unbound_method
+
+ class OldStyle:
+ def run(self):
+ return 'OK'
+
+ self.assertTrue(is_unbound_method(OldStyle.run))
+
+ def test_new_style_unbound(self):
+ from pyramid.compat import is_unbound_method
+
+ class NewStyle(object):
+ def run(self):
+ return 'OK'
+
+ self.assertTrue(is_unbound_method(NewStyle.run))
+
+ def test_normal_func_unbound(self):
+ from pyramid.compat import is_unbound_method
+
+ def func():
+ return 'OK'
+
+ self.assertFalse(is_unbound_method(func))
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 664208fad..d1eb1ed3c 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -1669,23 +1669,16 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def test_add_view_class_method_no_attr(self):
from pyramid.renderers import null_renderer
from zope.interface import directlyProvides
-
- class ViewClass(object):
- def __init__(self, request):
- self.request = request
-
- def run(self):
- return 'OK'
+ from pyramid.exceptions import ConfigurationError
config = self._makeOne(autocommit=True)
- config.add_view(view=ViewClass.run, renderer=null_renderer)
+ class DummyViewClass(object):
+ def run(self): pass
- wrapper = self._getViewCallable(config)
- context = DummyContext()
- directlyProvides(context, IDummy)
- request = self._makeRequest(config)
- result = wrapper(context, request)
- self.assertEqual(result, 'OK')
+ def configure_view():
+ config.add_view(view=DummyViewClass.run, renderer=null_renderer)
+
+ self.assertRaises(ConfigurationError, configure_view)
def test_derive_view_function(self):
from pyramid.renderers import null_renderer