1# Test case for property 2# more tests are in test_descr 3 4import sys 5import unittest 6from test import support 7 8class PropertyBase(Exception): 9 pass 10 11class PropertyGet(PropertyBase): 12 pass 13 14class PropertySet(PropertyBase): 15 pass 16 17class PropertyDel(PropertyBase): 18 pass 19 20class BaseClass(object): 21 def __init__(self): 22 self._spam = 5 23 24 @property 25 def spam(self): 26 """BaseClass.getter""" 27 return self._spam 28 29 @spam.setter 30 def spam(self, value): 31 self._spam = value 32 33 @spam.deleter 34 def spam(self): 35 del self._spam 36 37class SubClass(BaseClass): 38 39 @BaseClass.spam.getter 40 def spam(self): 41 """SubClass.getter""" 42 raise PropertyGet(self._spam) 43 44 @spam.setter 45 def spam(self, value): 46 raise PropertySet(self._spam) 47 48 @spam.deleter 49 def spam(self): 50 raise PropertyDel(self._spam) 51 52class PropertyDocBase(object): 53 _spam = 1 54 def _get_spam(self): 55 return self._spam 56 spam = property(_get_spam, doc="spam spam spam") 57 58class PropertyDocSub(PropertyDocBase): 59 @PropertyDocBase.spam.getter 60 def spam(self): 61 """The decorator does not use this doc string""" 62 return self._spam 63 64class PropertySubNewGetter(BaseClass): 65 @BaseClass.spam.getter 66 def spam(self): 67 """new docstring""" 68 return 5 69 70class PropertyNewGetter(object): 71 @property 72 def spam(self): 73 """original docstring""" 74 return 1 75 @spam.getter 76 def spam(self): 77 """new docstring""" 78 return 8 79 80class PropertyTests(unittest.TestCase): 81 def test_property_decorator_baseclass(self): 82 # see #1620 83 base = BaseClass() 84 self.assertEqual(base.spam, 5) 85 self.assertEqual(base._spam, 5) 86 base.spam = 10 87 self.assertEqual(base.spam, 10) 88 self.assertEqual(base._spam, 10) 89 delattr(base, "spam") 90 self.assertTrue(not hasattr(base, "spam")) 91 self.assertTrue(not hasattr(base, "_spam")) 92 base.spam = 20 93 self.assertEqual(base.spam, 20) 94 self.assertEqual(base._spam, 20) 95 96 def test_property_decorator_subclass(self): 97 # see #1620 98 sub = SubClass() 99 self.assertRaises(PropertyGet, getattr, sub, "spam") 100 self.assertRaises(PropertySet, setattr, sub, "spam", None) 101 self.assertRaises(PropertyDel, delattr, sub, "spam") 102 103 @unittest.skipIf(sys.flags.optimize >= 2, 104 "Docstrings are omitted with -O2 and above") 105 def test_property_decorator_subclass_doc(self): 106 sub = SubClass() 107 self.assertEqual(sub.__class__.spam.__doc__, "SubClass.getter") 108 109 @unittest.skipIf(sys.flags.optimize >= 2, 110 "Docstrings are omitted with -O2 and above") 111 def test_property_decorator_baseclass_doc(self): 112 base = BaseClass() 113 self.assertEqual(base.__class__.spam.__doc__, "BaseClass.getter") 114 115 def test_property_decorator_doc(self): 116 base = PropertyDocBase() 117 sub = PropertyDocSub() 118 self.assertEqual(base.__class__.spam.__doc__, "spam spam spam") 119 self.assertEqual(sub.__class__.spam.__doc__, "spam spam spam") 120 121 @unittest.skipIf(sys.flags.optimize >= 2, 122 "Docstrings are omitted with -O2 and above") 123 def test_property_getter_doc_override(self): 124 newgettersub = PropertySubNewGetter() 125 self.assertEqual(newgettersub.spam, 5) 126 self.assertEqual(newgettersub.__class__.spam.__doc__, "new docstring") 127 newgetter = PropertyNewGetter() 128 self.assertEqual(newgetter.spam, 8) 129 self.assertEqual(newgetter.__class__.spam.__doc__, "new docstring") 130 131 def test_property___isabstractmethod__descriptor(self): 132 for val in (True, False, [], [1], '', '1'): 133 class C(object): 134 def foo(self): 135 pass 136 foo.__isabstractmethod__ = val 137 foo = property(foo) 138 self.assertIs(C.foo.__isabstractmethod__, bool(val)) 139 140 # check that the property's __isabstractmethod__ descriptor does the 141 # right thing when presented with a value that fails truth testing: 142 class NotBool(object): 143 def __bool__(self): 144 raise ValueError() 145 __len__ = __bool__ 146 with self.assertRaises(ValueError): 147 class C(object): 148 def foo(self): 149 pass 150 foo.__isabstractmethod__ = NotBool() 151 foo = property(foo) 152 C.foo.__isabstractmethod__ 153 154 @unittest.skipIf(sys.flags.optimize >= 2, 155 "Docstrings are omitted with -O2 and above") 156 def test_property_builtin_doc_writable(self): 157 p = property(doc='basic') 158 self.assertEqual(p.__doc__, 'basic') 159 p.__doc__ = 'extended' 160 self.assertEqual(p.__doc__, 'extended') 161 162 @unittest.skipIf(sys.flags.optimize >= 2, 163 "Docstrings are omitted with -O2 and above") 164 def test_property_decorator_doc_writable(self): 165 class PropertyWritableDoc(object): 166 167 @property 168 def spam(self): 169 """Eggs""" 170 return "eggs" 171 172 sub = PropertyWritableDoc() 173 self.assertEqual(sub.__class__.spam.__doc__, 'Eggs') 174 sub.__class__.spam.__doc__ = 'Spam' 175 self.assertEqual(sub.__class__.spam.__doc__, 'Spam') 176 177 @support.refcount_test 178 def test_refleaks_in___init__(self): 179 gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount') 180 fake_prop = property('fget', 'fset', 'fdel', 'doc') 181 refs_before = gettotalrefcount() 182 for i in range(100): 183 fake_prop.__init__('fget', 'fset', 'fdel', 'doc') 184 self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10) 185 186 @unittest.skipIf(sys.flags.optimize >= 2, 187 "Docstrings are omitted with -O2 and above") 188 def test_class_property(self): 189 class A: 190 @classmethod 191 @property 192 def __doc__(cls): 193 return 'A doc for %r' % cls.__name__ 194 self.assertEqual(A.__doc__, "A doc for 'A'") 195 196 @unittest.skipIf(sys.flags.optimize >= 2, 197 "Docstrings are omitted with -O2 and above") 198 def test_class_property_override(self): 199 class A: 200 """First""" 201 @classmethod 202 @property 203 def __doc__(cls): 204 return 'Second' 205 self.assertEqual(A.__doc__, 'Second') 206 207 def test_property_set_name_incorrect_args(self): 208 p = property() 209 210 for i in (0, 1, 3): 211 with self.assertRaisesRegex( 212 TypeError, 213 fr'^__set_name__\(\) takes 2 positional arguments but {i} were given$' 214 ): 215 p.__set_name__(*([0] * i)) 216 217 def test_property_setname_on_property_subclass(self): 218 # https://github.com/python/cpython/issues/100942 219 # Copy was setting the name field without first 220 # verifying that the copy was an actual property 221 # instance. As a result, the code below was 222 # causing a segfault. 223 224 class pro(property): 225 def __new__(typ, *args, **kwargs): 226 return "abcdef" 227 228 class A: 229 pass 230 231 p = property.__new__(pro) 232 p.__set_name__(A, 1) 233 np = p.getter(lambda self: 1) 234 235# Issue 5890: subclasses of property do not preserve method __doc__ strings 236class PropertySub(property): 237 """This is a subclass of property""" 238 239class PropertySubSlots(property): 240 """This is a subclass of property that defines __slots__""" 241 __slots__ = () 242 243class PropertySubclassTests(unittest.TestCase): 244 245 def test_slots_docstring_copy_exception(self): 246 try: 247 class Foo(object): 248 @PropertySubSlots 249 def spam(self): 250 """Trying to copy this docstring will raise an exception""" 251 return 1 252 except AttributeError: 253 pass 254 else: 255 raise Exception("AttributeError not raised") 256 257 @unittest.skipIf(sys.flags.optimize >= 2, 258 "Docstrings are omitted with -O2 and above") 259 def test_docstring_copy(self): 260 class Foo(object): 261 @PropertySub 262 def spam(self): 263 """spam wrapped in property subclass""" 264 return 1 265 self.assertEqual( 266 Foo.spam.__doc__, 267 "spam wrapped in property subclass") 268 269 @unittest.skipIf(sys.flags.optimize >= 2, 270 "Docstrings are omitted with -O2 and above") 271 def test_property_setter_copies_getter_docstring(self): 272 class Foo(object): 273 def __init__(self): self._spam = 1 274 @PropertySub 275 def spam(self): 276 """spam wrapped in property subclass""" 277 return self._spam 278 @spam.setter 279 def spam(self, value): 280 """this docstring is ignored""" 281 self._spam = value 282 foo = Foo() 283 self.assertEqual(foo.spam, 1) 284 foo.spam = 2 285 self.assertEqual(foo.spam, 2) 286 self.assertEqual( 287 Foo.spam.__doc__, 288 "spam wrapped in property subclass") 289 class FooSub(Foo): 290 @Foo.spam.setter 291 def spam(self, value): 292 """another ignored docstring""" 293 self._spam = 'eggs' 294 foosub = FooSub() 295 self.assertEqual(foosub.spam, 1) 296 foosub.spam = 7 297 self.assertEqual(foosub.spam, 'eggs') 298 self.assertEqual( 299 FooSub.spam.__doc__, 300 "spam wrapped in property subclass") 301 302 @unittest.skipIf(sys.flags.optimize >= 2, 303 "Docstrings are omitted with -O2 and above") 304 def test_property_new_getter_new_docstring(self): 305 306 class Foo(object): 307 @PropertySub 308 def spam(self): 309 """a docstring""" 310 return 1 311 @spam.getter 312 def spam(self): 313 """a new docstring""" 314 return 2 315 self.assertEqual(Foo.spam.__doc__, "a new docstring") 316 class FooBase(object): 317 @PropertySub 318 def spam(self): 319 """a docstring""" 320 return 1 321 class Foo2(FooBase): 322 @FooBase.spam.getter 323 def spam(self): 324 """a new docstring""" 325 return 2 326 self.assertEqual(Foo.spam.__doc__, "a new docstring") 327 328 329class _PropertyUnreachableAttribute: 330 msg_format = None 331 obj = None 332 cls = None 333 334 def _format_exc_msg(self, msg): 335 return self.msg_format.format(msg) 336 337 @classmethod 338 def setUpClass(cls): 339 cls.obj = cls.cls() 340 341 def test_get_property(self): 342 with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no getter")): 343 self.obj.foo 344 345 def test_set_property(self): 346 with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no setter")): 347 self.obj.foo = None 348 349 def test_del_property(self): 350 with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no deleter")): 351 del self.obj.foo 352 353 354class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase): 355 msg_format = r"^property 'foo' of 'PropertyUnreachableAttributeWithName\.cls' object {}$" 356 357 class cls: 358 foo = property() 359 360 361class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase): 362 msg_format = r"^property of 'PropertyUnreachableAttributeNoName\.cls' object {}$" 363 364 class cls: 365 pass 366 367 cls.foo = property() 368 369 370if __name__ == '__main__': 371 unittest.main() 372