summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog7
-rw-r--r--decorators.py36
-rw-r--r--test/unittest_decorators.py32
3 files changed, 27 insertions, 48 deletions
diff --git a/ChangeLog b/ChangeLog
index c579d02..0a2ac84 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -21,7 +21,7 @@ ChangeLog for logilab.common
- use register_all when no registration callback defined (closes #111011)
- * loggin_ext: on windows, use colorama to display colored logs, if available (closes #107436)
+ * logging_ext: on windows, use colorama to display colored logs, if available (closes #107436)
* packaging: remove references to ftp at logilab
@@ -35,7 +35,10 @@ ChangeLog for logilab.common
* configuration: enhance merge_options function (closes #113458)
-
+ * decorators: fix @monkeypatch decorator contract for dark corner
+ cases such as monkeypatching of a callable instance: no more
+ turned into an unbound method, which was broken in python 3 and
+ probably not used anywhere (actually closes #104047).
2012-11-14 -- 0.58.3
* date: fix ustrftime() impl. for python3 (closes #82161, patch by Arfrever
diff --git a/decorators.py b/decorators.py
index 5b21cd1..34bbd3a 100644
--- a/decorators.py
+++ b/decorators.py
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of logilab-common.
@@ -250,7 +250,9 @@ def locked(acquire, release):
def monkeypatch(klass, methodname=None):
- """Decorator extending class with the decorated callable
+ """Decorator extending class with the decorated callable. This is basically
+ a syntactic sugar vs class assignment.
+
>>> class A:
... pass
>>> @monkeypatch(A)
@@ -274,34 +276,6 @@ def monkeypatch(klass, methodname=None):
raise AttributeError('%s has no __name__ attribute: '
'you should provide an explicit `methodname`'
% func)
- if callable(func):
- if sys.version_info < (3, 0):
- setattr(klass, name, method_type(func, None, klass))
- #elif isinstance(func, types.FunctionType):
- else:
- 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)
+ 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 f532890..688d837 100644
--- a/test/unittest_decorators.py
+++ b/test/unittest_decorators.py
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of logilab-common.
@@ -45,26 +45,28 @@ class DecoratorsTC(TestCase):
self.assertEqual(MyClass().meth1(), 12)
self.assertEqual(MyClass().meth2(), 12)
- def test_monkeypatch_callable_non_callable(self):
- tester = self
+ def test_monkeypatch_property(self):
class MyClass: pass
@monkeypatch(MyClass, methodname='prop1')
@property
def meth1(self):
return 12
- # class XXX(object):
- # def __call__(self, other):
- # tester.assertIsInstance(other, MyClass)
- # return 12
- # try:
- # monkeypatch(MyClass)(XXX())
- # except AttributeError, err:
- # self.assertTrue(str(err).endswith('has no __name__ attribute: you should provide an explicit `methodname`'))
- # monkeypatch(MyClass, 'foo')(XXX())
- # self.assertIsInstance(MyClass.prop1, property)
- # self.assertTrue(callable(MyClass.foo))
+ self.assertIsInstance(MyClass.prop1, property)
self.assertEqual(MyClass().prop1, 12)
- # self.assertEqual(MyClass().foo(), 12)
+
+ def test_monkeypatch_arbitrary_callable(self):
+ class MyClass: pass
+ class ArbitraryCallable(object):
+ def __call__(self):
+ return 12
+ # ensure it complains about missing __name__
+ with self.assertRaises(AttributeError) as cm:
+ monkeypatch(MyClass)(ArbitraryCallable())
+ self.assertTrue(str(cm.exception).endswith('has no __name__ attribute: you should provide an explicit `methodname`'))
+ # ensure no black magic under the hood
+ monkeypatch(MyClass, 'foo')(ArbitraryCallable())
+ self.assertTrue(callable(MyClass.foo))
+ self.assertEqual(MyClass().foo(), 12)
def test_monkeypatch_with_same_name(self):
class MyClass: pass