blob: 9f694d9eb46771fb26eed5a695fafccdeb6dc3ec [file] [log] [blame]
Ethan Furman9efcb6b2013-10-13 10:52:10 -07001# Test case for DynamicClassAttribute
2# more tests are in test_descr
3
4import abc
5import sys
6import unittest
Ethan Furman9efcb6b2013-10-13 10:52:10 -07007from 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):
Serhiy Storchakaa60c2fe2015-03-12 21:56:08 +0200160 def __bool__(self):
Ethan Furman9efcb6b2013-10-13 10:52:10 -0700161 raise ValueError()
Serhiy Storchakaa60c2fe2015-03-12 21:56:08 +0200162 __len__ = __bool__
Ethan Furman9efcb6b2013-10-13 10:52:10 -0700163 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
Ethan Furman9efcb6b2013-10-13 10:52:10 -0700299if __name__ == '__main__':
Zachary Ware38c707e2015-04-13 15:00:43 -0500300 unittest.main()