Fixed #32076 -- Added async methods to BaseCache.
This also makes DummyCache async-compatible.
This commit is contained in:
parent
42dfa97e19
commit
301a85a12f
|
@ -2,6 +2,8 @@
|
||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
|
|
||||||
|
@ -130,6 +132,9 @@ class BaseCache:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('subclasses of BaseCache must provide an add() method')
|
raise NotImplementedError('subclasses of BaseCache must provide an add() method')
|
||||||
|
|
||||||
|
async def aadd(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
|
return await sync_to_async(self.add, thread_sensitive=True)(key, value, timeout, version)
|
||||||
|
|
||||||
def get(self, key, default=None, version=None):
|
def get(self, key, default=None, version=None):
|
||||||
"""
|
"""
|
||||||
Fetch a given key from the cache. If the key does not exist, return
|
Fetch a given key from the cache. If the key does not exist, return
|
||||||
|
@ -137,6 +142,9 @@ class BaseCache:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('subclasses of BaseCache must provide a get() method')
|
raise NotImplementedError('subclasses of BaseCache must provide a get() method')
|
||||||
|
|
||||||
|
async def aget(self, key, default=None, version=None):
|
||||||
|
return await sync_to_async(self.get, thread_sensitive=True)(key, default, version)
|
||||||
|
|
||||||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
"""
|
"""
|
||||||
Set a value in the cache. If timeout is given, use that timeout for the
|
Set a value in the cache. If timeout is given, use that timeout for the
|
||||||
|
@ -144,6 +152,9 @@ class BaseCache:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('subclasses of BaseCache must provide a set() method')
|
raise NotImplementedError('subclasses of BaseCache must provide a set() method')
|
||||||
|
|
||||||
|
async def aset(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
|
return await sync_to_async(self.set, thread_sensitive=True)(key, value, timeout, version)
|
||||||
|
|
||||||
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
"""
|
"""
|
||||||
Update the key's expiry time using timeout. Return True if successful
|
Update the key's expiry time using timeout. Return True if successful
|
||||||
|
@ -151,6 +162,9 @@ class BaseCache:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('subclasses of BaseCache must provide a touch() method')
|
raise NotImplementedError('subclasses of BaseCache must provide a touch() method')
|
||||||
|
|
||||||
|
async def atouch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
|
return await sync_to_async(self.touch, thread_sensitive=True)(key, timeout, version)
|
||||||
|
|
||||||
def delete(self, key, version=None):
|
def delete(self, key, version=None):
|
||||||
"""
|
"""
|
||||||
Delete a key from the cache and return whether it succeeded, failing
|
Delete a key from the cache and return whether it succeeded, failing
|
||||||
|
@ -158,6 +172,9 @@ class BaseCache:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('subclasses of BaseCache must provide a delete() method')
|
raise NotImplementedError('subclasses of BaseCache must provide a delete() method')
|
||||||
|
|
||||||
|
async def adelete(self, key, version=None):
|
||||||
|
return await sync_to_async(self.delete, thread_sensitive=True)(key, version)
|
||||||
|
|
||||||
def get_many(self, keys, version=None):
|
def get_many(self, keys, version=None):
|
||||||
"""
|
"""
|
||||||
Fetch a bunch of keys from the cache. For certain backends (memcached,
|
Fetch a bunch of keys from the cache. For certain backends (memcached,
|
||||||
|
@ -173,6 +190,15 @@ class BaseCache:
|
||||||
d[k] = val
|
d[k] = val
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
async def aget_many(self, keys, version=None):
|
||||||
|
"""See get_many()."""
|
||||||
|
d = {}
|
||||||
|
for k in keys:
|
||||||
|
val = await self.aget(k, self._missing_key, version=version)
|
||||||
|
if val is not self._missing_key:
|
||||||
|
d[k] = val
|
||||||
|
return d
|
||||||
|
|
||||||
def get_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None):
|
def get_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
"""
|
"""
|
||||||
Fetch a given key from the cache. If the key does not exist,
|
Fetch a given key from the cache. If the key does not exist,
|
||||||
|
@ -192,12 +218,30 @@ class BaseCache:
|
||||||
return self.get(key, default, version=version)
|
return self.get(key, default, version=version)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
async def aget_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
|
"""See get_or_set()."""
|
||||||
|
val = await self.aget(key, self._missing_key, version=version)
|
||||||
|
if val is self._missing_key:
|
||||||
|
if callable(default):
|
||||||
|
default = default()
|
||||||
|
await self.aadd(key, default, timeout=timeout, version=version)
|
||||||
|
# Fetch the value again to avoid a race condition if another caller
|
||||||
|
# added a value between the first aget() and the aadd() above.
|
||||||
|
return await self.aget(key, default, version=version)
|
||||||
|
return val
|
||||||
|
|
||||||
def has_key(self, key, version=None):
|
def has_key(self, key, version=None):
|
||||||
"""
|
"""
|
||||||
Return True if the key is in the cache and has not expired.
|
Return True if the key is in the cache and has not expired.
|
||||||
"""
|
"""
|
||||||
return self.get(key, self._missing_key, version=version) is not self._missing_key
|
return self.get(key, self._missing_key, version=version) is not self._missing_key
|
||||||
|
|
||||||
|
async def ahas_key(self, key, version=None):
|
||||||
|
return (
|
||||||
|
await self.aget(key, self._missing_key, version=version)
|
||||||
|
is not self._missing_key
|
||||||
|
)
|
||||||
|
|
||||||
def incr(self, key, delta=1, version=None):
|
def incr(self, key, delta=1, version=None):
|
||||||
"""
|
"""
|
||||||
Add delta to value in the cache. If the key does not exist, raise a
|
Add delta to value in the cache. If the key does not exist, raise a
|
||||||
|
@ -210,6 +254,15 @@ class BaseCache:
|
||||||
self.set(key, new_value, version=version)
|
self.set(key, new_value, version=version)
|
||||||
return new_value
|
return new_value
|
||||||
|
|
||||||
|
async def aincr(self, key, delta=1, version=None):
|
||||||
|
"""See incr()."""
|
||||||
|
value = await self.aget(key, self._missing_key, version=version)
|
||||||
|
if value is self._missing_key:
|
||||||
|
raise ValueError("Key '%s' not found" % key)
|
||||||
|
new_value = value + delta
|
||||||
|
await self.aset(key, new_value, version=version)
|
||||||
|
return new_value
|
||||||
|
|
||||||
def decr(self, key, delta=1, version=None):
|
def decr(self, key, delta=1, version=None):
|
||||||
"""
|
"""
|
||||||
Subtract delta from value in the cache. If the key does not exist, raise
|
Subtract delta from value in the cache. If the key does not exist, raise
|
||||||
|
@ -217,6 +270,9 @@ class BaseCache:
|
||||||
"""
|
"""
|
||||||
return self.incr(key, -delta, version=version)
|
return self.incr(key, -delta, version=version)
|
||||||
|
|
||||||
|
async def adecr(self, key, delta=1, version=None):
|
||||||
|
return await self.aincr(key, -delta, version=version)
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
"""
|
"""
|
||||||
Return True if the key is in the cache and has not expired.
|
Return True if the key is in the cache and has not expired.
|
||||||
|
@ -242,6 +298,11 @@ class BaseCache:
|
||||||
self.set(key, value, timeout=timeout, version=version)
|
self.set(key, value, timeout=timeout, version=version)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
async def aset_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
|
for key, value in data.items():
|
||||||
|
await self.aset(key, value, timeout=timeout, version=version)
|
||||||
|
return []
|
||||||
|
|
||||||
def delete_many(self, keys, version=None):
|
def delete_many(self, keys, version=None):
|
||||||
"""
|
"""
|
||||||
Delete a bunch of values in the cache at once. For certain backends
|
Delete a bunch of values in the cache at once. For certain backends
|
||||||
|
@ -251,10 +312,17 @@ class BaseCache:
|
||||||
for key in keys:
|
for key in keys:
|
||||||
self.delete(key, version=version)
|
self.delete(key, version=version)
|
||||||
|
|
||||||
|
async def adelete_many(self, keys, version=None):
|
||||||
|
for key in keys:
|
||||||
|
await self.adelete(key, version=version)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Remove *all* values from the cache at once."""
|
"""Remove *all* values from the cache at once."""
|
||||||
raise NotImplementedError('subclasses of BaseCache must provide a clear() method')
|
raise NotImplementedError('subclasses of BaseCache must provide a clear() method')
|
||||||
|
|
||||||
|
async def aclear(self):
|
||||||
|
return await sync_to_async(self.clear, thread_sensitive=True)()
|
||||||
|
|
||||||
def incr_version(self, key, delta=1, version=None):
|
def incr_version(self, key, delta=1, version=None):
|
||||||
"""
|
"""
|
||||||
Add delta to the cache version for the supplied key. Return the new
|
Add delta to the cache version for the supplied key. Return the new
|
||||||
|
@ -271,6 +339,19 @@ class BaseCache:
|
||||||
self.delete(key, version=version)
|
self.delete(key, version=version)
|
||||||
return version + delta
|
return version + delta
|
||||||
|
|
||||||
|
async def aincr_version(self, key, delta=1, version=None):
|
||||||
|
"""See incr_version()."""
|
||||||
|
if version is None:
|
||||||
|
version = self.version
|
||||||
|
|
||||||
|
value = await self.aget(key, self._missing_key, version=version)
|
||||||
|
if value is self._missing_key:
|
||||||
|
raise ValueError("Key '%s' not found" % key)
|
||||||
|
|
||||||
|
await self.aset(key, value, version=version + delta)
|
||||||
|
await self.adelete(key, version=version)
|
||||||
|
return version + delta
|
||||||
|
|
||||||
def decr_version(self, key, delta=1, version=None):
|
def decr_version(self, key, delta=1, version=None):
|
||||||
"""
|
"""
|
||||||
Subtract delta from the cache version for the supplied key. Return the
|
Subtract delta from the cache version for the supplied key. Return the
|
||||||
|
@ -278,10 +359,16 @@ class BaseCache:
|
||||||
"""
|
"""
|
||||||
return self.incr_version(key, -delta, version)
|
return self.incr_version(key, -delta, version)
|
||||||
|
|
||||||
|
async def adecr_version(self, key, delta=1, version=None):
|
||||||
|
return await self.aincr_version(key, -delta, version)
|
||||||
|
|
||||||
def close(self, **kwargs):
|
def close(self, **kwargs):
|
||||||
"""Close the cache connection"""
|
"""Close the cache connection"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def aclose(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def memcache_key_warnings(key):
|
def memcache_key_warnings(key):
|
||||||
if len(key) > MEMCACHE_MAX_KEY_LENGTH:
|
if len(key) > MEMCACHE_MAX_KEY_LENGTH:
|
||||||
|
|
|
@ -187,7 +187,13 @@ Minor features
|
||||||
Cache
|
Cache
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
* ...
|
* The new async API for ``django.core.cache.backends.base.BaseCache`` begins
|
||||||
|
the process of making cache backends async-compatible. The new async methods
|
||||||
|
all have ``a`` prefixed names, e.g. ``aadd()``, ``aget()``, ``aset()``,
|
||||||
|
``aget_or_set()``, or ``adelete_many()``.
|
||||||
|
|
||||||
|
Going forward, the ``a`` prefix will be used for async variants of methods
|
||||||
|
generally.
|
||||||
|
|
||||||
CSRF
|
CSRF
|
||||||
~~~~
|
~~~~
|
||||||
|
|
|
@ -808,6 +808,8 @@ Accessing the cache
|
||||||
|
|
||||||
This object is equivalent to ``caches['default']``.
|
This object is equivalent to ``caches['default']``.
|
||||||
|
|
||||||
|
.. _cache-basic-interface:
|
||||||
|
|
||||||
Basic usage
|
Basic usage
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -997,6 +999,16 @@ the cache backend.
|
||||||
|
|
||||||
For caches that don't implement ``close`` methods it is a no-op.
|
For caches that don't implement ``close`` methods it is a no-op.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The async variants of base methods are prefixed with ``a``, e.g.
|
||||||
|
``cache.aadd()`` or ``cache.adelete_many()``. See `Asynchronous support`_
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.0
|
||||||
|
|
||||||
|
The async variants of methods were added to the ``BaseCache``.
|
||||||
|
|
||||||
.. _cache_key_prefixing:
|
.. _cache_key_prefixing:
|
||||||
|
|
||||||
Cache key prefixing
|
Cache key prefixing
|
||||||
|
@ -1123,6 +1135,25 @@ instance, to do this for the ``locmem`` backend, put this code in a module::
|
||||||
...and use the dotted Python path to this class in the
|
...and use the dotted Python path to this class in the
|
||||||
:setting:`BACKEND <CACHES-BACKEND>` portion of your :setting:`CACHES` setting.
|
:setting:`BACKEND <CACHES-BACKEND>` portion of your :setting:`CACHES` setting.
|
||||||
|
|
||||||
|
.. _asynchronous_support:
|
||||||
|
|
||||||
|
Asynchronous support
|
||||||
|
====================
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
|
||||||
|
Django has developing support for asynchronous cache backends, but does not
|
||||||
|
yet support asynchronous caching. It will be coming in a future release.
|
||||||
|
|
||||||
|
``django.core.cache.backends.base.BaseCache`` has async variants of :ref:`all
|
||||||
|
base methods <cache-basic-interface>`. By convention, the asynchronous versions
|
||||||
|
of all methods are prefixed with ``a``. By default, the arguments for both
|
||||||
|
variants are the same::
|
||||||
|
|
||||||
|
>>> await cache.aset('num', 1)
|
||||||
|
>>> await cache.ahas_key('num')
|
||||||
|
True
|
||||||
|
|
||||||
.. _downstream-caches:
|
.. _downstream-caches:
|
||||||
|
|
||||||
Downstream caches
|
Downstream caches
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from django.core.cache import CacheKeyWarning, cache
|
||||||
|
from django.test import SimpleTestCase, override_settings
|
||||||
|
|
||||||
|
from .tests import KEY_ERRORS_WITH_MEMCACHED_MSG
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(CACHES={
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
class AsyncDummyCacheTests(SimpleTestCase):
|
||||||
|
async def test_simple(self):
|
||||||
|
"""Dummy cache backend ignores cache set calls."""
|
||||||
|
await cache.aset('key', 'value')
|
||||||
|
self.assertIsNone(await cache.aget('key'))
|
||||||
|
|
||||||
|
async def test_aadd(self):
|
||||||
|
"""Add doesn't do anything in dummy cache backend."""
|
||||||
|
self.assertIs(await cache.aadd('key', 'value'), True)
|
||||||
|
self.assertIs(await cache.aadd('key', 'new_value'), True)
|
||||||
|
self.assertIsNone(await cache.aget('key'))
|
||||||
|
|
||||||
|
async def test_non_existent(self):
|
||||||
|
"""Nonexistent keys aren't found in the dummy cache backend."""
|
||||||
|
self.assertIsNone(await cache.aget('does_not_exist'))
|
||||||
|
self.assertEqual(await cache.aget('does_not_exist', 'default'), 'default')
|
||||||
|
|
||||||
|
async def test_aget_many(self):
|
||||||
|
"""aget_many() returns nothing for the dummy cache backend."""
|
||||||
|
await cache.aset_many({'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd'})
|
||||||
|
self.assertEqual(await cache.aget_many(['a', 'c', 'd']), {})
|
||||||
|
self.assertEqual(await cache.aget_many(['a', 'b', 'e']), {})
|
||||||
|
|
||||||
|
async def test_aget_many_invalid_key(self):
|
||||||
|
msg = KEY_ERRORS_WITH_MEMCACHED_MSG % ':1:key with spaces'
|
||||||
|
with self.assertWarnsMessage(CacheKeyWarning, msg):
|
||||||
|
await cache.aget_many(['key with spaces'])
|
||||||
|
|
||||||
|
async def test_adelete(self):
|
||||||
|
"""
|
||||||
|
Cache deletion is transparently ignored on the dummy cache backend.
|
||||||
|
"""
|
||||||
|
await cache.aset_many({'key1': 'spam', 'key2': 'eggs'})
|
||||||
|
self.assertIsNone(await cache.aget('key1'))
|
||||||
|
self.assertIs(await cache.adelete('key1'), False)
|
||||||
|
self.assertIsNone(await cache.aget('key1'))
|
||||||
|
self.assertIsNone(await cache.aget('key2'))
|
||||||
|
|
||||||
|
async def test_ahas_key(self):
|
||||||
|
"""ahas_key() doesn't ever return True for the dummy cache backend."""
|
||||||
|
await cache.aset('hello1', 'goodbye1')
|
||||||
|
self.assertIs(await cache.ahas_key('hello1'), False)
|
||||||
|
self.assertIs(await cache.ahas_key('goodbye1'), False)
|
||||||
|
|
||||||
|
async def test_aincr(self):
|
||||||
|
"""Dummy cache values can't be incremented."""
|
||||||
|
await cache.aset('answer', 42)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.aincr('answer')
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.aincr('does_not_exist')
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.aincr('does_not_exist', -1)
|
||||||
|
|
||||||
|
async def test_adecr(self):
|
||||||
|
"""Dummy cache values can't be decremented."""
|
||||||
|
await cache.aset('answer', 42)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.adecr('answer')
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.adecr('does_not_exist')
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.adecr('does_not_exist', -1)
|
||||||
|
|
||||||
|
async def test_atouch(self):
|
||||||
|
self.assertIs(await cache.atouch('key'), False)
|
||||||
|
|
||||||
|
async def test_data_types(self):
|
||||||
|
"""All data types are ignored equally by the dummy cache."""
|
||||||
|
def f():
|
||||||
|
return 42
|
||||||
|
|
||||||
|
class C:
|
||||||
|
def m(n):
|
||||||
|
return 24
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'string': 'this is a string',
|
||||||
|
'int': 42,
|
||||||
|
'list': [1, 2, 3, 4],
|
||||||
|
'tuple': (1, 2, 3, 4),
|
||||||
|
'dict': {'A': 1, 'B': 2},
|
||||||
|
'function': f,
|
||||||
|
'class': C,
|
||||||
|
}
|
||||||
|
await cache.aset('data', data)
|
||||||
|
self.assertIsNone(await cache.aget('data'))
|
||||||
|
|
||||||
|
async def test_expiration(self):
|
||||||
|
"""Expiration has no effect on the dummy cache."""
|
||||||
|
await cache.aset('expire1', 'very quickly', 1)
|
||||||
|
await cache.aset('expire2', 'very quickly', 1)
|
||||||
|
await cache.aset('expire3', 'very quickly', 1)
|
||||||
|
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
self.assertIsNone(await cache.aget('expire1'))
|
||||||
|
|
||||||
|
self.assertIs(await cache.aadd('expire2', 'new_value'), True)
|
||||||
|
self.assertIsNone(await cache.aget('expire2'))
|
||||||
|
self.assertIs(await cache.ahas_key('expire3'), False)
|
||||||
|
|
||||||
|
async def test_unicode(self):
|
||||||
|
"""Unicode values are ignored by the dummy cache."""
|
||||||
|
tests = {
|
||||||
|
'ascii': 'ascii_value',
|
||||||
|
'unicode_ascii': 'Iñtërnâtiônàlizætiøn1',
|
||||||
|
'Iñtërnâtiônàlizætiøn': 'Iñtërnâtiônàlizætiøn2',
|
||||||
|
'ascii2': {'x': 1},
|
||||||
|
}
|
||||||
|
for key, value in tests.items():
|
||||||
|
with self.subTest(key=key):
|
||||||
|
await cache.aset(key, value)
|
||||||
|
self.assertIsNone(await cache.aget(key))
|
||||||
|
|
||||||
|
async def test_aset_many(self):
|
||||||
|
"""aset_many() does nothing for the dummy cache backend."""
|
||||||
|
self.assertEqual(await cache.aset_many({'a': 1, 'b': 2}), [])
|
||||||
|
self.assertEqual(
|
||||||
|
await cache.aset_many({'a': 1, 'b': 2}, timeout=2, version='1'),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def test_aset_many_invalid_key(self):
|
||||||
|
msg = KEY_ERRORS_WITH_MEMCACHED_MSG % ':1:key with spaces'
|
||||||
|
with self.assertWarnsMessage(CacheKeyWarning, msg):
|
||||||
|
await cache.aset_many({'key with spaces': 'foo'})
|
||||||
|
|
||||||
|
async def test_adelete_many(self):
|
||||||
|
"""adelete_many() does nothing for the dummy cache backend."""
|
||||||
|
await cache.adelete_many(['a', 'b'])
|
||||||
|
|
||||||
|
async def test_adelete_many_invalid_key(self):
|
||||||
|
msg = KEY_ERRORS_WITH_MEMCACHED_MSG % ':1:key with spaces'
|
||||||
|
with self.assertWarnsMessage(CacheKeyWarning, msg):
|
||||||
|
await cache.adelete_many({'key with spaces': 'foo'})
|
||||||
|
|
||||||
|
async def test_aclear(self):
|
||||||
|
"""aclear() does nothing for the dummy cache backend."""
|
||||||
|
await cache.aclear()
|
||||||
|
|
||||||
|
async def test_aclose(self):
|
||||||
|
"""aclose() does nothing for the dummy cache backend."""
|
||||||
|
await cache.aclose()
|
||||||
|
|
||||||
|
async def test_aincr_version(self):
|
||||||
|
"""Dummy cache versions can't be incremented."""
|
||||||
|
await cache.aset('answer', 42)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.aincr_version('answer')
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.aincr_version('answer', version=2)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.aincr_version('does_not_exist')
|
||||||
|
|
||||||
|
async def test_adecr_version(self):
|
||||||
|
"""Dummy cache versions can't be decremented."""
|
||||||
|
await cache.aset('answer', 42)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.adecr_version('answer')
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.adecr_version('answer', version=2)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await cache.adecr_version('does_not_exist')
|
||||||
|
|
||||||
|
async def test_aget_or_set(self):
|
||||||
|
self.assertEqual(await cache.aget_or_set('key', 'default'), 'default')
|
||||||
|
self.assertIsNone(await cache.aget_or_set('key', None))
|
||||||
|
|
||||||
|
async def test_aget_or_set_callable(self):
|
||||||
|
def my_callable():
|
||||||
|
return 'default'
|
||||||
|
|
||||||
|
self.assertEqual(await cache.aget_or_set('key', my_callable), 'default')
|
||||||
|
self.assertEqual(await cache.aget_or_set('key', my_callable()), 'default')
|
Loading…
Reference in New Issue