blob: bc6a39bb3762fd6e401b0fba9dcd035316e2dfdb [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
7from test.support import run_unittest
8from types import DynamicClassAttribute
9
10class PropertyBase(Exception):
11 pass
12
13class PropertyGet(PropertyBase):
14 pass
15
16class PropertySet(PropertyBase):
17 pass
18
19class PropertyDel(PropertyBase):
20 pass
21
22class BaseClass(object):
23 def __init__(self):
24 self._spam = 5
25
26 @DynamicClassAttribute
27 def spam(self):
28 """BaseClass.getter"""
29 return self._spam
30
31 @spam.setter
32 def spam(self, value):
33 self._spam = value
34
35 @spam.deleter
36 def spam(self):
37 del self._spam
38
39class SubClass(BaseClass):
40
41 spam = BaseClass.__dict__['spam']
42
43 @spam.getter
44 def spam(self):
45 """SubClass.getter"""
46 raise PropertyGet(self._spam)
47
48 @spam.setter
49 def spam(self, value):
50 raise PropertySet(self._spam)
51
52 @spam.deleter
53 def spam(self):
54 raise PropertyDel(self._spam)
55
56class PropertyDocBase(object):
57 _spam = 1
58 def _get_spam(self):
59 return self._spam
60 spam = DynamicClassAttribute(_get_spam, doc="spam spam spam")
61
62class PropertyDocSub(PropertyDocBase):
63 spam = PropertyDocBase.__dict__['spam']
64 @spam.getter
65 def spam(self):
66 """The decorator does not use this doc string"""
67 return self._spam
68
69class PropertySubNewGetter(BaseClass):
70 spam = BaseClass.__dict__['spam']
71 @spam.getter
72 def spam(self):
73 """new docstring"""
74 return 5
75
76class PropertyNewGetter(object):
77 @DynamicClassAttribute
78 def spam(self):
79 """original docstring"""
80 return 1
81 @spam.getter
82 def spam(self):
83 """new docstring"""
84 return 8
85
86class ClassWithAbstractVirtualProperty(metaclass=abc.ABCMeta):
87 @DynamicClassAttribute
88 @abc.abstractmethod
89 def color():
90 pass
91
92class ClassWithPropertyAbstractVirtual(metaclass=abc.ABCMeta):
93 @abc.abstractmethod
94 @DynamicClassAttribute
95 def color():
96 pass
97
98class PropertyTests(unittest.TestCase):
99 def test_property_decorator_baseclass(self):
100 # see #1620
101 base = BaseClass()
102 self.assertEqual(base.spam, 5)
103 self.assertEqual(base._spam, 5)
104 base.spam = 10
105 self.assertEqual(base.spam, 10)
106 self.assertEqual(base._spam, 10)
107 delattr(base, "spam")
108 self.assertTrue(not hasattr(base, "spam"))
109 self.assertTrue(not hasattr(base, "_spam"))
110 base.spam = 20
111 self.assertEqual(base.spam, 20)
112 self.assertEqual(base._spam, 20)
113
114 def test_property_decorator_subclass(self):
115 # see #1620
116 sub = SubClass()
117 self.assertRaises(PropertyGet, getattr, sub, "spam")
118 self.assertRaises(PropertySet, setattr, sub, "spam", None)
119 self.assertRaises(PropertyDel, delattr, sub, "spam")
120
121 @unittest.skipIf(sys.flags.optimize >= 2,
122 "Docstrings are omitted with -O2 and above")
123 def test_property_decorator_subclass_doc(self):
124 sub = SubClass()
125 self.assertEqual(sub.__class__.__dict__['spam'].__doc__, "SubClass.getter")
126
127 @unittest.skipIf(sys.flags.optimize >= 2,
128 "Docstrings are omitted with -O2 and above")
129 def test_property_decorator_baseclass_doc(self):
130 base = BaseClass()
131 self.assertEqual(base.__class__.__dict__['spam'].__doc__, "BaseClass.getter")
132
133 def test_property_decorator_doc(self):
134 base = PropertyDocBase()
135 sub = PropertyDocSub()
136 self.assertEqual(base.__class__.__dict__['spam'].__doc__, "spam spam spam")
137 self.assertEqual(sub.__class__.__dict__['spam'].__doc__, "spam spam spam")
138
139 @unittest.skipIf(sys.flags.optimize >= 2,
140 "Docstrings are omitted with -O2 and above")
141 def test_property_getter_doc_override(self):
142 newgettersub = PropertySubNewGetter()
143 self.assertEqual(newgettersub.spam, 5)
144 self.assertEqual(newgettersub.__class__.__dict__['spam'].__doc__, "new docstring")
145 newgetter = PropertyNewGetter()
146 self.assertEqual(newgetter.spam, 8)
147 self.assertEqual(newgetter.__class__.__dict__['spam'].__doc__, "new docstring")
148
149 def test_property___isabstractmethod__descriptor(self):
150 for val in (True, False, [], [1], '', '1'):
151 class C(object):
152 def foo(self):
153 pass
154 foo.__isabstractmethod__ = val
155 foo = DynamicClassAttribute(foo)
156 self.assertIs(C.__dict__['foo'].__isabstractmethod__, bool(val))
157
158 # check that the DynamicClassAttribute's __isabstractmethod__ descriptor does the
159 # right thing when presented with a value that fails truth testing:
160 class NotBool(object):
Serhiy Storchakaa60c2fe2015-03-12 21:56:08 +0200161 def __bool__(self):
Ethan Furman9efcb6b2013-10-13 10:52:10 -0700162 raise ValueError()
Serhiy Storchakaa60c2fe2015-03-12 21:56:08 +0200163 __len__ = __bool__
Ethan Furman9efcb6b2013-10-13 10:52:10 -0700164 with self.assertRaises(ValueError):
165 class C(object):
166 def foo(self):
167 pass
168 foo.__isabstractmethod__ = NotBool()
169 foo = DynamicClassAttribute(foo)
170
171 def test_abstract_virtual(self):
172 self.assertRaises(TypeError, ClassWithAbstractVirtualProperty)
173 self.assertRaises(TypeError, ClassWithPropertyAbstractVirtual)
174 class APV(ClassWithPropertyAbstractVirtual):
175 pass
176 self.assertRaises(TypeError, APV)
177 class AVP(ClassWithAbstractVirtualProperty):
178 pass
179 self.assertRaises(TypeError, AVP)
180 class Okay1(ClassWithAbstractVirtualProperty):
181 @DynamicClassAttribute
182 def color(self):
183 return self._color
184 def __init__(self):
185 self._color = 'cyan'
186 with self.assertRaises(AttributeError):
187 Okay1.color
188 self.assertEqual(Okay1().color, 'cyan')
189 class Okay2(ClassWithAbstractVirtualProperty):
190 @DynamicClassAttribute
191 def color(self):
192 return self._color
193 def __init__(self):
194 self._color = 'magenta'
195 with self.assertRaises(AttributeError):
196 Okay2.color
197 self.assertEqual(Okay2().color, 'magenta')
198
199
200# Issue 5890: subclasses of DynamicClassAttribute do not preserve method __doc__ strings
201class PropertySub(DynamicClassAttribute):
202 """This is a subclass of DynamicClassAttribute"""
203
204class PropertySubSlots(DynamicClassAttribute):
205 """This is a subclass of DynamicClassAttribute that defines __slots__"""
206 __slots__ = ()
207
208class PropertySubclassTests(unittest.TestCase):
209
210 @unittest.skipIf(hasattr(PropertySubSlots, '__doc__'),
211 "__doc__ is already present, __slots__ will have no effect")
212 def test_slots_docstring_copy_exception(self):
213 try:
214 class Foo(object):
215 @PropertySubSlots
216 def spam(self):
217 """Trying to copy this docstring will raise an exception"""
218 return 1
219 print('\n',spam.__doc__)
220 except AttributeError:
221 pass
222 else:
223 raise Exception("AttributeError not raised")
224
225 @unittest.skipIf(sys.flags.optimize >= 2,
226 "Docstrings are omitted with -O2 and above")
227 def test_docstring_copy(self):
228 class Foo(object):
229 @PropertySub
230 def spam(self):
231 """spam wrapped in DynamicClassAttribute subclass"""
232 return 1
233 self.assertEqual(
234 Foo.__dict__['spam'].__doc__,
235 "spam wrapped in DynamicClassAttribute subclass")
236
237 @unittest.skipIf(sys.flags.optimize >= 2,
238 "Docstrings are omitted with -O2 and above")
239 def test_property_setter_copies_getter_docstring(self):
240 class Foo(object):
241 def __init__(self): self._spam = 1
242 @PropertySub
243 def spam(self):
244 """spam wrapped in DynamicClassAttribute subclass"""
245 return self._spam
246 @spam.setter
247 def spam(self, value):
248 """this docstring is ignored"""
249 self._spam = value
250 foo = Foo()
251 self.assertEqual(foo.spam, 1)
252 foo.spam = 2
253 self.assertEqual(foo.spam, 2)
254 self.assertEqual(
255 Foo.__dict__['spam'].__doc__,
256 "spam wrapped in DynamicClassAttribute subclass")
257 class FooSub(Foo):
258 spam = Foo.__dict__['spam']
259 @spam.setter
260 def spam(self, value):
261 """another ignored docstring"""
262 self._spam = 'eggs'
263 foosub = FooSub()
264 self.assertEqual(foosub.spam, 1)
265 foosub.spam = 7
266 self.assertEqual(foosub.spam, 'eggs')
267 self.assertEqual(
268 FooSub.__dict__['spam'].__doc__,
269 "spam wrapped in DynamicClassAttribute subclass")
270
271 @unittest.skipIf(sys.flags.optimize >= 2,
272 "Docstrings are omitted with -O2 and above")
273 def test_property_new_getter_new_docstring(self):
274
275 class Foo(object):
276 @PropertySub
277 def spam(self):
278 """a docstring"""
279 return 1
280 @spam.getter
281 def spam(self):
282 """a new docstring"""
283 return 2
284 self.assertEqual(Foo.__dict__['spam'].__doc__, "a new docstring")
285 class FooBase(object):
286 @PropertySub
287 def spam(self):
288 """a docstring"""
289 return 1
290 class Foo2(FooBase):
291 spam = FooBase.__dict__['spam']
292 @spam.getter
293 def spam(self):
294 """a new docstring"""
295 return 2
296 self.assertEqual(Foo.__dict__['spam'].__doc__, "a new docstring")
297
298
299
300def test_main():
301 run_unittest(PropertyTests, PropertySubclassTests)
302
303if __name__ == '__main__':
304 test_main()