cachify 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cachify/__init__.py +24 -22
- cachify/cache.py +116 -116
- cachify/config/__init__.py +4 -4
- cachify/features/never_die.py +219 -219
- cachify/memory_cache.py +37 -37
- cachify/redis/__init__.py +19 -19
- cachify/redis/config.py +115 -115
- cachify/redis/lock.py +232 -232
- cachify/redis_cache.py +27 -27
- cachify/storage/__init__.py +9 -9
- cachify/storage/memory_storage.py +52 -52
- cachify/storage/redis_storage.py +138 -138
- cachify/types/__init__.py +95 -95
- cachify/utils/arguments.py +65 -65
- cachify/utils/decorator_factory.py +44 -44
- cachify/utils/functions.py +10 -10
- cachify/utils/locks.py +6 -6
- {cachify-0.1.0.dist-info → cachify-0.2.0.dist-info}/METADATA +4 -3
- cachify-0.2.0.dist-info/RECORD +24 -0
- {cachify-0.1.0.dist-info → cachify-0.2.0.dist-info}/WHEEL +1 -1
- {cachify-0.1.0.dist-info → cachify-0.2.0.dist-info/licenses}/LICENSE +21 -21
- cachify-0.1.0.dist-info/RECORD +0 -24
- {cachify-0.1.0.dist-info → cachify-0.2.0.dist-info}/entry_points.txt +0 -0
cachify/features/never_die.py
CHANGED
|
@@ -1,219 +1,219 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import functools
|
|
3
|
-
import inspect
|
|
4
|
-
import threading
|
|
5
|
-
import time
|
|
6
|
-
from asyncio import AbstractEventLoop
|
|
7
|
-
from concurrent.futures import Future as ConcurrentFuture
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
from typing import Any, Callable
|
|
10
|
-
|
|
11
|
-
from cachify.config import logger
|
|
12
|
-
from cachify.types import CacheConfig, CacheKeyFunction, Number
|
|
13
|
-
from cachify.utils.arguments import create_cache_key
|
|
14
|
-
|
|
15
|
-
_NEVER_DIE_THREAD: threading.Thread | None = None
|
|
16
|
-
_NEVER_DIE_LOCK: threading.Lock = threading.Lock()
|
|
17
|
-
_NEVER_DIE_REGISTRY: list["NeverDieCacheEntry"] = []
|
|
18
|
-
_NEVER_DIE_CACHE_THREADS: dict[str, threading.Thread] = {}
|
|
19
|
-
_NEVER_DIE_CACHE_FUTURES: dict[str, ConcurrentFuture] = {}
|
|
20
|
-
|
|
21
|
-
_MAX_BACKOFF: int = 10
|
|
22
|
-
_BACKOFF_MULTIPLIER: float = 1.25
|
|
23
|
-
_REFRESH_INTERVAL_SECONDS: float = 0.1
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@dataclass
|
|
27
|
-
class NeverDieCacheEntry:
|
|
28
|
-
function: Callable[..., Any]
|
|
29
|
-
ttl: Number
|
|
30
|
-
args: tuple
|
|
31
|
-
kwargs: dict
|
|
32
|
-
cache_key_func: CacheKeyFunction | None
|
|
33
|
-
ignore_fields: tuple[str, ...]
|
|
34
|
-
loop: AbstractEventLoop | None
|
|
35
|
-
config: CacheConfig
|
|
36
|
-
|
|
37
|
-
def __post_init__(self):
|
|
38
|
-
self._backoff: float = 1
|
|
39
|
-
self._expires_at: float = time.monotonic() + self.ttl
|
|
40
|
-
|
|
41
|
-
@functools.cached_property
|
|
42
|
-
def cache_key(self) -> str:
|
|
43
|
-
return create_cache_key(
|
|
44
|
-
self.function,
|
|
45
|
-
self.cache_key_func,
|
|
46
|
-
self.ignore_fields,
|
|
47
|
-
self.args,
|
|
48
|
-
self.kwargs,
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
def __eq__(self, other: Any) -> bool:
|
|
52
|
-
if not isinstance(other, NeverDieCacheEntry):
|
|
53
|
-
return False
|
|
54
|
-
return self.cache_key == other.cache_key
|
|
55
|
-
|
|
56
|
-
def __hash__(self) -> int:
|
|
57
|
-
return hash(self.cache_key)
|
|
58
|
-
|
|
59
|
-
def is_expired(self) -> bool:
|
|
60
|
-
return time.monotonic() > self._expires_at
|
|
61
|
-
|
|
62
|
-
def reset(self):
|
|
63
|
-
self._backoff = 1
|
|
64
|
-
self._expires_at = time.monotonic() + self.ttl
|
|
65
|
-
|
|
66
|
-
def revive(self):
|
|
67
|
-
self._backoff = min(self._backoff * _BACKOFF_MULTIPLIER, _MAX_BACKOFF)
|
|
68
|
-
self._expires_at = time.monotonic() + self.ttl * self._backoff
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def _run_sync_function_and_cache(entry: NeverDieCacheEntry):
|
|
72
|
-
"""Run a function and cache its result"""
|
|
73
|
-
try:
|
|
74
|
-
with entry.config.sync_lock(entry.cache_key):
|
|
75
|
-
result = entry.function(*entry.args, **entry.kwargs)
|
|
76
|
-
entry.config.storage.set(entry.cache_key, result, None)
|
|
77
|
-
entry.reset()
|
|
78
|
-
except BaseException:
|
|
79
|
-
entry.revive()
|
|
80
|
-
logger.debug(
|
|
81
|
-
"Exception caching function with never_die",
|
|
82
|
-
extra={"function": entry.function.__qualname__},
|
|
83
|
-
exc_info=True,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
async def _run_async_function_and_cache(entry: NeverDieCacheEntry):
|
|
88
|
-
"""Run a function and cache its result"""
|
|
89
|
-
try:
|
|
90
|
-
async with entry.config.async_lock(entry.cache_key):
|
|
91
|
-
result = await entry.function(*entry.args, **entry.kwargs)
|
|
92
|
-
await entry.config.storage.aset(entry.cache_key, result, None)
|
|
93
|
-
entry.reset()
|
|
94
|
-
except BaseException:
|
|
95
|
-
entry.revive()
|
|
96
|
-
logger.debug(
|
|
97
|
-
"Exception caching function with never_die",
|
|
98
|
-
extra={"function": entry.function.__qualname__},
|
|
99
|
-
exc_info=True,
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def _cache_is_being_set(entry: NeverDieCacheEntry) -> bool:
|
|
104
|
-
if entry.loop:
|
|
105
|
-
return entry.cache_key in _NEVER_DIE_CACHE_FUTURES and not _NEVER_DIE_CACHE_FUTURES[entry.cache_key].done()
|
|
106
|
-
return entry.cache_key in _NEVER_DIE_CACHE_THREADS and _NEVER_DIE_CACHE_THREADS[entry.cache_key].is_alive()
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _clear_dead_futures():
|
|
110
|
-
"""Clear dead futures from the cache future registry"""
|
|
111
|
-
for cache_key, thread in list(_NEVER_DIE_CACHE_FUTURES.items()):
|
|
112
|
-
if thread.done():
|
|
113
|
-
del _NEVER_DIE_CACHE_FUTURES[cache_key]
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def _clear_dead_threads():
|
|
117
|
-
"""Clear dead threads from the cache thread registry"""
|
|
118
|
-
for cache_key, thread in list(_NEVER_DIE_CACHE_THREADS.items()):
|
|
119
|
-
if thread.is_alive():
|
|
120
|
-
continue
|
|
121
|
-
del _NEVER_DIE_CACHE_THREADS[cache_key]
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def _refresh_never_die_caches():
|
|
125
|
-
"""Background thread function that periodically refreshes never_die cache entries"""
|
|
126
|
-
while True:
|
|
127
|
-
try:
|
|
128
|
-
for entry in list(_NEVER_DIE_REGISTRY):
|
|
129
|
-
if not entry.is_expired():
|
|
130
|
-
continue
|
|
131
|
-
|
|
132
|
-
if _cache_is_being_set(entry):
|
|
133
|
-
continue
|
|
134
|
-
|
|
135
|
-
if not entry.loop: # sync
|
|
136
|
-
thread = threading.Thread(target=_run_sync_function_and_cache, args=(entry,), daemon=True)
|
|
137
|
-
thread.start()
|
|
138
|
-
_NEVER_DIE_CACHE_THREADS[entry.cache_key] = thread
|
|
139
|
-
continue
|
|
140
|
-
|
|
141
|
-
if entry.loop.is_closed():
|
|
142
|
-
logger.debug(
|
|
143
|
-
f"Loop is closed, skipping future creation",
|
|
144
|
-
extra={"function": entry.function.__qualname__},
|
|
145
|
-
exc_info=True,
|
|
146
|
-
)
|
|
147
|
-
continue
|
|
148
|
-
|
|
149
|
-
try:
|
|
150
|
-
coroutine = _run_async_function_and_cache(entry)
|
|
151
|
-
future = asyncio.run_coroutine_threadsafe(coroutine, entry.loop)
|
|
152
|
-
except RuntimeError:
|
|
153
|
-
coroutine.close()
|
|
154
|
-
logger.debug(
|
|
155
|
-
f"Loop is closed, skipping future creation",
|
|
156
|
-
extra={"function": entry.function.__qualname__},
|
|
157
|
-
exc_info=True,
|
|
158
|
-
)
|
|
159
|
-
continue
|
|
160
|
-
|
|
161
|
-
_NEVER_DIE_CACHE_FUTURES[entry.cache_key] = future
|
|
162
|
-
finally:
|
|
163
|
-
time.sleep(_REFRESH_INTERVAL_SECONDS)
|
|
164
|
-
_clear_dead_futures()
|
|
165
|
-
_clear_dead_threads()
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def _start_never_die_thread():
|
|
169
|
-
"""Start the background thread if it's not already running"""
|
|
170
|
-
global _NEVER_DIE_THREAD
|
|
171
|
-
with _NEVER_DIE_LOCK:
|
|
172
|
-
if _NEVER_DIE_THREAD and _NEVER_DIE_THREAD.is_alive():
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
_NEVER_DIE_THREAD = threading.Thread(target=_refresh_never_die_caches, daemon=True)
|
|
176
|
-
_NEVER_DIE_THREAD.start()
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def register_never_die_function(
|
|
180
|
-
function: Callable[..., Any],
|
|
181
|
-
ttl: Number,
|
|
182
|
-
args: tuple,
|
|
183
|
-
kwargs: dict,
|
|
184
|
-
cache_key_func: CacheKeyFunction | None,
|
|
185
|
-
ignore_fields: tuple[str, ...],
|
|
186
|
-
config: CacheConfig,
|
|
187
|
-
):
|
|
188
|
-
"""Register a function for never_die cache refreshing"""
|
|
189
|
-
is_async = inspect.iscoroutinefunction(function)
|
|
190
|
-
|
|
191
|
-
entry = NeverDieCacheEntry(
|
|
192
|
-
function=function,
|
|
193
|
-
ttl=ttl,
|
|
194
|
-
args=args,
|
|
195
|
-
kwargs=kwargs,
|
|
196
|
-
cache_key_func=cache_key_func,
|
|
197
|
-
ignore_fields=ignore_fields,
|
|
198
|
-
loop=asyncio.get_running_loop() if is_async else None,
|
|
199
|
-
config=config,
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
with _NEVER_DIE_LOCK:
|
|
203
|
-
if entry not in _NEVER_DIE_REGISTRY:
|
|
204
|
-
_NEVER_DIE_REGISTRY.append(entry)
|
|
205
|
-
|
|
206
|
-
_start_never_die_thread()
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def clear_never_die_registry():
|
|
210
|
-
"""
|
|
211
|
-
Clear all entries from the never_die registry.
|
|
212
|
-
|
|
213
|
-
Useful for testing to prevent background threads from
|
|
214
|
-
accessing resources that have been cleaned up.
|
|
215
|
-
"""
|
|
216
|
-
with _NEVER_DIE_LOCK:
|
|
217
|
-
_NEVER_DIE_REGISTRY.clear()
|
|
218
|
-
_NEVER_DIE_CACHE_THREADS.clear()
|
|
219
|
-
_NEVER_DIE_CACHE_FUTURES.clear()
|
|
1
|
+
import asyncio
|
|
2
|
+
import functools
|
|
3
|
+
import inspect
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from asyncio import AbstractEventLoop
|
|
7
|
+
from concurrent.futures import Future as ConcurrentFuture
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, Callable
|
|
10
|
+
|
|
11
|
+
from cachify.config import logger
|
|
12
|
+
from cachify.types import CacheConfig, CacheKeyFunction, Number
|
|
13
|
+
from cachify.utils.arguments import create_cache_key
|
|
14
|
+
|
|
15
|
+
_NEVER_DIE_THREAD: threading.Thread | None = None
|
|
16
|
+
_NEVER_DIE_LOCK: threading.Lock = threading.Lock()
|
|
17
|
+
_NEVER_DIE_REGISTRY: list["NeverDieCacheEntry"] = []
|
|
18
|
+
_NEVER_DIE_CACHE_THREADS: dict[str, threading.Thread] = {}
|
|
19
|
+
_NEVER_DIE_CACHE_FUTURES: dict[str, ConcurrentFuture] = {}
|
|
20
|
+
|
|
21
|
+
_MAX_BACKOFF: int = 10
|
|
22
|
+
_BACKOFF_MULTIPLIER: float = 1.25
|
|
23
|
+
_REFRESH_INTERVAL_SECONDS: float = 0.1
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class NeverDieCacheEntry:
|
|
28
|
+
function: Callable[..., Any]
|
|
29
|
+
ttl: Number
|
|
30
|
+
args: tuple
|
|
31
|
+
kwargs: dict
|
|
32
|
+
cache_key_func: CacheKeyFunction | None
|
|
33
|
+
ignore_fields: tuple[str, ...]
|
|
34
|
+
loop: AbstractEventLoop | None
|
|
35
|
+
config: CacheConfig
|
|
36
|
+
|
|
37
|
+
def __post_init__(self):
|
|
38
|
+
self._backoff: float = 1
|
|
39
|
+
self._expires_at: float = time.monotonic() + self.ttl
|
|
40
|
+
|
|
41
|
+
@functools.cached_property
|
|
42
|
+
def cache_key(self) -> str:
|
|
43
|
+
return create_cache_key(
|
|
44
|
+
self.function,
|
|
45
|
+
self.cache_key_func,
|
|
46
|
+
self.ignore_fields,
|
|
47
|
+
self.args,
|
|
48
|
+
self.kwargs,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def __eq__(self, other: Any) -> bool:
|
|
52
|
+
if not isinstance(other, NeverDieCacheEntry):
|
|
53
|
+
return False
|
|
54
|
+
return self.cache_key == other.cache_key
|
|
55
|
+
|
|
56
|
+
def __hash__(self) -> int:
|
|
57
|
+
return hash(self.cache_key)
|
|
58
|
+
|
|
59
|
+
def is_expired(self) -> bool:
|
|
60
|
+
return time.monotonic() > self._expires_at
|
|
61
|
+
|
|
62
|
+
def reset(self):
|
|
63
|
+
self._backoff = 1
|
|
64
|
+
self._expires_at = time.monotonic() + self.ttl
|
|
65
|
+
|
|
66
|
+
def revive(self):
|
|
67
|
+
self._backoff = min(self._backoff * _BACKOFF_MULTIPLIER, _MAX_BACKOFF)
|
|
68
|
+
self._expires_at = time.monotonic() + self.ttl * self._backoff
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _run_sync_function_and_cache(entry: NeverDieCacheEntry):
|
|
72
|
+
"""Run a function and cache its result"""
|
|
73
|
+
try:
|
|
74
|
+
with entry.config.sync_lock(entry.cache_key):
|
|
75
|
+
result = entry.function(*entry.args, **entry.kwargs)
|
|
76
|
+
entry.config.storage.set(entry.cache_key, result, None)
|
|
77
|
+
entry.reset()
|
|
78
|
+
except BaseException:
|
|
79
|
+
entry.revive()
|
|
80
|
+
logger.debug(
|
|
81
|
+
"Exception caching function with never_die",
|
|
82
|
+
extra={"function": entry.function.__qualname__},
|
|
83
|
+
exc_info=True,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def _run_async_function_and_cache(entry: NeverDieCacheEntry):
|
|
88
|
+
"""Run a function and cache its result"""
|
|
89
|
+
try:
|
|
90
|
+
async with entry.config.async_lock(entry.cache_key):
|
|
91
|
+
result = await entry.function(*entry.args, **entry.kwargs)
|
|
92
|
+
await entry.config.storage.aset(entry.cache_key, result, None)
|
|
93
|
+
entry.reset()
|
|
94
|
+
except BaseException:
|
|
95
|
+
entry.revive()
|
|
96
|
+
logger.debug(
|
|
97
|
+
"Exception caching function with never_die",
|
|
98
|
+
extra={"function": entry.function.__qualname__},
|
|
99
|
+
exc_info=True,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _cache_is_being_set(entry: NeverDieCacheEntry) -> bool:
|
|
104
|
+
if entry.loop:
|
|
105
|
+
return entry.cache_key in _NEVER_DIE_CACHE_FUTURES and not _NEVER_DIE_CACHE_FUTURES[entry.cache_key].done()
|
|
106
|
+
return entry.cache_key in _NEVER_DIE_CACHE_THREADS and _NEVER_DIE_CACHE_THREADS[entry.cache_key].is_alive()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _clear_dead_futures():
|
|
110
|
+
"""Clear dead futures from the cache future registry"""
|
|
111
|
+
for cache_key, thread in list(_NEVER_DIE_CACHE_FUTURES.items()):
|
|
112
|
+
if thread.done():
|
|
113
|
+
del _NEVER_DIE_CACHE_FUTURES[cache_key]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _clear_dead_threads():
|
|
117
|
+
"""Clear dead threads from the cache thread registry"""
|
|
118
|
+
for cache_key, thread in list(_NEVER_DIE_CACHE_THREADS.items()):
|
|
119
|
+
if thread.is_alive():
|
|
120
|
+
continue
|
|
121
|
+
del _NEVER_DIE_CACHE_THREADS[cache_key]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _refresh_never_die_caches():
|
|
125
|
+
"""Background thread function that periodically refreshes never_die cache entries"""
|
|
126
|
+
while True:
|
|
127
|
+
try:
|
|
128
|
+
for entry in list(_NEVER_DIE_REGISTRY):
|
|
129
|
+
if not entry.is_expired():
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
if _cache_is_being_set(entry):
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
if not entry.loop: # sync
|
|
136
|
+
thread = threading.Thread(target=_run_sync_function_and_cache, args=(entry,), daemon=True)
|
|
137
|
+
thread.start()
|
|
138
|
+
_NEVER_DIE_CACHE_THREADS[entry.cache_key] = thread
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
if entry.loop.is_closed():
|
|
142
|
+
logger.debug(
|
|
143
|
+
f"Loop is closed, skipping future creation",
|
|
144
|
+
extra={"function": entry.function.__qualname__},
|
|
145
|
+
exc_info=True,
|
|
146
|
+
)
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
coroutine = _run_async_function_and_cache(entry)
|
|
151
|
+
future = asyncio.run_coroutine_threadsafe(coroutine, entry.loop)
|
|
152
|
+
except RuntimeError:
|
|
153
|
+
coroutine.close()
|
|
154
|
+
logger.debug(
|
|
155
|
+
f"Loop is closed, skipping future creation",
|
|
156
|
+
extra={"function": entry.function.__qualname__},
|
|
157
|
+
exc_info=True,
|
|
158
|
+
)
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
_NEVER_DIE_CACHE_FUTURES[entry.cache_key] = future
|
|
162
|
+
finally:
|
|
163
|
+
time.sleep(_REFRESH_INTERVAL_SECONDS)
|
|
164
|
+
_clear_dead_futures()
|
|
165
|
+
_clear_dead_threads()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _start_never_die_thread():
|
|
169
|
+
"""Start the background thread if it's not already running"""
|
|
170
|
+
global _NEVER_DIE_THREAD
|
|
171
|
+
with _NEVER_DIE_LOCK:
|
|
172
|
+
if _NEVER_DIE_THREAD and _NEVER_DIE_THREAD.is_alive():
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
_NEVER_DIE_THREAD = threading.Thread(target=_refresh_never_die_caches, daemon=True)
|
|
176
|
+
_NEVER_DIE_THREAD.start()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def register_never_die_function(
|
|
180
|
+
function: Callable[..., Any],
|
|
181
|
+
ttl: Number,
|
|
182
|
+
args: tuple,
|
|
183
|
+
kwargs: dict,
|
|
184
|
+
cache_key_func: CacheKeyFunction | None,
|
|
185
|
+
ignore_fields: tuple[str, ...],
|
|
186
|
+
config: CacheConfig,
|
|
187
|
+
):
|
|
188
|
+
"""Register a function for never_die cache refreshing"""
|
|
189
|
+
is_async = inspect.iscoroutinefunction(function)
|
|
190
|
+
|
|
191
|
+
entry = NeverDieCacheEntry(
|
|
192
|
+
function=function,
|
|
193
|
+
ttl=ttl,
|
|
194
|
+
args=args,
|
|
195
|
+
kwargs=kwargs,
|
|
196
|
+
cache_key_func=cache_key_func,
|
|
197
|
+
ignore_fields=ignore_fields,
|
|
198
|
+
loop=asyncio.get_running_loop() if is_async else None,
|
|
199
|
+
config=config,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
with _NEVER_DIE_LOCK:
|
|
203
|
+
if entry not in _NEVER_DIE_REGISTRY:
|
|
204
|
+
_NEVER_DIE_REGISTRY.append(entry)
|
|
205
|
+
|
|
206
|
+
_start_never_die_thread()
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def clear_never_die_registry():
|
|
210
|
+
"""
|
|
211
|
+
Clear all entries from the never_die registry.
|
|
212
|
+
|
|
213
|
+
Useful for testing to prevent background threads from
|
|
214
|
+
accessing resources that have been cleaned up.
|
|
215
|
+
"""
|
|
216
|
+
with _NEVER_DIE_LOCK:
|
|
217
|
+
_NEVER_DIE_REGISTRY.clear()
|
|
218
|
+
_NEVER_DIE_CACHE_THREADS.clear()
|
|
219
|
+
_NEVER_DIE_CACHE_FUTURES.clear()
|
cachify/memory_cache.py
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import threading
|
|
2
|
-
from typing import Callable
|
|
3
|
-
|
|
4
|
-
from cachify.cache import base_cache
|
|
5
|
-
from cachify.storage.memory_storage import MemoryStorage
|
|
6
|
-
from cachify.types import CacheConfig, CacheKeyFunction, F, Number
|
|
7
|
-
from cachify.utils.locks import ASYNC_LOCKS, SYNC_LOCKS
|
|
8
|
-
|
|
9
|
-
_CACHE_CLEAR_THREAD: threading.Thread | None = None
|
|
10
|
-
_CACHE_CLEAR_LOCK: threading.Lock = threading.Lock()
|
|
11
|
-
|
|
12
|
-
_MEMORY_CONFIG = CacheConfig(
|
|
13
|
-
storage=MemoryStorage,
|
|
14
|
-
sync_lock=lambda cache_key: SYNC_LOCKS[cache_key],
|
|
15
|
-
async_lock=lambda cache_key: ASYNC_LOCKS[cache_key],
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _start_cache_clear_thread():
|
|
20
|
-
"""This is to avoid memory leaks by clearing expired cache items periodically."""
|
|
21
|
-
global _CACHE_CLEAR_THREAD
|
|
22
|
-
with _CACHE_CLEAR_LOCK:
|
|
23
|
-
if _CACHE_CLEAR_THREAD and _CACHE_CLEAR_THREAD.is_alive():
|
|
24
|
-
return
|
|
25
|
-
_CACHE_CLEAR_THREAD = threading.Thread(target=MemoryStorage.clear_expired_cached_items, daemon=True)
|
|
26
|
-
_CACHE_CLEAR_THREAD.start()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def cache(
|
|
30
|
-
ttl: Number = 300,
|
|
31
|
-
never_die: bool = False,
|
|
32
|
-
cache_key_func: CacheKeyFunction | None = None,
|
|
33
|
-
ignore_fields: tuple[str, ...] = (),
|
|
34
|
-
) -> Callable[[F], F]:
|
|
35
|
-
"""In-memory cache decorator. See `base_cache` for full documentation."""
|
|
36
|
-
_start_cache_clear_thread()
|
|
37
|
-
return base_cache(ttl, never_die, cache_key_func, ignore_fields, _MEMORY_CONFIG)
|
|
1
|
+
import threading
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
from cachify.cache import base_cache
|
|
5
|
+
from cachify.storage.memory_storage import MemoryStorage
|
|
6
|
+
from cachify.types import CacheConfig, CacheKeyFunction, F, Number
|
|
7
|
+
from cachify.utils.locks import ASYNC_LOCKS, SYNC_LOCKS
|
|
8
|
+
|
|
9
|
+
_CACHE_CLEAR_THREAD: threading.Thread | None = None
|
|
10
|
+
_CACHE_CLEAR_LOCK: threading.Lock = threading.Lock()
|
|
11
|
+
|
|
12
|
+
_MEMORY_CONFIG = CacheConfig(
|
|
13
|
+
storage=MemoryStorage,
|
|
14
|
+
sync_lock=lambda cache_key: SYNC_LOCKS[cache_key],
|
|
15
|
+
async_lock=lambda cache_key: ASYNC_LOCKS[cache_key],
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _start_cache_clear_thread():
|
|
20
|
+
"""This is to avoid memory leaks by clearing expired cache items periodically."""
|
|
21
|
+
global _CACHE_CLEAR_THREAD
|
|
22
|
+
with _CACHE_CLEAR_LOCK:
|
|
23
|
+
if _CACHE_CLEAR_THREAD and _CACHE_CLEAR_THREAD.is_alive():
|
|
24
|
+
return
|
|
25
|
+
_CACHE_CLEAR_THREAD = threading.Thread(target=MemoryStorage.clear_expired_cached_items, daemon=True)
|
|
26
|
+
_CACHE_CLEAR_THREAD.start()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def cache(
|
|
30
|
+
ttl: Number = 300,
|
|
31
|
+
never_die: bool = False,
|
|
32
|
+
cache_key_func: CacheKeyFunction | None = None,
|
|
33
|
+
ignore_fields: tuple[str, ...] = (),
|
|
34
|
+
) -> Callable[[F], F]:
|
|
35
|
+
"""In-memory cache decorator. See `base_cache` for full documentation."""
|
|
36
|
+
_start_cache_clear_thread()
|
|
37
|
+
return base_cache(ttl, never_die, cache_key_func, ignore_fields, _MEMORY_CONFIG)
|
cachify/redis/__init__.py
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
from cachify.redis.config import (
|
|
2
|
-
DEFAULT_KEY_PREFIX,
|
|
3
|
-
DEFAULT_LOCK_TIMEOUT,
|
|
4
|
-
RedisConfig,
|
|
5
|
-
get_redis_config,
|
|
6
|
-
reset_redis_config,
|
|
7
|
-
setup_redis_config,
|
|
8
|
-
)
|
|
9
|
-
from cachify.redis.lock import RedisLockManager
|
|
10
|
-
|
|
11
|
-
__all__ = [
|
|
12
|
-
"DEFAULT_KEY_PREFIX",
|
|
13
|
-
"DEFAULT_LOCK_TIMEOUT",
|
|
14
|
-
"RedisConfig",
|
|
15
|
-
"RedisLockManager",
|
|
16
|
-
"get_redis_config",
|
|
17
|
-
"reset_redis_config",
|
|
18
|
-
"setup_redis_config",
|
|
19
|
-
]
|
|
1
|
+
from cachify.redis.config import (
|
|
2
|
+
DEFAULT_KEY_PREFIX,
|
|
3
|
+
DEFAULT_LOCK_TIMEOUT,
|
|
4
|
+
RedisConfig,
|
|
5
|
+
get_redis_config,
|
|
6
|
+
reset_redis_config,
|
|
7
|
+
setup_redis_config,
|
|
8
|
+
)
|
|
9
|
+
from cachify.redis.lock import RedisLockManager
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DEFAULT_KEY_PREFIX",
|
|
13
|
+
"DEFAULT_LOCK_TIMEOUT",
|
|
14
|
+
"RedisConfig",
|
|
15
|
+
"RedisLockManager",
|
|
16
|
+
"get_redis_config",
|
|
17
|
+
"reset_redis_config",
|
|
18
|
+
"setup_redis_config",
|
|
19
|
+
]
|