cachu 0.1.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.
- cachu/__init__.py +27 -0
- cachu/backends/__init__.py +47 -0
- cachu/backends/file.py +158 -0
- cachu/backends/memory.py +102 -0
- cachu/backends/redis.py +131 -0
- cachu/cache.py +636 -0
- cachu/config.py +193 -0
- cachu/decorator.py +257 -0
- cachu/keys.py +122 -0
- cachu/operations.py +174 -0
- cachu/types.py +37 -0
- cachu-0.1.1.dist-info/METADATA +410 -0
- cachu-0.1.1.dist-info/RECORD +15 -0
- cachu-0.1.1.dist-info/WHEEL +5 -0
- cachu-0.1.1.dist-info/top_level.txt +1 -0
cachu/operations.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Cache CRUD operations.
|
|
2
|
+
"""
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
|
|
7
|
+
from .backends import NO_VALUE
|
|
8
|
+
from .config import _get_caller_package, get_config
|
|
9
|
+
from .decorator import _get_backend, get_cache_info
|
|
10
|
+
from .keys import mangle_key
|
|
11
|
+
from .types import CacheInfo, CacheMeta
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
_MISSING = object()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_meta(fn: Callable[..., Any]) -> CacheMeta:
|
|
19
|
+
"""Get CacheMeta from a decorated function.
|
|
20
|
+
"""
|
|
21
|
+
meta = getattr(fn, '_cache_meta', None)
|
|
22
|
+
if meta is None:
|
|
23
|
+
raise ValueError(f'{fn.__name__} is not decorated with @cache')
|
|
24
|
+
return meta
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def cache_get(fn: Callable[..., Any], default: Any = _MISSING, **kwargs: Any) -> Any:
|
|
28
|
+
"""Get a cached value without calling the function.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
fn: A function decorated with @cache
|
|
32
|
+
default: Value to return if not found (raises KeyError if not provided)
|
|
33
|
+
**kwargs: Function arguments to build the cache key
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
The cached value or default
|
|
37
|
+
|
|
38
|
+
Raises
|
|
39
|
+
KeyError: If not found and no default provided
|
|
40
|
+
ValueError: If function is not decorated with @cache
|
|
41
|
+
"""
|
|
42
|
+
meta = _get_meta(fn)
|
|
43
|
+
cfg = get_config(meta.package)
|
|
44
|
+
|
|
45
|
+
key_generator = fn._cache_key_generator
|
|
46
|
+
base_key = key_generator(**kwargs)
|
|
47
|
+
cache_key = mangle_key(base_key, cfg.key_prefix, meta.ttl)
|
|
48
|
+
|
|
49
|
+
backend = _get_backend(meta.package, meta.backend, meta.ttl)
|
|
50
|
+
value = backend.get(cache_key)
|
|
51
|
+
|
|
52
|
+
if value is NO_VALUE:
|
|
53
|
+
if default is _MISSING:
|
|
54
|
+
raise KeyError(f'No cached value for {fn.__name__} with {kwargs}')
|
|
55
|
+
return default
|
|
56
|
+
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def cache_set(fn: Callable[..., Any], value: Any, **kwargs: Any) -> None:
|
|
61
|
+
"""Set a cached value directly without calling the function.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
fn: A function decorated with @cache
|
|
65
|
+
value: The value to cache
|
|
66
|
+
**kwargs: Function arguments to build the cache key
|
|
67
|
+
|
|
68
|
+
Raises
|
|
69
|
+
ValueError: If function is not decorated with @cache
|
|
70
|
+
"""
|
|
71
|
+
meta = _get_meta(fn)
|
|
72
|
+
cfg = get_config(meta.package)
|
|
73
|
+
|
|
74
|
+
key_generator = fn._cache_key_generator
|
|
75
|
+
base_key = key_generator(**kwargs)
|
|
76
|
+
cache_key = mangle_key(base_key, cfg.key_prefix, meta.ttl)
|
|
77
|
+
|
|
78
|
+
backend = _get_backend(meta.package, meta.backend, meta.ttl)
|
|
79
|
+
backend.set(cache_key, value, meta.ttl)
|
|
80
|
+
|
|
81
|
+
logger.debug(f'Set cache for {fn.__name__} with key {cache_key}')
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def cache_delete(fn: Callable[..., Any], **kwargs: Any) -> None:
|
|
85
|
+
"""Delete a specific cached entry.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
fn: A function decorated with @cache
|
|
89
|
+
**kwargs: Function arguments to build the cache key
|
|
90
|
+
|
|
91
|
+
Raises
|
|
92
|
+
ValueError: If function is not decorated with @cache
|
|
93
|
+
"""
|
|
94
|
+
meta = _get_meta(fn)
|
|
95
|
+
cfg = get_config(meta.package)
|
|
96
|
+
|
|
97
|
+
key_generator = fn._cache_key_generator
|
|
98
|
+
base_key = key_generator(**kwargs)
|
|
99
|
+
cache_key = mangle_key(base_key, cfg.key_prefix, meta.ttl)
|
|
100
|
+
|
|
101
|
+
backend = _get_backend(meta.package, meta.backend, meta.ttl)
|
|
102
|
+
backend.delete(cache_key)
|
|
103
|
+
|
|
104
|
+
logger.debug(f'Deleted cache for {fn.__name__} with key {cache_key}')
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def cache_clear(
|
|
108
|
+
tag: str | None = None,
|
|
109
|
+
backend: str | None = None,
|
|
110
|
+
ttl: int | None = None,
|
|
111
|
+
package: str | None = None,
|
|
112
|
+
) -> int:
|
|
113
|
+
"""Clear cache entries matching criteria.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
tag: Clear only entries with this tag
|
|
117
|
+
backend: Backend type to clear ('memory', 'file', 'redis'). Clears all if None.
|
|
118
|
+
ttl: Specific TTL region to clear. Clears all TTLs if None.
|
|
119
|
+
package: Package to clear for. Auto-detected if None.
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
Number of entries cleared (may be approximate)
|
|
123
|
+
"""
|
|
124
|
+
if package is None:
|
|
125
|
+
package = _get_caller_package()
|
|
126
|
+
|
|
127
|
+
cfg = get_config(package)
|
|
128
|
+
|
|
129
|
+
if backend is not None:
|
|
130
|
+
backends_to_clear = [backend]
|
|
131
|
+
else:
|
|
132
|
+
backends_to_clear = ['memory', 'file', 'redis']
|
|
133
|
+
|
|
134
|
+
if tag:
|
|
135
|
+
from .keys import _normalize_tag
|
|
136
|
+
pattern = f'*|{_normalize_tag(tag)}|*'
|
|
137
|
+
else:
|
|
138
|
+
pattern = None
|
|
139
|
+
|
|
140
|
+
total_cleared = 0
|
|
141
|
+
|
|
142
|
+
from .decorator import _backends, _backends_lock
|
|
143
|
+
|
|
144
|
+
with _backends_lock:
|
|
145
|
+
for (pkg, btype, bttl), backend_instance in list(_backends.items()):
|
|
146
|
+
if pkg != package:
|
|
147
|
+
continue
|
|
148
|
+
if btype not in backends_to_clear:
|
|
149
|
+
continue
|
|
150
|
+
if ttl is not None and bttl != ttl:
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
cleared = backend_instance.clear(pattern)
|
|
154
|
+
if cleared > 0:
|
|
155
|
+
total_cleared += cleared
|
|
156
|
+
logger.debug(f'Cleared {cleared} entries from {btype} backend (ttl={bttl})')
|
|
157
|
+
|
|
158
|
+
return total_cleared
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def cache_info(fn: Callable[..., Any]) -> CacheInfo:
|
|
162
|
+
"""Get cache statistics for a decorated function.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
fn: A function decorated with @cache
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
CacheInfo with hits, misses, and currsize
|
|
169
|
+
|
|
170
|
+
Raises
|
|
171
|
+
ValueError: If function is not decorated with @cache
|
|
172
|
+
"""
|
|
173
|
+
_get_meta(fn) # Validate it's decorated
|
|
174
|
+
return get_cache_info(fn)
|
cachu/types.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Type definitions for the cache library.
|
|
2
|
+
"""
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class CacheEntry:
|
|
10
|
+
"""Cache entry metadata passed to validate callbacks.
|
|
11
|
+
"""
|
|
12
|
+
value: Any
|
|
13
|
+
created_at: float
|
|
14
|
+
age: float
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class CacheInfo:
|
|
19
|
+
"""Cache statistics for a decorated function.
|
|
20
|
+
"""
|
|
21
|
+
hits: int
|
|
22
|
+
misses: int
|
|
23
|
+
currsize: int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class CacheMeta:
|
|
28
|
+
"""Metadata attached to cached functions.
|
|
29
|
+
"""
|
|
30
|
+
ttl: int
|
|
31
|
+
backend: str
|
|
32
|
+
tag: str
|
|
33
|
+
exclude: set[str]
|
|
34
|
+
cache_if: Callable[[Any], bool] | None
|
|
35
|
+
validate: Callable[[CacheEntry], bool] | None
|
|
36
|
+
package: str
|
|
37
|
+
key_generator: Callable[..., str]
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cachu
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Flexible caching library built on dogpile.cache
|
|
5
|
+
Author: bissli
|
|
6
|
+
License-Expression: 0BSD
|
|
7
|
+
Project-URL: Repository, https://github.com/bissli/cachu.git
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: dogpile.cache
|
|
11
|
+
Requires-Dist: func-timeout
|
|
12
|
+
Provides-Extra: redis
|
|
13
|
+
Requires-Dist: redis; extra == "redis"
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest; extra == "test"
|
|
16
|
+
Requires-Dist: pytest-mock; extra == "test"
|
|
17
|
+
Requires-Dist: testcontainers[redis]; extra == "test"
|
|
18
|
+
|
|
19
|
+
# cachu
|
|
20
|
+
|
|
21
|
+
Flexible caching library with support for memory, file, and Redis backends.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
**Basic installation:**
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install git+https://github.com/bissli/cachu.git
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**With Redis support:**
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install git+https://github.com/bissli/cachu.git#egg=cachu[redis]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import cachu
|
|
41
|
+
|
|
42
|
+
# Configure once at startup
|
|
43
|
+
cachu.configure(backend='memory', key_prefix='v1:')
|
|
44
|
+
|
|
45
|
+
# Use the @cache decorator
|
|
46
|
+
@cachu.cache(ttl=300)
|
|
47
|
+
def get_user(user_id: int) -> dict:
|
|
48
|
+
return fetch_from_database(user_id)
|
|
49
|
+
|
|
50
|
+
# Cached automatically
|
|
51
|
+
user = get_user(123) # Cache miss - fetches from DB
|
|
52
|
+
user = get_user(123) # Cache hit - returns cached value
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
Configure cache settings at application startup:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import cachu
|
|
61
|
+
|
|
62
|
+
cachu.configure(
|
|
63
|
+
backend='memory', # Default backend: 'memory', 'file', or 'redis'
|
|
64
|
+
key_prefix='v1:', # Prefix for all cache keys
|
|
65
|
+
file_dir='/var/cache/app', # Directory for file cache
|
|
66
|
+
redis_url='redis://localhost:6379/0', # Redis connection URL
|
|
67
|
+
redis_distributed=False, # Use distributed locks for Redis
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Configuration Options
|
|
72
|
+
|
|
73
|
+
| Option | Default | Description |
|
|
74
|
+
|--------|---------|-------------|
|
|
75
|
+
| `backend` | `'memory'` | Default backend type |
|
|
76
|
+
| `key_prefix` | `''` | Prefix for all cache keys (useful for versioning) |
|
|
77
|
+
| `file_dir` | `'/tmp'` | Directory for file-based caches |
|
|
78
|
+
| `redis_url` | `'redis://localhost:6379/0'` | Redis connection URL |
|
|
79
|
+
| `redis_distributed` | `False` | Enable distributed locks for Redis |
|
|
80
|
+
|
|
81
|
+
### Package Isolation
|
|
82
|
+
|
|
83
|
+
Each package automatically gets isolated configuration. This prevents conflicts when multiple libraries use the cachu package:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# In library_a/config.py
|
|
87
|
+
import cachu
|
|
88
|
+
cachu.configure(key_prefix='lib_a:', redis_url='redis://redis-a:6379/0')
|
|
89
|
+
|
|
90
|
+
# In library_b/config.py
|
|
91
|
+
import cachu
|
|
92
|
+
cachu.configure(key_prefix='lib_b:', redis_url='redis://redis-b:6379/0')
|
|
93
|
+
|
|
94
|
+
# Each library uses its own configuration automatically
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Retrieve configuration:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
cfg = cachu.get_config() # Current package's config
|
|
101
|
+
cfg = cachu.get_config(package='mylib') # Specific package's config
|
|
102
|
+
all_configs = cachu.get_all_configs() # All configurations
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Usage
|
|
106
|
+
|
|
107
|
+
### Basic Caching
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from cachu import cachu
|
|
111
|
+
|
|
112
|
+
@cache(ttl=300, backend='memory')
|
|
113
|
+
def expensive_operation(param: str) -> dict:
|
|
114
|
+
return compute_result(param)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Backend Types
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
# Memory cache (default)
|
|
121
|
+
@cache(ttl=300, backend='memory')
|
|
122
|
+
def fast_lookup(key: str) -> str:
|
|
123
|
+
return fetch(key)
|
|
124
|
+
|
|
125
|
+
# File cache (persists across restarts)
|
|
126
|
+
@cache(ttl=3600, backend='file')
|
|
127
|
+
def load_config(name: str) -> dict:
|
|
128
|
+
return parse_config_file(name)
|
|
129
|
+
|
|
130
|
+
# Redis cache (shared across processes)
|
|
131
|
+
@cache(ttl=86400, backend='redis')
|
|
132
|
+
def fetch_external_data(api_key: str) -> dict:
|
|
133
|
+
return call_external_api(api_key)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Tags for Grouping
|
|
137
|
+
|
|
138
|
+
Tags organize cache entries into logical groups for selective clearing:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
@cache(ttl=300, tag='users')
|
|
142
|
+
def get_user(user_id: int) -> dict:
|
|
143
|
+
return fetch_user(user_id)
|
|
144
|
+
|
|
145
|
+
@cache(ttl=300, tag='products')
|
|
146
|
+
def get_product(product_id: int) -> dict:
|
|
147
|
+
return fetch_product(product_id)
|
|
148
|
+
|
|
149
|
+
# Clear only user caches
|
|
150
|
+
cachu.cache_clear(tag='users', backend='memory', ttl=300)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Conditional Caching
|
|
154
|
+
|
|
155
|
+
Cache results only when a condition is met:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# Don't cache None results
|
|
159
|
+
@cache(ttl=300, cache_if=lambda result: result is not None)
|
|
160
|
+
def find_user(email: str) -> dict | None:
|
|
161
|
+
return db.find_by_email(email)
|
|
162
|
+
|
|
163
|
+
# Don't cache empty lists
|
|
164
|
+
@cache(ttl=300, cache_if=lambda result: len(result) > 0)
|
|
165
|
+
def search(query: str) -> list:
|
|
166
|
+
return db.search(query)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Validation Callbacks
|
|
170
|
+
|
|
171
|
+
Validate cached entries before returning:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
@cache(ttl=3600, validate=lambda entry: entry.age < 1800)
|
|
175
|
+
def get_price(symbol: str) -> float:
|
|
176
|
+
# TTL is 1 hour, but recompute after 30 minutes
|
|
177
|
+
return fetch_live_price(symbol)
|
|
178
|
+
|
|
179
|
+
# Validate based on value
|
|
180
|
+
def check_version(entry):
|
|
181
|
+
return entry.value.get('version') == CURRENT_VERSION
|
|
182
|
+
|
|
183
|
+
@cache(ttl=86400, validate=check_version)
|
|
184
|
+
def get_config() -> dict:
|
|
185
|
+
return load_config()
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The `entry` parameter is a `CacheEntry` with:
|
|
189
|
+
- `value`: The cached value
|
|
190
|
+
- `created_at`: Unix timestamp when cached
|
|
191
|
+
- `age`: Seconds since creation
|
|
192
|
+
|
|
193
|
+
### Per-Call Control
|
|
194
|
+
|
|
195
|
+
Control caching behavior for individual calls:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
@cache(ttl=300)
|
|
199
|
+
def get_data(id: int) -> dict:
|
|
200
|
+
return fetch(id)
|
|
201
|
+
|
|
202
|
+
# Normal call - uses cache
|
|
203
|
+
result = get_data(123)
|
|
204
|
+
|
|
205
|
+
# Skip cache for this call only (don't read or write cache)
|
|
206
|
+
result = get_data(123, _skip_cache=True)
|
|
207
|
+
|
|
208
|
+
# Force refresh - execute and overwrite cached value
|
|
209
|
+
result = get_data(123, _overwrite_cache=True)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Cache Statistics
|
|
213
|
+
|
|
214
|
+
Track hits and misses:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
@cache(ttl=300)
|
|
218
|
+
def get_user(user_id: int) -> dict:
|
|
219
|
+
return fetch_user(user_id)
|
|
220
|
+
|
|
221
|
+
# After some usage
|
|
222
|
+
info = cachu.cache_info(get_user)
|
|
223
|
+
print(f"Hits: {info.hits}, Misses: {info.misses}, Size: {info.currsize}")
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Excluding Parameters
|
|
227
|
+
|
|
228
|
+
Exclude parameters from the cache key:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
@cache(ttl=300, exclude={'logger', 'context'})
|
|
232
|
+
def process_data(logger, context, user_id: int, data: str) -> dict:
|
|
233
|
+
logger.info(f"Processing for user {user_id}")
|
|
234
|
+
return compute(data)
|
|
235
|
+
|
|
236
|
+
# Different logger/context values use the same cache entry
|
|
237
|
+
process_data(logger1, ctx1, 123, 'test') # Cache miss
|
|
238
|
+
process_data(logger2, ctx2, 123, 'test') # Cache hit
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Automatic filtering**: The library automatically excludes:
|
|
242
|
+
- `self` and `cls` parameters
|
|
243
|
+
- Parameters starting with underscore (`_`)
|
|
244
|
+
- Database connection objects
|
|
245
|
+
|
|
246
|
+
## CRUD Operations
|
|
247
|
+
|
|
248
|
+
### Direct Cache Manipulation
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
from cachu import cache_get, cache_set, cache_delete, cache_clear
|
|
252
|
+
|
|
253
|
+
@cache(ttl=300, tag='users')
|
|
254
|
+
def get_user(user_id: int) -> dict:
|
|
255
|
+
return fetch_user(user_id)
|
|
256
|
+
|
|
257
|
+
# Get cached value without calling function
|
|
258
|
+
user = cache_get(get_user, user_id=123, default=None)
|
|
259
|
+
|
|
260
|
+
# Set cache value directly
|
|
261
|
+
cache_set(get_user, {'id': 123, 'name': 'Updated'}, user_id=123)
|
|
262
|
+
|
|
263
|
+
# Delete specific cache entry
|
|
264
|
+
cache_delete(get_user, user_id=123)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Clearing Caches
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
from cachu import cachu_clear
|
|
271
|
+
|
|
272
|
+
# Clear specific region
|
|
273
|
+
cache_clear(backend='memory', ttl=300)
|
|
274
|
+
|
|
275
|
+
# Clear by tag
|
|
276
|
+
cache_clear(tag='users', backend='memory', ttl=300)
|
|
277
|
+
|
|
278
|
+
# Clear all TTLs for a backend
|
|
279
|
+
cache_clear(backend='memory')
|
|
280
|
+
|
|
281
|
+
# Clear everything
|
|
282
|
+
cache_clear()
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Clearing behavior:**
|
|
286
|
+
|
|
287
|
+
| `ttl` | `tag` | `backend` | Behavior |
|
|
288
|
+
|-------|-------|-----------|----------|
|
|
289
|
+
| `300` | `None` | `'memory'` | All keys in 300s memory region |
|
|
290
|
+
| `300` | `'users'` | `'memory'` | Only "users" tag in 300s memory region |
|
|
291
|
+
| `None` | `None` | `'memory'` | All memory regions |
|
|
292
|
+
| `None` | `'users'` | `None` | "users" tag across all backends |
|
|
293
|
+
|
|
294
|
+
### Cross-Module Clearing
|
|
295
|
+
|
|
296
|
+
When clearing from a different module, use the `package` parameter:
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
# In myapp/service.py
|
|
300
|
+
@cache(ttl=300)
|
|
301
|
+
def get_data(id: int) -> dict:
|
|
302
|
+
return fetch(id)
|
|
303
|
+
|
|
304
|
+
# In tests/conftest.py
|
|
305
|
+
cachu.cache_clear(backend='memory', ttl=300, package='myapp')
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Instance and Class Methods
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
class UserRepository:
|
|
312
|
+
def __init__(self, db):
|
|
313
|
+
self.db = db
|
|
314
|
+
|
|
315
|
+
@cache(ttl=300)
|
|
316
|
+
def get_user(self, user_id: int) -> dict:
|
|
317
|
+
return self.db.fetch(user_id)
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
@cache(ttl=300)
|
|
321
|
+
def get_default_user(cls) -> dict:
|
|
322
|
+
return cls.DEFAULT_USER
|
|
323
|
+
|
|
324
|
+
@staticmethod
|
|
325
|
+
@cache(ttl=300)
|
|
326
|
+
def get_guest() -> dict:
|
|
327
|
+
return {'id': 0, 'name': 'Guest'}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Testing
|
|
331
|
+
|
|
332
|
+
Disable caching globally for tests:
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
import cachu
|
|
336
|
+
import pytest
|
|
337
|
+
|
|
338
|
+
@pytest.fixture(autouse=True)
|
|
339
|
+
def disable_caching():
|
|
340
|
+
cachu.disable()
|
|
341
|
+
yield
|
|
342
|
+
cachu.enable()
|
|
343
|
+
|
|
344
|
+
# Check state
|
|
345
|
+
if cachu.is_disabled():
|
|
346
|
+
print("Caching is disabled")
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Advanced
|
|
350
|
+
|
|
351
|
+
### Direct Backend Access
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
from cachu import get_backend
|
|
355
|
+
|
|
356
|
+
backend = get_backend('memory', ttl=300)
|
|
357
|
+
backend.set('my_key', {'data': 'value'}, ttl=300)
|
|
358
|
+
value = backend.get('my_key')
|
|
359
|
+
backend.delete('my_key')
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Redis Client Access
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
from cachu import get_redis_client
|
|
366
|
+
|
|
367
|
+
client = get_redis_client()
|
|
368
|
+
client.set('direct_key', 'value')
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Public API
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
from cachu import (
|
|
375
|
+
# Configuration
|
|
376
|
+
configure,
|
|
377
|
+
get_config,
|
|
378
|
+
get_all_configs,
|
|
379
|
+
disable,
|
|
380
|
+
enable,
|
|
381
|
+
is_disabled,
|
|
382
|
+
|
|
383
|
+
# Decorator
|
|
384
|
+
cache,
|
|
385
|
+
|
|
386
|
+
# CRUD Operations
|
|
387
|
+
cache_get,
|
|
388
|
+
cache_set,
|
|
389
|
+
cache_delete,
|
|
390
|
+
cache_clear,
|
|
391
|
+
cache_info,
|
|
392
|
+
|
|
393
|
+
# Advanced
|
|
394
|
+
get_backend,
|
|
395
|
+
get_redis_client,
|
|
396
|
+
)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## Features
|
|
400
|
+
|
|
401
|
+
- **Multiple backends**: Memory, file (DBM), and Redis
|
|
402
|
+
- **Flexible TTL**: Configure different TTLs for different use cases
|
|
403
|
+
- **Tags**: Organize and selectively clear cache entries
|
|
404
|
+
- **Package isolation**: Each package gets isolated configuration
|
|
405
|
+
- **Conditional caching**: Cache based on result value
|
|
406
|
+
- **Validation callbacks**: Validate entries before returning
|
|
407
|
+
- **Per-call control**: Skip or overwrite cache per call
|
|
408
|
+
- **Statistics**: Track hits, misses, and cache size
|
|
409
|
+
- **Intelligent filtering**: Auto-excludes `self`, `cls`, connections, and `_` params
|
|
410
|
+
- **Global disable**: Bypass all caching for testing
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
cachu/__init__.py,sha256=snSKE_XCc1SikF_FiyolTjTVc5E52QtXjvaXEQJqHs0,676
|
|
2
|
+
cachu/cache.py,sha256=UOh1hsvo5wqpf-quU0glGZi5bgjF8gkaSmYIkAwWfUA,23362
|
|
3
|
+
cachu/config.py,sha256=KtcDGpSTJmjRrcNLz9_Om3O814oJJ3p8gntB84Pd6Dk,5922
|
|
4
|
+
cachu/decorator.py,sha256=FqD-On66WYYIAOWIe-umlILFc8XcuE8IQHTV6MnFA9o,8254
|
|
5
|
+
cachu/keys.py,sha256=fwwNOpnDJFCIWZoQ5UGJWhJa6xu36hsBsURI-n2NJKU,3557
|
|
6
|
+
cachu/operations.py,sha256=_hHFo9mBgsGT_45-08rprddxNhnGAezCFbQAR_CgI80,5001
|
|
7
|
+
cachu/types.py,sha256=FghBN5GhxnrpuT4WUL9iNnAfdoH__cw9_Ag4kHbIXq4,723
|
|
8
|
+
cachu/backends/__init__.py,sha256=dM6NfSRXMCOeTg9A9-scgiT_6r_BfzbmT1GVNqL6egU,1228
|
|
9
|
+
cachu/backends/file.py,sha256=2ssQmqvpLRDLX21joXRZKxfHekjAjb5gd_gHt52SgVA,5313
|
|
10
|
+
cachu/backends/memory.py,sha256=kIgrVU8k_3Aquyj2PDf8IPbTjCITM_0V5GU47m3fJmo,3138
|
|
11
|
+
cachu/backends/redis.py,sha256=yE5rEBgOij9QOeC1VhWdIbGCgi442q-aWfmbbG4aNSE,3858
|
|
12
|
+
cachu-0.1.1.dist-info/METADATA,sha256=G8ue2kOMja2ER0KKUjKDDjVetNYS7OKJcQp1r-oWYY4,9629
|
|
13
|
+
cachu-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
cachu-0.1.1.dist-info/top_level.txt,sha256=g80nNoMvLMzhSwQWV-JotCBqtsLAHeFMBo_g8hCK8hQ,6
|
|
15
|
+
cachu-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cachu
|