mirror of
				https://github.com/django/django.git
				synced 2025-11-04 13:39:16 +00:00 
			
		
		
		
	Shallow copying of `django.utils.functional.LazyObject` or its subclasses has
been broken in a couple of different ways in the past, most recently due to
35355a4.
		
	
			
		
			
				
	
	
		
			449 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			449 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from __future__ import unicode_literals
 | 
						|
 | 
						|
import copy
 | 
						|
import pickle
 | 
						|
import sys
 | 
						|
import warnings
 | 
						|
from unittest import TestCase
 | 
						|
 | 
						|
from django.utils import six
 | 
						|
from django.utils.functional import LazyObject, SimpleLazyObject, empty
 | 
						|
 | 
						|
from .models import Category, CategoryInfo
 | 
						|
 | 
						|
 | 
						|
class Foo(object):
 | 
						|
    """
 | 
						|
    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_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(six.text_type(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 = {}
 | 
						|
        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(object):
 | 
						|
 | 
						|
            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())
 | 
						|
        pickled = pickle.dumps(obj)
 | 
						|
        unpickled = pickle.loads(pickled)
 | 
						|
        self.assertIsInstance(unpickled, Foo)
 | 
						|
        self.assertEqual(unpickled, obj)
 | 
						|
        self.assertEqual(unpickled.foo, obj.foo)
 | 
						|
 | 
						|
    # 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.
 | 
						|
        l = [1, 2, 3]
 | 
						|
 | 
						|
        obj = self.lazy_wrap(l)
 | 
						|
        len(l)  # 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.
 | 
						|
        l = [1, 2, 3]
 | 
						|
 | 
						|
        obj = self.lazy_wrap(l)
 | 
						|
        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.
 | 
						|
        l = [1, 2, 3]
 | 
						|
 | 
						|
        obj = self.lazy_wrap(l)
 | 
						|
        len(l)  # 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.
 | 
						|
        l = [1, 2, 3]
 | 
						|
 | 
						|
        obj = self.lazy_wrap(l)
 | 
						|
        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
 | 
						|
        six.assertRegex(self, 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(object):
 | 
						|
    """
 | 
						|
    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(BaseBaz, self).__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(Baz, self).__init__()
 | 
						|
 | 
						|
    def __reduce_ex__(self, proto):
 | 
						|
        self.quux = 'quux'
 | 
						|
        return super(Baz, self).__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
 | 
						|
        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)
 |