-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathlocal_cache.py
More file actions
162 lines (135 loc) · 5.34 KB
/
Copy pathlocal_cache.py
File metadata and controls
162 lines (135 loc) · 5.34 KB
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import pickle
import time
import typing as t
from abc import ABC
from collections import OrderedDict
from anyio import Lock
from ellar.utils.event_loop import get_or_create_eventloop
from ..interface import IBaseCacheBackendAsync
from ..make_key_decorator import make_key_decorator, make_key_decorator_and_validate
from ..model import BaseCacheBackend
class _LocalMemCacheBackendSync(IBaseCacheBackendAsync, ABC):
def _async_executor(self, func: t.Awaitable) -> t.Any:
return get_or_create_eventloop().run_until_complete(func)
def get(self, key: str, version: t.Optional[str] = None) -> t.Any:
return self._async_executor(self.get_async(key, version=version))
def delete(self, key: str, version: t.Optional[str] = None) -> bool:
res = self._async_executor(self.delete_async(key, version=version))
return bool(res)
def set(
self,
key: str,
value: t.Any,
ttl: t.Union[float, int, None] = None,
version: t.Optional[str] = None,
) -> bool:
res = self._async_executor(self.set_async(key, value, ttl=ttl, version=version))
return bool(res)
def touch(
self,
key: str,
ttl: t.Union[float, int, None] = None,
version: t.Optional[str] = None,
) -> bool:
res = self._async_executor(self.touch_async(key, ttl=ttl, version=version))
return bool(res)
def incr(self, key: str, delta: int = 1, version: t.Optional[str] = None) -> int:
res = self._async_executor(self.incr_async(key, delta=delta, version=version))
return t.cast(int, res)
def decr(self, key: str, delta: int = 1, version: t.Optional[str] = None) -> int:
res = self._async_executor(self.decr_async(key, delta=delta, version=version))
return t.cast(int, res)
class LocalMemCacheBackend(_LocalMemCacheBackendSync, BaseCacheBackend):
"""
A thread-safe in-memory cache backend.
"""
pickle_protocol = pickle.HIGHEST_PROTOCOL
def __init__(self, **kwargs: t.Any) -> None:
super().__init__(**kwargs)
self._cache: t.Dict[str, bytes] = OrderedDict()
self._expire_track: t.Dict[str, float] = {}
self._lock = Lock()
@make_key_decorator
async def get_async(self, key: str, version: t.Optional[str] = None) -> t.Any:
async with self._lock:
if self._has_expired(key):
await self._delete(key)
return None
pickled = self._cache[key]
return pickle.loads(pickled)
async def _delete(self, key: str) -> bool:
try:
self._cache.pop(key)
self._expire_track.pop(key)
except KeyError:
return False
return True
@make_key_decorator
async def delete_async(self, key: str, version: t.Optional[str] = None) -> bool:
async with self._lock:
return await self._delete(key)
@make_key_decorator_and_validate
async def set_async(
self,
key: str,
value: t.Any,
ttl: t.Union[float, int, None] = None,
version: t.Optional[str] = None,
) -> bool:
async with self._lock:
self._cache[key] = pickle.dumps(value, self.pickle_protocol)
self._expire_track[key] = self.get_backend_ttl(ttl)
return True
def _has_expired(self, key: str) -> bool:
exp = self._expire_track.get(key, -1)
return exp is not None and exp <= time.time()
@make_key_decorator
async def has_key_async(self, key: str, version: t.Optional[str] = None) -> bool:
async with self._lock:
if self._has_expired(key):
await self._delete(key)
return False
return True
@make_key_decorator
async def touch_async(
self,
key: str,
ttl: t.Union[float, int, None] = None,
version: t.Optional[str] = None,
) -> bool:
async with self._lock:
if self._has_expired(key):
return False
self._expire_track[key] = self.get_backend_ttl(ttl)
return True
def has_key(self, key: str, version: t.Optional[str] = None) -> bool:
res = self._async_executor(self.has_key_async(key, version=version))
return bool(res)
def _incr_decr_action(self, key: str, delta: int) -> int:
pickled = self._cache[key]
value = t.cast(int, pickle.loads(pickled))
new_value = value + delta
pickled = pickle.dumps(new_value, self.pickle_protocol)
self._cache[key] = pickled
return new_value
@make_key_decorator
async def incr_async(
self, key: str, delta: int = 1, version: t.Optional[str] = None
) -> int:
async with self._lock:
if self._has_expired(key):
await self._delete(key)
raise ValueError("Key '%s' not found" % key)
return self._incr_decr_action(key, delta)
@make_key_decorator
async def decr_async(
self, key: str, delta: int = 1, version: t.Optional[str] = None
) -> int:
async with self._lock:
if self._has_expired(key):
await self._delete(key)
raise ValueError("Key '%s' not found" % key)
res = self._incr_decr_action(key, delta * -1)
if res < 0:
return self._incr_decr_action(key, res * -1)
return res