1
+ import dataclasses
1
2
import typing as t
2
- import uuid
3
- from functools import wraps
4
3
5
4
from ellar .cache .interface import ICacheService
6
- from ellar .common import Context , IExecutionContext , Provide , extra_args
7
- from ellar .common .helper import is_async_callable
8
- from ellar .common .params import ExtraEndpointArg
5
+ from ellar .common import EllarInterceptor , IExecutionContext , set_metadata
6
+ from ellar .common .constants import ROUTE_CACHE_OPTIONS , ROUTE_INTERCEPTORS
7
+ from ellar .core import Reflector
8
+ from ellar .di import injectable
9
9
10
10
11
- class _CacheDecorator :
11
+ @dataclasses .dataclass
12
+ class RouteCacheOptions :
13
+ ttl : t .Union [int , float ]
14
+ key_prefix : str
15
+ make_key_callback : t .Callable [[IExecutionContext , str ], str ]
16
+ version : t .Optional [str ] = None
17
+ backend : str = "default"
18
+
19
+
20
+ def route_cache_make_key (context : IExecutionContext , key_prefix : str ) -> str :
21
+ """Defaults key generator for caching view"""
22
+ connection = context .switch_to_http_connection ()
23
+ return f"{ connection .get_client ().url } :{ key_prefix or 'view' } "
24
+
25
+
26
+ @injectable
27
+ class CacheEllarInterceptor (EllarInterceptor ):
12
28
__slots__ = (
13
- "_is_async" ,
14
- "_key_prefix" ,
15
- "_version" ,
16
- "_backend" ,
17
- "_func" ,
18
- "_ttl" ,
19
- "_cache_service_arg" ,
20
- "_context_arg" ,
21
- "_make_key_callback" ,
29
+ "_cache_service" ,
30
+ "_reflector" ,
22
31
)
23
32
24
- def __init__ (
25
- self ,
26
- func : t .Callable ,
27
- ttl : t .Union [int , float ],
28
- * ,
29
- key_prefix : str = "" ,
30
- version : str = None ,
31
- backend : str = "default" ,
32
- make_key_callback : t .Callable [[IExecutionContext , str ], str ] = None ,
33
- ) -> None :
34
- self ._is_async = is_async_callable (func )
35
- self ._key_prefix = key_prefix
36
- self ._version = version
37
- self ._backend = backend
38
- self ._func = func
39
- self ._ttl = ttl
40
-
41
- # create extra args
42
- self ._cache_service_arg = ExtraEndpointArg (
43
- name = f"cache_service_{ uuid .uuid4 ().hex [:4 ]} " ,
44
- annotation = ICacheService , # type:ignore[misc]
45
- default_value = Provide (),
33
+ def __init__ (self , cache_service : ICacheService , reflector : "Reflector" ) -> None :
34
+ self ._cache_service = cache_service
35
+ self ._reflector = reflector
36
+
37
+ async def intercept (
38
+ self , context : IExecutionContext , next_interceptor : t .Callable [..., t .Coroutine ]
39
+ ) -> t .Any :
40
+ opts : RouteCacheOptions = self ._reflector .get (
41
+ ROUTE_CACHE_OPTIONS , context .get_handler ()
46
42
)
47
- self ._context_arg = ExtraEndpointArg (
48
- name = f"route_context_{ uuid .uuid4 ().hex [:4 ]} " ,
49
- annotation = IExecutionContext , # type:ignore[misc]
50
- default_value = Context (),
43
+
44
+ backend = self ._cache_service .get_backend (backend = opts .backend )
45
+ key = opts .make_key_callback (context , opts .key_prefix or backend .key_prefix )
46
+
47
+ cached_value = await self ._cache_service .get_async (
48
+ key , opts .version , backend = opts .backend
51
49
)
52
- # apply extra_args to endpoint
53
- extra_args (self ._cache_service_arg , self ._context_arg )(func )
54
- self ._make_key_callback : t .Callable [[IExecutionContext , str ], str ] = (
55
- make_key_callback or self .route_cache_make_key
50
+ if cached_value :
51
+ return cached_value
52
+
53
+ response = await next_interceptor ()
54
+ await self ._cache_service .set_async (
55
+ key ,
56
+ response ,
57
+ ttl = opts .ttl ,
58
+ version = opts .version ,
59
+ backend = opts .backend ,
56
60
)
57
-
58
- def get_decorator_wrapper (self ) -> t .Callable :
59
- if self ._is_async :
60
- return self .get_async_cache_wrapper ()
61
- return self .get_cache_wrapper ()
62
-
63
- def route_cache_make_key (self , context : IExecutionContext , key_prefix : str ) -> str :
64
- """Defaults key generator for caching view"""
65
- connection = context .switch_to_http_connection ()
66
- return f"{ connection .get_client ().url } :{ key_prefix or 'view' } "
67
-
68
- def get_async_cache_wrapper (self ) -> t .Callable :
69
- """Gets endpoint asynchronous wrapper function"""
70
-
71
- @wraps (self ._func )
72
- async def _async_wrapper (* args : t .Any , ** kwargs : t .Any ) -> t .Any :
73
- cache_service : ICacheService = self ._cache_service_arg .resolve (kwargs )
74
- context : IExecutionContext = self ._context_arg .resolve (kwargs )
75
-
76
- backend = cache_service .get_backend (backend = self ._backend )
77
- key = self ._make_key_callback (
78
- context , self ._key_prefix or backend .key_prefix
79
- )
80
-
81
- cached_value = await cache_service .get_async (
82
- key , self ._version , backend = self ._backend
83
- )
84
- if cached_value :
85
- return cached_value
86
-
87
- response = await self ._func (* args , ** kwargs )
88
- await cache_service .set_async (
89
- key ,
90
- response ,
91
- ttl = self ._ttl ,
92
- version = self ._version ,
93
- backend = self ._backend ,
94
- )
95
- return response
96
-
97
- return _async_wrapper
98
-
99
- def get_cache_wrapper (self ) -> t .Callable :
100
- """Gets endpoint synchronous wrapper function"""
101
-
102
- @wraps (self ._func )
103
- def _wrapper (* args : t .Any , ** kwargs : t .Any ) -> t .Any :
104
- cache_service : ICacheService = self ._cache_service_arg .resolve (kwargs )
105
- context : IExecutionContext = self ._context_arg .resolve (kwargs )
106
-
107
- backend = cache_service .get_backend (backend = self ._backend )
108
- key = self ._make_key_callback (
109
- context , self ._key_prefix or backend .key_prefix
110
- )
111
-
112
- cached_value = cache_service .get (key , self ._version , backend = self ._backend )
113
- if cached_value :
114
- return cached_value
115
-
116
- response = self ._func (* args , ** kwargs )
117
- cache_service .set (
118
- key ,
119
- response ,
120
- ttl = self ._ttl ,
121
- version = self ._version ,
122
- backend = self ._backend ,
123
- )
124
- return response
125
-
126
- return _wrapper
61
+ return response
127
62
128
63
129
64
def cache (
@@ -145,14 +80,14 @@ def cache(
145
80
"""
146
81
147
82
def _wraps (func : t .Callable ) -> t .Callable :
148
- cache_decorator = _CacheDecorator (
149
- func ,
150
- ttl ,
83
+ options = RouteCacheOptions (
84
+ ttl = ttl ,
151
85
key_prefix = key_prefix ,
152
86
version = version ,
153
- backend = backend ,
154
- make_key_callback = make_key_callback ,
87
+ backend = backend or "default" ,
88
+ make_key_callback = make_key_callback or route_cache_make_key ,
155
89
)
156
- return cache_decorator .get_decorator_wrapper ()
90
+ func = set_metadata (ROUTE_CACHE_OPTIONS , options )(func )
91
+ return set_metadata (ROUTE_INTERCEPTORS , [CacheEllarInterceptor ])(func ) # type: ignore[no-any-return]
157
92
158
93
return _wraps
0 commit comments