Loading django/utils/functional.py +20 −0 Original line number Diff line number Diff line Loading @@ -249,6 +249,8 @@ class LazyObject(object): _wrapped = None def __init__(self): # Note: if a subclass overrides __init__(), it will likely need to # override __copy__() and __deepcopy__() as well. self._wrapped = empty __getattr__ = new_method_proxy(getattr) Loading Loading @@ -301,6 +303,15 @@ class LazyObject(object): def __getstate__(self): return {} def __copy__(self): if self._wrapped is empty: # If uninitialized, copy the wrapper. Use type(self), not # self.__class__, because the latter is proxied. return type(self)() else: # If initialized, return a copy of the wrapped object. return copy.copy(self._wrapped) def __deepcopy__(self, memo): if self._wrapped is empty: # We have to use type(self), not self.__class__, because the Loading Loading @@ -377,6 +388,15 @@ class SimpleLazyObject(LazyObject): repr_attr = self._wrapped return '<%s: %r>' % (type(self).__name__, repr_attr) def __copy__(self): if self._wrapped is empty: # If uninitialized, copy the wrapper. Use SimpleLazyObject, not # self.__class__, because the latter is proxied. return SimpleLazyObject(self._setupfunc) else: # If initialized, return a copy of the wrapped object. return copy.copy(self._wrapped) def __deepcopy__(self, memo): if self._wrapped is empty: # We have to use SimpleLazyObject, not self.__class__, because the Loading docs/releases/1.8.9.txt +3 −0 Original line number Diff line number Diff line Loading @@ -31,3 +31,6 @@ Bugfixes * Fixed a crash when using a reverse ``OneToOneField`` in ``ModelAdmin.readonly_fields`` (:ticket:`26060`). * Fixed a regression in Django 1.8.5 that broke copying a ``SimpleLazyObject`` with ``copy.copy()`` (:ticket:`26122`). docs/releases/1.9.2.txt +3 −0 Original line number Diff line number Diff line Loading @@ -82,3 +82,6 @@ Bugfixes origin from the node via ``Node.token.source[0]``. This was an undocumented, private API. The origin is now available directly on each node using the ``Node.origin`` attribute (:ticket:`25848`). * Fixed a regression in Django 1.8.5 that broke copying a ``SimpleLazyObject`` with ``copy.copy()`` (:ticket:`26122`). tests/utils_tests/test_lazyobject.py +78 −7 Original line number Diff line number Diff line Loading @@ -194,28 +194,99 @@ class LazyObjectTestCase(TestCase): self.assertEqual(unpickled, obj) self.assertEqual(unpickled.foo, obj.foo) def test_deepcopy(self): # Check that we *can* do deep copy, and that it returns the right # objects. # 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.deepcopy(obj) obj2 = copy.copy(obj) self.assertIsNot(obj, obj2) self.assertIsInstance(obj2, list) self.assertEqual(obj2, [1, 2, 3]) def test_deepcopy_no_evaluation(self): # copying doesn't force evaluation 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) # Copying shouldn't force evaluation 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) Loading Loading
django/utils/functional.py +20 −0 Original line number Diff line number Diff line Loading @@ -249,6 +249,8 @@ class LazyObject(object): _wrapped = None def __init__(self): # Note: if a subclass overrides __init__(), it will likely need to # override __copy__() and __deepcopy__() as well. self._wrapped = empty __getattr__ = new_method_proxy(getattr) Loading Loading @@ -301,6 +303,15 @@ class LazyObject(object): def __getstate__(self): return {} def __copy__(self): if self._wrapped is empty: # If uninitialized, copy the wrapper. Use type(self), not # self.__class__, because the latter is proxied. return type(self)() else: # If initialized, return a copy of the wrapped object. return copy.copy(self._wrapped) def __deepcopy__(self, memo): if self._wrapped is empty: # We have to use type(self), not self.__class__, because the Loading Loading @@ -377,6 +388,15 @@ class SimpleLazyObject(LazyObject): repr_attr = self._wrapped return '<%s: %r>' % (type(self).__name__, repr_attr) def __copy__(self): if self._wrapped is empty: # If uninitialized, copy the wrapper. Use SimpleLazyObject, not # self.__class__, because the latter is proxied. return SimpleLazyObject(self._setupfunc) else: # If initialized, return a copy of the wrapped object. return copy.copy(self._wrapped) def __deepcopy__(self, memo): if self._wrapped is empty: # We have to use SimpleLazyObject, not self.__class__, because the Loading
docs/releases/1.8.9.txt +3 −0 Original line number Diff line number Diff line Loading @@ -31,3 +31,6 @@ Bugfixes * Fixed a crash when using a reverse ``OneToOneField`` in ``ModelAdmin.readonly_fields`` (:ticket:`26060`). * Fixed a regression in Django 1.8.5 that broke copying a ``SimpleLazyObject`` with ``copy.copy()`` (:ticket:`26122`).
docs/releases/1.9.2.txt +3 −0 Original line number Diff line number Diff line Loading @@ -82,3 +82,6 @@ Bugfixes origin from the node via ``Node.token.source[0]``. This was an undocumented, private API. The origin is now available directly on each node using the ``Node.origin`` attribute (:ticket:`25848`). * Fixed a regression in Django 1.8.5 that broke copying a ``SimpleLazyObject`` with ``copy.copy()`` (:ticket:`26122`).
tests/utils_tests/test_lazyobject.py +78 −7 Original line number Diff line number Diff line Loading @@ -194,28 +194,99 @@ class LazyObjectTestCase(TestCase): self.assertEqual(unpickled, obj) self.assertEqual(unpickled.foo, obj.foo) def test_deepcopy(self): # Check that we *can* do deep copy, and that it returns the right # objects. # 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.deepcopy(obj) obj2 = copy.copy(obj) self.assertIsNot(obj, obj2) self.assertIsInstance(obj2, list) self.assertEqual(obj2, [1, 2, 3]) def test_deepcopy_no_evaluation(self): # copying doesn't force evaluation 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) # Copying shouldn't force evaluation 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) Loading