Commit cb9aaac9 authored by Daniel Lindsley's avatar Daniel Lindsley
Browse files

[1.5.x] Fixed #20212 - __reduce__ should only be defined for Py3+.

parent bac187c0
Loading
Loading
Loading
Loading
+19 −5
Original line number Diff line number Diff line
@@ -5,6 +5,12 @@ import sys

from django.utils import six

try:
    import copyreg
except ImportError:
    import copy_reg as copyreg


# You can't trivially replace this `functools.partial` because this binds to
# classes and returns bound instances, whereas functools.partial (on CPython)
# is a type and its instances don't bind.
@@ -294,15 +300,23 @@ class SimpleLazyObject(LazyObject):
            self._setup()
        return self._wrapped.__dict__

    # Python 3.3 will call __reduce__ when pickling; these methods are needed
    # to serialize and deserialize correctly. They are not called in earlier
    # versions of Python.
    # Python 3.3 will call __reduce__ when pickling; this method is needed
    # to serialize and deserialize correctly.
    @classmethod
    def __newobj__(cls, *args):
        return cls.__new__(cls, *args)

    def __reduce__(self):
    def __reduce_ex__(self, proto):
        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__())

    # Need to pretend to be the wrapped class, for the sake of objects that care
    # about this (especially in equality tests)
+22 −0
Original line number Diff line number Diff line
@@ -121,3 +121,25 @@ class TestUtilsSimpleLazyObject(TestCase):
        self.assertEqual(unpickled, x)
        self.assertEqual(six.text_type(unpickled), six.text_type(x))
        self.assertEqual(unpickled.name, x.name)

    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)