Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wrapt/caching.py: 24%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

84 statements  

1"""Caching decorators. Currently provides ``lru_cache``, a drop-in 

2replacement for ``functools.lru_cache`` with correct handling of instance 

3methods: a separate cache is maintained per instance, stored as an 

4attribute on the instance itself so it is cleaned up with the instance 

5by the garbage collector. For plain functions, class methods, and static 

6methods a single shared cache is used, matching ``functools.lru_cache``. 

7""" 

8 

9from functools import lru_cache as _functools_lru_cache 

10from functools import partial 

11 

12from .__wrapt__ import BaseObjectProxy, BoundFunctionWrapper, FunctionWrapper 

13from .decorators import decorator 

14from .synchronization import synchronized 

15 

16# Decorator that applies functools.lru_cache to the wrapped function. 

17# Unlike using functools.lru_cache directly, this works correctly with 

18# instance methods and class methods by maintaining a separate cache 

19# per instance or class. Each per-instance cache is stored as an 

20# attribute on the instance itself, so it is automatically cleaned up 

21# when the instance is garbage collected. For plain functions and 

22# static methods, a single cache is stored on the wrapper itself. 

23# The underlying caches are created lazily on first call, with 

24# double-checked locking via synchronized to avoid races in 

25# multi-threaded code. 

26# 

27# Custom FunctionWrapper and BoundFunctionWrapper subclasses are used 

28# so that cache_info() and cache_clear() are available directly on 

29# the decorated function. For bound methods, the BoundFunctionWrapper 

30# uses _self_instance to locate the per-instance cache. 

31 

32 

33class _BoundLRUCacheFunctionWrapper(BoundFunctionWrapper): 

34 

35 def _is_instance_method(self): 

36 return self._self_binding == "function" 

37 

38 def __call__(self, *args, **kwargs): 

39 parent = self._self_parent 

40 

41 # For class methods and static methods, use a single shared 

42 # cache stored on the parent wrapper. The cache is created 

43 # from the bound __wrapped__ since the parent's __wrapped__ 

44 # is the raw classmethod/staticmethod descriptor. 

45 

46 if not self._is_instance_method(): 

47 if parent._self_cache is None: 

48 with synchronized(parent): 

49 if parent._self_cache is None: 

50 parent._self_cache = _functools_lru_cache( 

51 **parent._self_lru_kwargs 

52 )(self.__wrapped__) 

53 

54 return parent._self_cache(*args, **kwargs) 

55 

56 # Instance method — per-instance cache stored as an attribute 

57 # on the instance so it is cleaned up with the instance by the 

58 # garbage collector. 

59 

60 instance = self._self_instance 

61 cache_attr = parent._self_cache_attr 

62 

63 cache = getattr(instance, cache_attr, None) 

64 

65 if cache is None: 

66 with synchronized(parent): 

67 cache = getattr(instance, cache_attr, None) 

68 

69 if cache is None: 

70 cache = _functools_lru_cache(**parent._self_lru_kwargs)( 

71 self.__wrapped__ 

72 ) 

73 

74 # If the instance the method is bound to is a wrapt 

75 # object proxy, a plain setattr() would fall through and 

76 # store the cache on the wrapped object rather than the 

77 # proxy. Use type() rather than isinstance() so the check 

78 # sees the real proxy type and is not fooled by the proxy 

79 # overriding __class__ to report the wrapped object's 

80 # type. Proxies expose __self_setattr__() which stores the 

81 # attribute on the proxy itself. 

82 

83 if issubclass(type(instance), BaseObjectProxy): 

84 instance.__self_setattr__(cache_attr, cache) 

85 else: 

86 setattr(instance, cache_attr, cache) 

87 

88 return cache(*args, **kwargs) 

89 

90 def cache_info(self): 

91 """Return the cache statistics for this binding's cache, or 

92 ``None`` if the cache has not yet been created. 

93 """ 

94 

95 if not self._is_instance_method(): 

96 return self._self_parent.cache_info() 

97 

98 cache = getattr(self._self_instance, self._self_parent._self_cache_attr, None) 

99 

100 if cache is not None: 

101 return cache.cache_info() 

102 

103 return None 

104 

105 def cache_clear(self): 

106 """Clear this binding's cache.""" 

107 

108 if not self._is_instance_method(): 

109 self._self_parent.cache_clear() 

110 return 

111 

112 cache = getattr(self._self_instance, self._self_parent._self_cache_attr, None) 

113 

114 if cache is not None: 

115 cache.cache_clear() 

116 

117 def cache_parameters(self): 

118 """Return the parameters used to create the cache.""" 

119 

120 if not self._is_instance_method(): 

121 return self._self_parent.cache_parameters() 

122 

123 cache = getattr(self._self_instance, self._self_parent._self_cache_attr, None) 

124 

125 if cache is not None: 

126 return cache.cache_parameters() 

127 

128 return None 

129 

130 

131class _LRUCacheFunctionWrapper(FunctionWrapper): 

132 

133 __bound_function_wrapper__ = _BoundLRUCacheFunctionWrapper 

134 

135 def __init__(self, wrapped, wrapper, **kwargs): 

136 super().__init__(wrapped, wrapper, **kwargs) 

137 

138 # Extract the LRU cache configuration that was attached to the 

139 # wrapper function before it was passed to the decorator. 

140 

141 self._self_lru_kwargs = wrapper._self_lru_kwargs 

142 self._self_cache = None 

143 

144 # Use __func__ to get the name for classmethod/staticmethod 

145 # descriptors which lack __name__ on Python < 3.10. 

146 

147 name = getattr(wrapped, "__name__", None) 

148 

149 if name is None: 

150 name = wrapped.__func__.__name__ 

151 

152 # The cache attribute name must be unique per decorated method so 

153 # that a method overridden in a subclass does not share the same 

154 # per-instance cache slot as the method it overrides. If they shared 

155 # a slot, a subclass method calling super() would find the subclass 

156 # cache and re-enter its own body, recursing forever. The owning 

157 # class is not known here, since the decorator runs on the raw 

158 # function before the class exists, but each decorated method has its 

159 # own wrapper instance, so id(self) is a stable discriminator unique 

160 # to this method definition. 

161 

162 self._self_cache_attr = "_lru_cache_" + name + "_" + str(id(self)) 

163 

164 def __call__(self, *args, **kwargs): 

165 # Plain function or static method — single cache stored 

166 # on the wrapper itself. 

167 

168 if self._self_cache is None: 

169 with synchronized(self): 

170 if self._self_cache is None: 

171 self._self_cache = _functools_lru_cache(**self._self_lru_kwargs)( 

172 self.__wrapped__ 

173 ) 

174 

175 return self._self_cache(*args, **kwargs) 

176 

177 def cache_info(self): 

178 """Return the cache statistics, or ``None`` if the cache has 

179 not yet been created. 

180 """ 

181 

182 if self._self_cache is not None: 

183 return self._self_cache.cache_info() 

184 

185 return None 

186 

187 def cache_clear(self): 

188 """Clear the cache and reset the statistics.""" 

189 

190 if self._self_cache is not None: 

191 self._self_cache.cache_clear() 

192 

193 def cache_parameters(self): 

194 """Return the parameters used to create the cache, or ``None`` 

195 if the cache has not yet been created. 

196 """ 

197 

198 if self._self_cache is not None: 

199 return self._self_cache.cache_parameters() 

200 

201 return None 

202 

203 

204def lru_cache(func=None, /, **kwargs): 

205 """A decorator that applies ``functools.lru_cache`` to the wrapped 

206 function, with correct handling for instance methods, class methods, 

207 and static methods. 

208 

209 For instance methods, a separate ``functools.lru_cache`` is 

210 maintained per instance. The cache is stored as an attribute on the 

211 instance itself, so it is automatically cleaned up when the instance 

212 is garbage collected. This means instances do not need to be 

213 hashable, each instance gets its own full ``maxsize`` budget, and 

214 no external mapping prevents garbage collection. 

215 

216 For plain functions, class methods, and static methods, a single 

217 shared cache is used. 

218 

219 All keyword arguments are passed through to ``functools.lru_cache``. 

220 

221 Cache management methods ``cache_info()`` and ``cache_clear()`` are 

222 available directly on the decorated function. For bound methods, 

223 these operate on the per-instance cache for the bound instance. 

224 """ 

225 

226 if func is None: 

227 return partial(lru_cache, **kwargs) 

228 

229 def _wrapper(wrapped, instance, args, _kwargs): 

230 return wrapped(*args, **_kwargs) 

231 

232 _wrapper._self_lru_kwargs = kwargs 

233 

234 return decorator(_wrapper, proxy=_LRUCacheFunctionWrapper)(func)