diff options
| author | John Anderson <sontek@gmail.com> | 2014-12-26 22:48:41 -0800 |
|---|---|---|
| committer | John Anderson <sontek@gmail.com> | 2014-12-26 22:48:41 -0800 |
| commit | 4a7029f6b313b65ba94d0726042ea3adbad38e81 (patch) | |
| tree | 79827131ef2a6cbb16c824584d5c04e744f5eb21 | |
| parent | bc26debd9ed2a46fca1b0931c78b4054bd37841d (diff) | |
| download | pyramid-4a7029f6b313b65ba94d0726042ea3adbad38e81.tar.gz pyramid-4a7029f6b313b65ba94d0726042ea3adbad38e81.tar.bz2 pyramid-4a7029f6b313b65ba94d0726042ea3adbad38e81.zip | |
Raise errors if unbound methods are passed in
| -rw-r--r-- | pyramid/compat.py | 17 | ||||
| -rw-r--r-- | pyramid/config/views.py | 15 | ||||
| -rw-r--r-- | pyramid/tests/test_compat.py | 46 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_views.py | 21 |
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 |
