From 5ad4907a0df2d30cf17fa3515404ccdcb8398ef6 Mon Sep 17 00:00:00 2001 From: Jeppe Fihl-Pearson Date: Thu, 25 Apr 2019 10:45:05 +0100 Subject: [PATCH] Add more context to warnings emitted from Django when unpickling objects When upgrading Django, Django will emit a warning whenever an object is being unpickled which was created with a different version of Django. This warning isn't very easy to action as it doesn't contain any context about what object it is or where it may have got the object from. The warning might look something like this: > WARNING [2019-04-25 09:41:48,525] warnings: /path/to/virtualenv/lib/python3.7/site-packages/django_redis/serializers/pickle.py:35: RuntimeWarning: Pickled model instance's Django version 2.1.8 does not match the current version 2.2. > return pickle.loads(value) This change will loop through each warning emitted during the deserialization of the cached item and append the cache key to the warning which at least would give some context as to why that is happening, so the warning message instead becomes: > WARNING [2019-04-25 09:41:48,525] warnings: /path/to/virtualenv/lib/python3.7/site-packages/django_redis/serializers/pickle.py:35: RuntimeWarning: Pickled model instance's Django version 2.1.8 does not match the current version 2.2. (cache key: :1:user) > return pickle.loads(value) given the cache key `user`. --- django_redis/client/default.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/django_redis/client/default.py b/django_redis/client/default.py index 9fc2cb72..ea2e45e3 100644 --- a/django_redis/client/default.py +++ b/django_redis/client/default.py @@ -5,6 +5,7 @@ import random import re import socket +import warnings from collections import OrderedDict from django.conf import settings @@ -208,7 +209,7 @@ def get(self, key, default=None, version=None, client=None): if value is None: return default - return self.decode(value) + return self.decode(value, key) def persist(self, key, version=None, client=None): if client is None: @@ -304,9 +305,11 @@ def clear(self, client=None): except _main_exceptions as e: raise ConnectionInterrupted(connection=client, parent=e) - def decode(self, value): + def decode(self, value, key): """ Decode the given value. + + Key is used to provide better context to warnings. """ try: value = int(value) @@ -316,7 +319,18 @@ def decode(self, value): except CompressorError: # Handle little values, chosen to be not compressed pass - value = self._serializer.loads(value) + + with warnings.catch_warnings(record=True) as caught_warnings: + value = self._serializer.loads(value) + + for warning in caught_warnings: + warnings.warn_explicit( + warning.category("{} (cache key: {})".format(str(warning.message), key)), + warning.category, + warning.filename, + warning.lineno, + ) + return value def encode(self, value): @@ -356,7 +370,7 @@ def get_many(self, keys, version=None, client=None): for key, value in zip(map_keys, results): if value is None: continue - recovered_data[map_keys[key]] = self.decode(value) + recovered_data[map_keys[key]] = self.decode(value, key) return recovered_data def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None, client=None):