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