Fixed #9595 -- Allow non-expiring cache timeouts.
Also, streamline the use of 0 and None between cache backends.
This commit is contained in:
parent
e0df647143
commit
89955cc35f
1
AUTHORS
1
AUTHORS
|
@ -123,6 +123,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
bthomas
|
bthomas
|
||||||
btoll@bestweb.net
|
btoll@bestweb.net
|
||||||
Jonathan Buchanan <jonathan.buchanan@gmail.com>
|
Jonathan Buchanan <jonathan.buchanan@gmail.com>
|
||||||
|
Jacob Burch <jacobburch@gmail.com>
|
||||||
Keith Bussell <kbussell@gmail.com>
|
Keith Bussell <kbussell@gmail.com>
|
||||||
C8E
|
C8E
|
||||||
Chris Cahoon <chris.cahoon@gmail.com>
|
Chris Cahoon <chris.cahoon@gmail.com>
|
||||||
|
|
|
@ -15,6 +15,10 @@ class CacheKeyWarning(DjangoRuntimeWarning):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Stub class to ensure not passing in a `timeout` argument results in
|
||||||
|
# the default timeout
|
||||||
|
DEFAULT_TIMEOUT = object()
|
||||||
|
|
||||||
# Memcached does not accept keys longer than this.
|
# Memcached does not accept keys longer than this.
|
||||||
MEMCACHE_MAX_KEY_LENGTH = 250
|
MEMCACHE_MAX_KEY_LENGTH = 250
|
||||||
|
|
||||||
|
@ -84,7 +88,7 @@ class BaseCache(object):
|
||||||
new_key = self.key_func(key, self.key_prefix, version)
|
new_key = self.key_func(key, self.key_prefix, version)
|
||||||
return new_key
|
return new_key
|
||||||
|
|
||||||
def add(self, key, value, timeout=None, version=None):
|
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
"""
|
"""
|
||||||
Set a value in the cache if the key does not already exist. If
|
Set a value in the cache if the key does not already exist. If
|
||||||
timeout is given, that timeout will be used for the key; otherwise
|
timeout is given, that timeout will be used for the key; otherwise
|
||||||
|
@ -101,7 +105,7 @@ class BaseCache(object):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def set(self, key, value, timeout=None, version=None):
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
"""
|
"""
|
||||||
Set a value in the cache. If timeout is given, that timeout will be
|
Set a value in the cache. If timeout is given, that timeout will be
|
||||||
used for the key; otherwise the default cache timeout will be used.
|
used for the key; otherwise the default cache timeout will be used.
|
||||||
|
@ -163,7 +167,7 @@ class BaseCache(object):
|
||||||
# if a subclass overrides it.
|
# if a subclass overrides it.
|
||||||
return self.has_key(key)
|
return self.has_key(key)
|
||||||
|
|
||||||
def set_many(self, data, timeout=None, version=None):
|
def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
"""
|
"""
|
||||||
Set a bunch of values in the cache at once from a dict of key/value
|
Set a bunch of values in the cache at once from a dict of key/value
|
||||||
pairs. For certain backends (memcached), this is much more efficient
|
pairs. For certain backends (memcached), this is much more efficient
|
||||||
|
|
|
@ -9,7 +9,7 @@ except ImportError:
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache.backends.base import BaseCache
|
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
|
||||||
from django.db import connections, transaction, router, DatabaseError
|
from django.db import connections, transaction, router, DatabaseError
|
||||||
from django.utils import timezone, six
|
from django.utils import timezone, six
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
|
@ -65,6 +65,7 @@ class DatabaseCache(BaseDatabaseCache):
|
||||||
if row is None:
|
if row is None:
|
||||||
return default
|
return default
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
|
||||||
if row[2] < now:
|
if row[2] < now:
|
||||||
db = router.db_for_write(self.cache_model_class)
|
db = router.db_for_write(self.cache_model_class)
|
||||||
cursor = connections[db].cursor()
|
cursor = connections[db].cursor()
|
||||||
|
@ -74,18 +75,18 @@ class DatabaseCache(BaseDatabaseCache):
|
||||||
value = connections[db].ops.process_clob(row[1])
|
value = connections[db].ops.process_clob(row[1])
|
||||||
return pickle.loads(base64.b64decode(force_bytes(value)))
|
return pickle.loads(base64.b64decode(force_bytes(value)))
|
||||||
|
|
||||||
def set(self, key, value, timeout=None, version=None):
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
self._base_set('set', key, value, timeout)
|
self._base_set('set', key, value, timeout)
|
||||||
|
|
||||||
def add(self, key, value, timeout=None, version=None):
|
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
return self._base_set('add', key, value, timeout)
|
return self._base_set('add', key, value, timeout)
|
||||||
|
|
||||||
def _base_set(self, mode, key, value, timeout=None):
|
def _base_set(self, mode, key, value, timeout=DEFAULT_TIMEOUT):
|
||||||
if timeout is None:
|
if timeout == DEFAULT_TIMEOUT:
|
||||||
timeout = self.default_timeout
|
timeout = self.default_timeout
|
||||||
db = router.db_for_write(self.cache_model_class)
|
db = router.db_for_write(self.cache_model_class)
|
||||||
table = connections[db].ops.quote_name(self._table)
|
table = connections[db].ops.quote_name(self._table)
|
||||||
|
@ -95,7 +96,9 @@ class DatabaseCache(BaseDatabaseCache):
|
||||||
num = cursor.fetchone()[0]
|
num = cursor.fetchone()[0]
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
now = now.replace(microsecond=0)
|
now = now.replace(microsecond=0)
|
||||||
if settings.USE_TZ:
|
if timeout is None:
|
||||||
|
exp = datetime.max
|
||||||
|
elif settings.USE_TZ:
|
||||||
exp = datetime.utcfromtimestamp(time.time() + timeout)
|
exp = datetime.utcfromtimestamp(time.time() + timeout)
|
||||||
else:
|
else:
|
||||||
exp = datetime.fromtimestamp(time.time() + timeout)
|
exp = datetime.fromtimestamp(time.time() + timeout)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
"Dummy cache backend"
|
"Dummy cache backend"
|
||||||
|
|
||||||
from django.core.cache.backends.base import BaseCache
|
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
|
||||||
|
|
||||||
class DummyCache(BaseCache):
|
class DummyCache(BaseCache):
|
||||||
def __init__(self, host, *args, **kwargs):
|
def __init__(self, host, *args, **kwargs):
|
||||||
BaseCache.__init__(self, *args, **kwargs)
|
BaseCache.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
def add(self, key, value, timeout=None, version=None):
|
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
return True
|
return True
|
||||||
|
@ -16,7 +16,7 @@ class DummyCache(BaseCache):
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def set(self, key, value, timeout=None, version=None):
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ class DummyCache(BaseCache):
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def set_many(self, data, timeout=0, version=None):
|
def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def delete_many(self, keys, version=None):
|
def delete_many(self, keys, version=None):
|
||||||
|
|
|
@ -9,9 +9,10 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
from django.core.cache.backends.base import BaseCache
|
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
|
|
||||||
|
|
||||||
class FileBasedCache(BaseCache):
|
class FileBasedCache(BaseCache):
|
||||||
def __init__(self, dir, params):
|
def __init__(self, dir, params):
|
||||||
BaseCache.__init__(self, params)
|
BaseCache.__init__(self, params)
|
||||||
|
@ -19,7 +20,7 @@ class FileBasedCache(BaseCache):
|
||||||
if not os.path.exists(self._dir):
|
if not os.path.exists(self._dir):
|
||||||
self._createdir()
|
self._createdir()
|
||||||
|
|
||||||
def add(self, key, value, timeout=None, version=None):
|
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
if self.has_key(key, version=version):
|
if self.has_key(key, version=version):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ class FileBasedCache(BaseCache):
|
||||||
with open(fname, 'rb') as f:
|
with open(fname, 'rb') as f:
|
||||||
exp = pickle.load(f)
|
exp = pickle.load(f)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if exp < now:
|
if exp is not None and exp < now:
|
||||||
self._delete(fname)
|
self._delete(fname)
|
||||||
else:
|
else:
|
||||||
return pickle.load(f)
|
return pickle.load(f)
|
||||||
|
@ -43,14 +44,14 @@ class FileBasedCache(BaseCache):
|
||||||
pass
|
pass
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def set(self, key, value, timeout=None, version=None):
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
|
|
||||||
fname = self._key_to_file(key)
|
fname = self._key_to_file(key)
|
||||||
dirname = os.path.dirname(fname)
|
dirname = os.path.dirname(fname)
|
||||||
|
|
||||||
if timeout is None:
|
if timeout == DEFAULT_TIMEOUT:
|
||||||
timeout = self.default_timeout
|
timeout = self.default_timeout
|
||||||
|
|
||||||
self._cull()
|
self._cull()
|
||||||
|
@ -60,8 +61,8 @@ class FileBasedCache(BaseCache):
|
||||||
os.makedirs(dirname)
|
os.makedirs(dirname)
|
||||||
|
|
||||||
with open(fname, 'wb') as f:
|
with open(fname, 'wb') as f:
|
||||||
now = time.time()
|
expiry = None if timeout is None else time.time() + timeout
|
||||||
pickle.dump(now + timeout, f, pickle.HIGHEST_PROTOCOL)
|
pickle.dump(expiry, f, pickle.HIGHEST_PROTOCOL)
|
||||||
pickle.dump(value, f, pickle.HIGHEST_PROTOCOL)
|
pickle.dump(value, f, pickle.HIGHEST_PROTOCOL)
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -6,7 +6,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
from django.core.cache.backends.base import BaseCache
|
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
|
||||||
from django.utils.synch import RWLock
|
from django.utils.synch import RWLock
|
||||||
|
|
||||||
# Global in-memory store of cache data. Keyed by name, to provide
|
# Global in-memory store of cache data. Keyed by name, to provide
|
||||||
|
@ -23,7 +23,7 @@ class LocMemCache(BaseCache):
|
||||||
self._expire_info = _expire_info.setdefault(name, {})
|
self._expire_info = _expire_info.setdefault(name, {})
|
||||||
self._lock = _locks.setdefault(name, RWLock())
|
self._lock = _locks.setdefault(name, RWLock())
|
||||||
|
|
||||||
def add(self, key, value, timeout=None, version=None):
|
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
with self._lock.writer():
|
with self._lock.writer():
|
||||||
|
@ -41,10 +41,8 @@ class LocMemCache(BaseCache):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
with self._lock.reader():
|
with self._lock.reader():
|
||||||
exp = self._expire_info.get(key)
|
exp = self._expire_info.get(key, 0)
|
||||||
if exp is None:
|
if exp is None or exp > time.time():
|
||||||
return default
|
|
||||||
elif exp > time.time():
|
|
||||||
try:
|
try:
|
||||||
pickled = self._cache[key]
|
pickled = self._cache[key]
|
||||||
return pickle.loads(pickled)
|
return pickle.loads(pickled)
|
||||||
|
@ -58,15 +56,16 @@ class LocMemCache(BaseCache):
|
||||||
pass
|
pass
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def _set(self, key, value, timeout=None):
|
def _set(self, key, value, timeout=DEFAULT_TIMEOUT):
|
||||||
if len(self._cache) >= self._max_entries:
|
if len(self._cache) >= self._max_entries:
|
||||||
self._cull()
|
self._cull()
|
||||||
if timeout is None:
|
if timeout == DEFAULT_TIMEOUT:
|
||||||
timeout = self.default_timeout
|
timeout = self.default_timeout
|
||||||
|
expiry = None if timeout is None else time.time() + timeout
|
||||||
self._cache[key] = value
|
self._cache[key] = value
|
||||||
self._expire_info[key] = time.time() + timeout
|
self._expire_info[key] = expiry
|
||||||
|
|
||||||
def set(self, key, value, timeout=None, version=None):
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
with self._lock.writer():
|
with self._lock.writer():
|
||||||
|
|
|
@ -4,7 +4,7 @@ import time
|
||||||
import pickle
|
import pickle
|
||||||
from threading import local
|
from threading import local
|
||||||
|
|
||||||
from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
|
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
|
||||||
|
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
@ -36,12 +36,22 @@ class BaseMemcachedCache(BaseCache):
|
||||||
|
|
||||||
return self._client
|
return self._client
|
||||||
|
|
||||||
def _get_memcache_timeout(self, timeout):
|
def _get_memcache_timeout(self, timeout=DEFAULT_TIMEOUT):
|
||||||
"""
|
"""
|
||||||
Memcached deals with long (> 30 days) timeouts in a special
|
Memcached deals with long (> 30 days) timeouts in a special
|
||||||
way. Call this function to obtain a safe value for your timeout.
|
way. Call this function to obtain a safe value for your timeout.
|
||||||
"""
|
"""
|
||||||
timeout = timeout or self.default_timeout
|
if timeout == DEFAULT_TIMEOUT:
|
||||||
|
return self.default_timeout
|
||||||
|
|
||||||
|
if timeout is None:
|
||||||
|
# Using 0 in memcache sets a non-expiring timeout.
|
||||||
|
return 0
|
||||||
|
elif int(timeout) == 0:
|
||||||
|
# Other cache backends treat 0 as set-and-expire. To achieve this
|
||||||
|
# in memcache backends, a negative timeout must be passed.
|
||||||
|
timeout = -1
|
||||||
|
|
||||||
if timeout > 2592000: # 60*60*24*30, 30 days
|
if timeout > 2592000: # 60*60*24*30, 30 days
|
||||||
# See http://code.google.com/p/memcached/wiki/FAQ
|
# See http://code.google.com/p/memcached/wiki/FAQ
|
||||||
# "You can set expire times up to 30 days in the future. After that
|
# "You can set expire times up to 30 days in the future. After that
|
||||||
|
@ -56,7 +66,7 @@ class BaseMemcachedCache(BaseCache):
|
||||||
# Python 2 memcache requires the key to be a byte string.
|
# Python 2 memcache requires the key to be a byte string.
|
||||||
return force_str(super(BaseMemcachedCache, self).make_key(key, version))
|
return force_str(super(BaseMemcachedCache, self).make_key(key, version))
|
||||||
|
|
||||||
def add(self, key, value, timeout=0, version=None):
|
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
return self._cache.add(key, value, self._get_memcache_timeout(timeout))
|
return self._cache.add(key, value, self._get_memcache_timeout(timeout))
|
||||||
|
|
||||||
|
@ -67,7 +77,7 @@ class BaseMemcachedCache(BaseCache):
|
||||||
return default
|
return default
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def set(self, key, value, timeout=0, version=None):
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self._cache.set(key, value, self._get_memcache_timeout(timeout))
|
self._cache.set(key, value, self._get_memcache_timeout(timeout))
|
||||||
|
|
||||||
|
@ -125,7 +135,7 @@ class BaseMemcachedCache(BaseCache):
|
||||||
raise ValueError("Key '%s' not found" % key)
|
raise ValueError("Key '%s' not found" % key)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def set_many(self, data, timeout=0, version=None):
|
def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
safe_data = {}
|
safe_data = {}
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
|
|
|
@ -485,6 +485,12 @@ Miscellaneous
|
||||||
changes in 1.6 particularly affect :class:`~django.forms.DecimalField` and
|
changes in 1.6 particularly affect :class:`~django.forms.DecimalField` and
|
||||||
:class:`~django.forms.ModelMultipleChoiceField`.
|
:class:`~django.forms.ModelMultipleChoiceField`.
|
||||||
|
|
||||||
|
* There have been changes in the way timeouts are handled in cache backends.
|
||||||
|
Explicitly passing in ``timeout=None`` no longer results in using the
|
||||||
|
default timeout. It will now set a non-expiring timeout. Passing 0 into the
|
||||||
|
memcache backend no longer uses the default timeout, and now will
|
||||||
|
set-and-expire-immediately the value.
|
||||||
|
|
||||||
Features deprecated in 1.6
|
Features deprecated in 1.6
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
|
|
@ -707,10 +707,15 @@ The basic interface is ``set(key, value, timeout)`` and ``get(key)``::
|
||||||
>>> cache.get('my_key')
|
>>> cache.get('my_key')
|
||||||
'hello, world!'
|
'hello, world!'
|
||||||
|
|
||||||
The ``timeout`` argument is optional and defaults to the ``timeout``
|
The ``timeout`` argument is optional and defaults to the ``timeout`` argument
|
||||||
argument of the appropriate backend in the :setting:`CACHES` setting
|
of the appropriate backend in the :setting:`CACHES` setting (explained above).
|
||||||
(explained above). It's the number of seconds the value should be stored
|
It's the number of seconds the value should be stored in the cache. Passing in
|
||||||
in the cache.
|
``None`` for ``timeout`` will cache the value forever.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.6
|
||||||
|
|
||||||
|
Previously, passing ``None`` explicitly would use the default timeout
|
||||||
|
value.
|
||||||
|
|
||||||
If the object doesn't exist in the cache, ``cache.get()`` returns ``None``::
|
If the object doesn't exist in the cache, ``cache.get()`` returns ``None``::
|
||||||
|
|
||||||
|
|
|
@ -441,6 +441,34 @@ class BaseCacheTests(object):
|
||||||
self.assertEqual(self.cache.get('key3'), 'sausage')
|
self.assertEqual(self.cache.get('key3'), 'sausage')
|
||||||
self.assertEqual(self.cache.get('key4'), 'lobster bisque')
|
self.assertEqual(self.cache.get('key4'), 'lobster bisque')
|
||||||
|
|
||||||
|
def test_forever_timeout(self):
|
||||||
|
'''
|
||||||
|
Passing in None into timeout results in a value that is cached forever
|
||||||
|
'''
|
||||||
|
self.cache.set('key1', 'eggs', None)
|
||||||
|
self.assertEqual(self.cache.get('key1'), 'eggs')
|
||||||
|
|
||||||
|
self.cache.add('key2', 'ham', None)
|
||||||
|
self.assertEqual(self.cache.get('key2'), 'ham')
|
||||||
|
|
||||||
|
self.cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, None)
|
||||||
|
self.assertEqual(self.cache.get('key3'), 'sausage')
|
||||||
|
self.assertEqual(self.cache.get('key4'), 'lobster bisque')
|
||||||
|
|
||||||
|
def test_zero_timeout(self):
|
||||||
|
'''
|
||||||
|
Passing in None into timeout results in a value that is cached forever
|
||||||
|
'''
|
||||||
|
self.cache.set('key1', 'eggs', 0)
|
||||||
|
self.assertEqual(self.cache.get('key1'), None)
|
||||||
|
|
||||||
|
self.cache.add('key2', 'ham', 0)
|
||||||
|
self.assertEqual(self.cache.get('key2'), None)
|
||||||
|
|
||||||
|
self.cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, 0)
|
||||||
|
self.assertEqual(self.cache.get('key3'), None)
|
||||||
|
self.assertEqual(self.cache.get('key4'), None)
|
||||||
|
|
||||||
def test_float_timeout(self):
|
def test_float_timeout(self):
|
||||||
# Make sure a timeout given as a float doesn't crash anything.
|
# Make sure a timeout given as a float doesn't crash anything.
|
||||||
self.cache.set("key1", "spam", 100.2)
|
self.cache.set("key1", "spam", 100.2)
|
||||||
|
|
Loading…
Reference in New Issue