test_functional.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import unittest
  2. from django.test import SimpleTestCase
  3. from django.utils.functional import cached_property, lazy
  4. from django.utils.version import PY36
  5. class FunctionalTests(SimpleTestCase):
  6. def test_lazy(self):
  7. t = lazy(lambda: tuple(range(3)), list, tuple)
  8. for a, b in zip(t(), range(3)):
  9. self.assertEqual(a, b)
  10. def test_lazy_base_class(self):
  11. """lazy also finds base class methods in the proxy object"""
  12. class Base:
  13. def base_method(self):
  14. pass
  15. class Klazz(Base):
  16. pass
  17. t = lazy(lambda: Klazz(), Klazz)()
  18. self.assertIn('base_method', dir(t))
  19. def test_lazy_base_class_override(self):
  20. """lazy finds the correct (overridden) method implementation"""
  21. class Base:
  22. def method(self):
  23. return 'Base'
  24. class Klazz(Base):
  25. def method(self):
  26. return 'Klazz'
  27. t = lazy(lambda: Klazz(), Base)()
  28. self.assertEqual(t.method(), 'Klazz')
  29. def test_lazy_object_to_string(self):
  30. class Klazz:
  31. def __str__(self):
  32. return "Î am ā Ǩlâzz."
  33. def __bytes__(self):
  34. return b"\xc3\x8e am \xc4\x81 binary \xc7\xa8l\xc3\xa2zz."
  35. t = lazy(lambda: Klazz(), Klazz)()
  36. self.assertEqual(str(t), "Î am ā Ǩlâzz.")
  37. self.assertEqual(bytes(t), b"\xc3\x8e am \xc4\x81 binary \xc7\xa8l\xc3\xa2zz.")
  38. def assertCachedPropertyWorks(self, attr, Class):
  39. with self.subTest(attr=attr):
  40. def get(source):
  41. return getattr(source, attr)
  42. obj = Class()
  43. class SubClass(Class):
  44. pass
  45. subobj = SubClass()
  46. # Docstring is preserved.
  47. self.assertEqual(get(Class).__doc__, 'Here is the docstring...')
  48. self.assertEqual(get(SubClass).__doc__, 'Here is the docstring...')
  49. # It's cached.
  50. self.assertEqual(get(obj), get(obj))
  51. self.assertEqual(get(subobj), get(subobj))
  52. # The correct value is returned.
  53. self.assertEqual(get(obj)[0], 1)
  54. self.assertEqual(get(subobj)[0], 1)
  55. # State isn't shared between instances.
  56. obj2 = Class()
  57. subobj2 = SubClass()
  58. self.assertNotEqual(get(obj), get(obj2))
  59. self.assertNotEqual(get(subobj), get(subobj2))
  60. # It behaves like a property when there's no instance.
  61. self.assertIsInstance(get(Class), cached_property)
  62. self.assertIsInstance(get(SubClass), cached_property)
  63. # 'other_value' doesn't become a property.
  64. self.assertTrue(callable(obj.other_value))
  65. self.assertTrue(callable(subobj.other_value))
  66. def test_cached_property(self):
  67. """cached_property caches its value and behaves like a property."""
  68. class Class:
  69. @cached_property
  70. def value(self):
  71. """Here is the docstring..."""
  72. return 1, object()
  73. @cached_property
  74. def __foo__(self):
  75. """Here is the docstring..."""
  76. return 1, object()
  77. def other_value(self):
  78. """Here is the docstring..."""
  79. return 1, object()
  80. other = cached_property(other_value, name='other')
  81. attrs = ['value', 'other', '__foo__']
  82. for attr in attrs:
  83. self.assertCachedPropertyWorks(attr, Class)
  84. @unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6')
  85. def test_cached_property_auto_name(self):
  86. """
  87. cached_property caches its value and behaves like a property
  88. on mangled methods or when the name kwarg isn't set.
  89. """
  90. class Class:
  91. @cached_property
  92. def __value(self):
  93. """Here is the docstring..."""
  94. return 1, object()
  95. def other_value(self):
  96. """Here is the docstring..."""
  97. return 1, object()
  98. other = cached_property(other_value)
  99. other2 = cached_property(other_value, name='different_name')
  100. attrs = ['_Class__value', 'other']
  101. for attr in attrs:
  102. self.assertCachedPropertyWorks(attr, Class)
  103. # An explicit name is ignored.
  104. obj = Class()
  105. obj.other2
  106. self.assertFalse(hasattr(obj, 'different_name'))
  107. @unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6')
  108. def test_cached_property_reuse_different_names(self):
  109. """Disallow this case because the decorated function wouldn't be cached."""
  110. with self.assertRaises(RuntimeError) as ctx:
  111. class ReusedCachedProperty:
  112. @cached_property
  113. def a(self):
  114. pass
  115. b = a
  116. self.assertEqual(
  117. str(ctx.exception.__context__),
  118. str(TypeError(
  119. "Cannot assign the same cached_property to two different "
  120. "names ('a' and 'b')."
  121. ))
  122. )
  123. @unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6')
  124. def test_cached_property_reuse_same_name(self):
  125. """
  126. Reusing a cached_property on different classes under the same name is
  127. allowed.
  128. """
  129. counter = 0
  130. @cached_property
  131. def _cp(_self):
  132. nonlocal counter
  133. counter += 1
  134. return counter
  135. class A:
  136. cp = _cp
  137. class B:
  138. cp = _cp
  139. a = A()
  140. b = B()
  141. self.assertEqual(a.cp, 1)
  142. self.assertEqual(b.cp, 2)
  143. self.assertEqual(a.cp, 1)
  144. @unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6')
  145. def test_cached_property_set_name_not_called(self):
  146. cp = cached_property(lambda s: None)
  147. class Foo:
  148. pass
  149. Foo.cp = cp
  150. msg = 'Cannot use cached_property instance without calling __set_name__() on it.'
  151. with self.assertRaisesMessage(TypeError, msg):
  152. Foo().cp
  153. @unittest.skipIf(PY36, '__set_name__ is new in Python 3.6')
  154. def test_cached_property_mangled_error(self):
  155. msg = (
  156. 'cached_property does not work with mangled methods on '
  157. 'Python < 3.6 without the appropriate `name` argument.'
  158. )
  159. with self.assertRaisesMessage(ValueError, msg):
  160. @cached_property
  161. def __value(self):
  162. pass
  163. with self.assertRaisesMessage(ValueError, msg):
  164. def func(self):
  165. pass
  166. cached_property(func, name='__value')
  167. @unittest.skipIf(PY36, '__set_name__ is new in Python 3.6')
  168. def test_cached_property_name_validation(self):
  169. msg = "%s can't be used as the name of a cached_property."
  170. with self.assertRaisesMessage(ValueError, msg % "'<lambda>'"):
  171. cached_property(lambda x: None)
  172. with self.assertRaisesMessage(ValueError, msg % 42):
  173. cached_property(str, name=42)
  174. def test_lazy_equality(self):
  175. """
  176. == and != work correctly for Promises.
  177. """
  178. lazy_a = lazy(lambda: 4, int)
  179. lazy_b = lazy(lambda: 4, int)
  180. lazy_c = lazy(lambda: 5, int)
  181. self.assertEqual(lazy_a(), lazy_b())
  182. self.assertNotEqual(lazy_b(), lazy_c())
  183. def test_lazy_repr_text(self):
  184. original_object = 'Lazy translation text'
  185. lazy_obj = lazy(lambda: original_object, str)
  186. self.assertEqual(repr(original_object), repr(lazy_obj()))
  187. def test_lazy_repr_int(self):
  188. original_object = 15
  189. lazy_obj = lazy(lambda: original_object, int)
  190. self.assertEqual(repr(original_object), repr(lazy_obj()))
  191. def test_lazy_repr_bytes(self):
  192. original_object = b'J\xc3\xbcst a str\xc3\xadng'
  193. lazy_obj = lazy(lambda: original_object, bytes)
  194. self.assertEqual(repr(original_object), repr(lazy_obj()))