From 9ead1d8e84edcb86ea9e07b4d2c31e7b74a098ed Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Nov 2018 18:45:53 -0600 Subject: move is_unbound_method to pyramid.util --- src/pyramid/compat.py | 28 ---------------------------- src/pyramid/util.py | 34 ++++++++++++++++++++++++---------- src/pyramid/viewderivers.py | 9 ++++++--- tests/test_compat.py | 32 -------------------------------- tests/test_config/test_init.py | 6 ++---- tests/test_config/test_views.py | 14 +++++++------- tests/test_util.py | 23 +++++++++++++++++++++++ 7 files changed, 62 insertions(+), 84 deletions(-) delete mode 100644 tests/test_compat.py diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 3ad8720d6..12b4b7b00 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -76,9 +76,6 @@ def is_nonstr_iter(v): return hasattr(v, '__iter__') -im_func = '__func__' -im_self = '__self__' - from http.cookies import SimpleCookie from html import escape @@ -95,28 +92,3 @@ from urllib.parse import unquote_to_bytes def unquote_bytes_to_wsgi(bytestring): return unquote_to_bytes(bytestring).decode('latin-1') - - -def is_bound_method(ob): - return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None - - -# support annotations and keyword-only arguments in PY3 -from inspect import getfullargspec as getargspec - - -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 = getargspec(fn) - has_self = len(spec.args) > 0 and spec.args[0] == 'self' - - if inspect.isfunction(fn) and has_self: - return True - - return False diff --git a/src/pyramid/util.py b/src/pyramid/util.py index d3e5d1578..23cce195a 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -6,14 +6,7 @@ import weakref from pyramid.exceptions import ConfigurationError, CyclicDependencyError -from pyramid.compat import ( - getargspec, - im_func, - is_nonstr_iter, - bytes_, - text_, - native_, -) +from pyramid.compat import is_nonstr_iter, bytes_, text_, native_ from pyramid.path import DottedNameResolver as _DottedNameResolver @@ -616,13 +609,13 @@ def takes_one_arg(callee, attr=None, argname=None): return False try: - argspec = getargspec(fn) + argspec = inspect.getfullargspec(fn) except TypeError: return False args = argspec[0] - if hasattr(fn, im_func) or ismethod: + if hasattr(fn, '__func__') or ismethod: # it's an instance method (or unbound method on py2) if not args: return False @@ -653,3 +646,24 @@ class SimpleSerializer(object): def dumps(self, appstruct): return bytes_(appstruct) + + +def is_bound_method(ob): + return inspect.ismethod(ob) and getattr(ob, '__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.getfullargspec(fn) + has_self = len(spec.args) > 0 and spec.args[0] == 'self' + + if inspect.isfunction(fn) and has_self: + return True + + return False diff --git a/src/pyramid/viewderivers.py b/src/pyramid/viewderivers.py index fbe0c252c..181cc9e5c 100644 --- a/src/pyramid/viewderivers.py +++ b/src/pyramid/viewderivers.py @@ -17,11 +17,14 @@ from pyramid.interfaces import ( IViewMapperFactory, ) -from pyramid.compat import is_bound_method, is_unbound_method - from pyramid.exceptions import ConfigurationError from pyramid.httpexceptions import HTTPForbidden -from pyramid.util import object_description, takes_one_arg +from pyramid.util import ( + object_description, + takes_one_arg, + is_bound_method, + is_unbound_method, +) from pyramid.view import render_view_to_response from pyramid import renderers diff --git a/tests/test_compat.py b/tests/test_compat.py deleted file mode 100644 index 4a14caedf..000000000 --- a/tests/test_compat.py +++ /dev/null @@ -1,32 +0,0 @@ -import unittest -from pyramid.compat import is_unbound_method - - -class TestUnboundMethods(unittest.TestCase): - def test_old_style_bound(self): - self.assertFalse(is_unbound_method(OldStyle().run)) - - def test_new_style_bound(self): - self.assertFalse(is_unbound_method(NewStyle().run)) - - def test_old_style_unbound(self): - self.assertTrue(is_unbound_method(OldStyle.run)) - - def test_new_style_unbound(self): - self.assertTrue(is_unbound_method(NewStyle.run)) - - def test_normal_func_unbound(self): - def func(): # pragma: no cover - return 'OK' - - self.assertFalse(is_unbound_method(func)) - - -class OldStyle: - def run(self): # pragma: no cover - return 'OK' - - -class NewStyle(object): - def run(self): # pragma: no cover - return 'OK' diff --git a/tests/test_config/test_init.py b/tests/test_config/test_init.py index 1cd63f113..ce2b042ec 100644 --- a/tests/test_config/test_init.py +++ b/tests/test_config/test_init.py @@ -1,8 +1,6 @@ import os import unittest -from pyramid.compat import im_func - from . import dummy_tween_factory from . import dummy_include from . import dummy_extend @@ -1205,7 +1203,7 @@ test_config.dummy_include2""" directives = {'foo': (foo, True)} config.registry._directives = directives foo_meth = config.foo - self.assertTrue(getattr(foo_meth, im_func).__docobj__ is foo) + self.assertTrue(getattr(foo_meth, '__func__').__docobj__ is foo) def test___getattr__matches_no_action_wrap(self): config = self._makeOne() @@ -1216,7 +1214,7 @@ test_config.dummy_include2""" directives = {'foo': (foo, False)} config.registry._directives = directives foo_meth = config.foo - self.assertTrue(getattr(foo_meth, im_func) is foo) + self.assertTrue(getattr(foo_meth, '__func__') is foo) class TestConfigurator_add_directive(unittest.TestCase): diff --git a/tests/test_config/test_views.py b/tests/test_config/test_views.py index aa5b67050..d530542b7 100644 --- a/tests/test_config/test_views.py +++ b/tests/test_config/test_views.py @@ -3,7 +3,7 @@ import unittest from zope.interface import implementer from pyramid import testing -from pyramid.compat import im_func, text_ +from pyramid.compat import text_ from pyramid.exceptions import ConfigurationError from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationConflictError @@ -3723,16 +3723,16 @@ class Test_preserve_view_attrs(unittest.TestCase): self.assertTrue(view1.__module__ is view2.__module__) self.assertTrue(view1.__name__ is view2.__name__) self.assertTrue( - getattr(view1.__call_permissive__, im_func) - is getattr(view2.__call_permissive__, im_func) + getattr(view1.__call_permissive__, '__func__') + is getattr(view2.__call_permissive__, '__func__') ) self.assertTrue( - getattr(view1.__permitted__, im_func) - is getattr(view2.__permitted__, im_func) + getattr(view1.__permitted__, '__func__') + is getattr(view2.__permitted__, '__func__') ) self.assertTrue( - getattr(view1.__predicated__, im_func) - is getattr(view2.__predicated__, im_func) + getattr(view1.__predicated__, '__func__') + is getattr(view2.__predicated__, '__func__') ) diff --git a/tests/test_util.py b/tests/test_util.py index 8af5fe557..676290676 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1223,3 +1223,26 @@ class TestSimpleSerializer(unittest.TestCase): def test_dumps(self): inst = self._makeOne() self.assertEqual(inst.dumps('abc'), bytes_('abc')) + + +class TestUnboundMethods(unittest.TestCase): + class Dummy(object): + def run(self): # pragma: no cover + return 'OK' + + def _callFUT(self, val): + from pyramid.util import is_unbound_method + + return is_unbound_method(val) + + def test_bound_method(self): + self.assertFalse(self._callFUT(self.Dummy().run)) + + def test_unbound_method(self): + self.assertTrue(self._callFUT(self.Dummy.run)) + + def test_normal_func_unbound(self): + def func(): # pragma: no cover + return 'OK' + + self.assertFalse(self._callFUT(func)) -- cgit v1.2.3