cachify 0.2.1__py3-none-any.whl → 0.3.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 +5 -4
- cachify/cache.py +14 -6
- cachify/features/never_die.py +3 -3
- cachify/memory_cache.py +11 -6
- cachify/redis_cache.py +4 -3
- cachify/utils/arguments.py +5 -10
- cachify/utils/errors.py +2 -0
- cachify/utils/hash.py +18 -0
- {cachify-0.2.1.dist-info → cachify-0.3.0.dist-info}/METADATA +8 -7
- {cachify-0.2.1.dist-info → cachify-0.3.0.dist-info}/RECORD +13 -13
- cachify/utils/decorator_factory.py +0 -44
- cachify/utils/locks.py +0 -6
- {cachify-0.2.1.dist-info → cachify-0.3.0.dist-info}/WHEEL +0 -0
- {cachify-0.2.1.dist-info → cachify-0.3.0.dist-info}/entry_points.txt +0 -0
- {cachify-0.2.1.dist-info → cachify-0.3.0.dist-info}/licenses/LICENSE +0 -0
cachify/__init__.py
CHANGED
|
@@ -12,12 +12,13 @@ rcache = redis_cache
|
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
14
14
|
"__version__",
|
|
15
|
-
"cache",
|
|
16
15
|
"rcache",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"clear_never_die_registry",
|
|
17
|
+
"cache",
|
|
18
|
+
"DEFAULT_KEY_PREFIX",
|
|
19
19
|
"get_redis_config",
|
|
20
20
|
"reset_redis_config",
|
|
21
|
-
"
|
|
21
|
+
"setup_redis_config",
|
|
22
|
+
"redis_cache",
|
|
22
23
|
"CacheKwargs",
|
|
23
24
|
]
|
cachify/cache.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import inspect
|
|
3
|
-
from typing import Any, Callable, cast
|
|
3
|
+
from typing import Any, Callable, Sequence, cast
|
|
4
4
|
|
|
5
5
|
from cachify.features.never_die import register_never_die_function
|
|
6
6
|
from cachify.types import CacheConfig, CacheKeyFunction, F, Number
|
|
@@ -73,7 +73,8 @@ def base_cache(
|
|
|
73
73
|
ttl: Number,
|
|
74
74
|
never_die: bool,
|
|
75
75
|
cache_key_func: CacheKeyFunction | None,
|
|
76
|
-
ignore_fields:
|
|
76
|
+
ignore_fields: Sequence[str],
|
|
77
|
+
no_self: bool,
|
|
77
78
|
config: CacheConfig,
|
|
78
79
|
) -> Callable[[F], F]:
|
|
79
80
|
"""
|
|
@@ -83,7 +84,8 @@ def base_cache(
|
|
|
83
84
|
ttl: Time to live for cached items in seconds
|
|
84
85
|
never_die: If True, the cache will never expire and will be recalculated based on the ttl
|
|
85
86
|
cache_key_func: Custom cache key function, used for more complex cache scenarios
|
|
86
|
-
ignore_fields:
|
|
87
|
+
ignore_fields: Sequence of strings with the function params to ignore when creating the cache key
|
|
88
|
+
no_self: if True, the first parameter (typically 'self' for methods) will be ignored when creating the cache key
|
|
87
89
|
config: Cache configuration specifying storage, locks, and never_die registration
|
|
88
90
|
|
|
89
91
|
Features:
|
|
@@ -91,17 +93,23 @@ def base_cache(
|
|
|
91
93
|
- Only allows one execution at a time per function+args
|
|
92
94
|
- Makes subsequent calls wait for the first call to complete
|
|
93
95
|
"""
|
|
94
|
-
|
|
96
|
+
|
|
97
|
+
if cache_key_func and (ignore_fields or no_self):
|
|
95
98
|
raise ValueError("Either cache_key_func or ignore_fields can be provided, but not both")
|
|
96
99
|
|
|
97
100
|
def decorator(function: F) -> F:
|
|
101
|
+
ignore = tuple(ignore_fields)
|
|
102
|
+
|
|
103
|
+
if no_self:
|
|
104
|
+
ignore += function.__code__.co_varnames[:1]
|
|
105
|
+
|
|
98
106
|
if inspect.iscoroutinefunction(function):
|
|
99
107
|
return _async_decorator(
|
|
100
108
|
function=function,
|
|
101
109
|
ttl=ttl,
|
|
102
110
|
never_die=never_die,
|
|
103
111
|
cache_key_func=cache_key_func,
|
|
104
|
-
ignore_fields=
|
|
112
|
+
ignore_fields=ignore,
|
|
105
113
|
config=config,
|
|
106
114
|
)
|
|
107
115
|
return _sync_decorator(
|
|
@@ -109,7 +117,7 @@ def base_cache(
|
|
|
109
117
|
ttl=ttl,
|
|
110
118
|
never_die=never_die,
|
|
111
119
|
cache_key_func=cache_key_func,
|
|
112
|
-
ignore_fields=
|
|
120
|
+
ignore_fields=ignore,
|
|
113
121
|
config=config,
|
|
114
122
|
)
|
|
115
123
|
|
cachify/features/never_die.py
CHANGED
|
@@ -140,19 +140,19 @@ def _refresh_never_die_caches():
|
|
|
140
140
|
|
|
141
141
|
if entry.loop.is_closed():
|
|
142
142
|
logger.debug(
|
|
143
|
-
|
|
143
|
+
"Loop is closed, skipping future creation",
|
|
144
144
|
extra={"function": entry.function.__qualname__},
|
|
145
145
|
exc_info=True,
|
|
146
146
|
)
|
|
147
147
|
continue
|
|
148
148
|
|
|
149
|
+
coroutine = _run_async_function_and_cache(entry)
|
|
149
150
|
try:
|
|
150
|
-
coroutine = _run_async_function_and_cache(entry)
|
|
151
151
|
future = asyncio.run_coroutine_threadsafe(coroutine, entry.loop)
|
|
152
152
|
except RuntimeError:
|
|
153
153
|
coroutine.close()
|
|
154
154
|
logger.debug(
|
|
155
|
-
|
|
155
|
+
"Loop is closed, skipping future creation",
|
|
156
156
|
extra={"function": entry.function.__qualname__},
|
|
157
157
|
exc_info=True,
|
|
158
158
|
)
|
cachify/memory_cache.py
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import threading
|
|
2
|
-
from
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from typing import Callable, Sequence
|
|
3
5
|
|
|
4
6
|
from cachify.cache import base_cache
|
|
5
7
|
from cachify.storage.memory_storage import MemoryStorage
|
|
6
8
|
from cachify.types import CacheConfig, CacheKeyFunction, F, Number
|
|
7
|
-
from cachify.utils.locks import ASYNC_LOCKS, SYNC_LOCKS
|
|
8
9
|
|
|
9
10
|
_CACHE_CLEAR_THREAD: threading.Thread | None = None
|
|
10
11
|
_CACHE_CLEAR_LOCK: threading.Lock = threading.Lock()
|
|
11
12
|
|
|
13
|
+
_ASYNC_LOCKS: defaultdict[str, asyncio.Lock] = defaultdict(asyncio.Lock)
|
|
14
|
+
_SYNC_LOCKS: defaultdict[str, threading.Lock] = defaultdict(threading.Lock)
|
|
15
|
+
|
|
12
16
|
_MEMORY_CONFIG = CacheConfig(
|
|
13
17
|
storage=MemoryStorage,
|
|
14
|
-
sync_lock=
|
|
15
|
-
async_lock=
|
|
18
|
+
sync_lock=_SYNC_LOCKS.__getitem__,
|
|
19
|
+
async_lock=_ASYNC_LOCKS.__getitem__,
|
|
16
20
|
)
|
|
17
21
|
|
|
18
22
|
|
|
@@ -30,8 +34,9 @@ def cache(
|
|
|
30
34
|
ttl: Number = 300,
|
|
31
35
|
never_die: bool = False,
|
|
32
36
|
cache_key_func: CacheKeyFunction | None = None,
|
|
33
|
-
ignore_fields:
|
|
37
|
+
ignore_fields: Sequence[str] = (),
|
|
38
|
+
no_self: bool = False,
|
|
34
39
|
) -> Callable[[F], F]:
|
|
35
40
|
"""In-memory cache decorator. See `base_cache` for full documentation."""
|
|
36
41
|
_start_cache_clear_thread()
|
|
37
|
-
return base_cache(ttl, never_die, cache_key_func, ignore_fields, _MEMORY_CONFIG)
|
|
42
|
+
return base_cache(ttl, never_die, cache_key_func, ignore_fields, no_self, _MEMORY_CONFIG)
|
cachify/redis_cache.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Callable
|
|
1
|
+
from typing import Callable, Sequence
|
|
2
2
|
|
|
3
3
|
from cachify.cache import base_cache
|
|
4
4
|
from cachify.redis.lock import RedisLockManager
|
|
@@ -16,7 +16,8 @@ def redis_cache(
|
|
|
16
16
|
ttl: Number = 300,
|
|
17
17
|
never_die: bool = False,
|
|
18
18
|
cache_key_func: CacheKeyFunction | None = None,
|
|
19
|
-
ignore_fields:
|
|
19
|
+
ignore_fields: Sequence[str] = (),
|
|
20
|
+
no_self: bool = False,
|
|
20
21
|
) -> Callable[[F], F]:
|
|
21
22
|
"""
|
|
22
23
|
Redis cache decorator. See `base_cache` for full documentation.
|
|
@@ -24,4 +25,4 @@ def redis_cache(
|
|
|
24
25
|
Requires setup_redis_config() to be called before use.
|
|
25
26
|
Uses Redis for distributed caching across multiple processes/machines.
|
|
26
27
|
"""
|
|
27
|
-
return base_cache(ttl, never_die, cache_key_func, ignore_fields, _REDIS_CONFIG)
|
|
28
|
+
return base_cache(ttl, never_die, cache_key_func, ignore_fields, no_self, _REDIS_CONFIG)
|
cachify/utils/arguments.py
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
import hashlib
|
|
2
1
|
import inspect
|
|
3
|
-
import pickle
|
|
4
2
|
from collections.abc import Callable, Generator
|
|
5
3
|
from inspect import Signature
|
|
6
4
|
from typing import Any
|
|
7
5
|
|
|
8
6
|
from cachify.types import CacheKeyFunction
|
|
7
|
+
from cachify.utils.errors import CacheKeyError
|
|
9
8
|
from cachify.utils.functions import get_function_id
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _cache_key_fingerprint(value: object) -> str:
|
|
13
|
-
payload = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL)
|
|
14
|
-
return hashlib.blake2b(payload, digest_size=16).hexdigest()
|
|
9
|
+
from cachify.utils.hash import object_hash
|
|
15
10
|
|
|
16
11
|
|
|
17
12
|
def _iter_arguments(
|
|
@@ -54,12 +49,12 @@ def create_cache_key(
|
|
|
54
49
|
if not cache_key_func:
|
|
55
50
|
function_signature = inspect.signature(function)
|
|
56
51
|
items = tuple(_iter_arguments(function_signature, args, kwargs, ignore_fields))
|
|
57
|
-
return f"{function_id}:{
|
|
52
|
+
return f"{function_id}:{object_hash(items)}"
|
|
58
53
|
|
|
59
54
|
cache_key = cache_key_func(args, kwargs)
|
|
60
55
|
try:
|
|
61
|
-
return f"{function_id}:{
|
|
56
|
+
return f"{function_id}:{object_hash(cache_key)}"
|
|
62
57
|
except TypeError as exc:
|
|
63
|
-
raise
|
|
58
|
+
raise CacheKeyError(
|
|
64
59
|
"Cache key function must return a hashable cache key - be careful with mutable types (list, dict, set) and non built-in types"
|
|
65
60
|
) from exc
|
cachify/utils/errors.py
ADDED
cachify/utils/hash.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import pickle
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from cachify.utils.errors import CacheKeyError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def object_hash(value: Any) -> str:
|
|
9
|
+
try:
|
|
10
|
+
payload = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL)
|
|
11
|
+
|
|
12
|
+
except Exception as exc:
|
|
13
|
+
raise CacheKeyError(
|
|
14
|
+
"Unable to serialize object for hashing - ensure all parts of the object are pickleable. "
|
|
15
|
+
"Hint: create a custom __reduce__ method for the suspected object if necessary."
|
|
16
|
+
) from exc
|
|
17
|
+
|
|
18
|
+
return hashlib.blake2b(payload, digest_size=16).hexdigest()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cachify
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A simple cache library with sync/async support, Memory and Redis backend
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -86,12 +86,13 @@ async def another_calculation(url):
|
|
|
86
86
|
|
|
87
87
|
### Decorator Parameters
|
|
88
88
|
|
|
89
|
-
| Parameter | Type
|
|
90
|
-
| ---------------- |
|
|
91
|
-
| `ttl` | `int \| float`
|
|
92
|
-
| `never_die` | `bool`
|
|
93
|
-
| `cache_key_func` | `Callable`
|
|
94
|
-
| `ignore_fields` | `
|
|
89
|
+
| Parameter | Type | Default | Description |
|
|
90
|
+
| ---------------- | --------------- | ------- | -------------------------------------------------------------- |
|
|
91
|
+
| `ttl` | `int \| float` | `300` | Time to live for cached items in seconds |
|
|
92
|
+
| `never_die` | `bool` | `False` | If True, cache refreshes automatically in background |
|
|
93
|
+
| `cache_key_func` | `Callable` | `None` | Custom function to generate cache keys |
|
|
94
|
+
| `ignore_fields` | `Sequence[str]` | `()` | Function parameters to exclude from cache key |
|
|
95
|
+
| `no_self` | `bool` | `False` | If True, ignores the first parameter (usually `self` or `cls`) |
|
|
95
96
|
|
|
96
97
|
### Custom Cache Key Function
|
|
97
98
|
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
cachify/__init__.py,sha256=
|
|
2
|
-
cachify/cache.py,sha256=
|
|
1
|
+
cachify/__init__.py,sha256=zzW2gPgLF6PNRMvUmGo-2V-vXsrpYFJitgRFPlp-RgU,582
|
|
2
|
+
cachify/cache.py,sha256=Dg-vHIQrlXOMR4ueiTdw6fZYbfsJo8b95aTetPK-MUE,4288
|
|
3
3
|
cachify/config/__init__.py,sha256=ZvLLEsprTbKai3-uUTbZ6Axbgf-xw-7sxmz8Y62YdT4,98
|
|
4
4
|
cachify/features/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
cachify/features/never_die.py,sha256=
|
|
6
|
-
cachify/memory_cache.py,sha256=
|
|
5
|
+
cachify/features/never_die.py,sha256=virRY8788D1fGmwQQYmbMhLIKRkWVYNPywR-kl18u9c,7105
|
|
6
|
+
cachify/memory_cache.py,sha256=Ge1yU6fJfv_o3aCgU7ky5pHxvDSmfQ0xBFnJeC61jLQ,1485
|
|
7
7
|
cachify/redis/__init__.py,sha256=geuYVjwJdddlhrnt45cHzkJ7Anrhttm2F3Q3r0TTMLk,410
|
|
8
8
|
cachify/redis/config.py,sha256=Gvlj_hrUlsIGYqu50dghrv83R5UEOMJa4jVeK-nBAOs,3558
|
|
9
9
|
cachify/redis/lock.py,sha256=XbE5GHZIWUs9gqyfgle3Bur5CfKJ1pLhgtLctmZLB94,7281
|
|
10
|
-
cachify/redis_cache.py,sha256=
|
|
10
|
+
cachify/redis_cache.py,sha256=s9Uf2DdBwoZAFSld0fK18S70gz7B4APUB701_mfL-2A,917
|
|
11
11
|
cachify/storage/__init__.py,sha256=8FJOYwjg8LHVoNkyupdgKOR61LPfX5Bpz_mvnn-T-oU,250
|
|
12
12
|
cachify/storage/memory_storage.py,sha256=rsQqoymWczzkfXisF-9Tc9UGQem936U6_vd9Uo-sWnU,1481
|
|
13
13
|
cachify/storage/redis_storage.py,sha256=wZaKg7h98cmESpqiOmh906D5wfNBxYN80x-LQDLtb-c,4767
|
|
14
14
|
cachify/types/__init__.py,sha256=OataAgBEQXxpc376uZFM6cmdzJLUkzkj_H8XiKq5zpU,2641
|
|
15
15
|
cachify/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
cachify/utils/arguments.py,sha256=
|
|
17
|
-
cachify/utils/
|
|
16
|
+
cachify/utils/arguments.py,sha256=UJhcC8erS9h8sJTNlILK3uvr6Mg1BWkaI-2esV7-LNs,1858
|
|
17
|
+
cachify/utils/errors.py,sha256=IuPQuOV0rfzeHUoLi5DrdmGSz3YnB1OF4URPdIy6qQI,42
|
|
18
18
|
cachify/utils/functions.py,sha256=AA1GXJEEBsIr2NX9h6AslAdmqKaWUcjnf1q5kA216uY,312
|
|
19
|
-
cachify/utils/
|
|
20
|
-
cachify-0.
|
|
21
|
-
cachify-0.
|
|
22
|
-
cachify-0.
|
|
23
|
-
cachify-0.
|
|
24
|
-
cachify-0.
|
|
19
|
+
cachify/utils/hash.py,sha256=L95dFggFMKa-fQXXIwlRtPsJfWYKFlWzvbfTKlHR-o0,558
|
|
20
|
+
cachify-0.3.0.dist-info/METADATA,sha256=o0QL_IVJif6GyK1iF72ewBzOApjbDLL7F4IDORdGNI4,7594
|
|
21
|
+
cachify-0.3.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
22
|
+
cachify-0.3.0.dist-info/entry_points.txt,sha256=a8B7GSYgDPfUClqct0o7yyBbJ61AWgSSy1idL6YZLUM,45
|
|
23
|
+
cachify-0.3.0.dist-info/licenses/LICENSE,sha256=zUzOFiuoPIQzGktjk4kL78hpi57iWD6-Kug_96OgUO0,1071
|
|
24
|
+
cachify-0.3.0.dist-info/RECORD,,
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
from typing import Callable
|
|
3
|
-
|
|
4
|
-
from cachify._async import async_decorator
|
|
5
|
-
from cachify._sync import sync_decorator
|
|
6
|
-
from cachify.types import CacheConfig, CacheKeyFunction, F, Number
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def create_cache_decorator(
|
|
10
|
-
ttl: Number,
|
|
11
|
-
never_die: bool,
|
|
12
|
-
cache_key_func: CacheKeyFunction | None,
|
|
13
|
-
ignore_fields: tuple[str, ...],
|
|
14
|
-
config: CacheConfig,
|
|
15
|
-
) -> Callable[[F], F]:
|
|
16
|
-
"""
|
|
17
|
-
Create a cache decorator with the given configuration.
|
|
18
|
-
|
|
19
|
-
This is a shared factory used by both memory_cache and redis_cache
|
|
20
|
-
to avoid code duplication.
|
|
21
|
-
"""
|
|
22
|
-
if cache_key_func and ignore_fields:
|
|
23
|
-
raise ValueError("Either cache_key_func or ignore_fields can be provided, but not both")
|
|
24
|
-
|
|
25
|
-
def decorator(function: F) -> F:
|
|
26
|
-
if inspect.iscoroutinefunction(function):
|
|
27
|
-
return async_decorator(
|
|
28
|
-
function=function,
|
|
29
|
-
ttl=ttl,
|
|
30
|
-
never_die=never_die,
|
|
31
|
-
cache_key_func=cache_key_func,
|
|
32
|
-
ignore_fields=ignore_fields,
|
|
33
|
-
config=config,
|
|
34
|
-
)
|
|
35
|
-
return sync_decorator(
|
|
36
|
-
function=function,
|
|
37
|
-
ttl=ttl,
|
|
38
|
-
never_die=never_die,
|
|
39
|
-
cache_key_func=cache_key_func,
|
|
40
|
-
ignore_fields=ignore_fields,
|
|
41
|
-
config=config,
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
return decorator
|
cachify/utils/locks.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|