summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog7
-rw-r--r--decorators.py15
-rw-r--r--test/unittest_decorators.py34
3 files changed, 50 insertions, 6 deletions
diff --git a/ChangeLog b/ChangeLog
index c4f87cb..8bc7f3e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,11 +1,10 @@
ChangeLog for logilab.common
============================
- --
- * modutils: allow overriding of _getobj by suppressing mangling
-
--
- * daemon: call initgroups/setgid before setuid
+ * daemon: call initgroups/setgid before setuid (closes #74173)
+ * decorators: @monkeypatch should produce a method object (closes #73920)
+ * modutils: allow overriding of _getobj by suppressing mangling
2011-08-05 -- 0.56.1
* clcommands: #72450 --rc-file option doesn't work
diff --git a/decorators.py b/decorators.py
index 39144d1..7bb08fc 100644
--- a/decorators.py
+++ b/decorators.py
@@ -209,7 +209,7 @@ def locked(acquire, release):
def monkeypatch(klass, methodname=None):
- """Decorator extending class with the decorated function
+ """Decorator extending class with the decorated callable
>>> class A:
... pass
>>> @monkeypatch(A)
@@ -227,6 +227,17 @@ def monkeypatch(klass, methodname=None):
12
"""
def decorator(func):
- setattr(klass, methodname or func.__name__, func)
+ try:
+ name = methodname or func.__name__
+ except AttributeError:
+ raise AttributeError('%s has no __name__ attribute: '
+ 'you should provide an explicit `methodname`'
+ % func)
+ if callable(func):
+ setattr(klass, name, types.MethodType(func, None, klass))
+ else:
+ # likely a property
+ # this is quite borderline but usage already in the wild ...
+ setattr(klass, name, func)
return func
return decorator
diff --git a/test/unittest_decorators.py b/test/unittest_decorators.py
index 75261c9..355e934 100644
--- a/test/unittest_decorators.py
+++ b/test/unittest_decorators.py
@@ -17,12 +17,46 @@
# with logilab-common. If not, see <http://www.gnu.org/licenses/>.
"""unit tests for the decorators module
"""
+import types
from logilab.common.testlib import TestCase, unittest_main
from logilab.common.decorators import monkeypatch, cached, clear_cache, copy_cache
class DecoratorsTC(TestCase):
+ def test_monkeypatch_instance_method(self):
+ class MyClass: pass
+ @monkeypatch(MyClass)
+ def meth1(self):
+ return 12
+ class XXX(object):
+ @monkeypatch(MyClass)
+ def meth2(self):
+ return 12
+ self.assertTrue(isinstance(MyClass.meth1, types.MethodType))
+ self.assertTrue(isinstance(MyClass.meth2, types.MethodType))
+
+ def test_monkeypatch_callable_non_callable(self):
+ tester = self
+ class MyClass: pass
+ @monkeypatch(MyClass, methodname='prop1')
+ @property
+ def meth1(self):
+ return 12
+ class XXX(object):
+ def __call__(self, other):
+ tester.assertTrue(isinstance(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.assertTrue(isinstance(MyClass.prop1, property))
+ self.assertTrue(callable(MyClass.foo))
+ self.assertEqual(MyClass().prop1, 12)
+ self.assertEqual(MyClass().foo(), 12)
+
def test_monkeypatch_with_same_name(self):
class MyClass: pass
@monkeypatch(MyClass)