Fixed #20212 - __reduce__ should only be defined for Py3+.
This commit is contained in:
parent
4ba1c2e785
commit
e24d486fbc
|
@ -5,6 +5,11 @@ import sys
|
||||||
|
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
try:
|
||||||
|
import copyreg
|
||||||
|
except ImportError:
|
||||||
|
import copy_reg as copyreg
|
||||||
|
|
||||||
|
|
||||||
# You can't trivially replace this with `functools.partial` because this binds
|
# You can't trivially replace this with `functools.partial` because this binds
|
||||||
# to classes and returns bound instances, whereas functools.partial (on
|
# to classes and returns bound instances, whereas functools.partial (on
|
||||||
|
@ -323,15 +328,23 @@ class SimpleLazyObject(LazyObject):
|
||||||
self._setup()
|
self._setup()
|
||||||
return self._wrapped.__dict__
|
return self._wrapped.__dict__
|
||||||
|
|
||||||
# Python 3.3 will call __reduce__ when pickling; these methods are needed
|
# Python 3.3 will call __reduce__ when pickling; this method is needed
|
||||||
# to serialize and deserialize correctly. They are not called in earlier
|
# to serialize and deserialize correctly.
|
||||||
# versions of Python.
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __newobj__(cls, *args):
|
def __newobj__(cls, *args):
|
||||||
return cls.__new__(cls, *args)
|
return cls.__new__(cls, *args)
|
||||||
|
|
||||||
def __reduce__(self):
|
def __reduce_ex__(self, proto):
|
||||||
return (self.__newobj__, (self.__class__,), self.__getstate__())
|
if proto >= 2:
|
||||||
|
# On Py3, since the default protocol is 3, pickle uses the
|
||||||
|
# ``__newobj__`` method (& more efficient opcodes) for writing.
|
||||||
|
return (self.__newobj__, (self.__class__,), self.__getstate__())
|
||||||
|
else:
|
||||||
|
# On Py2, the default protocol is 0 (for back-compat) & the above
|
||||||
|
# code fails miserably (see regression test). Instead, we return
|
||||||
|
# exactly what's returned if there's no ``__reduce__`` method at
|
||||||
|
# all.
|
||||||
|
return (copyreg._reconstructor, (self.__class__, object, None), self.__getstate__())
|
||||||
|
|
||||||
# Return a meaningful representation of the lazy object for debugging
|
# Return a meaningful representation of the lazy object for debugging
|
||||||
# without evaluating the wrapped object.
|
# without evaluating the wrapped object.
|
||||||
|
|
|
@ -161,3 +161,25 @@ class TestUtilsSimpleLazyObject(TestCase):
|
||||||
self.assertNotEqual(lazy1, lazy3)
|
self.assertNotEqual(lazy1, lazy3)
|
||||||
self.assertTrue(lazy1 != lazy3)
|
self.assertTrue(lazy1 != lazy3)
|
||||||
self.assertFalse(lazy1 != lazy2)
|
self.assertFalse(lazy1 != lazy2)
|
||||||
|
|
||||||
|
def test_pickle_py2_regression(self):
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
# See ticket #20212
|
||||||
|
user = User.objects.create_user('johndoe', 'john@example.com', 'pass')
|
||||||
|
x = SimpleLazyObject(lambda: user)
|
||||||
|
|
||||||
|
# This would fail with "TypeError: can't pickle instancemethod objects",
|
||||||
|
# only on Python 2.X.
|
||||||
|
pickled = pickle.dumps(x)
|
||||||
|
|
||||||
|
# Try the variant protocol levels.
|
||||||
|
pickled = pickle.dumps(x, 0)
|
||||||
|
pickled = pickle.dumps(x, 1)
|
||||||
|
pickled = pickle.dumps(x, 2)
|
||||||
|
|
||||||
|
if not six.PY3:
|
||||||
|
import cPickle
|
||||||
|
|
||||||
|
# This would fail with "TypeError: expected string or Unicode object, NoneType found".
|
||||||
|
pickled = cPickle.dumps(x)
|
||||||
|
|
Loading…
Reference in New Issue