# HG changeset patch
# User Sylvain Thénault <sylvain.thenault@logilab.fr>
# Date 1358789584 -3600
# Mon Jan 21 18:33:04 2013 +0100
# Branch stable
# Node ID a93679c86bfd724bea7aeff098af51c18b6234c4
# Parent 9f1a4c1593eecf26ceec88d2126535613f6e27a5
Properly fix @monkeypatch by changing its contract. Actually closes #104047
This follows 8d13747da834 and f8fb4a6d9249 which should not have been commited
before this cleanup.
# User Sylvain Thénault <sylvain.thenault@logilab.fr>
# Date 1358789584 -3600
# Mon Jan 21 18:33:04 2013 +0100
# Branch stable
# Node ID a93679c86bfd724bea7aeff098af51c18b6234c4
# Parent 9f1a4c1593eecf26ceec88d2126535613f6e27a5
Properly fix @monkeypatch by changing its contract. Actually closes #104047
This follows 8d13747da834 and f8fb4a6d9249 which should not have been commited
before this cleanup.
@@ -19,11 +19,11 @@
1 __abstract__ explicitly is better and notion of registered object 'name' 2 is now somewhat fuzzy 3 4 - use register_all when no registration callback defined (closes #111011) 5 6 - * loggin_ext: on windows, use colorama to display colored logs, if available (closes #107436) 7 + * logging_ext: on windows, use colorama to display colored logs, if available (closes #107436) 8 9 * packaging: remove references to ftp at logilab 10 11 * deprecations: really check them 12
@@ -33,11 +33,14 @@
13 14 * packaging Update download and project urls (closes #113099) 15 16 * configuration: enhance merge_options function (closes #113458) 17 18 - 19 + * decorators: fix @monkeypatch decorator contract for dark corner 20 + cases such as monkeypatching of a callable instance: no more 21 + turned into an unbound method, which was broken in python 3 and 22 + probably not used anywhere (actually closes #104047). 23 24 2012-11-14 -- 0.58.3 25 * date: fix ustrftime() impl. for python3 (closes #82161, patch by Arfrever 26 Frehtes Taifersar Arahesis) and encoding detection for python2 (closes 27 #109740)
@@ -1,6 +1,6 @@
28 -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 29 +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 30 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr 31 # 32 # This file is part of logilab-common. 33 # 34 # logilab-common is free software: you can redistribute it and/or modify it under
@@ -248,11 +248,13 @@
35 return wrapper 36 return decorator 37 38 39 def monkeypatch(klass, methodname=None): 40 - """Decorator extending class with the decorated callable 41 + """Decorator extending class with the decorated callable. This is basically 42 + a syntactic sugar vs class assignment. 43 + 44 >>> class A: 45 ... pass 46 >>> @monkeypatch(A) 47 ... def meth(self): 48 ... return 12
@@ -272,36 +274,8 @@
49 name = methodname or func.__name__ 50 except AttributeError: 51 raise AttributeError('%s has no __name__ attribute: ' 52 'you should provide an explicit `methodname`' 53 % func) 54 - if callable(func): 55 - if sys.version_info < (3, 0): 56 - setattr(klass, name, method_type(func, None, klass)) 57 - #elif isinstance(func, types.FunctionType): 58 - else: 59 - setattr(klass, name, func) 60 - #else: 61 - # setattr(klass, name, UnboundMethod(func)) 62 - else: 63 - # likely a property 64 - # this is quite borderline but usage already in the wild ... 65 - setattr(klass, name, func) 66 + setattr(klass, name, func) 67 return func 68 return decorator 69 - 70 -if sys.version_info >= (3, 0): 71 - class UnboundMethod(object): 72 - """unbound method wrapper necessary for python3 where we can't turn 73 - arbitrary object (eg class implementing __call__) into a method, as 74 - there is no more unbound method and only function are turned 75 - automatically to method when accessed through an instance. 76 - """ 77 - __slots__ = ('_callable',) 78 - 79 - def __init__(self, callable): 80 - self._callable = callable 81 - 82 - def __get__(self, instance, objtype): 83 - if instance is None: 84 - return self._callable 85 - return types.MethodType(self._callable, instance)
@@ -1,6 +1,6 @@
86 -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 87 +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 88 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr 89 # 90 # This file is part of logilab-common. 91 # 92 # logilab-common is free software: you can redistribute it and/or modify it under
@@ -43,30 +43,32 @@
93 self.assertIsInstance(MyClass.meth1, types.FunctionType) 94 self.assertIsInstance(MyClass.meth2, types.FunctionType) 95 self.assertEqual(MyClass().meth1(), 12) 96 self.assertEqual(MyClass().meth2(), 12) 97 98 - def test_monkeypatch_callable_non_callable(self): 99 - tester = self 100 + def test_monkeypatch_property(self): 101 class MyClass: pass 102 @monkeypatch(MyClass, methodname='prop1') 103 @property 104 def meth1(self): 105 return 12 106 - # class XXX(object): 107 - # def __call__(self, other): 108 - # tester.assertIsInstance(other, MyClass) 109 - # return 12 110 - # try: 111 - # monkeypatch(MyClass)(XXX()) 112 - # except AttributeError, err: 113 - # self.assertTrue(str(err).endswith('has no __name__ attribute: you should provide an explicit `methodname`')) 114 - # monkeypatch(MyClass, 'foo')(XXX()) 115 - # self.assertIsInstance(MyClass.prop1, property) 116 - # self.assertTrue(callable(MyClass.foo)) 117 + self.assertIsInstance(MyClass.prop1, property) 118 self.assertEqual(MyClass().prop1, 12) 119 - # self.assertEqual(MyClass().foo(), 12) 120 + 121 + def test_monkeypatch_arbitrary_callable(self): 122 + class MyClass: pass 123 + class ArbitraryCallable(object): 124 + def __call__(self): 125 + return 12 126 + # ensure it complains about missing __name__ 127 + with self.assertRaises(AttributeError) as cm: 128 + monkeypatch(MyClass)(ArbitraryCallable()) 129 + self.assertTrue(str(cm.exception).endswith('has no __name__ attribute: you should provide an explicit `methodname`')) 130 + # ensure no black magic under the hood 131 + monkeypatch(MyClass, 'foo')(ArbitraryCallable()) 132 + self.assertTrue(callable(MyClass.foo)) 133 + self.assertEqual(MyClass().foo(), 12) 134 135 def test_monkeypatch_with_same_name(self): 136 class MyClass: pass 137 @monkeypatch(MyClass) 138 def meth1(self):