test_dynamicclassattribute.py 9.6 KB

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