cachify 0.1.0__py3-none-any.whl → 0.2.1__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 +23 -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.1.dist-info}/METADATA +72 -13
- cachify-0.2.1.dist-info/RECORD +24 -0
- {cachify-0.1.0.dist-info → cachify-0.2.1.dist-info}/WHEEL +1 -1
- {cachify-0.1.0.dist-info → cachify-0.2.1.dist-info/licenses}/LICENSE +21 -21
- cachify-0.1.0.dist-info/RECORD +0 -24
- {cachify-0.1.0.dist-info → cachify-0.2.1.dist-info}/entry_points.txt +0 -0
cachify/__init__.py
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
from .
|
|
2
|
-
|
|
3
|
-
from .
|
|
4
|
-
from .
|
|
5
|
-
from .
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
|
|
3
|
+
from .features.never_die import clear_never_die_registry
|
|
4
|
+
from .memory_cache import cache
|
|
5
|
+
from .redis import DEFAULT_KEY_PREFIX, get_redis_config, reset_redis_config, setup_redis_config
|
|
6
|
+
from .redis_cache import redis_cache
|
|
7
|
+
from .types import CacheKwargs
|
|
8
|
+
|
|
9
|
+
__version__ = version("cachify")
|
|
10
|
+
|
|
11
|
+
rcache = redis_cache
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"__version__",
|
|
15
|
+
"cache",
|
|
16
|
+
"rcache",
|
|
17
|
+
"redis_cache",
|
|
18
|
+
"setup_redis_config",
|
|
19
|
+
"get_redis_config",
|
|
20
|
+
"reset_redis_config",
|
|
21
|
+
"DEFAULT_KEY_PREFIX",
|
|
22
|
+
"CacheKwargs",
|
|
23
|
+
]
|
cachify/cache.py
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
import functools
|
|
2
|
-
import inspect
|
|
3
|
-
from typing import Any, Callable, cast
|
|
4
|
-
|
|
5
|
-
from cachify.features.never_die import register_never_die_function
|
|
6
|
-
from cachify.types import CacheConfig, CacheKeyFunction, F, Number
|
|
7
|
-
from cachify.utils.arguments import create_cache_key
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _async_decorator(
|
|
11
|
-
function: F,
|
|
12
|
-
ttl: Number,
|
|
13
|
-
never_die: bool,
|
|
14
|
-
cache_key_func: CacheKeyFunction | None,
|
|
15
|
-
ignore_fields: tuple[str, ...],
|
|
16
|
-
config: CacheConfig,
|
|
17
|
-
) -> F:
|
|
18
|
-
@functools.wraps(function)
|
|
19
|
-
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
20
|
-
skip_cache = kwargs.pop("skip_cache", False)
|
|
21
|
-
cache_key = create_cache_key(function, cache_key_func, ignore_fields, args, kwargs)
|
|
22
|
-
|
|
23
|
-
if cache_entry := await config.storage.aget(cache_key, skip_cache):
|
|
24
|
-
return cache_entry.result
|
|
25
|
-
|
|
26
|
-
async with config.async_lock(cache_key):
|
|
27
|
-
if cache_entry := await config.storage.aget(cache_key, skip_cache):
|
|
28
|
-
return cache_entry.result
|
|
29
|
-
|
|
30
|
-
result = await function(*args, **kwargs)
|
|
31
|
-
await config.storage.aset(cache_key, result, None if never_die else ttl)
|
|
32
|
-
|
|
33
|
-
if never_die:
|
|
34
|
-
register_never_die_function(function, ttl, args, kwargs, cache_key_func, ignore_fields, config)
|
|
35
|
-
|
|
36
|
-
return result
|
|
37
|
-
|
|
38
|
-
return cast(F, async_wrapper)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _sync_decorator(
|
|
42
|
-
function: F,
|
|
43
|
-
ttl: Number,
|
|
44
|
-
never_die: bool,
|
|
45
|
-
cache_key_func: CacheKeyFunction | None,
|
|
46
|
-
ignore_fields: tuple[str, ...],
|
|
47
|
-
config: CacheConfig,
|
|
48
|
-
) -> F:
|
|
49
|
-
@functools.wraps(function)
|
|
50
|
-
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
51
|
-
skip_cache = kwargs.pop("skip_cache", False)
|
|
52
|
-
cache_key = create_cache_key(function, cache_key_func, ignore_fields, args, kwargs)
|
|
53
|
-
|
|
54
|
-
if cache_entry := config.storage.get(cache_key, skip_cache):
|
|
55
|
-
return cache_entry.result
|
|
56
|
-
|
|
57
|
-
with config.sync_lock(cache_key):
|
|
58
|
-
if cache_entry := config.storage.get(cache_key, skip_cache):
|
|
59
|
-
return cache_entry.result
|
|
60
|
-
|
|
61
|
-
result = function(*args, **kwargs)
|
|
62
|
-
config.storage.set(cache_key, result, None if never_die else ttl)
|
|
63
|
-
|
|
64
|
-
if never_die:
|
|
65
|
-
register_never_die_function(function, ttl, args, kwargs, cache_key_func, ignore_fields, config)
|
|
66
|
-
|
|
67
|
-
return result
|
|
68
|
-
|
|
69
|
-
return cast(F, sync_wrapper)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def base_cache(
|
|
73
|
-
ttl: Number,
|
|
74
|
-
never_die: bool,
|
|
75
|
-
cache_key_func: CacheKeyFunction | None,
|
|
76
|
-
ignore_fields: tuple[str, ...],
|
|
77
|
-
config: CacheConfig,
|
|
78
|
-
) -> Callable[[F], F]:
|
|
79
|
-
"""
|
|
80
|
-
Base cache decorator factory used by both memory and Redis cache implementations.
|
|
81
|
-
|
|
82
|
-
Args:
|
|
83
|
-
ttl: Time to live for cached items in seconds
|
|
84
|
-
never_die: If True, the cache will never expire and will be recalculated based on the ttl
|
|
85
|
-
cache_key_func: Custom cache key function, used for more complex cache scenarios
|
|
86
|
-
ignore_fields: Tuple of strings with the function params to ignore when creating the cache key
|
|
87
|
-
config: Cache configuration specifying storage, locks, and never_die registration
|
|
88
|
-
|
|
89
|
-
Features:
|
|
90
|
-
- Works for both sync and async functions
|
|
91
|
-
- Only allows one execution at a time per function+args
|
|
92
|
-
- Makes subsequent calls wait for the first call to complete
|
|
93
|
-
"""
|
|
94
|
-
if cache_key_func and ignore_fields:
|
|
95
|
-
raise ValueError("Either cache_key_func or ignore_fields can be provided, but not both")
|
|
96
|
-
|
|
97
|
-
def decorator(function: F) -> F:
|
|
98
|
-
if inspect.iscoroutinefunction(function):
|
|
99
|
-
return _async_decorator(
|
|
100
|
-
function=function,
|
|
101
|
-
ttl=ttl,
|
|
102
|
-
never_die=never_die,
|
|
103
|
-
cache_key_func=cache_key_func,
|
|
104
|
-
ignore_fields=ignore_fields,
|
|
105
|
-
config=config,
|
|
106
|
-
)
|
|
107
|
-
return _sync_decorator(
|
|
108
|
-
function=function,
|
|
109
|
-
ttl=ttl,
|
|
110
|
-
never_die=never_die,
|
|
111
|
-
cache_key_func=cache_key_func,
|
|
112
|
-
ignore_fields=ignore_fields,
|
|
113
|
-
config=config,
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
return decorator
|
|
1
|
+
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
from typing import Any, Callable, cast
|
|
4
|
+
|
|
5
|
+
from cachify.features.never_die import register_never_die_function
|
|
6
|
+
from cachify.types import CacheConfig, CacheKeyFunction, F, Number
|
|
7
|
+
from cachify.utils.arguments import create_cache_key
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _async_decorator(
|
|
11
|
+
function: F,
|
|
12
|
+
ttl: Number,
|
|
13
|
+
never_die: bool,
|
|
14
|
+
cache_key_func: CacheKeyFunction | None,
|
|
15
|
+
ignore_fields: tuple[str, ...],
|
|
16
|
+
config: CacheConfig,
|
|
17
|
+
) -> F:
|
|
18
|
+
@functools.wraps(function)
|
|
19
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
20
|
+
skip_cache = kwargs.pop("skip_cache", False)
|
|
21
|
+
cache_key = create_cache_key(function, cache_key_func, ignore_fields, args, kwargs)
|
|
22
|
+
|
|
23
|
+
if cache_entry := await config.storage.aget(cache_key, skip_cache):
|
|
24
|
+
return cache_entry.result
|
|
25
|
+
|
|
26
|
+
async with config.async_lock(cache_key):
|
|
27
|
+
if cache_entry := await config.storage.aget(cache_key, skip_cache):
|
|
28
|
+
return cache_entry.result
|
|
29
|
+
|
|
30
|
+
result = await function(*args, **kwargs)
|
|
31
|
+
await config.storage.aset(cache_key, result, None if never_die else ttl)
|
|
32
|
+
|
|
33
|
+
if never_die:
|
|
34
|
+
register_never_die_function(function, ttl, args, kwargs, cache_key_func, ignore_fields, config)
|
|
35
|
+
|
|
36
|
+
return result
|
|
37
|
+
|
|
38
|
+
return cast(F, async_wrapper)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _sync_decorator(
|
|
42
|
+
function: F,
|
|
43
|
+
ttl: Number,
|
|
44
|
+
never_die: bool,
|
|
45
|
+
cache_key_func: CacheKeyFunction | None,
|
|
46
|
+
ignore_fields: tuple[str, ...],
|
|
47
|
+
config: CacheConfig,
|
|
48
|
+
) -> F:
|
|
49
|
+
@functools.wraps(function)
|
|
50
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
51
|
+
skip_cache = kwargs.pop("skip_cache", False)
|
|
52
|
+
cache_key = create_cache_key(function, cache_key_func, ignore_fields, args, kwargs)
|
|
53
|
+
|
|
54
|
+
if cache_entry := config.storage.get(cache_key, skip_cache):
|
|
55
|
+
return cache_entry.result
|
|
56
|
+
|
|
57
|
+
with config.sync_lock(cache_key):
|
|
58
|
+
if cache_entry := config.storage.get(cache_key, skip_cache):
|
|
59
|
+
return cache_entry.result
|
|
60
|
+
|
|
61
|
+
result = function(*args, **kwargs)
|
|
62
|
+
config.storage.set(cache_key, result, None if never_die else ttl)
|
|
63
|
+
|
|
64
|
+
if never_die:
|
|
65
|
+
register_never_die_function(function, ttl, args, kwargs, cache_key_func, ignore_fields, config)
|
|
66
|
+
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
return cast(F, sync_wrapper)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def base_cache(
|
|
73
|
+
ttl: Number,
|
|
74
|
+
never_die: bool,
|
|
75
|
+
cache_key_func: CacheKeyFunction | None,
|
|
76
|
+
ignore_fields: tuple[str, ...],
|
|
77
|
+
config: CacheConfig,
|
|
78
|
+
) -> Callable[[F], F]:
|
|
79
|
+
"""
|
|
80
|
+
Base cache decorator factory used by both memory and Redis cache implementations.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
ttl: Time to live for cached items in seconds
|
|
84
|
+
never_die: If True, the cache will never expire and will be recalculated based on the ttl
|
|
85
|
+
cache_key_func: Custom cache key function, used for more complex cache scenarios
|
|
86
|
+
ignore_fields: Tuple of strings with the function params to ignore when creating the cache key
|
|
87
|
+
config: Cache configuration specifying storage, locks, and never_die registration
|
|
88
|
+
|
|
89
|
+
Features:
|
|
90
|
+
- Works for both sync and async functions
|
|
91
|
+
- Only allows one execution at a time per function+args
|
|
92
|
+
- Makes subsequent calls wait for the first call to complete
|
|
93
|
+
"""
|
|
94
|
+
if cache_key_func and ignore_fields:
|
|
95
|
+
raise ValueError("Either cache_key_func or ignore_fields can be provided, but not both")
|
|
96
|
+
|
|
97
|
+
def decorator(function: F) -> F:
|
|
98
|
+
if inspect.iscoroutinefunction(function):
|
|
99
|
+
return _async_decorator(
|
|
100
|
+
function=function,
|
|
101
|
+
ttl=ttl,
|
|
102
|
+
never_die=never_die,
|
|
103
|
+
cache_key_func=cache_key_func,
|
|
104
|
+
ignore_fields=ignore_fields,
|
|
105
|
+
config=config,
|
|
106
|
+
)
|
|
107
|
+
return _sync_decorator(
|
|
108
|
+
function=function,
|
|
109
|
+
ttl=ttl,
|
|
110
|
+
never_die=never_die,
|
|
111
|
+
cache_key_func=cache_key_func,
|
|
112
|
+
ignore_fields=ignore_fields,
|
|
113
|
+
config=config,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return decorator
|
cachify/config/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
logger = logging.getLogger("cachify")
|
|
4
|
-
logger.addHandler(logging.NullHandler())
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger("cachify")
|
|
4
|
+
logger.addHandler(logging.NullHandler())
|