File: decorators.py

package info (click to toggle)
drf-extensions 0.8.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,308 kB
  • sloc: python: 7,421; makefile: 11
file content (134 lines) | stat: -rw-r--r-- 4,464 bytes parent folder | download | duplicates (3)
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
from functools import wraps, WRAPPER_ASSIGNMENTS

from django.http.response import HttpResponse


from rest_framework_extensions.settings import extensions_api_settings


def get_cache(alias):
    from django.core.cache import caches
    return caches[alias]


class CacheResponse:
    """
    Store/Receive and return cached `HttpResponse` based on DRF response.


    .. note::
        This decorator will render and discard the original DRF response in
        favor of Django's `HttpResponse`. The allows the cache to retain a
        smaller memory footprint and eliminates the need to re-render
        responses on each request. Furthermore it eliminates the risk for users
        to unknowingly cache whole Serializers and QuerySets.

    """
    def __init__(self,
                 timeout=None,
                 key_func=None,
                 cache=None,
                 cache_errors=None):
        if timeout is None:
            self.timeout = extensions_api_settings.DEFAULT_CACHE_RESPONSE_TIMEOUT
        else:
            self.timeout = timeout

        if key_func is None:
            self.key_func = extensions_api_settings.DEFAULT_CACHE_KEY_FUNC
        else:
            self.key_func = key_func

        if cache_errors is None:
            self.cache_errors = extensions_api_settings.DEFAULT_CACHE_ERRORS
        else:
            self.cache_errors = cache_errors

        self.cache = get_cache(cache or extensions_api_settings.DEFAULT_USE_CACHE)

    def __call__(self, func):
        this = self

        @wraps(func, assigned=WRAPPER_ASSIGNMENTS)
        def inner(self, request, *args, **kwargs):
            return this.process_cache_response(
                view_instance=self,
                view_method=func,
                request=request,
                args=args,
                kwargs=kwargs,
            )
        return inner

    def process_cache_response(self,
                               view_instance,
                               view_method,
                               request,
                               args,
                               kwargs):

        key = self.calculate_key(
            view_instance=view_instance,
            view_method=view_method,
            request=request,
            args=args,
            kwargs=kwargs
        )

        timeout = self.calculate_timeout(view_instance=view_instance)

        response_triple = self.cache.get(key)
        if not response_triple:
            # render response to create and cache the content byte string
            response = view_method(view_instance, request, *args, **kwargs)
            response = view_instance.finalize_response(request, response, *args, **kwargs)
            response.render()

            if not response.status_code >= 400 or self.cache_errors:
                # django 3.0 has not .items() method, django 3.2 has not ._headers
                if hasattr(response, '_headers'):
                    headers = response._headers.copy()
                else:
                    headers = {k: (k, v) for k, v in response.items()}
                response_triple = (
                    response.rendered_content,
                    response.status_code,
                    headers
                )
                self.cache.set(key, response_triple, timeout)
        else:
            # build smaller Django HttpResponse
            content, status, headers = response_triple
            response = HttpResponse(content=content, status=status)
            for k, v in headers.values():
                response[k] = v
        if not hasattr(response, '_closable_objects'):
            response._closable_objects = []

        return response

    def calculate_key(self,
                      view_instance,
                      view_method,
                      request,
                      args,
                      kwargs):
        if isinstance(self.key_func, str):
            key_func = getattr(view_instance, self.key_func)
        else:
            key_func = self.key_func
        return key_func(
            view_instance=view_instance,
            view_method=view_method,
            request=request,
            args=args,
            kwargs=kwargs,
        )

    def calculate_timeout(self, view_instance, **_):
        if isinstance(self.timeout, str):
            self.timeout = getattr(view_instance, self.timeout)
        return self.timeout


cache_response = CacheResponse