diff options
| author | Daniel Lindsley <daniel@toastdriven.com> | 2013-05-20 22:17:56 -0700 |
|---|---|---|
| committer | Daniel Lindsley <daniel@toastdriven.com> | 2013-05-21 00:49:21 -0700 |
| commit | e24d486fbc0b1c42abe8b54217ff428e449c48cc (patch) | |
| tree | f6a129623297f4ef4d1bc9b831d677b55d70b02e | |
| parent | 4ba1c2e785feecfa7a47aa5336a2b595f086a765 (diff) | |
Fixed #20212 - __reduce__ should only be defined for Py3+.
| -rw-r--r-- | django/utils/functional.py | 23 | ||||
| -rw-r--r-- | tests/utils_tests/test_simplelazyobject.py | 22 |
2 files changed, 40 insertions, 5 deletions
diff --git a/django/utils/functional.py b/django/utils/functional.py index 1592828c7f..bf32121e07 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -5,6 +5,11 @@ import sys 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 # to classes and returns bound instances, whereas functools.partial (on @@ -323,15 +328,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__()) # Return a meaningful representation of the lazy object for debugging # without evaluating the wrapped object. diff --git a/tests/utils_tests/test_simplelazyobject.py b/tests/utils_tests/test_simplelazyobject.py index f925e01eb6..ca7c2672eb 100644 --- a/tests/utils_tests/test_simplelazyobject.py +++ b/tests/utils_tests/test_simplelazyobject.py @@ -161,3 +161,25 @@ class TestUtilsSimpleLazyObject(TestCase): self.assertNotEqual(lazy1, lazy3) self.assertTrue(lazy1 != lazy3) 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) |
