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