a simple class with just one attribute wrap the given object into a lazyobject proxy methods don t exist on wrapped objects unless they re set same as testsetattr but in reversed order refs 21840 contains doesn t work when the haystack is a string and the needle a lazyobject tests whether an object s custom iter method is being used when iterating over it see ticket 16563 test copying lazy objects wrapping both builtin types and userdefined classes since a lot of the relevant code does dict manipulation and builtin types don t have dict copying a list works and returns the correct objects copying a list doesn t force evaluation copying a class works and returns the correct objects copying a class doesn t force evaluation deep copying a list works and returns the correct objects deep copying doesn t force evaluation deep copying a class works and returns the correct objects deep copying doesn t force evaluation by inheriting from lazyobjecttestcase and redefining the lazywrap method which all testcases use we get to make sure all behaviors tested in the parent testcase also apply to simplelazyobject first for an unevaluated simplelazyobject repr contains repr of setup function and does not evaluate the simplelazyobject see ticket 19456 see ticket 18447 a base class with a funky reduce method meant to simulate the reduce method of model which sets self djangoversion a class that inherits from basebaz and has its own reduceex method 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 grandparent super regression test for pickling a simplelazyobject wrapping a model 25389 also covers other classes with a custom reduce method test in a fairly synthetic setting test every pickle protocol available test on an actual model based on the report in 25426 test every pickle protocol available test both if we accessed a field on the model and if we didn t assert that there were no warnings a simple class with just one attribute wrap the given object into a lazyobject proxy methods don t exist on wrapped objects unless they re set same as test_setattr but in reversed order refs 21840 __contains__ doesn t work when the haystack is a string and the needle a lazyobject tests whether an object s custom __iter__ method is being used when iterating over it see ticket 16563 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__ copying a list works and returns the correct objects forces evaluation copying a list doesn t force evaluation copying a class works and returns the correct objects forces evaluation copying a class doesn t force evaluation deep copying a list works and returns the correct objects forces evaluation deep copying doesn t force evaluation deep copying a class works and returns the correct objects forces evaluation deep copying doesn t force evaluation 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 first for an unevaluated simplelazyobject __repr__ contains __repr__ of setup function and does not evaluate the simplelazyobject make sure evaluation hasn t been triggered evaluate the lazy object see ticket 19456 see ticket 18447 a base class with a funky __reduce__ method meant to simulate the __reduce__ method of model which sets self _django_version a class that inherits from basebaz and has its own __reduce_ex__ method 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 grandparent super regression test for pickling a simplelazyobject wrapping a model 25389 also covers other classes with a custom __reduce__ method test in a fairly synthetic setting test every pickle protocol available test on an actual model based on the report in 25426 test every pickle protocol available test both if we accessed a field on the model and if we didn t assert that there were no warnings
import copy
import pickle
import sys
import unittest
import warnings

from django.test import TestCase
from django.utils.functional import LazyObject, SimpleLazyObject, empty

from .models import Category, CategoryInfo


class Foo:
    

    foo = "bar"

    def __eq__(self, other):
        return self.foo == other.foo


class LazyObjectTestCase(unittest.TestCase):
    def lazy_wrap(self, wrapped_object):
        

        class AdHocLazyObject(LazyObject):
            def _setup(self):
                self._wrapped = wrapped_object

        return AdHocLazyObject()

    def test_getattribute(self):
        
        attrs = [
            "__getitem__",
            "__setitem__",
            "__delitem__",
            "__iter__",
            "__len__",
            "__contains__",
        ]
        foo = Foo()
        obj = self.lazy_wrap(foo)
        for attr in attrs:
            with self.subTest(attr):
                self.assertFalse(hasattr(obj, attr))
                setattr(foo, attr, attr)
                obj_with_attr = self.lazy_wrap(foo)
                self.assertTrue(hasattr(obj_with_attr, attr))
                self.assertEqual(getattr(obj_with_attr, attr), attr)

    def test_getattr(self):
        obj = self.lazy_wrap(Foo())
        self.assertEqual(obj.foo, "bar")

    def test_getattr_falsey(self):
        class Thing:
            def __getattr__(self, key):
                return []

        obj = self.lazy_wrap(Thing())
        self.assertEqual(obj.main, [])

    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):
        
        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):
        
        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))

        
        
        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):
        
        

        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):
        
        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)

    
    
    

    def test_copy_list(self):
        
        lst = [1, 2, 3]

        obj = self.lazy_wrap(lst)
        len(lst)  
        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):
        
        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):
        
        foo = Foo()

        obj = self.lazy_wrap(foo)
        str(foo)  
        obj2 = copy.copy(obj)

        self.assertIsNot(obj, obj2)
        self.assertIsInstance(obj2, Foo)
        self.assertEqual(obj2, Foo())

    def test_copy_class_no_evaluation(self):
        
        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):
        
        lst = [1, 2, 3]

        obj = self.lazy_wrap(lst)
        len(lst)  
        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):
        
        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):
        
        foo = Foo()

        obj = self.lazy_wrap(foo)
        str(foo)  
        obj2 = copy.deepcopy(obj)

        self.assertIsNot(obj, obj2)
        self.assertIsInstance(obj2, Foo)
        self.assertEqual(obj2, Foo())

    def test_deepcopy_class_no_evaluation(self):
        
        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):
    
    
    
    def lazy_wrap(self, wrapped_object):
        return SimpleLazyObject(lambda: wrapped_object)

    def test_repr(self):
        
        obj = self.lazy_wrap(42)
        
        
        self.assertRegex(repr(obj), "^<SimpleLazyObject:")
        self.assertIs(obj._wrapped, empty)  

        self.assertEqual(obj, 42)  
        self.assertIsInstance(obj._wrapped, int)
        self.assertEqual(repr(obj), "<SimpleLazyObject: 42>")

    def test_add(self):
        obj1 = self.lazy_wrap(1)
        self.assertEqual(obj1 + 1, 2)
        obj2 = self.lazy_wrap(2)
        self.assertEqual(obj2 + obj1, 3)
        self.assertEqual(obj1 + obj2, 3)

    def test_radd(self):
        obj1 = self.lazy_wrap(1)
        self.assertEqual(1 + obj1, 2)

    def test_trace(self):
        
        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):
        
        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:
    

    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):
    

    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):
    

    def __init__(self, baz):
        self.__dict__ = baz.__dict__
        self._baz = baz
        
        super(BaseBaz, self).__init__()


class SimpleLazyObjectPickleTestCase(TestCase):
    

    def test_pickle_with_reduce(self):
        
        
        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):
        
        category = Category.objects.create(name="thing1")
        CategoryInfo.objects.create(category=category)
        
        for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
            lazy_category = SimpleLazyObject(lambda: category)
            
            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
                )
                
                self.assertEqual(len(recorded), 0)