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

1"""Unified cache implementation for backward compatibility. 

2 

3This module provides the UnifiedCache class that maintains compatibility 

4with the existing cache interface while leveraging the new type-safe 

5caching infrastructure. 

6""" 

7 

8import typing as t 

9from threading import RLock 

10 

11from .typed import TypedCache 

12 

13 

14class UnifiedCache: 

15 """Unified cache system for backward compatibility. 

16 

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 """ 

21 

22 def __init__(self, default_ttl: int = 300) -> None: 

23 """Initialize the unified cache. 

24 

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 

31 

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 } 

43 

44 self._default_ttl = default_ttl 

45 self._lock = RLock() 

46 

47 def get(self, cache_type: str, key: t.Any, default: t.Any = None) -> t.Any: 

48 """Get a value from the specified cache. 

49 

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 

54 

55 Returns: 

56 Cached value or default 

57 """ 

58 with self._lock: 

59 if cache_type not in self._caches: 

60 return default 

61 

62 # Convert key to string for internal cache 

63 str_key = str(key) if not isinstance(key, str) else key 

64 

65 value = self._caches[cache_type].get(str_key) 

66 return value if value is not None else default 

67 

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. 

72 

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) 

86 

87 # Convert key to string for internal cache 

88 str_key = str(key) if not isinstance(key, str) else key 

89 

90 self._caches[cache_type].set(str_key, value, ttl or None) 

91 

92 def clear(self, cache_type: str | None = None) -> None: 

93 """Clear cache entries. 

94 

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() 

105 

106 def clear_all(self) -> None: 

107 """Clear all cache entries.""" 

108 self.clear() 

109 

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() 

115 

116 def get_statistics(self) -> dict[str, dict[str, t.Any]]: 

117 """Get statistics for all caches. 

118 

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 

127 

128 def _is_valid(self, cache_type: str, key: t.Any) -> bool: 

129 """Check if a cache entry is valid (exists and not expired). 

130 

131 Args: 

132 cache_type: Type of cache 

133 key: Cache key 

134 

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 

141 

142 str_key = str(key) if not isinstance(key, str) else key 

143 return self._caches[cache_type].contains(str_key) 

144 

145 def resize_cache(self, cache_type: str, new_size: int) -> None: 

146 """Resize a specific cache. 

147 

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) 

155 

156 def get_cache_types(self) -> list[str]: 

157 """Get list of available cache types. 

158 

159 Returns: 

160 List of cache type names 

161 """ 

162 with self._lock: 

163 return list(self._caches.keys()) 

164 

165 def contains(self, cache_type: str, key: t.Any) -> bool: 

166 """Check if a key exists in the specified cache. 

167 

168 Args: 

169 cache_type: Type of cache 

170 key: Cache key 

171 

172 Returns: 

173 True if key exists and is valid, False otherwise 

174 """ 

175 return self._is_valid(cache_type, key) 

176 

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()) 

181 

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})" 

187 

188 

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