summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Th?nault <sylvain.thenault@logilab.fr>2012-10-23 10:32:56 +0200
committerSylvain Th?nault <sylvain.thenault@logilab.fr>2012-10-23 10:32:56 +0200
commit19a3867a64d38cd2c9f16cc1988e2daeea7aadc6 (patch)
treefb193fa153cdc384bb65532487838f4b20ce9b8f
parent6b786f3fee5da562e94b9dc14ba08564e8b9bcc9 (diff)
downloadlogilab-common-19a3867a64d38cd2c9f16cc1988e2daeea7aadc6.tar.gz
[py3k @cached] fix compat of dark corners for the @monkeypatch decorator, making tests pass. Closes #104047
-rw-r--r--decorators.py29
-rw-r--r--test/unittest_decorators.py12
2 files changed, 36 insertions, 5 deletions
diff --git a/decorators.py b/decorators.py
index 43c3652..66539a7 100644
--- a/decorators.py
+++ b/decorators.py
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of logilab-common.
@@ -19,6 +19,7 @@
__docformat__ = "restructuredtext en"
import sys
+import types
from time import clock, time
from logilab.common.compat import callable, method_type
@@ -273,11 +274,33 @@ def monkeypatch(klass, methodname=None):
raise AttributeError('%s has no __name__ attribute: '
'you should provide an explicit `methodname`'
% func)
- if callable(func) and sys.version_info < (3, 0):
- setattr(klass, name, method_type(func, None, klass))
+ if callable(func):
+ if sys.version_info < (3, 0):
+ setattr(klass, name, method_type(func, None, klass))
+ elif isinstance(func, types.FunctionType):
+ setattr(klass, name, func)
+ else:
+ setattr(klass, name, UnboundMethod(func))
else:
# likely a property
# this is quite borderline but usage already in the wild ...
setattr(klass, name, func)
return func
return decorator
+
+if sys.version_info >= (3, 0):
+ class UnboundMethod(object):
+ """unbound method wrapper necessary for python3 where we can't turn
+ arbitrary object (eg class implementing __call__) into a method, as
+ there is no more unbound method and only function are turned
+ automatically to method when accessed through an instance.
+ """
+ __slots__ = ('_callable',)
+
+ def __init__(self, callable):
+ self._callable = callable
+
+ def __get__(self, instance, objtype):
+ if instance is None:
+ return self._callable
+ return types.MethodType(self._callable, instance)
diff --git a/test/unittest_decorators.py b/test/unittest_decorators.py
index 72d8b07..f6c0c75 100644
--- a/test/unittest_decorators.py
+++ b/test/unittest_decorators.py
@@ -17,6 +17,7 @@
# with logilab-common. If not, see <http://www.gnu.org/licenses/>.
"""unit tests for the decorators module
"""
+import sys
import types
from logilab.common.testlib import TestCase, unittest_main
@@ -34,8 +35,15 @@ class DecoratorsTC(TestCase):
@monkeypatch(MyClass)
def meth2(self):
return 12
- self.assertTrue(isinstance(MyClass.meth1, types.MethodType))
- self.assertTrue(isinstance(MyClass.meth2, types.MethodType))
+ if sys.version_info < (3, 0):
+ # with python3, unbound method are functions
+ self.assertIsInstance(MyClass.meth1, types.MethodType)
+ self.assertIsInstance(MyClass.meth2, types.MethodType)
+ else:
+ self.assertIsInstance(MyClass.meth1, types.FunctionType)
+ self.assertIsInstance(MyClass.meth2, types.FunctionType)
+ self.assertEqual(MyClass().meth1(), 12)
+ self.assertEqual(MyClass().meth2(), 12)
def test_monkeypatch_callable_non_callable(self):
tester = self