Fixed #26122 -- Fixed copying a LazyObject
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
.
This commit is contained in:
parent
cfda1fa3f8
commit
13023ba867
|
@ -249,6 +249,8 @@ class LazyObject(object):
|
||||||
_wrapped = None
|
_wrapped = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
# Note: if a subclass overrides __init__(), it will likely need to
|
||||||
|
# override __copy__() and __deepcopy__() as well.
|
||||||
self._wrapped = empty
|
self._wrapped = empty
|
||||||
|
|
||||||
__getattr__ = new_method_proxy(getattr)
|
__getattr__ = new_method_proxy(getattr)
|
||||||
|
@ -301,6 +303,15 @@ class LazyObject(object):
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
return {}
|
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):
|
def __deepcopy__(self, memo):
|
||||||
if self._wrapped is empty:
|
if self._wrapped is empty:
|
||||||
# We have to use type(self), not self.__class__, because the
|
# We have to use type(self), not self.__class__, because the
|
||||||
|
@ -377,6 +388,15 @@ class SimpleLazyObject(LazyObject):
|
||||||
repr_attr = self._wrapped
|
repr_attr = self._wrapped
|
||||||
return '<%s: %r>' % (type(self).__name__, repr_attr)
|
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):
|
def __deepcopy__(self, memo):
|
||||||
if self._wrapped is empty:
|
if self._wrapped is empty:
|
||||||
# We have to use SimpleLazyObject, not self.__class__, because the
|
# We have to use SimpleLazyObject, not self.__class__, because the
|
||||||
|
|
|
@ -31,3 +31,6 @@ Bugfixes
|
||||||
|
|
||||||
* Fixed a crash when using a reverse ``OneToOneField`` in
|
* Fixed a crash when using a reverse ``OneToOneField`` in
|
||||||
``ModelAdmin.readonly_fields`` (:ticket:`26060`).
|
``ModelAdmin.readonly_fields`` (:ticket:`26060`).
|
||||||
|
|
||||||
|
* Fixed a regression in Django 1.8.5 that broke copying a ``SimpleLazyObject``
|
||||||
|
with ``copy.copy()`` (:ticket:`26122`).
|
||||||
|
|
|
@ -82,3 +82,6 @@ Bugfixes
|
||||||
origin from the node via ``Node.token.source[0]``. This was an undocumented,
|
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
|
private API. The origin is now available directly on each node using the
|
||||||
``Node.origin`` attribute (:ticket:`25848`).
|
``Node.origin`` attribute (:ticket:`25848`).
|
||||||
|
|
||||||
|
* Fixed a regression in Django 1.8.5 that broke copying a ``SimpleLazyObject``
|
||||||
|
with ``copy.copy()`` (:ticket:`26122`).
|
||||||
|
|
|
@ -194,28 +194,99 @@ class LazyObjectTestCase(TestCase):
|
||||||
self.assertEqual(unpickled, obj)
|
self.assertEqual(unpickled, obj)
|
||||||
self.assertEqual(unpickled.foo, obj.foo)
|
self.assertEqual(unpickled.foo, obj.foo)
|
||||||
|
|
||||||
def test_deepcopy(self):
|
# Test copying lazy objects wrapping both builtin types and user-defined
|
||||||
# Check that we *can* do deep copy, and that it returns the right
|
# classes since a lot of the relevant code does __dict__ manipulation and
|
||||||
# objects.
|
# 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]
|
l = [1, 2, 3]
|
||||||
|
|
||||||
obj = self.lazy_wrap(l)
|
obj = self.lazy_wrap(l)
|
||||||
len(l) # forces evaluation
|
len(l) # forces evaluation
|
||||||
obj2 = copy.deepcopy(obj)
|
obj2 = copy.deepcopy(obj)
|
||||||
|
|
||||||
|
self.assertIsNot(obj, obj2)
|
||||||
self.assertIsInstance(obj2, list)
|
self.assertIsInstance(obj2, list)
|
||||||
self.assertEqual(obj2, [1, 2, 3])
|
self.assertEqual(obj2, [1, 2, 3])
|
||||||
|
|
||||||
def test_deepcopy_no_evaluation(self):
|
def test_deepcopy_list_no_evaluation(self):
|
||||||
# copying doesn't force evaluation
|
# Deep copying doesn't force evaluation.
|
||||||
|
|
||||||
l = [1, 2, 3]
|
l = [1, 2, 3]
|
||||||
|
|
||||||
obj = self.lazy_wrap(l)
|
obj = self.lazy_wrap(l)
|
||||||
obj2 = copy.deepcopy(obj)
|
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(obj._wrapped, empty)
|
||||||
self.assertIs(obj2._wrapped, empty)
|
self.assertIs(obj2._wrapped, empty)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue