/
cache.py
75 lines (64 loc) · 2.5 KB
/
cache.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
from contextlib import contextmanager
from django.utils.functional import cached_property
from rest_framework.serializers import ListSerializer
class SerializerCacheMixin:
"""Mixin for DRF serializer performance improvement.
Provides cache for slow "fields" property."""
@cached_property
def _is_first_cachable(self):
parent = self.parent
while parent is not None:
if isinstance(parent, SerializerCacheMixin):
return False
parent = parent.parent
return True
@contextmanager
def _setup_cache(self):
assert not hasattr(self.root, '_field_cache'), \
'Double cache setup detected.'
self.root._field_cache = {}
assert not hasattr(self.root, '_representation_cache'), \
'Double cache setup detected.'
self.root._representation_cache = {}
yield
del self.root._field_cache
del self.root._representation_cache
def to_representation(self, instance):
"""Convert instance to representation with result caching."""
if self._is_first_cachable:
with self._setup_cache():
return super().to_representation(instance)
cache = self.root._representation_cache
key = (instance, self.__class__)
try:
if key not in cache:
cache[key] = super().to_representation(instance)
return cache[key]
# key might not be hashable
except TypeError:
return super().to_representation(instance)
@property
def fields(self):
"""Return cached fields."""
try:
cache = self.root._field_cache
except AttributeError:
return super().fields
if self.__class__ not in cache:
cache[self.__class__] = super().fields
return cache[self.__class__]
@classmethod
def many_init(cls, *args, **kwargs):
"""Use cached list serializer if possible."""
meta = getattr(cls, 'Meta', None)
if meta is not None and not hasattr(meta, 'list_serializer_class'):
# Meta has no custom list serializer, it's safe to use optimized
meta.list_serializer_class = CachedListSerializer
elif meta is None:
cls.Meta = type(
'Meta',
tuple(),
{'list_serializer_class': CachedListSerializer})
return super().many_init(*args, **kwargs)
class CachedListSerializer(SerializerCacheMixin, ListSerializer):
pass