mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	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:
		| @@ -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) | ||||
| @@ -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 | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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`). | ||||
|   | ||||
| @@ -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`). | ||||
|   | ||||
| @@ -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.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_no_evaluation(self): | ||||
|         # copying doesn't force evaluation | ||||
|  | ||||
|     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) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user