1 # Test case for property
2 # more tests are in test_descr
3
4 import sys
5 import unittest
6 from test import support
7
8 class ESC[4;38;5;81mPropertyBase(ESC[4;38;5;149mException):
9 pass
10
11 class ESC[4;38;5;81mPropertyGet(ESC[4;38;5;149mPropertyBase):
12 pass
13
14 class ESC[4;38;5;81mPropertySet(ESC[4;38;5;149mPropertyBase):
15 pass
16
17 class ESC[4;38;5;81mPropertyDel(ESC[4;38;5;149mPropertyBase):
18 pass
19
20 class ESC[4;38;5;81mBaseClass(ESC[4;38;5;149mobject):
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
37 class ESC[4;38;5;81mSubClass(ESC[4;38;5;149mBaseClass):
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
52 class ESC[4;38;5;81mPropertyDocBase(ESC[4;38;5;149mobject):
53 _spam = 1
54 def _get_spam(self):
55 return self._spam
56 spam = property(_get_spam, doc="spam spam spam")
57
58 class ESC[4;38;5;81mPropertyDocSub(ESC[4;38;5;149mPropertyDocBase):
59 @PropertyDocBase.spam.getter
60 def spam(self):
61 """The decorator does not use this doc string"""
62 return self._spam
63
64 class ESC[4;38;5;81mPropertySubNewGetter(ESC[4;38;5;149mBaseClass):
65 @BaseClass.spam.getter
66 def spam(self):
67 """new docstring"""
68 return 5
69
70 class ESC[4;38;5;81mPropertyNewGetter(ESC[4;38;5;149mobject):
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
80 class ESC[4;38;5;81mPropertyTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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 ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
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 ESC[4;38;5;81mNotBool(ESC[4;38;5;149mobject):
143 def __bool__(self):
144 raise ValueError()
145 __len__ = __bool__
146 with self.assertRaises(ValueError):
147 class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
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 ESC[4;38;5;81mPropertyWritableDoc(ESC[4;38;5;149mobject):
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 ESC[4;38;5;81mA:
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 ESC[4;38;5;81mA:
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 ESC[4;38;5;81mpro(ESC[4;38;5;149mproperty):
225 def __new__(typ, *args, **kwargs):
226 return "abcdef"
227
228 class ESC[4;38;5;81mA:
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
236 class ESC[4;38;5;81mPropertySub(ESC[4;38;5;149mproperty):
237 """This is a subclass of property"""
238
239 class ESC[4;38;5;81mPropertySubWoDoc(ESC[4;38;5;149mproperty):
240 pass
241
242 class ESC[4;38;5;81mPropertySubSlots(ESC[4;38;5;149mproperty):
243 """This is a subclass of property that defines __slots__"""
244 __slots__ = ()
245
246 class ESC[4;38;5;81mPropertySubclassTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
247
248 def test_slots_docstring_copy_exception(self):
249 # A special case error that we preserve despite the GH-98963 behavior
250 # that would otherwise silently ignore this error.
251 # This came from commit b18500d39d791c879e9904ebac293402b4a7cd34
252 # as part of https://bugs.python.org/issue5890 which allowed docs to
253 # be set via property subclasses in the first place.
254 with self.assertRaises(AttributeError):
255 class ESC[4;38;5;81mFoo(ESC[4;38;5;149mobject):
256 @PropertySubSlots
257 def spam(self):
258 """Trying to copy this docstring will raise an exception"""
259 return 1
260
261 def test_property_with_slots_no_docstring(self):
262 # https://github.com/python/cpython/issues/98963#issuecomment-1574413319
263 class ESC[4;38;5;81mslotted_prop(ESC[4;38;5;149mproperty):
264 __slots__ = ("foo",)
265
266 p = slotted_prop() # no AttributeError
267 self.assertIsNone(getattr(p, "__doc__", None))
268
269 def undocumented_getter():
270 return 4
271
272 p = slotted_prop(undocumented_getter) # New in 3.12: no AttributeError
273 self.assertIsNone(getattr(p, "__doc__", None))
274
275 @unittest.skipIf(sys.flags.optimize >= 2,
276 "Docstrings are omitted with -O2 and above")
277 def test_property_with_slots_docstring_silently_dropped(self):
278 # https://github.com/python/cpython/issues/98963#issuecomment-1574413319
279 class ESC[4;38;5;81mslotted_prop(ESC[4;38;5;149mproperty):
280 __slots__ = ("foo",)
281
282 p = slotted_prop(doc="what's up") # no AttributeError
283 self.assertIsNone(p.__doc__)
284
285 def documented_getter():
286 """getter doc."""
287 return 4
288
289 # Historical behavior: A docstring from a getter always raises.
290 # (matches test_slots_docstring_copy_exception above).
291 with self.assertRaises(AttributeError):
292 p = slotted_prop(documented_getter)
293
294 @unittest.skipIf(sys.flags.optimize >= 2,
295 "Docstrings are omitted with -O2 and above")
296 def test_property_with_slots_and_doc_slot_docstring_present(self):
297 # https://github.com/python/cpython/issues/98963#issuecomment-1574413319
298 class ESC[4;38;5;81mslotted_prop(ESC[4;38;5;149mproperty):
299 __slots__ = ("foo", "__doc__")
300
301 p = slotted_prop(doc="what's up")
302 self.assertEqual("what's up", p.__doc__) # new in 3.12: This gets set.
303
304 def documented_getter():
305 """what's up getter doc?"""
306 return 4
307
308 p = slotted_prop(documented_getter)
309 self.assertEqual("what's up getter doc?", p.__doc__)
310
311 @unittest.skipIf(sys.flags.optimize >= 2,
312 "Docstrings are omitted with -O2 and above")
313 def test_issue41287(self):
314
315 self.assertEqual(PropertySub.__doc__, "This is a subclass of property",
316 "Docstring of `property` subclass is ignored")
317
318 doc = PropertySub(None, None, None, "issue 41287 is fixed").__doc__
319 self.assertEqual(doc, "issue 41287 is fixed",
320 "Subclasses of `property` ignores `doc` constructor argument")
321
322 def getter(x):
323 """Getter docstring"""
324
325 def getter_wo_doc(x):
326 pass
327
328 for ps in property, PropertySub, PropertySubWoDoc:
329 doc = ps(getter, None, None, "issue 41287 is fixed").__doc__
330 self.assertEqual(doc, "issue 41287 is fixed",
331 "Getter overrides explicit property docstring (%s)" % ps.__name__)
332
333 doc = ps(getter, None, None, None).__doc__
334 self.assertEqual(doc, "Getter docstring", "Getter docstring is not picked-up (%s)" % ps.__name__)
335
336 doc = ps(getter_wo_doc, None, None, "issue 41287 is fixed").__doc__
337 self.assertEqual(doc, "issue 41287 is fixed",
338 "Getter overrides explicit property docstring (%s)" % ps.__name__)
339
340 doc = ps(getter_wo_doc, None, None, None).__doc__
341 self.assertIsNone(doc, "Property class doc appears in instance __doc__ (%s)" % ps.__name__)
342
343 @unittest.skipIf(sys.flags.optimize >= 2,
344 "Docstrings are omitted with -O2 and above")
345 def test_docstring_copy(self):
346 class ESC[4;38;5;81mFoo(ESC[4;38;5;149mobject):
347 @PropertySub
348 def spam(self):
349 """spam wrapped in property subclass"""
350 return 1
351 self.assertEqual(
352 Foo.spam.__doc__,
353 "spam wrapped in property subclass")
354
355 @unittest.skipIf(sys.flags.optimize >= 2,
356 "Docstrings are omitted with -O2 and above")
357 def test_docstring_copy2(self):
358 """
359 Property tries to provide the best docstring it finds for its instances.
360 If a user-provided docstring is available, it is preserved on copies.
361 If no docstring is available during property creation, the property
362 will utilize the docstring from the getter if available.
363 """
364 def getter1(self):
365 return 1
366 def getter2(self):
367 """doc 2"""
368 return 2
369 def getter3(self):
370 """doc 3"""
371 return 3
372
373 # Case-1: user-provided doc is preserved in copies
374 # of property with undocumented getter
375 p = property(getter1, None, None, "doc-A")
376
377 p2 = p.getter(getter2)
378 self.assertEqual(p.__doc__, "doc-A")
379 self.assertEqual(p2.__doc__, "doc-A")
380
381 # Case-2: user-provided doc is preserved in copies
382 # of property with documented getter
383 p = property(getter2, None, None, "doc-A")
384
385 p2 = p.getter(getter3)
386 self.assertEqual(p.__doc__, "doc-A")
387 self.assertEqual(p2.__doc__, "doc-A")
388
389 # Case-3: with no user-provided doc new getter doc
390 # takes precendence
391 p = property(getter2, None, None, None)
392
393 p2 = p.getter(getter3)
394 self.assertEqual(p.__doc__, "doc 2")
395 self.assertEqual(p2.__doc__, "doc 3")
396
397 # Case-4: A user-provided doc is assigned after property construction
398 # with documented getter. The doc IS NOT preserved.
399 # It's an odd behaviour, but it's a strange enough
400 # use case with no easy solution.
401 p = property(getter2, None, None, None)
402 p.__doc__ = "user"
403 p2 = p.getter(getter3)
404 self.assertEqual(p.__doc__, "user")
405 self.assertEqual(p2.__doc__, "doc 3")
406
407 # Case-5: A user-provided doc is assigned after property construction
408 # with UNdocumented getter. The doc IS preserved.
409 p = property(getter1, None, None, None)
410 p.__doc__ = "user"
411 p2 = p.getter(getter2)
412 self.assertEqual(p.__doc__, "user")
413 self.assertEqual(p2.__doc__, "user")
414
415 @unittest.skipIf(sys.flags.optimize >= 2,
416 "Docstrings are omitted with -O2 and above")
417 def test_property_setter_copies_getter_docstring(self):
418 class ESC[4;38;5;81mFoo(ESC[4;38;5;149mobject):
419 def __init__(self): self._spam = 1
420 @PropertySub
421 def spam(self):
422 """spam wrapped in property subclass"""
423 return self._spam
424 @spam.setter
425 def spam(self, value):
426 """this docstring is ignored"""
427 self._spam = value
428 foo = Foo()
429 self.assertEqual(foo.spam, 1)
430 foo.spam = 2
431 self.assertEqual(foo.spam, 2)
432 self.assertEqual(
433 Foo.spam.__doc__,
434 "spam wrapped in property subclass")
435 class ESC[4;38;5;81mFooSub(ESC[4;38;5;149mFoo):
436 @Foo.spam.setter
437 def spam(self, value):
438 """another ignored docstring"""
439 self._spam = 'eggs'
440 foosub = FooSub()
441 self.assertEqual(foosub.spam, 1)
442 foosub.spam = 7
443 self.assertEqual(foosub.spam, 'eggs')
444 self.assertEqual(
445 FooSub.spam.__doc__,
446 "spam wrapped in property subclass")
447
448 @unittest.skipIf(sys.flags.optimize >= 2,
449 "Docstrings are omitted with -O2 and above")
450 def test_property_new_getter_new_docstring(self):
451
452 class ESC[4;38;5;81mFoo(ESC[4;38;5;149mobject):
453 @PropertySub
454 def spam(self):
455 """a docstring"""
456 return 1
457 @spam.getter
458 def spam(self):
459 """a new docstring"""
460 return 2
461 self.assertEqual(Foo.spam.__doc__, "a new docstring")
462 class ESC[4;38;5;81mFooBase(ESC[4;38;5;149mobject):
463 @PropertySub
464 def spam(self):
465 """a docstring"""
466 return 1
467 class ESC[4;38;5;81mFoo2(ESC[4;38;5;149mFooBase):
468 @FooBase.spam.getter
469 def spam(self):
470 """a new docstring"""
471 return 2
472 self.assertEqual(Foo.spam.__doc__, "a new docstring")
473
474
475 class ESC[4;38;5;81m_PropertyUnreachableAttribute:
476 msg_format = None
477 obj = None
478 cls = None
479
480 def _format_exc_msg(self, msg):
481 return self.msg_format.format(msg)
482
483 @classmethod
484 def setUpClass(cls):
485 cls.obj = cls.cls()
486
487 def test_get_property(self):
488 with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no getter")):
489 self.obj.foo
490
491 def test_set_property(self):
492 with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no setter")):
493 self.obj.foo = None
494
495 def test_del_property(self):
496 with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no deleter")):
497 del self.obj.foo
498
499
500 class ESC[4;38;5;81mPropertyUnreachableAttributeWithName(ESC[4;38;5;149m_PropertyUnreachableAttribute, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
501 msg_format = r"^property 'foo' of 'PropertyUnreachableAttributeWithName\.cls' object {}$"
502
503 class ESC[4;38;5;81mcls:
504 foo = property()
505
506
507 class ESC[4;38;5;81mPropertyUnreachableAttributeNoName(ESC[4;38;5;149m_PropertyUnreachableAttribute, ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
508 msg_format = r"^property of 'PropertyUnreachableAttributeNoName\.cls' object {}$"
509
510 class ESC[4;38;5;81mcls:
511 pass
512
513 cls.foo = property()
514
515
516 if __name__ == '__main__':
517 unittest.main()