Skip to content

Commit 0b92672

Browse files
committed
moved cache operation decorator to interceptor
1 parent 59086c5 commit 0b92672

File tree

3 files changed

+58
-122
lines changed

3 files changed

+58
-122
lines changed

ellar/cache/decorator.py

Lines changed: 56 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,64 @@
1+
import dataclasses
12
import typing as t
2-
import uuid
3-
from functools import wraps
43

54
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
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

12964
def 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

ellar/common/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
OPERATION_ENDPOINT_KEY = "OPERATION_ENDPOINT"
5757
ROUTE_OPERATION_PARAMETERS = "__ROUTE_OPERATION_PARAMETERS__"
5858
ROUTE_INTERCEPTORS = "ROUTE_INTERCEPTORS"
59+
ROUTE_CACHE_OPTIONS = "ROUTE_CACHE_OPTIONS"
5960
CONTROLLER_OPERATION_HANDLER_KEY = "CONTROLLER_OPERATION_HANDLER"
6061
CONTROLLER_CLASS_KEY = "CONTROLLER_CLASS_KEY"
6162

ellar/common/decorators/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def set_metadata(
1212
if meta_value is NOT_SET:
1313
return partial(set_metadata, meta_key)
1414

15-
def _decorator(target: t.Union[t.Callable, t.Any]) -> t.Union[t.Callable, t.Any]:
15+
def _decorator(target: t.Union[t.Callable, t.Any]) -> t.Callable:
1616
reflect.define_metadata(meta_key, meta_value, target)
1717
return target
1818

0 commit comments

Comments
 (0)