Opened 5 years ago

Last modified 5 years ago

#25426 closed Bug

pickling SimpleLazyObject fails just after accessing related object of wrapped model instance. — at Version 1

Reported by: Iru Hwang Owned by: nobody
Component: Core (Serialization) Version: 1.8
Severity: Release blocker Keywords:
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Iru Hwang)

Reproduction step :

import pickle
import django
django.setup()

from django.contrib.auth.models import User
from django.utils.functional import SimpleLazyObject

u = User.objects.select_related('profile').get(id=153)
o = SimpleLazyObject(lambda :u)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/pickle.py", line 1374, in dumps
    Pickler(file, protocol).dump(obj)
  File "/usr/local/lib/python2.7/pickle.py", line 224, in dump
    self.save(obj)
  File "/usr/local/lib/python2.7/pickle.py", line 331, in save
    self.save_reduce(obj=obj, *rv)
  File "/usr/local/lib/python2.7/pickle.py", line 419, in save_reduce
    save(state)
  File "/usr/local/lib/python2.7/pickle.py", line 286, in save
    f(self, obj) # Call unbound method with explicit self
  File "/usr/local/lib/python2.7/pickle.py", line 649, in save_dict
    self._batch_setitems(obj.iteritems())
  File "/usr/local/lib/python2.7/pickle.py", line 661, in _batch_setitems
    for k, v in items:
RuntimeError: dictionary changed size during iteration
pickle.dumps(o) # SUCCEEDS 

This also fails :

u = User.objects.get(id=153)
o = SimpleLazyObject(lambda :u)
o.profile
pickle.dumps(o)

This looks to be related with change in Model.reduce in django 1.8
adding DJANGO_VERSION_PICKLE_KEY in dict

    def __reduce__(self):
        """                                                                                                                                                                                                                                                                     
        Provides pickling support. Normally, this just dispatches to Python's                                                                                                                                                                                                   
        standard handling. However, for models with deferred field loading, we                                                                                                                                                                                                  
        need to do things manually, as they're dynamically created classes and                                                                                                                                                                                                  
        only module-level classes can be pickled by the default path.                                                                                                                                                                                                           
        """
        data = self.__dict__
        data[DJANGO_VERSION_PICKLE_KEY] = get_version()
        if not self._deferred:
            class_id = self._meta.app_label, self._meta.object_name
            return model_unpickle, (class_id, [], simple_class_factory), data
        defers = []
        for field in self._meta.fields:
            if isinstance(self.__class__.__dict__.get(field.attname),
                          DeferredAttribute):
                defers.append(field.attname)
        model = self._meta.proxy_for_model
        class_id = model._meta.app_label, model._meta.object_name
        return (model_unpickle, (class_id, defers, deferred_class_factory), data)

Change History (1)

comment:1 Changed 5 years ago by Iru Hwang

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top