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
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"Base Cache class."
2import time
3import warnings
5from asgiref.sync import sync_to_async
7from django.core.exceptions import ImproperlyConfigured
8from django.utils.module_loading import import_string
11class InvalidCacheBackendError(ImproperlyConfigured):
12 pass
15class CacheKeyWarning(RuntimeWarning):
16 pass
19class InvalidCacheKey(ValueError):
20 pass
23# Stub class to ensure not passing in a `timeout` argument results in
24# the default timeout
25DEFAULT_TIMEOUT = object()
27# Memcached does not accept keys longer than this.
28MEMCACHE_MAX_KEY_LENGTH = 250
31def default_key_func(key, key_prefix, version):
32 """
33 Default function to generate keys.
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)
42def get_key_func(key_func):
43 """
44 Function to decide which key function to use.
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
56class BaseCache:
57 _missing_key = object()
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
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
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
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"))
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
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
108 return self.key_func(key, self.key_prefix, version)
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)
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
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.
131 Return True if the value was stored, False otherwise.
132 """
133 raise NotImplementedError(
134 "subclasses of BaseCache must provide an add() method"
135 )
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 )
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")
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 )
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")
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 )
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 )
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 )
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 )
189 async def adelete(self, key, version=None):
190 return await sync_to_async(self.delete, thread_sensitive=True)(key, version)
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.
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
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
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.
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
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
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 )
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 )
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
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
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)
289 async def adecr(self, key, delta=1, version=None):
290 return await self.aincr(key, -delta, version=version)
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)
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.
307 If timeout is given, use that timeout for the key; otherwise use the
308 default cache timeout.
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 []
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 []
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)
331 async def adelete_many(self, keys, version=None):
332 for key in keys:
333 await self.adelete(key, version=version)
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 )
341 async def aclear(self):
342 return await sync_to_async(self.clear, thread_sensitive=True)()
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
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)
356 self.set(key, value, version=version + delta)
357 self.delete(key, version=version)
358 return version + delta
360 async def aincr_version(self, key, delta=1, version=None):
361 """See incr_version()."""
362 if version is None:
363 version = self.version
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)
369 await self.aset(key, value, version=version + delta)
370 await self.adelete(key, version=version)
371 return version + delta
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)
380 async def adecr_version(self, key, delta=1, version=None):
381 return await self.aincr_version(key, -delta, version)
383 def close(self, **kwargs):
384 """Close the cache connection"""
385 pass
387 async def aclose(self, **kwargs):
388 pass
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