Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/core/cache/backends/base.py: 31%

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

187 statements  

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 

9from django.utils.regex_helper import _lazy_re_compile 

10 

11 

12class InvalidCacheBackendError(ImproperlyConfigured): 

13 pass 

14 

15 

16class CacheKeyWarning(RuntimeWarning): 

17 pass 

18 

19 

20class InvalidCacheKey(ValueError): 

21 pass 

22 

23 

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

25# the default timeout 

26DEFAULT_TIMEOUT = object() 

27 

28# Memcached does not accept keys longer than this. 

29MEMCACHE_MAX_KEY_LENGTH = 250 

30 

31 

32def default_key_func(key, key_prefix, version): 

33 """ 

34 Default function to generate keys. 

35 

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

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

38 function with custom key making behavior. 

39 """ 

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

41 

42 

43def get_key_func(key_func): 

44 """ 

45 Function to decide which key function to use. 

46 

47 Default to ``default_key_func``. 

48 """ 

49 if key_func is not None: 

50 if callable(key_func): 

51 return key_func 

52 else: 

53 return import_string(key_func) 

54 return default_key_func 

55 

56 

57class BaseCache: 

58 _missing_key = object() 

59 

60 def __init__(self, params): 

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

62 if timeout is not None: 

63 try: 

64 timeout = int(timeout) 

65 except (ValueError, TypeError): 

66 timeout = 300 

67 self.default_timeout = timeout 

68 

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

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

71 try: 

72 self._max_entries = int(max_entries) 

73 except (ValueError, TypeError): 

74 self._max_entries = 300 

75 

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

77 try: 

78 self._cull_frequency = int(cull_frequency) 

79 except (ValueError, TypeError): 

80 self._cull_frequency = 3 

81 

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

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

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

85 

86 def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT): 

87 """ 

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

89 timeout. 

90 """ 

91 if timeout == DEFAULT_TIMEOUT: 

92 timeout = self.default_timeout 

93 elif timeout == 0: 

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

95 timeout = -1 

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

97 

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

99 """ 

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

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

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

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

104 cache backend to provide custom key making behavior. 

105 """ 

106 if version is None: 

107 version = self.version 

108 

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

110 

111 def validate_key(self, key): 

112 """ 

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

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

115 cache code. 

116 """ 

117 for warning in memcache_key_warnings(key): 

118 warnings.warn(warning, CacheKeyWarning) 

119 

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

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

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

123 self.validate_key(key) 

124 return key 

125 

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

127 """ 

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

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

130 default cache timeout. 

131 

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

133 """ 

134 raise NotImplementedError( 

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

136 ) 

137 

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

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

140 key, value, timeout, version 

141 ) 

142 

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

144 """ 

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

146 default, which itself defaults to None. 

147 """ 

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

149 

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

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

152 key, default, version 

153 ) 

154 

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

156 """ 

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

158 key; otherwise use the default cache timeout. 

159 """ 

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

161 

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

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

164 key, value, timeout, version 

165 ) 

166 

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

168 """ 

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

170 or False if the key does not exist. 

171 """ 

172 raise NotImplementedError( 

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

174 ) 

175 

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

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

178 key, timeout, version 

179 ) 

180 

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

182 """ 

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

184 silently. 

185 """ 

186 raise NotImplementedError( 

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

188 ) 

189 

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

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

192 

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

194 """ 

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

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

197 

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

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

200 """ 

201 d = {} 

202 for k in keys: 

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

204 if val is not self._missing_key: 

205 d[k] = val 

206 return d 

207 

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

209 """See get_many().""" 

210 d = {} 

211 for k in keys: 

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

213 if val is not self._missing_key: 

214 d[k] = val 

215 return d 

216 

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

218 """ 

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

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

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

222 key; otherwise use the default cache timeout. 

223 

224 Return the value of the key stored or retrieved. 

225 """ 

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

227 if val is self._missing_key: 

228 if callable(default): 

229 default = default() 

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

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

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

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

234 return val 

235 

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

237 """See get_or_set().""" 

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

239 if val is self._missing_key: 

240 if callable(default): 

241 default = default() 

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

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

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

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

246 return val 

247 

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

249 """ 

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

251 """ 

252 return ( 

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

254 ) 

255 

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

257 return ( 

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

259 is not self._missing_key 

260 ) 

261 

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

263 """ 

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

265 ValueError exception. 

266 """ 

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

268 if value is self._missing_key: 

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

270 new_value = value + delta 

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

272 return new_value 

273 

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

275 """See incr().""" 

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

277 if value is self._missing_key: 

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

279 new_value = value + delta 

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

281 return new_value 

282 

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

284 """ 

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

286 a ValueError exception. 

287 """ 

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

289 

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

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

292 

293 def __contains__(self, key): 

294 """ 

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

296 """ 

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

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

299 # if a subclass overrides it. 

300 return self.has_key(key) 

301 

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

303 """ 

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

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

306 than calling set() multiple times. 

307 

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

309 default cache timeout. 

310 

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

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

313 """ 

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

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

316 return [] 

317 

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

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

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

321 return [] 

322 

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

324 """ 

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

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

327 times. 

328 """ 

329 for key in keys: 

330 self.delete(key, version=version) 

331 

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

333 for key in keys: 

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

335 

336 def clear(self): 

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

338 raise NotImplementedError( 

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

340 ) 

341 

342 async def aclear(self): 

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

344 

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

346 """ 

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

348 version. 

349 """ 

350 if version is None: 

351 version = self.version 

352 

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

354 if value is self._missing_key: 

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

356 

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

358 self.delete(key, version=version) 

359 return version + delta 

360 

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

362 """See incr_version().""" 

363 if version is None: 

364 version = self.version 

365 

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

367 if value is self._missing_key: 

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

369 

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

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

372 return version + delta 

373 

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

375 """ 

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

377 new version. 

378 """ 

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

380 

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

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

383 

384 def close(self, **kwargs): 

385 """Close the cache connection""" 

386 pass 

387 

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

389 pass 

390 

391 

392memcached_error_chars_re = _lazy_re_compile(r"[\x00-\x20\x7f]") 

393 

394 

395def memcache_key_warnings(key): 

396 if len(key) > MEMCACHE_MAX_KEY_LENGTH: 

397 yield ( 

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

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

400 ) 

401 if memcached_error_chars_re.search(key): 

402 yield ( 

403 "Cache key contains characters that will cause errors if used with " 

404 f"memcached: {key!r}" 

405 )