cachu 0.1.2__tar.gz → 0.1.3__tar.gz
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.
- {cachu-0.1.2 → cachu-0.1.3}/PKG-INFO +14 -3
- {cachu-0.1.2 → cachu-0.1.3}/README.md +13 -2
- {cachu-0.1.2 → cachu-0.1.3}/pyproject.toml +1 -1
- {cachu-0.1.2 → cachu-0.1.3}/setup.cfg +1 -1
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu/__init__.py +1 -1
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu/backends/file.py +2 -2
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu/decorator.py +1 -6
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu/operations.py +24 -16
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu.egg-info/PKG-INFO +14 -3
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_clearing.py +61 -0
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu/backends/__init__.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu/backends/memory.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu/backends/redis.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu/config.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu/keys.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu/types.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu.egg-info/SOURCES.txt +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu.egg-info/dependency_links.txt +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu.egg-info/requires.txt +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/src/cachu.egg-info/top_level.txt +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_config.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_defaultcache.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_delete_keys.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_disable.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_exclude_params.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_file_cache.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_integration.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_memory_cache.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_namespace.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_namespace_isolation.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_redis_cache.py +0 -0
- {cachu-0.1.2 → cachu-0.1.3}/tests/test_set_keys.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cachu
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Flexible caching library built on dogpile.cache
|
|
5
5
|
Author: bissli
|
|
6
6
|
License-Expression: 0BSD
|
|
@@ -81,7 +81,7 @@ cachu.configure(
|
|
|
81
81
|
|
|
82
82
|
### Package Isolation
|
|
83
83
|
|
|
84
|
-
Each package automatically gets isolated configuration
|
|
84
|
+
Each package automatically gets isolated configuration, preventing conflicts when multiple libraries use cachu:
|
|
85
85
|
|
|
86
86
|
```python
|
|
87
87
|
# In library_a/config.py
|
|
@@ -92,7 +92,18 @@ cachu.configure(key_prefix='lib_a:', redis_url='redis://redis-a:6379/0')
|
|
|
92
92
|
import cachu
|
|
93
93
|
cachu.configure(key_prefix='lib_b:', redis_url='redis://redis-b:6379/0')
|
|
94
94
|
|
|
95
|
-
# Each library
|
|
95
|
+
# Each library's @cache calls use its own configuration automatically
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
To override the automatic detection, specify the `package` parameter:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from cachu import cache
|
|
102
|
+
|
|
103
|
+
# This function will use library_a's configuration
|
|
104
|
+
@cache(ttl=300, package='library_a')
|
|
105
|
+
def get_shared_data(id: int) -> dict:
|
|
106
|
+
return fetch(id)
|
|
96
107
|
```
|
|
97
108
|
|
|
98
109
|
Retrieve configuration:
|
|
@@ -62,7 +62,7 @@ cachu.configure(
|
|
|
62
62
|
|
|
63
63
|
### Package Isolation
|
|
64
64
|
|
|
65
|
-
Each package automatically gets isolated configuration
|
|
65
|
+
Each package automatically gets isolated configuration, preventing conflicts when multiple libraries use cachu:
|
|
66
66
|
|
|
67
67
|
```python
|
|
68
68
|
# In library_a/config.py
|
|
@@ -73,7 +73,18 @@ cachu.configure(key_prefix='lib_a:', redis_url='redis://redis-a:6379/0')
|
|
|
73
73
|
import cachu
|
|
74
74
|
cachu.configure(key_prefix='lib_b:', redis_url='redis://redis-b:6379/0')
|
|
75
75
|
|
|
76
|
-
# Each library
|
|
76
|
+
# Each library's @cache calls use its own configuration automatically
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
To override the automatic detection, specify the `package` parameter:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from cachu import cache
|
|
83
|
+
|
|
84
|
+
# This function will use library_a's configuration
|
|
85
|
+
@cache(ttl=300, package='library_a')
|
|
86
|
+
def get_shared_data(id: int) -> dict:
|
|
87
|
+
return fetch(id)
|
|
77
88
|
```
|
|
78
89
|
|
|
79
90
|
Retrieve configuration:
|
|
@@ -115,7 +115,7 @@ class FileBackend(Backend):
|
|
|
115
115
|
|
|
116
116
|
with dbm.open(self._filepath, 'c') as db:
|
|
117
117
|
keys_to_delete = [
|
|
118
|
-
k for k in db
|
|
118
|
+
k for k in db.keys()
|
|
119
119
|
if fnmatch.fnmatch(k.decode(), pattern)
|
|
120
120
|
]
|
|
121
121
|
for key in keys_to_delete:
|
|
@@ -131,7 +131,7 @@ class FileBackend(Backend):
|
|
|
131
131
|
with self._lock:
|
|
132
132
|
try:
|
|
133
133
|
with dbm.open(self._filepath, 'c') as db:
|
|
134
|
-
all_keys = [k.decode() for k in db]
|
|
134
|
+
all_keys = [k.decode() for k in db.keys()]
|
|
135
135
|
except Exception:
|
|
136
136
|
return
|
|
137
137
|
|
|
@@ -220,12 +220,7 @@ def get_cache_info(fn: Callable[..., Any]) -> CacheInfo:
|
|
|
220
220
|
Returns
|
|
221
221
|
CacheInfo with hits, misses, and currsize
|
|
222
222
|
"""
|
|
223
|
-
|
|
224
|
-
actual_fn = fn
|
|
225
|
-
else:
|
|
226
|
-
actual_fn = fn
|
|
227
|
-
|
|
228
|
-
fn_id = id(actual_fn)
|
|
223
|
+
fn_id = id(fn)
|
|
229
224
|
|
|
230
225
|
with _stats_lock:
|
|
231
226
|
hits, misses = _stats.get(fn_id, (0, 0))
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Cache CRUD operations.
|
|
2
2
|
"""
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Any
|
|
5
4
|
from collections.abc import Callable
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
from .backends import NO_VALUE
|
|
8
8
|
from .config import _get_caller_package, get_config
|
|
@@ -124,8 +124,6 @@ def cache_clear(
|
|
|
124
124
|
if package is None:
|
|
125
125
|
package = _get_caller_package()
|
|
126
126
|
|
|
127
|
-
cfg = get_config(package)
|
|
128
|
-
|
|
129
127
|
if backend is not None:
|
|
130
128
|
backends_to_clear = [backend]
|
|
131
129
|
else:
|
|
@@ -141,19 +139,29 @@ def cache_clear(
|
|
|
141
139
|
|
|
142
140
|
from .decorator import _backends, _backends_lock
|
|
143
141
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
142
|
+
# When both backend and ttl are specified, directly get/create and clear that backend.
|
|
143
|
+
# This is essential for distributed caches (Redis) where cache_clear may be called
|
|
144
|
+
# from a different process than the one that populated the cache.
|
|
145
|
+
if backend is not None and ttl is not None:
|
|
146
|
+
backend_instance = _get_backend(package, backend, ttl)
|
|
147
|
+
cleared = backend_instance.clear(pattern)
|
|
148
|
+
if cleared > 0:
|
|
149
|
+
total_cleared += cleared
|
|
150
|
+
logger.debug(f'Cleared {cleared} entries from {backend} backend (ttl={ttl})')
|
|
151
|
+
else:
|
|
152
|
+
with _backends_lock:
|
|
153
|
+
for (pkg, btype, bttl), backend_instance in list(_backends.items()):
|
|
154
|
+
if pkg != package:
|
|
155
|
+
continue
|
|
156
|
+
if btype not in backends_to_clear:
|
|
157
|
+
continue
|
|
158
|
+
if ttl is not None and bttl != ttl:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
cleared = backend_instance.clear(pattern)
|
|
162
|
+
if cleared > 0:
|
|
163
|
+
total_cleared += cleared
|
|
164
|
+
logger.debug(f'Cleared {cleared} entries from {btype} backend (ttl={bttl})')
|
|
157
165
|
|
|
158
166
|
return total_cleared
|
|
159
167
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cachu
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Flexible caching library built on dogpile.cache
|
|
5
5
|
Author: bissli
|
|
6
6
|
License-Expression: 0BSD
|
|
@@ -81,7 +81,7 @@ cachu.configure(
|
|
|
81
81
|
|
|
82
82
|
### Package Isolation
|
|
83
83
|
|
|
84
|
-
Each package automatically gets isolated configuration
|
|
84
|
+
Each package automatically gets isolated configuration, preventing conflicts when multiple libraries use cachu:
|
|
85
85
|
|
|
86
86
|
```python
|
|
87
87
|
# In library_a/config.py
|
|
@@ -92,7 +92,18 @@ cachu.configure(key_prefix='lib_a:', redis_url='redis://redis-a:6379/0')
|
|
|
92
92
|
import cachu
|
|
93
93
|
cachu.configure(key_prefix='lib_b:', redis_url='redis://redis-b:6379/0')
|
|
94
94
|
|
|
95
|
-
# Each library
|
|
95
|
+
# Each library's @cache calls use its own configuration automatically
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
To override the automatic detection, specify the `package` parameter:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from cachu import cache
|
|
102
|
+
|
|
103
|
+
# This function will use library_a's configuration
|
|
104
|
+
@cache(ttl=300, package='library_a')
|
|
105
|
+
def get_shared_data(id: int) -> dict:
|
|
106
|
+
return fetch(id)
|
|
96
107
|
```
|
|
97
108
|
|
|
98
109
|
Retrieve configuration:
|
|
@@ -111,3 +111,64 @@ def test_cache_clear_redis_by_tag(redis_docker):
|
|
|
111
111
|
get_product(1)
|
|
112
112
|
|
|
113
113
|
cachu.cache_clear(tag='users', backend='redis', ttl=300)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_cache_clear_without_instantiated_backend():
|
|
117
|
+
"""Verify cache_clear creates backend when none exists.
|
|
118
|
+
|
|
119
|
+
This tests that cache_clear() properly creates a backend instance when
|
|
120
|
+
both backend and ttl are specified, even if no cached function has been called.
|
|
121
|
+
|
|
122
|
+
This is essential for distributed caches (Redis) where cache_clear may be called
|
|
123
|
+
from a different process than the one that populated the cache.
|
|
124
|
+
"""
|
|
125
|
+
from cachu.decorator import _backends, clear_backends
|
|
126
|
+
|
|
127
|
+
# Clear all backends to simulate a fresh process
|
|
128
|
+
clear_backends()
|
|
129
|
+
|
|
130
|
+
# Verify no backends exist (simulates import script that hasn't called cached functions)
|
|
131
|
+
assert len(_backends) == 0
|
|
132
|
+
|
|
133
|
+
# Call cache_clear with specific backend and ttl
|
|
134
|
+
# Before the fix, this would do nothing because no backend existed
|
|
135
|
+
# After the fix, this should create the backend and attempt to clear it
|
|
136
|
+
cachu.cache_clear(backend='memory', ttl=999, tag='test_tag')
|
|
137
|
+
|
|
138
|
+
# With the fix, a backend should have been created
|
|
139
|
+
assert len(_backends) == 1
|
|
140
|
+
key = list(_backends.keys())[0]
|
|
141
|
+
assert key[1] == 'memory' # backend type
|
|
142
|
+
assert key[2] == 999 # ttl
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_cache_clear_creates_backend_and_clears(temp_cache_dir):
|
|
146
|
+
"""Verify cache_clear can clear data in file backend without prior instantiation.
|
|
147
|
+
|
|
148
|
+
File backend persists data to disk, allowing us to verify that cache_clear
|
|
149
|
+
can find and delete cached data even when called from a 'fresh' process state.
|
|
150
|
+
"""
|
|
151
|
+
from cachu.backends import NO_VALUE
|
|
152
|
+
from cachu.config import _get_caller_package
|
|
153
|
+
from cachu.decorator import _backends, _get_backend, clear_backends
|
|
154
|
+
|
|
155
|
+
package = _get_caller_package()
|
|
156
|
+
|
|
157
|
+
# First, create some cached data in a file backend
|
|
158
|
+
backend = _get_backend(package, 'file', 888)
|
|
159
|
+
backend.set('14m:test_func||file_tag||x=1', 'test_value', 888)
|
|
160
|
+
assert backend.get('14m:test_func||file_tag||x=1') == 'test_value'
|
|
161
|
+
|
|
162
|
+
# Clear backend instances (but file data persists on disk)
|
|
163
|
+
clear_backends()
|
|
164
|
+
assert len(_backends) == 0
|
|
165
|
+
|
|
166
|
+
# cache_clear should create a new backend instance and clear the persisted data
|
|
167
|
+
cleared = cachu.cache_clear(backend='file', ttl=888, tag='file_tag')
|
|
168
|
+
|
|
169
|
+
# Backend should have been created
|
|
170
|
+
assert len(_backends) == 1
|
|
171
|
+
|
|
172
|
+
# Data should have been cleared (verify by getting a fresh backend)
|
|
173
|
+
backend = _get_backend(package, 'file', 888)
|
|
174
|
+
assert backend.get('14m:test_func||file_tag||x=1') is NO_VALUE
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|