Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/core/cache/backends/base.py: 33%

187 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1"Base Cache class." 

2import time 

3import warnings 

4 

5from asgiref.sync import sync_to_async 

6 

7from django.core.exceptions import ImproperlyConfigured 

8from django.utils.module_loading import import_string 

9 

10 

11class InvalidCacheBackendError(ImproperlyConfigured): 

12 pass 

13 

14 

15class CacheKeyWarning(RuntimeWarning): 

16 pass 

17 

18 

19class InvalidCacheKey(ValueError): 

20 pass 

21 

22 

23# Stub class to ensure not passing in a `timeout` argument results in 

24# the default timeout 

25DEFAULT_TIMEOUT = object() 

26 

27# Memcached does not accept keys longer than this. 

28MEMCACHE_MAX_KEY_LENGTH = 250 

29 

30 

31def default_key_func(key, key_prefix, version): 

32 """ 

33 Default function to generate keys. 

34 

35 Construct the key used by all other methods. By default, prepend 

36 the `key_prefix`. KEY_FUNCTION can be used to specify an alternate 

37 function with custom key making behavior. 

38 """ 

39 return "%s:%s:%s" % (key_prefix, version, key) 

40 

41 

42def get_key_func(key_func): 

43 """ 

44 Function to decide which key function to use. 

45 

46 Default to ``default_key_func``. 

47 """ 

48 if key_func is not None: 48 ↛ 49line 48 didn't jump to line 49, because the condition on line 48 was never true

49 if callable(key_func): 

50 return key_func 

51 else: 

52 return import_string(key_func) 

53 return default_key_func 

54 

55 

56class BaseCache: 

57 _missing_key = object() 

58 

59 def __init__(self, params): 

60 timeout = params.get("timeout", params.get("TIMEOUT", 300)) 

61 if timeout is not None: 61 ↛ 66line 61 didn't jump to line 66, because the condition on line 61 was never false

62 try: 

63 timeout = int(timeout) 

64 except (ValueError, TypeError): 

65 timeout = 300 

66 self.default_timeout = timeout 

67 

68 options = params.get("OPTIONS", {}) 

69 max_entries = params.get("max_entries", options.get("MAX_ENTRIES", 300)) 

70 try: 

71 self._max_entries = int(max_entries) 

72 except (ValueError, TypeError): 

73 self._max_entries = 300 

74 

75 cull_frequency = params.get("cull_frequency", options.get("CULL_FREQUENCY", 3)) 

76 try: 

77 self._cull_frequency = int(cull_frequency) 

78 except (ValueError, TypeError): 

79 self._cull_frequency = 3 

80 

81 self.key_prefix = params.get("KEY_PREFIX", "") 

82 self.version = params.get("VERSION", 1) 

83 self.key_func = get_key_func(params.get("KEY_FUNCTION")) 

84 

85 def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT): 

86 """ 

87 Return the timeout value usable by this backend based upon the provided 

88 timeout. 

89 """ 

90 if timeout == DEFAULT_TIMEOUT: 

91 timeout = self.default_timeout 

92 elif timeout == 0: 

93 # ticket 21147 - avoid time.time() related precision issues 

94 timeout = -1 

95 return None if timeout is None else time.time() + timeout 

96 

97 def make_key(self, key, version=None): 

98 """ 

99 Construct the key used by all other methods. By default, use the 

100 key_func to generate a key (which, by default, prepends the 

101 `key_prefix' and 'version'). A different key function can be provided 

102 at the time of cache construction; alternatively, you can subclass the 

103 cache backend to provide custom key making behavior. 

104 """ 

105 if version is None: 

106 version = self.version 

107 

108 return self.key_func(key, self.key_prefix, version) 

109 

110 def validate_key(self, key): 

111 """ 

112 Warn about keys that would not be portable to the memcached 

113 backend. This encourages (but does not force) writing backend-portable 

114 cache code. 

115 """ 

116 for warning in memcache_key_warnings(key): 

117 warnings.warn(warning, CacheKeyWarning) 

118 

119 def make_and_validate_key(self, key, version=None): 

120 """Helper to make and validate keys.""" 

121 key = self.make_key(key, version=version) 

122 self.validate_key(key) 

123 return key 

124 

125 def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None): 

126 """ 

127 Set a value in the cache if the key does not already exist. If 

128 timeout is given, use that timeout for the key; otherwise use the 

129 default cache timeout. 

130 

131 Return True if the value was stored, False otherwise. 

132 """ 

133 raise NotImplementedError( 

134 "subclasses of BaseCache must provide an add() method" 

135 ) 

136 

137 async def aadd(self, key, value, timeout=DEFAULT_TIMEOUT, version=None): 

138 return await sync_to_async(self.add, thread_sensitive=True)( 

139 key, value, timeout, version 

140 ) 

141 

142 def get(self, key, default=None, version=None): 

143 """ 

144 Fetch a given key from the cache. If the key does not exist, return 

145 default, which itself defaults to None. 

146 """ 

147 raise NotImplementedError("subclasses of BaseCache must provide a get() method") 

148 

149 async def aget(self, key, default=None, version=None): 

150 return await sync_to_async(self.get, thread_sensitive=True)( 

151 key, default, version 

152 ) 

153 

154 def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None): 

155 """ 

156 Set a value in the cache. If timeout is given, use that timeout for the 

157 key; otherwise use the default cache timeout. 

158 """ 

159 raise NotImplementedError("subclasses of BaseCache must provide a set() method") 

160 

161 async def aset(self, key, value, timeout=DEFAULT_TIMEOUT, version=None): 

162 return await sync_to_async(self.set, thread_sensitive=True)( 

163 key, value, timeout, version 

164 ) 

165 

166 def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None): 

167 """ 

168 Update the key's expiry time using timeout. Return True if successful 

169 or False if the key does not exist. 

170 """ 

171 raise NotImplementedError( 

172 "subclasses of BaseCache must provide a touch() method" 

173 ) 

174 

175 async def atouch(self, key, timeout=DEFAULT_TIMEOUT, version=None): 

176 return await sync_to_async(self.touch, thread_sensitive=True)( 

177 key, timeout, version 

178 ) 

179 

180 def delete(self, key, version=None): 

181 """ 

182 Delete a key from the cache and return whether it succeeded, failing 

183 silently. 

184 """ 

185 raise NotImplementedError( 

186 "subclasses of BaseCache must provide a delete() method" 

187 ) 

188 

189 async def adelete(self, key, version=None): 

190 return await sync_to_async(self.delete, thread_sensitive=True)(key, version) 

191 

192 def get_many(self, keys, version=None): 

193 """ 

194 Fetch a bunch of keys from the cache. For certain backends (memcached, 

195 pgsql) this can be *much* faster when fetching multiple values. 

196 

197 Return a dict mapping each key in keys to its value. If the given 

198 key is missing, it will be missing from the response dict. 

199 """ 

200 d = {} 

201 for k in keys: 

202 val = self.get(k, self._missing_key, version=version) 

203 if val is not self._missing_key: 

204 d[k] = val 

205 return d 

206 

207 async def aget_many(self, keys, version=None): 

208 """See get_many().""" 

209 d = {} 

210 for k in keys: 

211 val = await self.aget(k, self._missing_key, version=version) 

212 if val is not self._missing_key: 

213 d[k] = val 

214 return d 

215 

216 def get_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None): 

217 """ 

218 Fetch a given key from the cache. If the key does not exist, 

219 add the key and set it to the default value. The default value can 

220 also be any callable. If timeout is given, use that timeout for the 

221 key; otherwise use the default cache timeout. 

222 

223 Return the value of the key stored or retrieved. 

224 """ 

225 val = self.get(key, self._missing_key, version=version) 

226 if val is self._missing_key: 

227 if callable(default): 

228 default = default() 

229 self.add(key, default, timeout=timeout, version=version) 

230 # Fetch the value again to avoid a race condition if another caller 

231 # added a value between the first get() and the add() above. 

232 return self.get(key, default, version=version) 

233 return val 

234 

235 async def aget_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None): 

236 """See get_or_set().""" 

237 val = await self.aget(key, self._missing_key, version=version) 

238 if val is self._missing_key: 

239 if callable(default): 

240 default = default() 

241 await self.aadd(key, default, timeout=timeout, version=version) 

242 # Fetch the value again to avoid a race condition if another caller 

243 # added a value between the first aget() and the aadd() above. 

244 return await self.aget(key, default, version=version) 

245 return val 

246 

247 def has_key(self, key, version=None): 

248 """ 

249 Return True if the key is in the cache and has not expired. 

250 """ 

251 return ( 

252 self.get(key, self._missing_key, version=version) is not self._missing_key 

253 ) 

254 

255 async def ahas_key(self, key, version=None): 

256 return ( 

257 await self.aget(key, self._missing_key, version=version) 

258 is not self._missing_key 

259 ) 

260 

261 def incr(self, key, delta=1, version=None): 

262 """ 

263 Add delta to value in the cache. If the key does not exist, raise a 

264 ValueError exception. 

265 """ 

266 value = self.get(key, self._missing_key, version=version) 

267 if value is self._missing_key: 

268 raise ValueError("Key '%s' not found" % key) 

269 new_value = value + delta 

270 self.set(key, new_value, version=version) 

271 return new_value 

272 

273 async def aincr(self, key, delta=1, version=None): 

274 """See incr().""" 

275 value = await self.aget(key, self._missing_key, version=version) 

276 if value is self._missing_key: 

277 raise ValueError("Key '%s' not found" % key) 

278 new_value = value + delta 

279 await self.aset(key, new_value, version=version) 

280 return new_value 

281 

282 def decr(self, key, delta=1, version=None): 

283 """ 

284 Subtract delta from value in the cache. If the key does not exist, raise 

285 a ValueError exception. 

286 """ 

287 return self.incr(key, -delta, version=version) 

288 

289 async def adecr(self, key, delta=1, version=None): 

290 return await self.aincr(key, -delta, version=version) 

291 

292 def __contains__(self, key): 

293 """ 

294 Return True if the key is in the cache and has not expired. 

295 """ 

296 # This is a separate method, rather than just a copy of has_key(), 

297 # so that it always has the same functionality as has_key(), even 

298 # if a subclass overrides it. 

299 return self.has_key(key) 

300 

301 def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None): 

302 """ 

303 Set a bunch of values in the cache at once from a dict of key/value 

304 pairs. For certain backends (memcached), this is much more efficient 

305 than calling set() multiple times. 

306 

307 If timeout is given, use that timeout for the key; otherwise use the 

308 default cache timeout. 

309 

310 On backends that support it, return a list of keys that failed 

311 insertion, or an empty list if all keys were inserted successfully. 

312 """ 

313 for key, value in data.items(): 

314 self.set(key, value, timeout=timeout, version=version) 

315 return [] 

316 

317 async def aset_many(self, data, timeout=DEFAULT_TIMEOUT, version=None): 

318 for key, value in data.items(): 

319 await self.aset(key, value, timeout=timeout, version=version) 

320 return [] 

321 

322 def delete_many(self, keys, version=None): 

323 """ 

324 Delete a bunch of values in the cache at once. For certain backends 

325 (memcached), this is much more efficient than calling delete() multiple 

326 times. 

327 """ 

328 for key in keys: 

329 self.delete(key, version=version) 

330 

331 async def adelete_many(self, keys, version=None): 

332 for key in keys: 

333 await self.adelete(key, version=version) 

334 

335 def clear(self): 

336 """Remove *all* values from the cache at once.""" 

337 raise NotImplementedError( 

338 "subclasses of BaseCache must provide a clear() method" 

339 ) 

340 

341 async def aclear(self): 

342 return await sync_to_async(self.clear, thread_sensitive=True)() 

343 

344 def incr_version(self, key, delta=1, version=None): 

345 """ 

346 Add delta to the cache version for the supplied key. Return the new 

347 version. 

348 """ 

349 if version is None: 

350 version = self.version 

351 

352 value = self.get(key, self._missing_key, version=version) 

353 if value is self._missing_key: 

354 raise ValueError("Key '%s' not found" % key) 

355 

356 self.set(key, value, version=version + delta) 

357 self.delete(key, version=version) 

358 return version + delta 

359 

360 async def aincr_version(self, key, delta=1, version=None): 

361 """See incr_version().""" 

362 if version is None: 

363 version = self.version 

364 

365 value = await self.aget(key, self._missing_key, version=version) 

366 if value is self._missing_key: 

367 raise ValueError("Key '%s' not found" % key) 

368 

369 await self.aset(key, value, version=version + delta) 

370 await self.adelete(key, version=version) 

371 return version + delta 

372 

373 def decr_version(self, key, delta=1, version=None): 

374 """ 

375 Subtract delta from the cache version for the supplied key. Return the 

376 new version. 

377 """ 

378 return self.incr_version(key, -delta, version) 

379 

380 async def adecr_version(self, key, delta=1, version=None): 

381 return await self.aincr_version(key, -delta, version) 

382 

383 def close(self, **kwargs): 

384 """Close the cache connection""" 

385 pass 

386 

387 async def aclose(self, **kwargs): 

388 pass 

389 

390 

391def memcache_key_warnings(key): 

392 if len(key) > MEMCACHE_MAX_KEY_LENGTH: 

393 yield ( 

394 "Cache key will cause errors if used with memcached: %r " 

395 "(longer than %s)" % (key, MEMCACHE_MAX_KEY_LENGTH) 

396 ) 

397 for char in key: 

398 if ord(char) < 33 or ord(char) == 127: 

399 yield ( 

400 "Cache key contains characters that will cause errors if " 

401 "used with memcached: %r" % key 

402 ) 

403 break