Coverage for src/objectstate/token_cache.py: 32%
62 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 07:22 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 07:22 +0000
1"""
2Token-based cache invalidation service.
4Provides a reusable abstraction for caching values that should be invalidated
5when a global token changes (e.g., when any form value changes).
6"""
8from typing import TypeVar, Generic, Optional, Callable, Tuple, Any, Dict
9from dataclasses import dataclass
11T = TypeVar('T')
14@dataclass(frozen=True)
15class CacheKey:
16 """Immutable cache key that can include multiple components."""
17 components: Tuple[Any, ...]
19 def __hash__(self):
20 return hash(self.components)
22 def __eq__(self, other):
23 if not isinstance(other, CacheKey):
24 return False
25 return self.components == other.components
27 @classmethod
28 def from_args(cls, *args) -> 'CacheKey':
29 """Create cache key from variable arguments."""
30 return cls(components=args)
33class TokenCache(Generic[T]):
34 """
35 Generic token-based cache with automatic invalidation.
37 The cache is invalidated when the token changes. This is useful for caching
38 values that depend on global state (e.g., form values) that can change.
40 Example:
41 # Create cache with token provider
42 cache = TokenCache(lambda: ParameterFormManager._live_context_token_counter)
44 # Get or compute value
45 value = cache.get_or_compute(
46 key=CacheKey.from_args('scope', 'param_name'),
47 compute_fn=lambda: expensive_computation()
48 )
50 # Cache is automatically invalidated when token changes
51 """
53 def __init__(self, token_provider: Callable[[], int]):
54 """
55 Initialize token cache.
57 Args:
58 token_provider: Function that returns the current token value
59 """
60 self._token_provider = token_provider
61 self._cache: Dict[CacheKey, T] = {}
62 self._last_token: int = -1
64 def get_or_compute(self, key: CacheKey, compute_fn: Callable[[], T]) -> T:
65 """
66 Get cached value or compute and cache it.
68 Args:
69 key: Cache key
70 compute_fn: Function to compute value if cache miss
72 Returns:
73 Cached or computed value
74 """
75 current_token = self._token_provider()
77 # Invalidate entire cache if token changed
78 if current_token != self._last_token:
79 self._cache.clear()
80 self._last_token = current_token
82 # Check cache
83 if key in self._cache:
84 return self._cache[key]
86 # Compute and cache
87 value = compute_fn()
88 self._cache[key] = value
89 return value
91 def invalidate(self):
92 """Manually invalidate the entire cache."""
93 self._cache.clear()
94 self._last_token = -1
96 def get(self, key: CacheKey) -> Optional[T]:
97 """
98 Get cached value without computing.
100 Args:
101 key: Cache key
103 Returns:
104 Cached value or None if not found or token changed
105 """
106 current_token = self._token_provider()
108 # Invalidate if token changed
109 if current_token != self._last_token:
110 self._cache.clear()
111 self._last_token = current_token
112 return None
114 return self._cache.get(key)
116 def put(self, key: CacheKey, value: T):
117 """
118 Put value in cache.
120 Args:
121 key: Cache key
122 value: Value to cache
123 """
124 current_token = self._token_provider()
126 # Update token if changed
127 if current_token != self._last_token:
128 self._cache.clear()
129 self._last_token = current_token
131 self._cache[key] = value
134class SingleValueTokenCache(Generic[T]):
135 """
136 Simplified token cache for single values (no key needed).
138 Useful when you only need to cache one value that depends on a token.
140 Example:
141 cache = SingleValueTokenCache(lambda: token_counter)
142 value = cache.get_or_compute(lambda: expensive_computation())
143 """
145 def __init__(self, token_provider: Callable[[], int]):
146 """
147 Initialize single-value token cache.
149 Args:
150 token_provider: Function that returns the current token value
151 """
152 self._token_provider = token_provider
153 self._cached_value: Optional[T] = None
154 self._cached_token: int = -1
156 def get_or_compute(self, compute_fn: Callable[[], T]) -> T:
157 """
158 Get cached value or compute and cache it.
160 Args:
161 compute_fn: Function to compute value if cache miss
163 Returns:
164 Cached or computed value
165 """
166 current_token = self._token_provider()
168 # Check cache
169 if current_token == self._cached_token and self._cached_value is not None:
170 return self._cached_value
172 # Compute and cache
173 value = compute_fn()
174 self._cached_value = value
175 self._cached_token = current_token
176 return value
178 def invalidate(self):
179 """Manually invalidate the cache."""
180 self._cached_value = None
181 self._cached_token = -1