Coverage for jinja2_async_environment/caching/unified.py: 100%

1"""Unified cache implementation for backward compatibility.

2

3 This module provides the UnifiedCache class that maintains compatibility

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

5 caching infrastructure.

6"""

7

8 import typing as t

9 from threading import RLock

10

11 from .typed import TypedCache

12

13

14 class 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