summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Lindsley <daniel@toastdriven.com>2013-05-20 22:17:56 -0700
committerDaniel Lindsley <daniel@toastdriven.com>2013-05-21 10:17:27 -0700
commitcb9aaac91fb41b29b3d0d94f3cd208123c02a2ca (patch)
treec757677c93a6d3d6bb2bb32f2bcf28cced6799cf
parentbac187c0d8e829fb3ca2ca82965eabbcbcb6ddd5 (diff)
[1.5.x] Fixed #20212 - __reduce__ should only be defined for Py3+.
-rw-r--r--django/utils/functional.py24
-rw-r--r--tests/regressiontests/utils/simplelazyobject.py22
2 files changed, 41 insertions, 5 deletions
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 1dcbd55664..839dd4ef85 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -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):
- return (self.__newobj__, (self.__class__,), self.__getstate__())
+ 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)
diff --git a/tests/regressiontests/utils/simplelazyobject.py b/tests/regressiontests/utils/simplelazyobject.py
index 3f81e8f608..2dd382cf04 100644
--- a/tests/regressiontests/utils/simplelazyobject.py
+++ b/tests/regressiontests/utils/simplelazyobject.py
@@ -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)