Coverage for jinja2_async_environment / caching / unified.py: 100%
63 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-26 21:26 -0800
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-26 21:26 -0800
1"""Unified cache implementation for backward compatibility.
3This module provides the UnifiedCache class that maintains compatibility
4with the existing cache interface while leveraging the new type-safe
5caching infrastructure.
6"""
8import typing as t
9from threading import RLock
11from .typed import TypedCache
14class UnifiedCache:
15 """Unified cache system for backward compatibility.
17 This cache maintains the existing API while internally using
18 the new type-safe cache infrastructure. It will be gradually
19 phased out in favor of the CacheManager approach.
20 """
22 def __init__(self, default_ttl: int = 300) -> None:
23 """Initialize the unified cache.
25 Args:
26 default_ttl: Default time-to-live in seconds
27 """
28 # Internal type-safe caches - using Any for backward compatibility
29 template_ttl = default_ttl * 6
30 import_ttl = default_ttl * 12
32 self._caches: dict[str, TypedCache[t.Any]] = {
33 "package_import": TypedCache(max_size=200)
34 if import_ttl == 300
35 else TypedCache(max_size=200, default_ttl=import_ttl),
36 "package_spec": TypedCache(max_size=500)
37 if template_ttl == 300
38 else TypedCache(max_size=500, default_ttl=template_ttl),
39 "template_root": TypedCache(max_size=1000)
40 if template_ttl == 300
41 else TypedCache(max_size=1000, default_ttl=template_ttl),
42 }
44 self._default_ttl = default_ttl
45 self._lock = RLock()
47 def get(self, cache_type: str, key: t.Any, default: t.Any = None) -> t.Any:
48 """Get a value from the specified cache.
50 Args:
51 cache_type: Type of cache ("package_import", "package_spec", "template_root")
52 key: Cache key
53 default: Default value if not found
55 Returns:
56 Cached value or default
57 """
58 with self._lock:
59 if cache_type not in self._caches:
60 return default
62 # Convert key to string for internal cache
63 str_key = str(key) if not isinstance(key, str) else key
65 value = self._caches[cache_type].get(str_key)
66 return value if value is not None else default
68 def set(
69 self, cache_type: str, key: t.Any, value: t.Any, ttl: int | None = None
70 ) -> None:
71 """Store a value in the specified cache.
73 Args:
74 cache_type: Type of cache
75 key: Cache key
76 value: Value to store
77 ttl: Time-to-live in seconds (uses default if None)
78 """
79 with self._lock:
80 if cache_type not in self._caches:
81 # Dynamically create cache for unknown types
82 cache_params = {"max_size": 1000}
83 if ttl is not None or self._default_ttl != 300:
84 cache_params["default_ttl"] = ttl or self._default_ttl
85 self._caches[cache_type] = TypedCache(**cache_params)
87 # Convert key to string for internal cache
88 str_key = str(key) if not isinstance(key, str) else key
90 self._caches[cache_type].set(str_key, value, ttl or None)
92 def clear(self, cache_type: str | None = None) -> None:
93 """Clear cache entries.
95 Args:
96 cache_type: Specific cache to clear (None for all caches)
97 """
98 with self._lock:
99 if cache_type is None:
100 # Clear all caches
101 for cache in self._caches.values():
102 cache.clear()
103 elif cache_type in self._caches:
104 self._caches[cache_type].clear()
106 def clear_all(self) -> None:
107 """Clear all cache entries."""
108 self.clear()
110 def cleanup_expired(self) -> None:
111 """Remove expired entries from all caches."""
112 with self._lock:
113 for cache in self._caches.values():
114 cache.cleanup_expired()
116 def get_statistics(self) -> dict[str, dict[str, t.Any]]:
117 """Get statistics for all caches.
119 Returns:
120 Dictionary with statistics for each cache type
121 """
122 with self._lock:
123 stats = {}
124 for cache_type, cache in self._caches.items():
125 stats[cache_type] = cache.get_statistics()
126 return stats
128 def _is_valid(self, cache_type: str, key: t.Any) -> bool:
129 """Check if a cache entry is valid (exists and not expired).
131 Args:
132 cache_type: Type of cache
133 key: Cache key
135 Returns:
136 True if entry is valid, False otherwise
137 """
138 with self._lock:
139 if cache_type not in self._caches:
140 return False
142 str_key = str(key) if not isinstance(key, str) else key
143 return self._caches[cache_type].contains(str_key)
145 def resize_cache(self, cache_type: str, new_size: int) -> None:
146 """Resize a specific cache.
148 Args:
149 cache_type: Type of cache to resize
150 new_size: New maximum size
151 """
152 with self._lock:
153 if cache_type in self._caches:
154 self._caches[cache_type].resize(new_size)
156 def get_cache_types(self) -> list[str]:
157 """Get list of available cache types.
159 Returns:
160 List of cache type names
161 """
162 with self._lock:
163 return list(self._caches.keys())
165 def contains(self, cache_type: str, key: t.Any) -> bool:
166 """Check if a key exists in the specified cache.
168 Args:
169 cache_type: Type of cache
170 key: Cache key
172 Returns:
173 True if key exists and is valid, False otherwise
174 """
175 return self._is_valid(cache_type, key)
177 def __len__(self) -> int:
178 """Get total number of entries across all caches."""
179 with self._lock:
180 return sum(len(cache) for cache in self._caches.values())
182 def __repr__(self) -> str:
183 """String representation of unified cache."""
184 with self._lock:
185 total_entries = len(self)
186 return f"UnifiedCache(types={len(self._caches)}, entries={total_entries})"
189# Backward compatibility function
190# def _clear_expired_cache() -> None:
191# """Clear expired cache entries (backward compatibility function)."""
192# # This will be connected to the global unified cache instance
193# # when it's available through the loader context
194# pass