# HG changeset patch
# User Sylvain Thénault <sylvain.thenault@logilab.fr>
# Date 1350981176 -7200
# Tue Oct 23 10:32:56 2012 +0200
# Branch stable
# Node ID 8d13747da8344f78a8a25c757a4bdd41692ac2dc
# Parent 42985b1dff16372e73939c6cde5ca00b768cbf60
[py3k @cached] fix compat of dark corners for the @monkeypatch decorator, making tests pass. Closes #104047
# User Sylvain Thénault <sylvain.thenault@logilab.fr>
# Date 1350981176 -7200
# Tue Oct 23 10:32:56 2012 +0200
# Branch stable
# Node ID 8d13747da8344f78a8a25c757a4bdd41692ac2dc
# Parent 42985b1dff16372e73939c6cde5ca00b768cbf60
[py3k @cached] fix compat of dark corners for the @monkeypatch decorator, making tests pass. Closes #104047
@@ -1,6 +1,6 @@
1 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 2 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 3 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr 4 # 5 # This file is part of logilab-common. 6 # 7 # logilab-common is free software: you can redistribute it and/or modify it under
@@ -17,10 +17,11 @@
8 # with logilab-common. If not, see <http://www.gnu.org/licenses/>. 9 """ A few useful function/method decorators. """ 10 __docformat__ = "restructuredtext en" 11 12 import sys 13 +import types 14 from time import clock, time 15 16 from logilab.common.compat import callable, method_type 17 18 # XXX rewrite so we can use the decorator syntax when keyarg has to be specified
@@ -271,13 +272,35 @@
19 name = methodname or func.__name__ 20 except AttributeError: 21 raise AttributeError('%s has no __name__ attribute: ' 22 'you should provide an explicit `methodname`' 23 % func) 24 - if callable(func) and sys.version_info < (3, 0): 25 - setattr(klass, name, method_type(func, None, klass)) 26 + if callable(func): 27 + if sys.version_info < (3, 0): 28 + setattr(klass, name, method_type(func, None, klass)) 29 + elif isinstance(func, types.FunctionType): 30 + setattr(klass, name, func) 31 + else: 32 + setattr(klass, name, UnboundMethod(func)) 33 else: 34 # likely a property 35 # this is quite borderline but usage already in the wild ... 36 setattr(klass, name, func) 37 return func 38 return decorator 39 + 40 +if sys.version_info >= (3, 0): 41 + class UnboundMethod(object): 42 + """unbound method wrapper necessary for python3 where we can't turn 43 + arbitrary object (eg class implementing __call__) into a method, as 44 + there is no more unbound method and only function are turned 45 + automatically to method when accessed through an instance. 46 + """ 47 + __slots__ = ('_callable',) 48 + 49 + def __init__(self, callable): 50 + self._callable = callable 51 + 52 + def __get__(self, instance, objtype): 53 + if instance is None: 54 + return self._callable 55 + return types.MethodType(self._callable, instance)
@@ -15,10 +15,11 @@
56 # 57 # You should have received a copy of the GNU Lesser General Public License along 58 # with logilab-common. If not, see <http://www.gnu.org/licenses/>. 59 """unit tests for the decorators module 60 """ 61 +import sys 62 import types 63 64 from logilab.common.testlib import TestCase, unittest_main 65 from logilab.common.decorators import (monkeypatch, cached, clear_cache, 66 copy_cache, cachedproperty)
@@ -32,12 +33,19 @@
67 return 12 68 class XXX(object): 69 @monkeypatch(MyClass) 70 def meth2(self): 71 return 12 72 - self.assertTrue(isinstance(MyClass.meth1, types.MethodType)) 73 - self.assertTrue(isinstance(MyClass.meth2, types.MethodType)) 74 + if sys.version_info < (3, 0): 75 + # with python3, unbound method are functions 76 + self.assertIsInstance(MyClass.meth1, types.MethodType) 77 + self.assertIsInstance(MyClass.meth2, types.MethodType) 78 + else: 79 + self.assertIsInstance(MyClass.meth1, types.FunctionType) 80 + self.assertIsInstance(MyClass.meth2, types.FunctionType) 81 + self.assertEqual(MyClass().meth1(), 12) 82 + self.assertEqual(MyClass().meth2(), 12) 83 84 def test_monkeypatch_callable_non_callable(self): 85 tester = self 86 class MyClass: pass 87 @monkeypatch(MyClass, methodname='prop1')