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
218# Issue 5890: subclasses of property do not preserve method __doc__ strings
219class PropertySub(property):
220    """This is a subclass of property"""
221
222class PropertySubSlots(property):
223    """This is a subclass of property that defines __slots__"""
224    __slots__ = ()
225
226class PropertySubclassTests(unittest.TestCase):
227
228    def test_slots_docstring_copy_exception(self):
229        try:
230            class Foo(object):
231                @PropertySubSlots
232                def spam(self):
233                    """Trying to copy this docstring will raise an exception"""
234                    return 1
235        except AttributeError:
236            pass
237        else:
238            raise Exception("AttributeError not raised")
239
240    @unittest.skipIf(sys.flags.optimize >= 2,
241                     "Docstrings are omitted with -O2 and above")
242    def test_docstring_copy(self):
243        class Foo(object):
244            @PropertySub
245            def spam(self):
246                """spam wrapped in property subclass"""
247                return 1
248        self.assertEqual(
249            Foo.spam.__doc__,
250            "spam wrapped in property subclass")
251
252    @unittest.skipIf(sys.flags.optimize >= 2,
253                     "Docstrings are omitted with -O2 and above")
254    def test_property_setter_copies_getter_docstring(self):
255        class Foo(object):
256            def __init__(self): self._spam = 1
257            @PropertySub
258            def spam(self):
259                """spam wrapped in property subclass"""
260                return self._spam
261            @spam.setter
262            def spam(self, value):
263                """this docstring is ignored"""
264                self._spam = value
265        foo = Foo()
266        self.assertEqual(foo.spam, 1)
267        foo.spam = 2
268        self.assertEqual(foo.spam, 2)
269        self.assertEqual(
270            Foo.spam.__doc__,
271            "spam wrapped in property subclass")
272        class FooSub(Foo):
273            @Foo.spam.setter
274            def spam(self, value):
275                """another ignored docstring"""
276                self._spam = 'eggs'
277        foosub = FooSub()
278        self.assertEqual(foosub.spam, 1)
279        foosub.spam = 7
280        self.assertEqual(foosub.spam, 'eggs')
281        self.assertEqual(
282            FooSub.spam.__doc__,
283            "spam wrapped in property subclass")
284
285    @unittest.skipIf(sys.flags.optimize >= 2,
286                     "Docstrings are omitted with -O2 and above")
287    def test_property_new_getter_new_docstring(self):
288
289        class Foo(object):
290            @PropertySub
291            def spam(self):
292                """a docstring"""
293                return 1
294            @spam.getter
295            def spam(self):
296                """a new docstring"""
297                return 2
298        self.assertEqual(Foo.spam.__doc__, "a new docstring")
299        class FooBase(object):
300            @PropertySub
301            def spam(self):
302                """a docstring"""
303                return 1
304        class Foo2(FooBase):
305            @FooBase.spam.getter
306            def spam(self):
307                """a new docstring"""
308                return 2
309        self.assertEqual(Foo.spam.__doc__, "a new docstring")
310
311
312class _PropertyUnreachableAttribute:
313    msg_format = None
314    obj = None
315    cls = None
316
317    def _format_exc_msg(self, msg):
318        return self.msg_format.format(msg)
319
320    @classmethod
321    def setUpClass(cls):
322        cls.obj = cls.cls()
323
324    def test_get_property(self):
325        with self.assertRaisesRegex(AttributeError, self._format_exc_msg("unreadable attribute")):
326            self.obj.foo
327
328    def test_set_property(self):
329        with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't set attribute")):
330            self.obj.foo = None
331
332    def test_del_property(self):
333        with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't delete attribute")):
334            del self.obj.foo
335
336
337class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase):
338    msg_format = "^{} 'foo'$"
339
340    class cls:
341        foo = property()
342
343
344class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase):
345    msg_format = "^{}$"
346
347    class cls:
348        pass
349
350    cls.foo = property()
351
352
353if __name__ == '__main__':
354    unittest.main()
355