1+ import dataclasses
12import typing as t
2- import uuid
3- from functools import wraps
43
54from 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
99
1010
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 ):
1228 __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" ,
2231 )
2332
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 ()
4642 )
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
5149 )
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 ,
5660 )
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
12762
12863
12964def cache (
@@ -145,14 +80,14 @@ def cache(
14580 """
14681
14782 def _wraps (func : t .Callable ) -> t .Callable :
148- cache_decorator = _CacheDecorator (
149- func ,
150- ttl ,
83+ options = RouteCacheOptions (
84+ ttl = ttl ,
15185 key_prefix = key_prefix ,
15286 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 ,
15589 )
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]
15792
15893 return _wraps
0 commit comments