Optimized Model and QuerySet pickling version comparison.

This commit is contained in:
Adam Johnson 2020-04-03 21:13:02 +01:00 committed by Mariusz Felisiak
parent 72a170b4c3
commit a8b2db1cae
4 changed files with 37 additions and 29 deletions

View File

@ -4,6 +4,7 @@ import warnings
from functools import partialmethod from functools import partialmethod
from itertools import chain from itertools import chain
import django
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
from django.core import checks from django.core import checks
@ -36,7 +37,6 @@ from django.utils.encoding import force_str
from django.utils.hashable import make_hashable from django.utils.hashable import make_hashable
from django.utils.text import capfirst, get_text_list from django.utils.text import capfirst, get_text_list
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.version import get_version
class Deferred: class Deferred:
@ -538,7 +538,7 @@ class Model(metaclass=ModelBase):
def __reduce__(self): def __reduce__(self):
data = self.__getstate__() data = self.__getstate__()
data[DJANGO_VERSION_PICKLE_KEY] = get_version() data[DJANGO_VERSION_PICKLE_KEY] = django.__version__
class_id = self._meta.app_label, self._meta.object_name class_id = self._meta.app_label, self._meta.object_name
return model_unpickle, (class_id,), data return model_unpickle, (class_id,), data
@ -547,21 +547,22 @@ class Model(metaclass=ModelBase):
return self.__dict__ return self.__dict__
def __setstate__(self, state): def __setstate__(self, state):
msg = None
pickled_version = state.get(DJANGO_VERSION_PICKLE_KEY) pickled_version = state.get(DJANGO_VERSION_PICKLE_KEY)
if pickled_version: if pickled_version:
current_version = get_version() if pickled_version != django.__version__:
if current_version != pickled_version: warnings.warn(
msg = ( "Pickled model instance's Django version %s does not "
"Pickled model instance's Django version %s does not match " "match the current version %s."
"the current version %s." % (pickled_version, current_version) % (pickled_version, django.__version__),
RuntimeWarning,
stacklevel=2,
) )
else: else:
msg = "Pickled model instance's Django version is not specified." warnings.warn(
"Pickled model instance's Django version is not specified.",
if msg: RuntimeWarning,
warnings.warn(msg, RuntimeWarning, stacklevel=2) stacklevel=2,
)
self.__dict__.update(state) self.__dict__.update(state)
def _get_pk_val(self, meta=None): def _get_pk_val(self, meta=None):

View File

@ -9,6 +9,7 @@ from collections import namedtuple
from functools import lru_cache from functools import lru_cache
from itertools import chain from itertools import chain
import django
from django.conf import settings from django.conf import settings
from django.core import exceptions from django.core import exceptions
from django.db import ( from django.db import (
@ -25,7 +26,6 @@ from django.db.models.sql.constants import CURSOR, GET_ITERATOR_CHUNK_SIZE
from django.db.models.utils import resolve_callables from django.db.models.utils import resolve_callables
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property, partition from django.utils.functional import cached_property, partition
from django.utils.version import get_version
# The maximum number of results to fetch in a get() query. # The maximum number of results to fetch in a get() query.
MAX_GET_RESULTS = 21 MAX_GET_RESULTS = 21
@ -238,24 +238,25 @@ class QuerySet:
def __getstate__(self): def __getstate__(self):
# Force the cache to be fully populated. # Force the cache to be fully populated.
self._fetch_all() self._fetch_all()
return {**self.__dict__, DJANGO_VERSION_PICKLE_KEY: get_version()} return {**self.__dict__, DJANGO_VERSION_PICKLE_KEY: django.__version__}
def __setstate__(self, state): def __setstate__(self, state):
msg = None
pickled_version = state.get(DJANGO_VERSION_PICKLE_KEY) pickled_version = state.get(DJANGO_VERSION_PICKLE_KEY)
if pickled_version: if pickled_version:
current_version = get_version() if pickled_version != django.__version__:
if current_version != pickled_version: warnings.warn(
msg = (
"Pickled queryset instance's Django version %s does not " "Pickled queryset instance's Django version %s does not "
"match the current version %s." % (pickled_version, current_version) "match the current version %s."
% (pickled_version, django.__version__),
RuntimeWarning,
stacklevel=2,
) )
else: else:
msg = "Pickled queryset instance's Django version is not specified." warnings.warn(
"Pickled queryset instance's Django version is not specified.",
if msg: RuntimeWarning,
warnings.warn(msg, RuntimeWarning, stacklevel=2) stacklevel=2,
)
self.__dict__.update(state) self.__dict__.update(state)
def __repr__(self): def __repr__(self):

View File

@ -1,8 +1,8 @@
import pickle import pickle
import django
from django.db import DJANGO_VERSION_PICKLE_KEY, models from django.db import DJANGO_VERSION_PICKLE_KEY, models
from django.test import SimpleTestCase from django.test import SimpleTestCase
from django.utils.version import get_version
class ModelPickleTests(SimpleTestCase): class ModelPickleTests(SimpleTestCase):
@ -40,7 +40,10 @@ class ModelPickleTests(SimpleTestCase):
return reduce_list return reduce_list
p = DifferentDjangoVersion(title="FooBar") p = DifferentDjangoVersion(title="FooBar")
msg = "Pickled model instance's Django version 1.0 does not match the current version %s." % get_version() msg = (
"Pickled model instance's Django version 1.0 does not match the "
"current version %s." % django.__version__
)
with self.assertRaisesMessage(RuntimeWarning, msg): with self.assertRaisesMessage(RuntimeWarning, msg):
pickle.loads(pickle.dumps(p)) pickle.loads(pickle.dumps(p))

View File

@ -1,9 +1,9 @@
import datetime import datetime
import pickle import pickle
import django
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase
from django.utils.version import get_version
from .models import Container, Event, Group, Happening, M2MModel, MyEvent from .models import Container, Event, Group, Happening, M2MModel, MyEvent
@ -234,7 +234,10 @@ class PickleabilityTestCase(TestCase):
unpickled with a different Django version than the current unpickled with a different Django version than the current
""" """
qs = Group.previous_django_version_objects.all() qs = Group.previous_django_version_objects.all()
msg = "Pickled queryset instance's Django version 1.0 does not match the current version %s." % get_version() msg = (
"Pickled queryset instance's Django version 1.0 does not match "
"the current version %s." % django.__version__
)
with self.assertRaisesMessage(RuntimeWarning, msg): with self.assertRaisesMessage(RuntimeWarning, msg):
pickle.loads(pickle.dumps(qs)) pickle.loads(pickle.dumps(qs))