1# Test case for DynamicClassAttribute 2# more tests are in test_descr 3 4import abc 5import sys 6import unittest 7from types import DynamicClassAttribute 8 9class PropertyBase(Exception): 10 pass 11 12class PropertyGet(PropertyBase): 13 pass 14 15class PropertySet(PropertyBase): 16 pass 17 18class PropertyDel(PropertyBase): 19 pass 20 21class BaseClass(object): 22 def __init__(self): 23 self._spam = 5 24 25 @DynamicClassAttribute 26 def spam(self): 27 """BaseClass.getter""" 28 return self._spam 29 30 @spam.setter 31 def spam(self, value): 32 self._spam = value 33 34 @spam.deleter 35 def spam(self): 36 del self._spam 37 38class SubClass(BaseClass): 39 40 spam = BaseClass.__dict__['spam'] 41 42 @spam.getter 43 def spam(self): 44 """SubClass.getter""" 45 raise PropertyGet(self._spam) 46 47 @spam.setter 48 def spam(self, value): 49 raise PropertySet(self._spam) 50 51 @spam.deleter 52 def spam(self): 53 raise PropertyDel(self._spam) 54 55class PropertyDocBase(object): 56 _spam = 1 57 def _get_spam(self): 58 return self._spam 59 spam = DynamicClassAttribute(_get_spam, doc="spam spam spam") 60 61class PropertyDocSub(PropertyDocBase): 62 spam = PropertyDocBase.__dict__['spam'] 63 @spam.getter 64 def spam(self): 65 """The decorator does not use this doc string""" 66 return self._spam 67 68class PropertySubNewGetter(BaseClass): 69 spam = BaseClass.__dict__['spam'] 70 @spam.getter 71 def spam(self): 72 """new docstring""" 73 return 5 74 75class PropertyNewGetter(object): 76 @DynamicClassAttribute 77 def spam(self): 78 """original docstring""" 79 return 1 80 @spam.getter 81 def spam(self): 82 """new docstring""" 83 return 8 84 85class ClassWithAbstractVirtualProperty(metaclass=abc.ABCMeta): 86 @DynamicClassAttribute 87 @abc.abstractmethod 88 def color(): 89 pass 90 91class ClassWithPropertyAbstractVirtual(metaclass=abc.ABCMeta): 92 @abc.abstractmethod 93 @DynamicClassAttribute 94 def color(): 95 pass 96 97class PropertyTests(unittest.TestCase): 98 def test_property_decorator_baseclass(self): 99 # see #1620 100 base = BaseClass() 101 self.assertEqual(base.spam, 5) 102 self.assertEqual(base._spam, 5) 103 base.spam = 10 104 self.assertEqual(base.spam, 10) 105 self.assertEqual(base._spam, 10) 106 delattr(base, "spam") 107 self.assertTrue(not hasattr(base, "spam")) 108 self.assertTrue(not hasattr(base, "_spam")) 109 base.spam = 20 110 self.assertEqual(base.spam, 20) 111 self.assertEqual(base._spam, 20) 112 113 def test_property_decorator_subclass(self): 114 # see #1620 115 sub = SubClass() 116 self.assertRaises(PropertyGet, getattr, sub, "spam") 117 self.assertRaises(PropertySet, setattr, sub, "spam", None) 118 self.assertRaises(PropertyDel, delattr, sub, "spam") 119 120 @unittest.skipIf(sys.flags.optimize >= 2, 121 "Docstrings are omitted with -O2 and above") 122 def test_property_decorator_subclass_doc(self): 123 sub = SubClass() 124 self.assertEqual(sub.__class__.__dict__['spam'].__doc__, "SubClass.getter") 125 126 @unittest.skipIf(sys.flags.optimize >= 2, 127 "Docstrings are omitted with -O2 and above") 128 def test_property_decorator_baseclass_doc(self): 129 base = BaseClass() 130 self.assertEqual(base.__class__.__dict__['spam'].__doc__, "BaseClass.getter") 131 132 def test_property_decorator_doc(self): 133 base = PropertyDocBase() 134 sub = PropertyDocSub() 135 self.assertEqual(base.__class__.__dict__['spam'].__doc__, "spam spam spam") 136 self.assertEqual(sub.__class__.__dict__['spam'].__doc__, "spam spam spam") 137 138 @unittest.skipIf(sys.flags.optimize >= 2, 139 "Docstrings are omitted with -O2 and above") 140 def test_property_getter_doc_override(self): 141 newgettersub = PropertySubNewGetter() 142 self.assertEqual(newgettersub.spam, 5) 143 self.assertEqual(newgettersub.__class__.__dict__['spam'].__doc__, "new docstring") 144 newgetter = PropertyNewGetter() 145 self.assertEqual(newgetter.spam, 8) 146 self.assertEqual(newgetter.__class__.__dict__['spam'].__doc__, "new docstring") 147 148 def test_property___isabstractmethod__descriptor(self): 149 for val in (True, False, [], [1], '', '1'): 150 class C(object): 151 def foo(self): 152 pass 153 foo.__isabstractmethod__ = val 154 foo = DynamicClassAttribute(foo) 155 self.assertIs(C.__dict__['foo'].__isabstractmethod__, bool(val)) 156 157 # check that the DynamicClassAttribute's __isabstractmethod__ descriptor does the 158 # right thing when presented with a value that fails truth testing: 159 class NotBool(object): 160 def __bool__(self): 161 raise ValueError() 162 __len__ = __bool__ 163 with self.assertRaises(ValueError): 164 class C(object): 165 def foo(self): 166 pass 167 foo.__isabstractmethod__ = NotBool() 168 foo = DynamicClassAttribute(foo) 169 170 def test_abstract_virtual(self): 171 self.assertRaises(TypeError, ClassWithAbstractVirtualProperty) 172 self.assertRaises(TypeError, ClassWithPropertyAbstractVirtual) 173 class APV(ClassWithPropertyAbstractVirtual): 174 pass 175 self.assertRaises(TypeError, APV) 176 class AVP(ClassWithAbstractVirtualProperty): 177 pass 178 self.assertRaises(TypeError, AVP) 179 class Okay1(ClassWithAbstractVirtualProperty): 180 @DynamicClassAttribute 181 def color(self): 182 return self._color 183 def __init__(self): 184 self._color = 'cyan' 185 with self.assertRaises(AttributeError): 186 Okay1.color 187 self.assertEqual(Okay1().color, 'cyan') 188 class Okay2(ClassWithAbstractVirtualProperty): 189 @DynamicClassAttribute 190 def color(self): 191 return self._color 192 def __init__(self): 193 self._color = 'magenta' 194 with self.assertRaises(AttributeError): 195 Okay2.color 196 self.assertEqual(Okay2().color, 'magenta') 197 198 199# Issue 5890: subclasses of DynamicClassAttribute do not preserve method __doc__ strings 200class PropertySub(DynamicClassAttribute): 201 """This is a subclass of DynamicClassAttribute""" 202 203class PropertySubSlots(DynamicClassAttribute): 204 """This is a subclass of DynamicClassAttribute that defines __slots__""" 205 __slots__ = () 206 207class PropertySubclassTests(unittest.TestCase): 208 209 @unittest.skipIf(hasattr(PropertySubSlots, '__doc__'), 210 "__doc__ is already present, __slots__ will have no effect") 211 def test_slots_docstring_copy_exception(self): 212 try: 213 class Foo(object): 214 @PropertySubSlots 215 def spam(self): 216 """Trying to copy this docstring will raise an exception""" 217 return 1 218 print('\n',spam.__doc__) 219 except AttributeError: 220 pass 221 else: 222 raise Exception("AttributeError not raised") 223 224 @unittest.skipIf(sys.flags.optimize >= 2, 225 "Docstrings are omitted with -O2 and above") 226 def test_docstring_copy(self): 227 class Foo(object): 228 @PropertySub 229 def spam(self): 230 """spam wrapped in DynamicClassAttribute subclass""" 231 return 1 232 self.assertEqual( 233 Foo.__dict__['spam'].__doc__, 234 "spam wrapped in DynamicClassAttribute subclass") 235 236 @unittest.skipIf(sys.flags.optimize >= 2, 237 "Docstrings are omitted with -O2 and above") 238 def test_property_setter_copies_getter_docstring(self): 239 class Foo(object): 240 def __init__(self): self._spam = 1 241 @PropertySub 242 def spam(self): 243 """spam wrapped in DynamicClassAttribute subclass""" 244 return self._spam 245 @spam.setter 246 def spam(self, value): 247 """this docstring is ignored""" 248 self._spam = value 249 foo = Foo() 250 self.assertEqual(foo.spam, 1) 251 foo.spam = 2 252 self.assertEqual(foo.spam, 2) 253 self.assertEqual( 254 Foo.__dict__['spam'].__doc__, 255 "spam wrapped in DynamicClassAttribute subclass") 256 class FooSub(Foo): 257 spam = Foo.__dict__['spam'] 258 @spam.setter 259 def spam(self, value): 260 """another ignored docstring""" 261 self._spam = 'eggs' 262 foosub = FooSub() 263 self.assertEqual(foosub.spam, 1) 264 foosub.spam = 7 265 self.assertEqual(foosub.spam, 'eggs') 266 self.assertEqual( 267 FooSub.__dict__['spam'].__doc__, 268 "spam wrapped in DynamicClassAttribute subclass") 269 270 @unittest.skipIf(sys.flags.optimize >= 2, 271 "Docstrings are omitted with -O2 and above") 272 def test_property_new_getter_new_docstring(self): 273 274 class Foo(object): 275 @PropertySub 276 def spam(self): 277 """a docstring""" 278 return 1 279 @spam.getter 280 def spam(self): 281 """a new docstring""" 282 return 2 283 self.assertEqual(Foo.__dict__['spam'].__doc__, "a new docstring") 284 class FooBase(object): 285 @PropertySub 286 def spam(self): 287 """a docstring""" 288 return 1 289 class Foo2(FooBase): 290 spam = FooBase.__dict__['spam'] 291 @spam.getter 292 def spam(self): 293 """a new docstring""" 294 return 2 295 self.assertEqual(Foo.__dict__['spam'].__doc__, "a new docstring") 296 297 298 299if __name__ == '__main__': 300 unittest.main() 301