cacheado 1.0.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.
- cache.py +678 -0
- cache_policies/__init__.py +0 -0
- cache_policies/cache_policy_manager.py +187 -0
- cache_scopes/__init__.py +0 -0
- cache_scopes/scope_config.py +228 -0
- cache_types.py +6 -0
- cacheado-1.0.1.dist-info/METADATA +553 -0
- cacheado-1.0.1.dist-info/RECORD +21 -0
- cacheado-1.0.1.dist-info/WHEEL +5 -0
- cacheado-1.0.1.dist-info/licenses/LICENSE +21 -0
- cacheado-1.0.1.dist-info/top_level.txt +7 -0
- eviction_policies/__init__.py +0 -0
- eviction_policies/lre_eviction.py +130 -0
- protocols/__init__.py +0 -0
- protocols/cache.py +183 -0
- protocols/cache_policy_manager_protocol.py +82 -0
- protocols/eviction_policy.py +70 -0
- protocols/scope.py +85 -0
- protocols/storage_provider.py +67 -0
- storages/__init__.py +0 -0
- storages/in_memory.py +109 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from collections import OrderedDict, defaultdict
|
|
3
|
+
from typing import DefaultDict, Optional
|
|
4
|
+
|
|
5
|
+
from cache_types import _CacheKey
|
|
6
|
+
from protocols.eviction_policy import IEvictionPolicy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LRUEvictionPolicy(IEvictionPolicy):
|
|
10
|
+
"""
|
|
11
|
+
Implements IEvictionPolicy using a thread-safe Least Recently Used (LRU) strategy.
|
|
12
|
+
Optimized with __slots__ for memory efficiency.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__slots__ = ("_lock", "_lru_tracker", "_namespaced_lru_trackers")
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
"""Initializes the LRU policy trackers and lock."""
|
|
19
|
+
self._lock = threading.Lock()
|
|
20
|
+
self._lru_tracker: OrderedDict[_CacheKey, None] = OrderedDict()
|
|
21
|
+
self._namespaced_lru_trackers: DefaultDict[str, OrderedDict[_CacheKey, None]] = defaultdict(OrderedDict)
|
|
22
|
+
|
|
23
|
+
def notify_set(
|
|
24
|
+
self, key: _CacheKey, namespace: str, max_items: Optional[int], global_max_size: Optional[int]
|
|
25
|
+
) -> Optional[_CacheKey]:
|
|
26
|
+
"""
|
|
27
|
+
Adds an item to LRU trackers and evicts an old item if limits are hit.
|
|
28
|
+
|
|
29
|
+
Logic:
|
|
30
|
+
1. Adds the new key to both global and namespace-specific LRU trackers
|
|
31
|
+
2. Checks namespace limit first - if exceeded, evicts oldest item from namespace
|
|
32
|
+
3. If no namespace eviction, checks global limit - if exceeded, evicts globally oldest item
|
|
33
|
+
4. Removes evicted key from all relevant trackers to maintain consistency
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
key (_CacheKey): The key that was set.
|
|
37
|
+
namespace (str): The namespace of the key.
|
|
38
|
+
max_items (Optional[int]): The max_items limit for this namespace.
|
|
39
|
+
global_max_size (Optional[int]): The global max_size limit.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Optional[_CacheKey]: The key to evict, or None.
|
|
43
|
+
"""
|
|
44
|
+
with self._lock:
|
|
45
|
+
self._lru_tracker[key] = None
|
|
46
|
+
if namespace:
|
|
47
|
+
self._namespaced_lru_trackers[namespace][key] = None
|
|
48
|
+
|
|
49
|
+
key_to_evict: Optional[_CacheKey] = None
|
|
50
|
+
|
|
51
|
+
if max_items is not None:
|
|
52
|
+
ns_tracker = self._namespaced_lru_trackers[namespace]
|
|
53
|
+
if len(ns_tracker) > max_items:
|
|
54
|
+
try:
|
|
55
|
+
key_to_evict, _ = ns_tracker.popitem(last=False)
|
|
56
|
+
except (KeyError, Exception):
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
if key_to_evict is None and global_max_size is not None:
|
|
60
|
+
if len(self._lru_tracker) > global_max_size:
|
|
61
|
+
try:
|
|
62
|
+
key_to_evict, _ = self._lru_tracker.popitem(last=False)
|
|
63
|
+
except KeyError:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
if key_to_evict:
|
|
67
|
+
self._lru_tracker.pop(key_to_evict, None)
|
|
68
|
+
|
|
69
|
+
evicted_ns = key_to_evict[1]
|
|
70
|
+
if evicted_ns in self._namespaced_lru_trackers:
|
|
71
|
+
self._namespaced_lru_trackers[evicted_ns].pop(key_to_evict, None)
|
|
72
|
+
|
|
73
|
+
return key_to_evict
|
|
74
|
+
|
|
75
|
+
def notify_get(self, key: _CacheKey, namespace: str) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Moves the accessed item to the end (MRU) of the LRU trackers.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
key (_CacheKey): The key that was accessed.
|
|
81
|
+
namespace (str): The namespace of the key.
|
|
82
|
+
"""
|
|
83
|
+
with self._lock:
|
|
84
|
+
try:
|
|
85
|
+
self._lru_tracker.move_to_end(key)
|
|
86
|
+
if namespace in self._namespaced_lru_trackers:
|
|
87
|
+
self._namespaced_lru_trackers[namespace].move_to_end(key)
|
|
88
|
+
except (KeyError, Exception):
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
def notify_evict(self, key: _CacheKey, namespace: str) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Removes an item from all LRU trackers.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
key (_CacheKey): The key that was evicted.
|
|
97
|
+
namespace (str): The namespace of the key.
|
|
98
|
+
"""
|
|
99
|
+
with self._lock:
|
|
100
|
+
self._lru_tracker.pop(key, None)
|
|
101
|
+
if namespace in self._namespaced_lru_trackers:
|
|
102
|
+
self._namespaced_lru_trackers[namespace].pop(key, None)
|
|
103
|
+
if not self._namespaced_lru_trackers[namespace]:
|
|
104
|
+
del self._namespaced_lru_trackers[namespace]
|
|
105
|
+
|
|
106
|
+
def notify_clear(self) -> None:
|
|
107
|
+
"""Clears all LRU trackers."""
|
|
108
|
+
with self._lock:
|
|
109
|
+
self._lru_tracker.clear()
|
|
110
|
+
self._namespaced_lru_trackers.clear()
|
|
111
|
+
|
|
112
|
+
def get_namespace_count(self) -> int:
|
|
113
|
+
"""
|
|
114
|
+
Returns the total number of tracked namespaces.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
int: The count of namespaces.
|
|
118
|
+
"""
|
|
119
|
+
with self._lock:
|
|
120
|
+
return len(self._namespaced_lru_trackers)
|
|
121
|
+
|
|
122
|
+
def get_global_size(self) -> int:
|
|
123
|
+
"""
|
|
124
|
+
Returns the total number of items tracked by the policy.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
int: The global item count.
|
|
128
|
+
"""
|
|
129
|
+
with self._lock:
|
|
130
|
+
return len(self._lru_tracker)
|
protocols/__init__.py
ADDED
|
File without changes
|
protocols/cache.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from typing import Any, Awaitable, Callable, Dict, Optional, Protocol, Union
|
|
2
|
+
|
|
3
|
+
from cache_types import _CacheScope, _FuncT
|
|
4
|
+
from protocols.eviction_policy import IEvictionPolicy
|
|
5
|
+
from protocols.storage_provider import IStorageProvider
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ICache(Protocol):
|
|
9
|
+
"""
|
|
10
|
+
Interface (Protocol) defining the public API for the Cache.
|
|
11
|
+
|
|
12
|
+
This allows for Dependency Injection and testability, enabling
|
|
13
|
+
consumers to depend on this interface rather than the concrete
|
|
14
|
+
Singleton implementation.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def get(
|
|
18
|
+
self, key: Any, scope: _CacheScope = "global", organization_id: Optional[str] = None, user_id: Optional[str] = None
|
|
19
|
+
) -> Optional[Any]:
|
|
20
|
+
"""
|
|
21
|
+
Gets an item programmatically from the cache.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
key (Any): The key to look up (must be hashable).
|
|
25
|
+
scope (_CacheScope): The scope ('global', 'organization', 'user').
|
|
26
|
+
organization_id (Optional[str]): Required if scope='organization'.
|
|
27
|
+
user_id (Optional[str]): Required if scope='user'.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Optional[Any]: The cached value or None if not found or expired.
|
|
31
|
+
"""
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
def set(
|
|
35
|
+
self,
|
|
36
|
+
key: Any,
|
|
37
|
+
value: Any,
|
|
38
|
+
ttl_seconds: Union[int, float],
|
|
39
|
+
scope: _CacheScope = "global",
|
|
40
|
+
organization_id: Optional[str] = None,
|
|
41
|
+
user_id: Optional[str] = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Sets an item programmatically in the cache.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
key (Any): The key (must be hashable).
|
|
48
|
+
value (Any): The value to store.
|
|
49
|
+
ttl_seconds (Union[int, float]): Time-to-live in seconds.
|
|
50
|
+
scope (_CacheScope): The scope ('global', 'organization', 'user').
|
|
51
|
+
organization_id (Optional[str]): Required if scope='organization'.
|
|
52
|
+
user_id (Optional[str]): Required if scope='user'.
|
|
53
|
+
"""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
def evict(
|
|
57
|
+
self, key: Any, scope: _CacheScope = "global", organization_id: Optional[str] = None, user_id: Optional[str] = None
|
|
58
|
+
) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Removes a specific item programmatically from the cache.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
key (Any): The key to remove (must be hashable).
|
|
64
|
+
scope (_CacheScope): The scope ('global', 'organization', 'user').
|
|
65
|
+
organization_id (Optional[str]): Required if scope='organization'.
|
|
66
|
+
user_id (Optional[str]): Required if scope='user'.
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
def clear(self) -> None:
|
|
71
|
+
"""Safely clears the entire cache."""
|
|
72
|
+
...
|
|
73
|
+
|
|
74
|
+
def aget(
|
|
75
|
+
self, key: Any, scope: _CacheScope = "global", organization_id: Optional[str] = None, user_id: Optional[str] = None
|
|
76
|
+
) -> Awaitable[Optional[Any]]:
|
|
77
|
+
"""
|
|
78
|
+
Asynchronously gets an item programmatically from the cache.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
key (Any): The key to look up (must be hashable).
|
|
82
|
+
scope (_CacheScope): The scope ('global', 'organization', 'user').
|
|
83
|
+
organization_id (Optional[str]): Required if scope='organization'.
|
|
84
|
+
user_id (Optional[str]): Required if scope='user'.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Awaitable[Optional[Any]]: The cached value or None.
|
|
88
|
+
"""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
def aset(
|
|
92
|
+
self,
|
|
93
|
+
key: Any,
|
|
94
|
+
value: Any,
|
|
95
|
+
ttl_seconds: Union[int, float],
|
|
96
|
+
scope: _CacheScope = "global",
|
|
97
|
+
organization_id: Optional[str] = None,
|
|
98
|
+
user_id: Optional[str] = None,
|
|
99
|
+
) -> Awaitable[None]:
|
|
100
|
+
"""
|
|
101
|
+
Asynchronously sets an item programmatically in the cache.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
key (Any): The key (must be hashable).
|
|
105
|
+
value (Any): The value to store.
|
|
106
|
+
ttl_seconds (Union[int, float]): Time-to-live in seconds.
|
|
107
|
+
scope (_CacheScope): The scope ('global', 'organization', 'user').
|
|
108
|
+
organization_id (Optional[str]): Required if scope='organization'.
|
|
109
|
+
user_id (Optional[str]): Required if scope='user'.
|
|
110
|
+
"""
|
|
111
|
+
...
|
|
112
|
+
|
|
113
|
+
def aevict(
|
|
114
|
+
self, key: Any, scope: _CacheScope = "global", organization_id: Optional[str] = None, user_id: Optional[str] = None
|
|
115
|
+
) -> Awaitable[None]:
|
|
116
|
+
"""
|
|
117
|
+
Asynchronously removes a specific item programmatically.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
key (Any): The key to remove (must be hashable).
|
|
121
|
+
scope (_CacheScope): The scope ('global', 'organization', 'user').
|
|
122
|
+
organization_id (Optional[str]): Required if scope='organization'.
|
|
123
|
+
user_id (Optional[str]): Required if scope='user'.
|
|
124
|
+
"""
|
|
125
|
+
...
|
|
126
|
+
|
|
127
|
+
def aclear(self) -> Awaitable[None]:
|
|
128
|
+
"""Asynchronously clears the entire cache."""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
def stats(self) -> Dict[str, Any]:
|
|
132
|
+
"""
|
|
133
|
+
Returns a dictionary of cache observability statistics.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dict[str, Any]: A dict containing keys like 'hits', 'misses',
|
|
137
|
+
'evictions', 'current_size', etc.
|
|
138
|
+
"""
|
|
139
|
+
...
|
|
140
|
+
|
|
141
|
+
def evict_by_scope(self, scope: _CacheScope, organization_id: Optional[str] = None, user_id: Optional[str] = None) -> int:
|
|
142
|
+
"""
|
|
143
|
+
Granularly evicts all items belonging to a specific tenant.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
scope (_CacheScope): The scope to target ('organization' or 'user').
|
|
147
|
+
organization_id (Optional[str]): Required if scope='organization'.
|
|
148
|
+
user_id (Optional[str]): Required if scope='user'.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
int: The number of items successfully evicted.
|
|
152
|
+
"""
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
def cache(
|
|
156
|
+
self, ttl_seconds: Union[int, float], scope: _CacheScope = "global", max_items: Optional[int] = None
|
|
157
|
+
) -> Callable[[_FuncT], _FuncT]:
|
|
158
|
+
"""
|
|
159
|
+
Decorator factory for caching function results.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
ttl_seconds (Union[int, float]): Time-to-live for items.
|
|
163
|
+
scope (_CacheScope): The cache scope.
|
|
164
|
+
max_items (Optional[int]): Max items for this function.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Callable[[_FuncT], _FuncT]: A decorator function.
|
|
168
|
+
"""
|
|
169
|
+
...
|
|
170
|
+
|
|
171
|
+
def configure(
|
|
172
|
+
self, backend: IStorageProvider, policy: IEvictionPolicy, max_size: int = 1000, cleanup_interval: int = 60
|
|
173
|
+
) -> None:
|
|
174
|
+
"""
|
|
175
|
+
Configures and starts the cache. Must be called once.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
backend (IStorageProvider): The storage backend (e.g., InMemoryStorageProvider).
|
|
179
|
+
policy (IEvictionPolicy): The eviction policy (e.g., LRUEvictionPolicy).
|
|
180
|
+
max_size (int): Max number of items to store globally.
|
|
181
|
+
cleanup_interval (int): Interval (in seconds) for background cleanup.
|
|
182
|
+
"""
|
|
183
|
+
...
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from typing import Optional, Protocol
|
|
2
|
+
|
|
3
|
+
from cache_types import _CacheKey
|
|
4
|
+
from protocols.eviction_policy import IEvictionPolicy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ICachePolicyManager(Protocol):
|
|
8
|
+
"""
|
|
9
|
+
Interface (Protocol) for cache policy management implementations.
|
|
10
|
+
|
|
11
|
+
Defines the contract for managing cache policies including
|
|
12
|
+
background cleanup and eviction coordination.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def policy(self) -> IEvictionPolicy:
|
|
17
|
+
"""Returns the eviction policy instance."""
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def global_max_size(self) -> Optional[int]:
|
|
22
|
+
"""Returns the global maximum cache size."""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def cleanup_interval(self) -> int:
|
|
27
|
+
"""Returns the cleanup interval in seconds."""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
def start_background_cleanup(self) -> None:
|
|
31
|
+
"""Starts the background cleanup thread."""
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
def stop_background_cleanup(self) -> None:
|
|
35
|
+
"""Stops the background cleanup thread gracefully."""
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
def notify_set(self, key: _CacheKey, namespace: str, max_items: Optional[int]) -> Optional[_CacheKey]:
|
|
39
|
+
"""
|
|
40
|
+
Notifies that an item was set and returns key to evict if needed.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
key: The key that was set.
|
|
44
|
+
namespace: The namespace of the key.
|
|
45
|
+
max_items: The max_items limit for this namespace.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Key to evict or None.
|
|
49
|
+
"""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
def notify_get(self, key: _CacheKey, namespace: str) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Notifies that an item was accessed.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
key: The key that was accessed.
|
|
58
|
+
namespace: The namespace of the key.
|
|
59
|
+
"""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
def notify_evict(self, key: _CacheKey, namespace: str) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Notifies that an item was evicted.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
key: The key that was evicted.
|
|
68
|
+
namespace: The namespace of the key.
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
def notify_clear(self) -> None:
|
|
73
|
+
"""Notifies that the entire cache was cleared."""
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
def get_namespace_count(self) -> int:
|
|
77
|
+
"""Returns the total number of tracked namespaces."""
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
def get_global_size(self) -> int:
|
|
81
|
+
"""Returns the global item count."""
|
|
82
|
+
...
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import Optional, Protocol
|
|
2
|
+
|
|
3
|
+
from cache_types import _CacheKey
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IEvictionPolicy(Protocol):
|
|
7
|
+
"""
|
|
8
|
+
Interface (Protocol) for all cache eviction policies (e.g., LRU, LFU).
|
|
9
|
+
Implementations MUST be thread-safe.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def notify_set(
|
|
13
|
+
self, key: _CacheKey, namespace: str, max_items: Optional[int], global_max_size: Optional[int]
|
|
14
|
+
) -> Optional[_CacheKey]:
|
|
15
|
+
"""
|
|
16
|
+
Notifies the policy that an item was set (added/updated).
|
|
17
|
+
The policy must enforce limits and return a key to evict if necessary.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
key (_CacheKey): The key that was set.
|
|
21
|
+
namespace (str): The namespace of the key.
|
|
22
|
+
max_items (Optional[int]): The max_items limit for this namespace.
|
|
23
|
+
global_max_size (Optional[int]): The global max_size limit.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Optional[_CacheKey]: A key to evict, or None.
|
|
27
|
+
"""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
def notify_get(self, key: _CacheKey, namespace: str) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Notifies the policy that an item was accessed (read).
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
key (_CacheKey): The key that was accessed.
|
|
36
|
+
namespace (str): The namespace of the key.
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
def notify_evict(self, key: _CacheKey, namespace: str) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Notifies the policy that an item was evicted (removed).
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
key (_CacheKey): The key that was evicted.
|
|
46
|
+
namespace (str): The namespace of the key.
|
|
47
|
+
"""
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
def notify_clear(self) -> None:
|
|
51
|
+
"""Notifies the policy that the entire cache was cleared."""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
def get_namespace_count(self) -> int:
|
|
55
|
+
"""
|
|
56
|
+
Returns the total number of tracked namespaces.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
int: The count of namespaces.
|
|
60
|
+
"""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
def get_global_size(self) -> int:
|
|
64
|
+
"""
|
|
65
|
+
Returns the total number of items tracked by the policy.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
int: The global item count.
|
|
69
|
+
"""
|
|
70
|
+
...
|
protocols/scope.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Protocol
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class IScope(Protocol):
|
|
5
|
+
"""
|
|
6
|
+
Interface (Protocol) for scope configuration implementations.
|
|
7
|
+
|
|
8
|
+
Defines the contract for managing hierarchical cache scoping,
|
|
9
|
+
allowing different implementations while maintaining type safety.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def level_names(self) -> List[str]:
|
|
14
|
+
"""
|
|
15
|
+
Returns the names of all scope levels.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
List of scope level names.
|
|
19
|
+
"""
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def get_param_name(self, level_name: str) -> str:
|
|
23
|
+
"""
|
|
24
|
+
Returns the parameter name for a specific scope level.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
level_name: Name of the scope level.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Corresponding parameter name.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError: If the level doesn't exist.
|
|
34
|
+
"""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
def build_scope_path(self, scope_params: Dict[str, Any]) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Builds the scope path based on provided parameters.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
scope_params: Dictionary with scope parameters.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
String representing the hierarchical scope path.
|
|
46
|
+
"""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def validate_scope_params(self, target_level: str, scope_params: Dict[str, Any]) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Validates that required parameters are present for the target level.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
target_level: Desired scope level.
|
|
55
|
+
scope_params: Provided parameters.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
ValueError: If mandatory parameters are missing.
|
|
59
|
+
"""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
def get_parent_scope_path(self, scope_path: str) -> Optional[str]:
|
|
63
|
+
"""
|
|
64
|
+
Returns the parent scope path.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
scope_path: Current scope path.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Parent scope path or None if global.
|
|
71
|
+
"""
|
|
72
|
+
...
|
|
73
|
+
|
|
74
|
+
def is_descendant_of(self, child_path: str, parent_path: str) -> bool:
|
|
75
|
+
"""
|
|
76
|
+
Checks if one scope is a descendant of another.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
child_path: Child scope path.
|
|
80
|
+
parent_path: Parent scope path.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
True if child_path is descendant of parent_path.
|
|
84
|
+
"""
|
|
85
|
+
...
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from typing import List, Optional, Protocol
|
|
2
|
+
|
|
3
|
+
from cache_types import _CacheKey, _CacheValue
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IStorageProvider(Protocol):
|
|
7
|
+
"""
|
|
8
|
+
Interface (Protocol) for all storage backends (e.g., In-Memory, Redis).
|
|
9
|
+
Implementations MUST be thread-safe for synchronous operations.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def get(self, key: _CacheKey) -> Optional[_CacheValue]:
|
|
13
|
+
"""
|
|
14
|
+
Atomically gets a value tuple (value, expiry) from storage.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
key (_CacheKey): The internal key to get.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Optional[_CacheValue]: The stored tuple, or None.
|
|
21
|
+
"""
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def get_value_no_lock(self, key: _CacheKey) -> Optional[_CacheValue]:
|
|
25
|
+
"""
|
|
26
|
+
Performs a non-locking ("dirty") read for the cleanup loop.
|
|
27
|
+
Only required for backends that support it.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
key (_CacheKey): The internal key to look up.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Optional[_CacheValue]: The stored tuple (value, expiry) or None.
|
|
34
|
+
"""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
def set(self, key: _CacheKey, value: _CacheValue) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Atomically sets a value tuple (value, expiry) in storage.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
key (_CacheKey): The internal key to set.
|
|
43
|
+
value (_CacheValue): The (value, expiry) tuple to store.
|
|
44
|
+
"""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
def evict(self, key: _CacheKey) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Atomically evicts a key from storage.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
key (_CacheKey): The internal key to evict.
|
|
53
|
+
"""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
def get_all_keys(self) -> List[_CacheKey]:
|
|
57
|
+
"""
|
|
58
|
+
Atomically gets a copy of all keys in storage.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List[_CacheKey]: A list of all cache keys.
|
|
62
|
+
"""
|
|
63
|
+
...
|
|
64
|
+
|
|
65
|
+
def clear(self) -> None:
|
|
66
|
+
"""Atomically clears the entire storage."""
|
|
67
|
+
...
|
storages/__init__.py
ADDED
|
File without changes
|