mirror of
				https://github.com/django/django.git
				synced 2025-10-27 23:56:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			459 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			459 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import copy
 | |
| import pickle
 | |
| import sys
 | |
| import warnings
 | |
| from unittest import TestCase
 | |
| 
 | |
| from django.utils.functional import LazyObject, SimpleLazyObject, empty
 | |
| 
 | |
| from .models import Category, CategoryInfo
 | |
| 
 | |
| 
 | |
| class Foo:
 | |
|     """
 | |
|     A simple class with just one attribute.
 | |
|     """
 | |
|     foo = 'bar'
 | |
| 
 | |
|     def __eq__(self, other):
 | |
|         return self.foo == other.foo
 | |
| 
 | |
| 
 | |
| class LazyObjectTestCase(TestCase):
 | |
|     def lazy_wrap(self, wrapped_object):
 | |
|         """
 | |
|         Wrap the given object into a LazyObject
 | |
|         """
 | |
|         class AdHocLazyObject(LazyObject):
 | |
|             def _setup(self):
 | |
|                 self._wrapped = wrapped_object
 | |
| 
 | |
|         return AdHocLazyObject()
 | |
| 
 | |
|     def test_getattr(self):
 | |
|         obj = self.lazy_wrap(Foo())
 | |
|         self.assertEqual(obj.foo, 'bar')
 | |
| 
 | |
|     def test_setattr(self):
 | |
|         obj = self.lazy_wrap(Foo())
 | |
|         obj.foo = 'BAR'
 | |
|         obj.bar = 'baz'
 | |
|         self.assertEqual(obj.foo, 'BAR')
 | |
|         self.assertEqual(obj.bar, 'baz')
 | |
| 
 | |
|     def test_setattr2(self):
 | |
|         # Same as test_setattr but in reversed order
 | |
|         obj = self.lazy_wrap(Foo())
 | |
|         obj.bar = 'baz'
 | |
|         obj.foo = 'BAR'
 | |
|         self.assertEqual(obj.foo, 'BAR')
 | |
|         self.assertEqual(obj.bar, 'baz')
 | |
| 
 | |
|     def test_delattr(self):
 | |
|         obj = self.lazy_wrap(Foo())
 | |
|         obj.bar = 'baz'
 | |
|         self.assertEqual(obj.bar, 'baz')
 | |
|         del obj.bar
 | |
|         with self.assertRaises(AttributeError):
 | |
|             obj.bar
 | |
| 
 | |
|     def test_cmp(self):
 | |
|         obj1 = self.lazy_wrap('foo')
 | |
|         obj2 = self.lazy_wrap('bar')
 | |
|         obj3 = self.lazy_wrap('foo')
 | |
|         self.assertEqual(obj1, 'foo')
 | |
|         self.assertEqual(obj1, obj3)
 | |
|         self.assertNotEqual(obj1, obj2)
 | |
|         self.assertNotEqual(obj1, 'bar')
 | |
| 
 | |
|     def test_lt(self):
 | |
|         obj1 = self.lazy_wrap(1)
 | |
|         obj2 = self.lazy_wrap(2)
 | |
|         self.assertLess(obj1, obj2)
 | |
| 
 | |
|     def test_gt(self):
 | |
|         obj1 = self.lazy_wrap(1)
 | |
|         obj2 = self.lazy_wrap(2)
 | |
|         self.assertGreater(obj2, obj1)
 | |
| 
 | |
|     def test_bytes(self):
 | |
|         obj = self.lazy_wrap(b'foo')
 | |
|         self.assertEqual(bytes(obj), b'foo')
 | |
| 
 | |
|     def test_text(self):
 | |
|         obj = self.lazy_wrap('foo')
 | |
|         self.assertEqual(str(obj), 'foo')
 | |
| 
 | |
|     def test_bool(self):
 | |
|         # Refs #21840
 | |
|         for f in [False, 0, (), {}, [], None, set()]:
 | |
|             self.assertFalse(self.lazy_wrap(f))
 | |
|         for t in [True, 1, (1,), {1: 2}, [1], object(), {1}]:
 | |
|             self.assertTrue(t)
 | |
| 
 | |
|     def test_dir(self):
 | |
|         obj = self.lazy_wrap('foo')
 | |
|         self.assertEqual(dir(obj), dir('foo'))
 | |
| 
 | |
|     def test_len(self):
 | |
|         for seq in ['asd', [1, 2, 3], {'a': 1, 'b': 2, 'c': 3}]:
 | |
|             obj = self.lazy_wrap(seq)
 | |
|             self.assertEqual(len(obj), 3)
 | |
| 
 | |
|     def test_class(self):
 | |
|         self.assertIsInstance(self.lazy_wrap(42), int)
 | |
| 
 | |
|         class Bar(Foo):
 | |
|             pass
 | |
| 
 | |
|         self.assertIsInstance(self.lazy_wrap(Bar()), Foo)
 | |
| 
 | |
|     def test_hash(self):
 | |
|         obj = self.lazy_wrap('foo')
 | |
|         d = {obj: 'bar'}
 | |
|         self.assertIn('foo', d)
 | |
|         self.assertEqual(d['foo'], 'bar')
 | |
| 
 | |
|     def test_contains(self):
 | |
|         test_data = [
 | |
|             ('c', 'abcde'),
 | |
|             (2, [1, 2, 3]),
 | |
|             ('a', {'a': 1, 'b': 2, 'c': 3}),
 | |
|             (2, {1, 2, 3}),
 | |
|         ]
 | |
|         for needle, haystack in test_data:
 | |
|             self.assertIn(needle, self.lazy_wrap(haystack))
 | |
| 
 | |
|         # __contains__ doesn't work when the haystack is a string and the needle a LazyObject
 | |
|         for needle_haystack in test_data[1:]:
 | |
|             self.assertIn(self.lazy_wrap(needle), haystack)
 | |
|             self.assertIn(self.lazy_wrap(needle), self.lazy_wrap(haystack))
 | |
| 
 | |
|     def test_getitem(self):
 | |
|         obj_list = self.lazy_wrap([1, 2, 3])
 | |
|         obj_dict = self.lazy_wrap({'a': 1, 'b': 2, 'c': 3})
 | |
| 
 | |
|         self.assertEqual(obj_list[0], 1)
 | |
|         self.assertEqual(obj_list[-1], 3)
 | |
|         self.assertEqual(obj_list[1:2], [2])
 | |
| 
 | |
|         self.assertEqual(obj_dict['b'], 2)
 | |
| 
 | |
|         with self.assertRaises(IndexError):
 | |
|             obj_list[3]
 | |
| 
 | |
|         with self.assertRaises(KeyError):
 | |
|             obj_dict['f']
 | |
| 
 | |
|     def test_setitem(self):
 | |
|         obj_list = self.lazy_wrap([1, 2, 3])
 | |
|         obj_dict = self.lazy_wrap({'a': 1, 'b': 2, 'c': 3})
 | |
| 
 | |
|         obj_list[0] = 100
 | |
|         self.assertEqual(obj_list, [100, 2, 3])
 | |
|         obj_list[1:2] = [200, 300, 400]
 | |
|         self.assertEqual(obj_list, [100, 200, 300, 400, 3])
 | |
| 
 | |
|         obj_dict['a'] = 100
 | |
|         obj_dict['d'] = 400
 | |
|         self.assertEqual(obj_dict, {'a': 100, 'b': 2, 'c': 3, 'd': 400})
 | |
| 
 | |
|     def test_delitem(self):
 | |
|         obj_list = self.lazy_wrap([1, 2, 3])
 | |
|         obj_dict = self.lazy_wrap({'a': 1, 'b': 2, 'c': 3})
 | |
| 
 | |
|         del obj_list[-1]
 | |
|         del obj_dict['c']
 | |
|         self.assertEqual(obj_list, [1, 2])
 | |
|         self.assertEqual(obj_dict, {'a': 1, 'b': 2})
 | |
| 
 | |
|         with self.assertRaises(IndexError):
 | |
|             del obj_list[3]
 | |
| 
 | |
|         with self.assertRaises(KeyError):
 | |
|             del obj_dict['f']
 | |
| 
 | |
|     def test_iter(self):
 | |
|         # Tests whether an object's custom `__iter__` method is being
 | |
|         # used when iterating over it.
 | |
| 
 | |
|         class IterObject:
 | |
| 
 | |
|             def __init__(self, values):
 | |
|                 self.values = values
 | |
| 
 | |
|             def __iter__(self):
 | |
|                 return iter(self.values)
 | |
| 
 | |
|         original_list = ['test', '123']
 | |
|         self.assertEqual(
 | |
|             list(self.lazy_wrap(IterObject(original_list))),
 | |
|             original_list
 | |
|         )
 | |
| 
 | |
|     def test_pickle(self):
 | |
|         # See ticket #16563
 | |
|         obj = self.lazy_wrap(Foo())
 | |
|         obj.bar = 'baz'
 | |
|         pickled = pickle.dumps(obj)
 | |
|         unpickled = pickle.loads(pickled)
 | |
|         self.assertIsInstance(unpickled, Foo)
 | |
|         self.assertEqual(unpickled, obj)
 | |
|         self.assertEqual(unpickled.foo, obj.foo)
 | |
|         self.assertEqual(unpickled.bar, obj.bar)
 | |
| 
 | |
|     # Test copying lazy objects wrapping both builtin types and user-defined
 | |
|     # classes since a lot of the relevant code does __dict__ manipulation and
 | |
|     # builtin types don't have __dict__.
 | |
| 
 | |
|     def test_copy_list(self):
 | |
|         # Copying a list works and returns the correct objects.
 | |
|         lst = [1, 2, 3]
 | |
| 
 | |
|         obj = self.lazy_wrap(lst)
 | |
|         len(lst)  # forces evaluation
 | |
|         obj2 = copy.copy(obj)
 | |
| 
 | |
|         self.assertIsNot(obj, obj2)
 | |
|         self.assertIsInstance(obj2, list)
 | |
|         self.assertEqual(obj2, [1, 2, 3])
 | |
| 
 | |
|     def test_copy_list_no_evaluation(self):
 | |
|         # Copying a list doesn't force evaluation.
 | |
|         lst = [1, 2, 3]
 | |
| 
 | |
|         obj = self.lazy_wrap(lst)
 | |
|         obj2 = copy.copy(obj)
 | |
| 
 | |
|         self.assertIsNot(obj, obj2)
 | |
|         self.assertIs(obj._wrapped, empty)
 | |
|         self.assertIs(obj2._wrapped, empty)
 | |
| 
 | |
|     def test_copy_class(self):
 | |
|         # Copying a class works and returns the correct objects.
 | |
|         foo = Foo()
 | |
| 
 | |
|         obj = self.lazy_wrap(foo)
 | |
|         str(foo)  # forces evaluation
 | |
|         obj2 = copy.copy(obj)
 | |
| 
 | |
|         self.assertIsNot(obj, obj2)
 | |
|         self.assertIsInstance(obj2, Foo)
 | |
|         self.assertEqual(obj2, Foo())
 | |
| 
 | |
|     def test_copy_class_no_evaluation(self):
 | |
|         # Copying a class doesn't force evaluation.
 | |
|         foo = Foo()
 | |
| 
 | |
|         obj = self.lazy_wrap(foo)
 | |
|         obj2 = copy.copy(obj)
 | |
| 
 | |
|         self.assertIsNot(obj, obj2)
 | |
|         self.assertIs(obj._wrapped, empty)
 | |
|         self.assertIs(obj2._wrapped, empty)
 | |
| 
 | |
|     def test_deepcopy_list(self):
 | |
|         # Deep copying a list works and returns the correct objects.
 | |
|         lst = [1, 2, 3]
 | |
| 
 | |
|         obj = self.lazy_wrap(lst)
 | |
|         len(lst)  # forces evaluation
 | |
|         obj2 = copy.deepcopy(obj)
 | |
| 
 | |
|         self.assertIsNot(obj, obj2)
 | |
|         self.assertIsInstance(obj2, list)
 | |
|         self.assertEqual(obj2, [1, 2, 3])
 | |
| 
 | |
|     def test_deepcopy_list_no_evaluation(self):
 | |
|         # Deep copying doesn't force evaluation.
 | |
|         lst = [1, 2, 3]
 | |
| 
 | |
|         obj = self.lazy_wrap(lst)
 | |
|         obj2 = copy.deepcopy(obj)
 | |
| 
 | |
|         self.assertIsNot(obj, obj2)
 | |
|         self.assertIs(obj._wrapped, empty)
 | |
|         self.assertIs(obj2._wrapped, empty)
 | |
| 
 | |
|     def test_deepcopy_class(self):
 | |
|         # Deep copying a class works and returns the correct objects.
 | |
|         foo = Foo()
 | |
| 
 | |
|         obj = self.lazy_wrap(foo)
 | |
|         str(foo)  # forces evaluation
 | |
|         obj2 = copy.deepcopy(obj)
 | |
| 
 | |
|         self.assertIsNot(obj, obj2)
 | |
|         self.assertIsInstance(obj2, Foo)
 | |
|         self.assertEqual(obj2, Foo())
 | |
| 
 | |
|     def test_deepcopy_class_no_evaluation(self):
 | |
|         # Deep copying doesn't force evaluation.
 | |
|         foo = Foo()
 | |
| 
 | |
|         obj = self.lazy_wrap(foo)
 | |
|         obj2 = copy.deepcopy(obj)
 | |
| 
 | |
|         self.assertIsNot(obj, obj2)
 | |
|         self.assertIs(obj._wrapped, empty)
 | |
|         self.assertIs(obj2._wrapped, empty)
 | |
| 
 | |
| 
 | |
| class SimpleLazyObjectTestCase(LazyObjectTestCase):
 | |
|     # By inheriting from LazyObjectTestCase and redefining the lazy_wrap()
 | |
|     # method which all testcases use, we get to make sure all behaviors
 | |
|     # tested in the parent testcase also apply to SimpleLazyObject.
 | |
|     def lazy_wrap(self, wrapped_object):
 | |
|         return SimpleLazyObject(lambda: wrapped_object)
 | |
| 
 | |
|     def test_repr(self):
 | |
|         # First, for an unevaluated SimpleLazyObject
 | |
|         obj = self.lazy_wrap(42)
 | |
|         # __repr__ contains __repr__ of setup function and does not evaluate
 | |
|         # the SimpleLazyObject
 | |
|         self.assertRegex(repr(obj), '^<SimpleLazyObject:')
 | |
|         self.assertIs(obj._wrapped, empty)  # make sure evaluation hasn't been triggered
 | |
| 
 | |
|         self.assertEqual(obj, 42)  # evaluate the lazy object
 | |
|         self.assertIsInstance(obj._wrapped, int)
 | |
|         self.assertEqual(repr(obj), '<SimpleLazyObject: 42>')
 | |
| 
 | |
|     def test_trace(self):
 | |
|         # See ticket #19456
 | |
|         old_trace_func = sys.gettrace()
 | |
|         try:
 | |
|             def trace_func(frame, event, arg):
 | |
|                 frame.f_locals['self'].__class__
 | |
|                 if old_trace_func is not None:
 | |
|                     old_trace_func(frame, event, arg)
 | |
|             sys.settrace(trace_func)
 | |
|             self.lazy_wrap(None)
 | |
|         finally:
 | |
|             sys.settrace(old_trace_func)
 | |
| 
 | |
|     def test_none(self):
 | |
|         i = [0]
 | |
| 
 | |
|         def f():
 | |
|             i[0] += 1
 | |
|             return None
 | |
| 
 | |
|         x = SimpleLazyObject(f)
 | |
|         self.assertEqual(str(x), "None")
 | |
|         self.assertEqual(i, [1])
 | |
|         self.assertEqual(str(x), "None")
 | |
|         self.assertEqual(i, [1])
 | |
| 
 | |
|     def test_dict(self):
 | |
|         # See ticket #18447
 | |
|         lazydict = SimpleLazyObject(lambda: {'one': 1})
 | |
|         self.assertEqual(lazydict['one'], 1)
 | |
|         lazydict['one'] = -1
 | |
|         self.assertEqual(lazydict['one'], -1)
 | |
|         self.assertIn('one', lazydict)
 | |
|         self.assertNotIn('two', lazydict)
 | |
|         self.assertEqual(len(lazydict), 1)
 | |
|         del lazydict['one']
 | |
|         with self.assertRaises(KeyError):
 | |
|             lazydict['one']
 | |
| 
 | |
|     def test_list_set(self):
 | |
|         lazy_list = SimpleLazyObject(lambda: [1, 2, 3, 4, 5])
 | |
|         lazy_set = SimpleLazyObject(lambda: {1, 2, 3, 4})
 | |
|         self.assertIn(1, lazy_list)
 | |
|         self.assertIn(1, lazy_set)
 | |
|         self.assertNotIn(6, lazy_list)
 | |
|         self.assertNotIn(6, lazy_set)
 | |
|         self.assertEqual(len(lazy_list), 5)
 | |
|         self.assertEqual(len(lazy_set), 4)
 | |
| 
 | |
| 
 | |
| class BaseBaz:
 | |
|     """
 | |
|     A base class with a funky __reduce__ method, meant to simulate the
 | |
|     __reduce__ method of Model, which sets self._django_version.
 | |
|     """
 | |
|     def __init__(self):
 | |
|         self.baz = 'wrong'
 | |
| 
 | |
|     def __reduce__(self):
 | |
|         self.baz = 'right'
 | |
|         return super().__reduce__()
 | |
| 
 | |
|     def __eq__(self, other):
 | |
|         if self.__class__ != other.__class__:
 | |
|             return False
 | |
|         for attr in ['bar', 'baz', 'quux']:
 | |
|             if hasattr(self, attr) != hasattr(other, attr):
 | |
|                 return False
 | |
|             elif getattr(self, attr, None) != getattr(other, attr, None):
 | |
|                 return False
 | |
|         return True
 | |
| 
 | |
| 
 | |
| class Baz(BaseBaz):
 | |
|     """
 | |
|     A class that inherits from BaseBaz and has its own __reduce_ex__ method.
 | |
|     """
 | |
|     def __init__(self, bar):
 | |
|         self.bar = bar
 | |
|         super().__init__()
 | |
| 
 | |
|     def __reduce_ex__(self, proto):
 | |
|         self.quux = 'quux'
 | |
|         return super().__reduce_ex__(proto)
 | |
| 
 | |
| 
 | |
| class BazProxy(Baz):
 | |
|     """
 | |
|     A class that acts as a proxy for Baz. It does some scary mucking about with
 | |
|     dicts, which simulates some crazy things that people might do with
 | |
|     e.g. proxy models.
 | |
|     """
 | |
|     def __init__(self, baz):
 | |
|         self.__dict__ = baz.__dict__
 | |
|         self._baz = baz
 | |
|         # Grandparent super
 | |
|         super(BaseBaz, self).__init__()
 | |
| 
 | |
| 
 | |
| class SimpleLazyObjectPickleTestCase(TestCase):
 | |
|     """
 | |
|     Regression test for pickling a SimpleLazyObject wrapping a model (#25389).
 | |
|     Also covers other classes with a custom __reduce__ method.
 | |
|     """
 | |
|     def test_pickle_with_reduce(self):
 | |
|         """
 | |
|         Test in a fairly synthetic setting.
 | |
|         """
 | |
|         # Test every pickle protocol available
 | |
|         for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
 | |
|             lazy_objs = [
 | |
|                 SimpleLazyObject(lambda: BaseBaz()),
 | |
|                 SimpleLazyObject(lambda: Baz(1)),
 | |
|                 SimpleLazyObject(lambda: BazProxy(Baz(2))),
 | |
|             ]
 | |
|             for obj in lazy_objs:
 | |
|                 pickled = pickle.dumps(obj, protocol)
 | |
|                 unpickled = pickle.loads(pickled)
 | |
|                 self.assertEqual(unpickled, obj)
 | |
|                 self.assertEqual(unpickled.baz, 'right')
 | |
| 
 | |
|     def test_pickle_model(self):
 | |
|         """
 | |
|         Test on an actual model, based on the report in #25426.
 | |
|         """
 | |
|         category = Category.objects.create(name="thing1")
 | |
|         CategoryInfo.objects.create(category=category)
 | |
|         # Test every pickle protocol available
 | |
|         for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
 | |
|             lazy_category = SimpleLazyObject(lambda: category)
 | |
|             # Test both if we accessed a field on the model and if we didn't.
 | |
|             lazy_category.categoryinfo
 | |
|             lazy_category_2 = SimpleLazyObject(lambda: category)
 | |
|             with warnings.catch_warnings(record=True) as recorded:
 | |
|                 self.assertEqual(pickle.loads(pickle.dumps(lazy_category, protocol)), category)
 | |
|                 self.assertEqual(pickle.loads(pickle.dumps(lazy_category_2, protocol)), category)
 | |
|                 # Assert that there were no warnings.
 | |
|                 self.assertEqual(len(recorded), 0)
 |