feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,301 @@
import unittest
class CacheTestMixin:
Cache = None
def test_defaults(self):
cache = self.Cache(maxsize=1)
self.assertEqual(0, len(cache))
self.assertEqual(1, cache.maxsize)
self.assertEqual(0, cache.currsize)
self.assertEqual(1, cache.getsizeof(None))
self.assertEqual(1, cache.getsizeof(""))
self.assertEqual(1, cache.getsizeof(0))
self.assertTrue(repr(cache).startswith(cache.__class__.__name__))
def test_insert(self):
cache = self.Cache(maxsize=2)
cache.update({1: 1, 2: 2})
self.assertEqual(2, len(cache))
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
cache[3] = 3
self.assertEqual(2, len(cache))
self.assertEqual(3, cache[3])
self.assertTrue(1 in cache or 2 in cache)
cache[4] = 4
self.assertEqual(2, len(cache))
self.assertEqual(4, cache[4])
self.assertTrue(1 in cache or 2 in cache or 3 in cache)
def test_update(self):
cache = self.Cache(maxsize=2)
cache.update({1: 1, 2: 2})
self.assertEqual(2, len(cache))
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
cache.update({1: 1, 2: 2})
self.assertEqual(2, len(cache))
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
cache.update({1: "a", 2: "b"})
self.assertEqual(2, len(cache))
self.assertEqual("a", cache[1])
self.assertEqual("b", cache[2])
def test_delete(self):
cache = self.Cache(maxsize=2)
cache.update({1: 1, 2: 2})
self.assertEqual(2, len(cache))
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
del cache[2]
self.assertEqual(1, len(cache))
self.assertEqual(1, cache[1])
self.assertNotIn(2, cache)
del cache[1]
self.assertEqual(0, len(cache))
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
with self.assertRaises(KeyError):
del cache[1]
self.assertEqual(0, len(cache))
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
def test_pop(self):
cache = self.Cache(maxsize=2)
cache.update({1: 1, 2: 2})
self.assertEqual(2, cache.pop(2))
self.assertEqual(1, len(cache))
self.assertEqual(1, cache.pop(1))
self.assertEqual(0, len(cache))
with self.assertRaises(KeyError):
cache.pop(2)
with self.assertRaises(KeyError):
cache.pop(1)
with self.assertRaises(KeyError):
cache.pop(0)
self.assertEqual(None, cache.pop(2, None))
self.assertEqual(None, cache.pop(1, None))
self.assertEqual(None, cache.pop(0, None))
def test_popitem(self):
cache = self.Cache(maxsize=2)
cache.update({1: 1, 2: 2})
self.assertIn(cache.pop(1), {1: 1, 2: 2})
self.assertEqual(1, len(cache))
self.assertIn(cache.pop(2), {1: 1, 2: 2})
self.assertEqual(0, len(cache))
with self.assertRaises(KeyError):
cache.popitem()
def test_popitem_exception_context(self):
# since Python 3.7, MutableMapping.popitem() suppresses
# exception context as implementation detail
exception = None
try:
self.Cache(maxsize=2).popitem()
except Exception as e:
exception = e
self.assertIsNone(exception.__cause__)
self.assertTrue(exception.__suppress_context__)
def test_missing(self):
class DefaultCache(self.Cache):
def __missing__(self, key):
self[key] = key
return key
cache = DefaultCache(maxsize=2)
self.assertEqual(0, cache.currsize)
self.assertEqual(2, cache.maxsize)
self.assertEqual(0, len(cache))
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
self.assertEqual(2, len(cache))
self.assertTrue(1 in cache and 2 in cache)
self.assertEqual(3, cache[3])
self.assertEqual(2, len(cache))
self.assertTrue(3 in cache)
self.assertTrue(1 in cache or 2 in cache)
self.assertTrue(1 not in cache or 2 not in cache)
self.assertEqual(4, cache[4])
self.assertEqual(2, len(cache))
self.assertTrue(4 in cache)
self.assertTrue(1 in cache or 2 in cache or 3 in cache)
# verify __missing__() is *not* called for any operations
# besides __getitem__()
self.assertEqual(4, cache.get(4))
self.assertEqual(None, cache.get(5))
self.assertEqual(5 * 5, cache.get(5, 5 * 5))
self.assertEqual(2, len(cache))
self.assertEqual(4, cache.pop(4))
with self.assertRaises(KeyError):
cache.pop(5)
self.assertEqual(None, cache.pop(5, None))
self.assertEqual(5 * 5, cache.pop(5, 5 * 5))
self.assertEqual(1, len(cache))
cache.clear()
cache[1] = 1 + 1
self.assertEqual(1 + 1, cache.setdefault(1))
self.assertEqual(1 + 1, cache.setdefault(1, 1))
self.assertEqual(1 + 1, cache[1])
self.assertEqual(2 + 2, cache.setdefault(2, 2 + 2))
self.assertEqual(2 + 2, cache.setdefault(2, None))
self.assertEqual(2 + 2, cache.setdefault(2))
self.assertEqual(2 + 2, cache[2])
self.assertEqual(2, len(cache))
self.assertTrue(1 in cache and 2 in cache)
self.assertEqual(None, cache.setdefault(3))
self.assertEqual(2, len(cache))
self.assertTrue(3 in cache)
self.assertTrue(1 in cache or 2 in cache)
self.assertTrue(1 not in cache or 2 not in cache)
def test_missing_getsizeof(self):
class DefaultCache(self.Cache):
def __missing__(self, key):
try:
self[key] = key
except ValueError:
pass # not stored
return key
cache = DefaultCache(maxsize=2, getsizeof=lambda x: x)
self.assertEqual(0, cache.currsize)
self.assertEqual(2, cache.maxsize)
self.assertEqual(1, cache[1])
self.assertEqual(1, len(cache))
self.assertEqual(1, cache.currsize)
self.assertIn(1, cache)
self.assertEqual(2, cache[2])
self.assertEqual(1, len(cache))
self.assertEqual(2, cache.currsize)
self.assertNotIn(1, cache)
self.assertIn(2, cache)
self.assertEqual(3, cache[3]) # not stored
self.assertEqual(1, len(cache))
self.assertEqual(2, cache.currsize)
self.assertEqual(1, cache[1])
self.assertEqual(1, len(cache))
self.assertEqual(1, cache.currsize)
self.assertEqual((1, 1), cache.popitem())
def _test_getsizeof(self, cache):
self.assertEqual(0, cache.currsize)
self.assertEqual(3, cache.maxsize)
self.assertEqual(1, cache.getsizeof(1))
self.assertEqual(2, cache.getsizeof(2))
self.assertEqual(3, cache.getsizeof(3))
cache.update({1: 1, 2: 2})
self.assertEqual(2, len(cache))
self.assertEqual(3, cache.currsize)
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
cache[1] = 2
self.assertEqual(1, len(cache))
self.assertEqual(2, cache.currsize)
self.assertEqual(2, cache[1])
self.assertNotIn(2, cache)
cache.update({1: 1, 2: 2})
self.assertEqual(2, len(cache))
self.assertEqual(3, cache.currsize)
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
cache[3] = 3
self.assertEqual(1, len(cache))
self.assertEqual(3, cache.currsize)
self.assertEqual(3, cache[3])
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
with self.assertRaises(ValueError):
cache[3] = 4
self.assertEqual(1, len(cache))
self.assertEqual(3, cache.currsize)
self.assertEqual(3, cache[3])
with self.assertRaises(ValueError):
cache[4] = 4
self.assertEqual(1, len(cache))
self.assertEqual(3, cache.currsize)
self.assertEqual(3, cache[3])
def test_getsizeof_param(self):
self._test_getsizeof(self.Cache(maxsize=3, getsizeof=lambda x: x))
def test_getsizeof_subclass(self):
class Cache(self.Cache):
def getsizeof(self, value):
return value
self._test_getsizeof(Cache(maxsize=3))
def test_pickle(self):
import pickle
source = self.Cache(maxsize=2)
source.update({1: 1, 2: 2})
cache = pickle.loads(pickle.dumps(source))
self.assertEqual(source, cache)
self.assertEqual(2, len(cache))
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
cache[3] = 3
self.assertEqual(2, len(cache))
self.assertEqual(3, cache[3])
self.assertTrue(1 in cache or 2 in cache)
cache[4] = 4
self.assertEqual(2, len(cache))
self.assertEqual(4, cache[4])
self.assertTrue(1 in cache or 2 in cache or 3 in cache)
self.assertEqual(cache, pickle.loads(pickle.dumps(cache)))
def test_pickle_maxsize(self):
import pickle
import sys
# test empty cache, single element, large cache (recursion limit)
for n in [0, 1, sys.getrecursionlimit() * 2]:
source = self.Cache(maxsize=n)
source.update((i, i) for i in range(n))
cache = pickle.loads(pickle.dumps(source))
self.assertEqual(n, len(cache))
self.assertEqual(source, cache)

View File

@@ -0,0 +1,9 @@
import unittest
import cachetools
from . import CacheTestMixin
class CacheTest(unittest.TestCase, CacheTestMixin):
Cache = cachetools.Cache

View File

@@ -0,0 +1,245 @@
import unittest
import cachetools
import cachetools.keys
class CountedLock:
def __init__(self):
self.count = 0
def __enter__(self):
self.count += 1
def __exit__(self, *exc):
pass
class DecoratorTestMixin:
def cache(self, minsize):
raise NotImplementedError
def func(self, *args, **kwargs):
if hasattr(self, "count"):
self.count += 1
else:
self.count = 0
return self.count
def test_decorator(self):
cache = self.cache(2)
wrapper = cachetools.cached(cache)(self.func)
self.assertEqual(len(cache), 0)
self.assertEqual(wrapper(0), 0)
self.assertEqual(len(cache), 1)
self.assertIn(cachetools.keys.hashkey(0), cache)
self.assertNotIn(cachetools.keys.hashkey(1), cache)
self.assertNotIn(cachetools.keys.hashkey(1.0), cache)
self.assertEqual(wrapper(1), 1)
self.assertEqual(len(cache), 2)
self.assertIn(cachetools.keys.hashkey(0), cache)
self.assertIn(cachetools.keys.hashkey(1), cache)
self.assertIn(cachetools.keys.hashkey(1.0), cache)
self.assertEqual(wrapper(1), 1)
self.assertEqual(len(cache), 2)
self.assertEqual(wrapper(1.0), 1)
self.assertEqual(len(cache), 2)
self.assertEqual(wrapper(1.0), 1)
self.assertEqual(len(cache), 2)
def test_decorator_typed(self):
cache = self.cache(3)
key = cachetools.keys.typedkey
wrapper = cachetools.cached(cache, key=key)(self.func)
self.assertEqual(len(cache), 0)
self.assertEqual(wrapper(0), 0)
self.assertEqual(len(cache), 1)
self.assertIn(cachetools.keys.typedkey(0), cache)
self.assertNotIn(cachetools.keys.typedkey(1), cache)
self.assertNotIn(cachetools.keys.typedkey(1.0), cache)
self.assertEqual(wrapper(1), 1)
self.assertEqual(len(cache), 2)
self.assertIn(cachetools.keys.typedkey(0), cache)
self.assertIn(cachetools.keys.typedkey(1), cache)
self.assertNotIn(cachetools.keys.typedkey(1.0), cache)
self.assertEqual(wrapper(1), 1)
self.assertEqual(len(cache), 2)
self.assertEqual(wrapper(1.0), 2)
self.assertEqual(len(cache), 3)
self.assertIn(cachetools.keys.typedkey(0), cache)
self.assertIn(cachetools.keys.typedkey(1), cache)
self.assertIn(cachetools.keys.typedkey(1.0), cache)
self.assertEqual(wrapper(1.0), 2)
self.assertEqual(len(cache), 3)
def test_decorator_lock(self):
cache = self.cache(2)
lock = CountedLock()
wrapper = cachetools.cached(cache, lock=lock)(self.func)
self.assertEqual(len(cache), 0)
self.assertEqual(wrapper(0), 0)
self.assertEqual(lock.count, 2)
self.assertEqual(wrapper(1), 1)
self.assertEqual(lock.count, 4)
self.assertEqual(wrapper(1), 1)
self.assertEqual(lock.count, 5)
def test_decorator_wrapped(self):
cache = self.cache(2)
wrapper = cachetools.cached(cache)(self.func)
self.assertEqual(wrapper.__wrapped__, self.func)
self.assertEqual(len(cache), 0)
self.assertEqual(wrapper.__wrapped__(0), 0)
self.assertEqual(len(cache), 0)
self.assertEqual(wrapper(0), 1)
self.assertEqual(len(cache), 1)
self.assertEqual(wrapper(0), 1)
self.assertEqual(len(cache), 1)
def test_decorator_attributes(self):
cache = self.cache(2)
wrapper = cachetools.cached(cache)(self.func)
self.assertIs(wrapper.cache, cache)
self.assertIs(wrapper.cache_key, cachetools.keys.hashkey)
self.assertIs(wrapper.cache_lock, None)
def test_decorator_attributes_lock(self):
cache = self.cache(2)
lock = CountedLock()
wrapper = cachetools.cached(cache, lock=lock)(self.func)
self.assertIs(wrapper.cache, cache)
self.assertIs(wrapper.cache_key, cachetools.keys.hashkey)
self.assertIs(wrapper.cache_lock, lock)
def test_decorator_clear(self):
cache = self.cache(2)
wrapper = cachetools.cached(cache)(self.func)
self.assertEqual(wrapper(0), 0)
self.assertEqual(len(cache), 1)
wrapper.cache_clear()
self.assertEqual(len(cache), 0)
def test_decorator_clear_lock(self):
cache = self.cache(2)
lock = CountedLock()
wrapper = cachetools.cached(cache, lock=lock)(self.func)
self.assertEqual(wrapper(0), 0)
self.assertEqual(len(cache), 1)
self.assertEqual(lock.count, 2)
wrapper.cache_clear()
self.assertEqual(len(cache), 0)
self.assertEqual(lock.count, 3)
class CacheWrapperTest(unittest.TestCase, DecoratorTestMixin):
def cache(self, minsize):
return cachetools.Cache(maxsize=minsize)
def test_decorator_info(self):
cache = self.cache(2)
wrapper = cachetools.cached(cache, info=True)(self.func)
self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
self.assertEqual(wrapper(0), 0)
self.assertEqual(wrapper.cache_info(), (0, 1, 2, 1))
self.assertEqual(wrapper(1), 1)
self.assertEqual(wrapper.cache_info(), (0, 2, 2, 2))
self.assertEqual(wrapper(0), 0)
self.assertEqual(wrapper.cache_info(), (1, 2, 2, 2))
wrapper.cache_clear()
self.assertEqual(len(cache), 0)
self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
def test_zero_size_cache_decorator(self):
cache = self.cache(0)
wrapper = cachetools.cached(cache)(self.func)
self.assertEqual(len(cache), 0)
self.assertEqual(wrapper(0), 0)
self.assertEqual(len(cache), 0)
def test_zero_size_cache_decorator_lock(self):
cache = self.cache(0)
lock = CountedLock()
wrapper = cachetools.cached(cache, lock=lock)(self.func)
self.assertEqual(len(cache), 0)
self.assertEqual(wrapper(0), 0)
self.assertEqual(len(cache), 0)
self.assertEqual(lock.count, 2)
def test_zero_size_cache_decorator_info(self):
cache = self.cache(0)
wrapper = cachetools.cached(cache, info=True)(self.func)
self.assertEqual(wrapper.cache_info(), (0, 0, 0, 0))
self.assertEqual(wrapper(0), 0)
self.assertEqual(wrapper.cache_info(), (0, 1, 0, 0))
class DictWrapperTest(unittest.TestCase, DecoratorTestMixin):
def cache(self, minsize):
return dict()
def test_decorator_info(self):
cache = self.cache(2)
wrapper = cachetools.cached(cache, info=True)(self.func)
self.assertEqual(wrapper.cache_info(), (0, 0, None, 0))
self.assertEqual(wrapper(0), 0)
self.assertEqual(wrapper.cache_info(), (0, 1, None, 1))
self.assertEqual(wrapper(1), 1)
self.assertEqual(wrapper.cache_info(), (0, 2, None, 2))
self.assertEqual(wrapper(0), 0)
self.assertEqual(wrapper.cache_info(), (1, 2, None, 2))
wrapper.cache_clear()
self.assertEqual(len(cache), 0)
self.assertEqual(wrapper.cache_info(), (0, 0, None, 0))
class NoneWrapperTest(unittest.TestCase):
def func(self, *args, **kwargs):
return args + tuple(kwargs.items())
def test_decorator(self):
wrapper = cachetools.cached(None)(self.func)
self.assertEqual(wrapper(0), (0,))
self.assertEqual(wrapper(1), (1,))
self.assertEqual(wrapper(1, foo="bar"), (1, ("foo", "bar")))
def test_decorator_attributes(self):
wrapper = cachetools.cached(None)(self.func)
self.assertIs(wrapper.cache, None)
self.assertIs(wrapper.cache_key, cachetools.keys.hashkey)
self.assertIs(wrapper.cache_lock, None)
def test_decorator_clear(self):
wrapper = cachetools.cached(None)(self.func)
wrapper.cache_clear() # no-op
def test_decorator_info(self):
wrapper = cachetools.cached(None, info=True)(self.func)
self.assertEqual(wrapper.cache_info(), (0, 0, 0, 0))
self.assertEqual(wrapper(0), (0,))
self.assertEqual(wrapper.cache_info(), (0, 1, 0, 0))
self.assertEqual(wrapper(1), (1,))
self.assertEqual(wrapper.cache_info(), (0, 2, 0, 0))
wrapper.cache_clear()
self.assertEqual(wrapper.cache_info(), (0, 0, 0, 0))

View File

@@ -0,0 +1,234 @@
import unittest
from cachetools import LRUCache, cachedmethod, keys
class Cached:
def __init__(self, cache, count=0):
self.cache = cache
self.count = count
@cachedmethod(lambda self: self.cache)
def get(self, value):
count = self.count
self.count += 1
return count
@cachedmethod(lambda self: self.cache, key=keys.typedmethodkey)
def get_typedmethod(self, value):
count = self.count
self.count += 1
return count
class Locked:
def __init__(self, cache):
self.cache = cache
self.count = 0
@cachedmethod(lambda self: self.cache, lock=lambda self: self)
def get(self, value):
return self.count
def __enter__(self):
self.count += 1
def __exit__(self, *exc):
pass
class Unhashable:
def __init__(self, cache):
self.cache = cache
@cachedmethod(lambda self: self.cache)
def get_default(self, value):
return value
@cachedmethod(lambda self: self.cache, key=keys.hashkey)
def get_hashkey(self, value):
return value
# https://github.com/tkem/cachetools/issues/107
def __hash__(self):
raise TypeError("unhashable type")
class CachedMethodTest(unittest.TestCase):
def test_dict(self):
cached = Cached({})
self.assertEqual(cached.get(0), 0)
self.assertEqual(cached.get(1), 1)
self.assertEqual(cached.get(1), 1)
self.assertEqual(cached.get(1.0), 1)
self.assertEqual(cached.get(1.0), 1)
cached.cache.clear()
self.assertEqual(cached.get(1), 2)
def test_typedmethod_dict(self):
cached = Cached(LRUCache(maxsize=2))
self.assertEqual(cached.get_typedmethod(0), 0)
self.assertEqual(cached.get_typedmethod(1), 1)
self.assertEqual(cached.get_typedmethod(1), 1)
self.assertEqual(cached.get_typedmethod(1.0), 2)
self.assertEqual(cached.get_typedmethod(1.0), 2)
self.assertEqual(cached.get_typedmethod(0.0), 3)
self.assertEqual(cached.get_typedmethod(0), 4)
def test_lru(self):
cached = Cached(LRUCache(maxsize=2))
self.assertEqual(cached.get(0), 0)
self.assertEqual(cached.get(1), 1)
self.assertEqual(cached.get(1), 1)
self.assertEqual(cached.get(1.0), 1)
self.assertEqual(cached.get(1.0), 1)
cached.cache.clear()
self.assertEqual(cached.get(1), 2)
def test_typedmethod_lru(self):
cached = Cached(LRUCache(maxsize=2))
self.assertEqual(cached.get_typedmethod(0), 0)
self.assertEqual(cached.get_typedmethod(1), 1)
self.assertEqual(cached.get_typedmethod(1), 1)
self.assertEqual(cached.get_typedmethod(1.0), 2)
self.assertEqual(cached.get_typedmethod(1.0), 2)
self.assertEqual(cached.get_typedmethod(0.0), 3)
self.assertEqual(cached.get_typedmethod(0), 4)
def test_nospace(self):
cached = Cached(LRUCache(maxsize=0))
self.assertEqual(cached.get(0), 0)
self.assertEqual(cached.get(1), 1)
self.assertEqual(cached.get(1), 2)
self.assertEqual(cached.get(1.0), 3)
self.assertEqual(cached.get(1.0), 4)
def test_nocache(self):
cached = Cached(None)
self.assertEqual(cached.get(0), 0)
self.assertEqual(cached.get(1), 1)
self.assertEqual(cached.get(1), 2)
self.assertEqual(cached.get(1.0), 3)
self.assertEqual(cached.get(1.0), 4)
def test_weakref(self):
import weakref
import fractions
import gc
# in Python 3.7, `int` does not support weak references even
# when subclassed, but Fraction apparently does...
class Int(fractions.Fraction):
def __add__(self, other):
return Int(fractions.Fraction.__add__(self, other))
cached = Cached(weakref.WeakValueDictionary(), count=Int(0))
self.assertEqual(cached.get(0), 0)
gc.collect()
self.assertEqual(cached.get(0), 1)
ref = cached.get(1)
self.assertEqual(ref, 2)
self.assertEqual(cached.get(1), 2)
self.assertEqual(cached.get(1.0), 2)
ref = cached.get_typedmethod(1)
self.assertEqual(ref, 3)
self.assertEqual(cached.get_typedmethod(1), 3)
self.assertEqual(cached.get_typedmethod(1.0), 4)
cached.cache.clear()
self.assertEqual(cached.get(1), 5)
def test_locked_dict(self):
cached = Locked({})
self.assertEqual(cached.get(0), 1)
self.assertEqual(cached.get(1), 3)
self.assertEqual(cached.get(1), 3)
self.assertEqual(cached.get(1.0), 3)
self.assertEqual(cached.get(2.0), 7)
def test_locked_nocache(self):
cached = Locked(None)
self.assertEqual(cached.get(0), 0)
self.assertEqual(cached.get(1), 0)
self.assertEqual(cached.get(1), 0)
self.assertEqual(cached.get(1.0), 0)
self.assertEqual(cached.get(1.0), 0)
def test_locked_nospace(self):
cached = Locked(LRUCache(maxsize=0))
self.assertEqual(cached.get(0), 1)
self.assertEqual(cached.get(1), 3)
self.assertEqual(cached.get(1), 5)
self.assertEqual(cached.get(1.0), 7)
self.assertEqual(cached.get(1.0), 9)
def test_unhashable(self):
cached = Unhashable(LRUCache(maxsize=0))
self.assertEqual(cached.get_default(0), 0)
self.assertEqual(cached.get_default(1), 1)
with self.assertRaises(TypeError):
cached.get_hashkey(0)
def test_wrapped(self):
cache = {}
cached = Cached(cache)
self.assertEqual(len(cache), 0)
self.assertEqual(cached.get.__wrapped__(cached, 0), 0)
self.assertEqual(len(cache), 0)
self.assertEqual(cached.get(0), 1)
self.assertEqual(len(cache), 1)
self.assertEqual(cached.get(0), 1)
self.assertEqual(len(cache), 1)
def test_attributes(self):
cache = {}
cached = Cached(cache)
self.assertIs(cached.get.cache(cached), cache)
self.assertIs(cached.get.cache_key, keys.methodkey)
self.assertIs(cached.get.cache_lock, None)
def test_attributes_lock(self):
cache = {}
cached = Locked(cache)
self.assertIs(cached.get.cache(cached), cache)
self.assertIs(cached.get.cache_key, keys.methodkey)
self.assertIs(cached.get.cache_lock(cached), cached)
def test_clear(self):
cache = {}
cached = Cached(cache)
self.assertEqual(cached.get(0), 0)
self.assertEqual(len(cache), 1)
cached.get.cache_clear(cached)
self.assertEqual(len(cache), 0)
def test_clear_locked(self):
cache = {}
cached = Locked(cache)
self.assertEqual(cached.get(0), 1)
self.assertEqual(len(cache), 1)
self.assertEqual(cached.count, 2)
cached.get.cache_clear(cached)
self.assertEqual(len(cache), 0)
self.assertEqual(cached.count, 3)

View File

@@ -0,0 +1,56 @@
import unittest
from cachetools import FIFOCache
from . import CacheTestMixin
class LRUCacheTest(unittest.TestCase, CacheTestMixin):
Cache = FIFOCache
def test_fifo(self):
cache = FIFOCache(maxsize=2)
cache[1] = 1
cache[2] = 2
cache[3] = 3
self.assertEqual(len(cache), 2)
self.assertEqual(cache[2], 2)
self.assertEqual(cache[3], 3)
self.assertNotIn(1, cache)
cache[2]
cache[4] = 4
self.assertEqual(len(cache), 2)
self.assertEqual(cache[3], 3)
self.assertEqual(cache[4], 4)
self.assertNotIn(2, cache)
cache[5] = 5
self.assertEqual(len(cache), 2)
self.assertEqual(cache[4], 4)
self.assertEqual(cache[5], 5)
self.assertNotIn(3, cache)
def test_fifo_getsizeof(self):
cache = FIFOCache(maxsize=3, getsizeof=lambda x: x)
cache[1] = 1
cache[2] = 2
self.assertEqual(len(cache), 2)
self.assertEqual(cache[1], 1)
self.assertEqual(cache[2], 2)
cache[3] = 3
self.assertEqual(len(cache), 1)
self.assertEqual(cache[3], 3)
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
with self.assertRaises(ValueError):
cache[4] = 4
self.assertEqual(len(cache), 1)
self.assertEqual(cache[3], 3)

View File

@@ -0,0 +1,131 @@
import unittest
import cachetools.func
class DecoratorTestMixin:
def decorator(self, maxsize, **kwargs):
return self.DECORATOR(maxsize, **kwargs)
def test_decorator(self):
cached = self.decorator(maxsize=2)(lambda n: n)
self.assertEqual(cached.cache_parameters(), {"maxsize": 2, "typed": False})
self.assertEqual(cached.cache_info(), (0, 0, 2, 0))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (0, 1, 2, 1))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (1, 1, 2, 1))
self.assertEqual(cached(1.0), 1.0)
self.assertEqual(cached.cache_info(), (2, 1, 2, 1))
def test_decorator_clear(self):
cached = self.decorator(maxsize=2)(lambda n: n)
self.assertEqual(cached.cache_parameters(), {"maxsize": 2, "typed": False})
self.assertEqual(cached.cache_info(), (0, 0, 2, 0))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (0, 1, 2, 1))
cached.cache_clear()
self.assertEqual(cached.cache_info(), (0, 0, 2, 0))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (0, 1, 2, 1))
def test_decorator_nocache(self):
cached = self.decorator(maxsize=0)(lambda n: n)
self.assertEqual(cached.cache_parameters(), {"maxsize": 0, "typed": False})
self.assertEqual(cached.cache_info(), (0, 0, 0, 0))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (0, 1, 0, 0))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (0, 2, 0, 0))
self.assertEqual(cached(1.0), 1.0)
self.assertEqual(cached.cache_info(), (0, 3, 0, 0))
def test_decorator_unbound(self):
cached = self.decorator(maxsize=None)(lambda n: n)
self.assertEqual(cached.cache_parameters(), {"maxsize": None, "typed": False})
self.assertEqual(cached.cache_info(), (0, 0, None, 0))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (0, 1, None, 1))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (1, 1, None, 1))
self.assertEqual(cached(1.0), 1.0)
self.assertEqual(cached.cache_info(), (2, 1, None, 1))
def test_decorator_typed(self):
cached = self.decorator(maxsize=2, typed=True)(lambda n: n)
self.assertEqual(cached.cache_parameters(), {"maxsize": 2, "typed": True})
self.assertEqual(cached.cache_info(), (0, 0, 2, 0))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (0, 1, 2, 1))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (1, 1, 2, 1))
self.assertEqual(cached(1.0), 1.0)
self.assertEqual(cached.cache_info(), (1, 2, 2, 2))
self.assertEqual(cached(1.0), 1.0)
self.assertEqual(cached.cache_info(), (2, 2, 2, 2))
def test_decorator_user_function(self):
cached = self.decorator(lambda n: n)
self.assertEqual(cached.cache_parameters(), {"maxsize": 128, "typed": False})
self.assertEqual(cached.cache_info(), (0, 0, 128, 0))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (0, 1, 128, 1))
self.assertEqual(cached(1), 1)
self.assertEqual(cached.cache_info(), (1, 1, 128, 1))
self.assertEqual(cached(1.0), 1.0)
self.assertEqual(cached.cache_info(), (2, 1, 128, 1))
def test_decorator_needs_rlock(self):
cached = self.decorator(lambda n: n)
class RecursiveEquals:
def __init__(self, use_cache):
self._use_cache = use_cache
def __hash__(self):
return hash(self._use_cache)
def __eq__(self, other):
if self._use_cache:
# This call will happen while the cache-lock is held,
# requiring a reentrant lock to avoid deadlock.
cached(self)
return self._use_cache == other._use_cache
# Prime the cache.
cached(RecursiveEquals(False))
cached(RecursiveEquals(True))
# Then do a call which will cause a deadlock with a non-reentrant lock.
self.assertEqual(cached(RecursiveEquals(True)), RecursiveEquals(True))
class FIFODecoratorTest(unittest.TestCase, DecoratorTestMixin):
DECORATOR = staticmethod(cachetools.func.fifo_cache)
class LFUDecoratorTest(unittest.TestCase, DecoratorTestMixin):
DECORATOR = staticmethod(cachetools.func.lfu_cache)
class LRUDecoratorTest(unittest.TestCase, DecoratorTestMixin):
DECORATOR = staticmethod(cachetools.func.lru_cache)
class MRUDecoratorTest(unittest.TestCase, DecoratorTestMixin):
def decorator(self, maxsize, **kwargs):
import warnings
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
d = cachetools.func.mru_cache(maxsize, **kwargs)
self.assertNotEqual(len(w), 0)
self.assertIs(w[0].category, DeprecationWarning)
return d
class RRDecoratorTest(unittest.TestCase, DecoratorTestMixin):
DECORATOR = staticmethod(cachetools.func.rr_cache)
class TTLDecoratorTest(unittest.TestCase, DecoratorTestMixin):
DECORATOR = staticmethod(cachetools.func.ttl_cache)

View File

@@ -0,0 +1,92 @@
import unittest
import cachetools.keys
class CacheKeysTest(unittest.TestCase):
def test_hashkey(self, key=cachetools.keys.hashkey):
self.assertEqual(key(), key())
self.assertEqual(hash(key()), hash(key()))
self.assertEqual(key(1, 2, 3), key(1, 2, 3))
self.assertEqual(hash(key(1, 2, 3)), hash(key(1, 2, 3)))
self.assertEqual(key(1, 2, 3, x=0), key(1, 2, 3, x=0))
self.assertEqual(hash(key(1, 2, 3, x=0)), hash(key(1, 2, 3, x=0)))
self.assertNotEqual(key(1, 2, 3), key(3, 2, 1))
self.assertNotEqual(key(1, 2, 3), key(1, 2, 3, x=None))
self.assertNotEqual(key(1, 2, 3, x=0), key(1, 2, 3, x=None))
self.assertNotEqual(key(1, 2, 3, x=0), key(1, 2, 3, y=0))
with self.assertRaises(TypeError):
hash(key({}))
# untyped keys compare equal
self.assertEqual(key(1, 2, 3), key(1.0, 2.0, 3.0))
self.assertEqual(hash(key(1, 2, 3)), hash(key(1.0, 2.0, 3.0)))
def methodkey(self, key=cachetools.keys.methodkey):
# similar to hashkey(), but ignores its first positional argument
self.assertEqual(key("x"), key("y"))
self.assertEqual(hash(key("x")), hash(key("y")))
self.assertEqual(key("x", 1, 2, 3), key("y", 1, 2, 3))
self.assertEqual(hash(key("x", 1, 2, 3)), hash(key("y", 1, 2, 3)))
self.assertEqual(key("x", 1, 2, 3, x=0), key("y", 1, 2, 3, x=0))
self.assertEqual(hash(key("x", 1, 2, 3, x=0)), hash(key("y", 1, 2, 3, x=0)))
self.assertNotEqual(key("x", 1, 2, 3), key("x", 3, 2, 1))
self.assertNotEqual(key("x", 1, 2, 3), key("x", 1, 2, 3, x=None))
self.assertNotEqual(key("x", 1, 2, 3, x=0), key("x", 1, 2, 3, x=None))
self.assertNotEqual(key("x", 1, 2, 3, x=0), key("x", 1, 2, 3, y=0))
with self.assertRaises(TypeError):
hash("x", key({}))
# untyped keys compare equal
self.assertEqual(key("x", 1, 2, 3), key("y", 1.0, 2.0, 3.0))
self.assertEqual(hash(key("x", 1, 2, 3)), hash(key("y", 1.0, 2.0, 3.0)))
def test_typedkey(self, key=cachetools.keys.typedkey):
self.assertEqual(key(), key())
self.assertEqual(hash(key()), hash(key()))
self.assertEqual(key(1, 2, 3), key(1, 2, 3))
self.assertEqual(hash(key(1, 2, 3)), hash(key(1, 2, 3)))
self.assertEqual(key(1, 2, 3, x=0), key(1, 2, 3, x=0))
self.assertEqual(hash(key(1, 2, 3, x=0)), hash(key(1, 2, 3, x=0)))
self.assertNotEqual(key(1, 2, 3), key(3, 2, 1))
self.assertNotEqual(key(1, 2, 3), key(1, 2, 3, x=None))
self.assertNotEqual(key(1, 2, 3, x=0), key(1, 2, 3, x=None))
self.assertNotEqual(key(1, 2, 3, x=0), key(1, 2, 3, y=0))
with self.assertRaises(TypeError):
hash(key({}))
# typed keys compare unequal
self.assertNotEqual(key(1, 2, 3), key(1.0, 2.0, 3.0))
def test_typedmethodkey(self, key=cachetools.keys.typedmethodkey):
# similar to typedkey(), but ignores its first positional argument
self.assertEqual(key("x"), key("y"))
self.assertEqual(hash(key("x")), hash(key("y")))
self.assertEqual(key("x", 1, 2, 3), key("y", 1, 2, 3))
self.assertEqual(hash(key("x", 1, 2, 3)), hash(key("y", 1, 2, 3)))
self.assertEqual(key("x", 1, 2, 3, x=0), key("y", 1, 2, 3, x=0))
self.assertEqual(hash(key("x", 1, 2, 3, x=0)), hash(key("y", 1, 2, 3, x=0)))
self.assertNotEqual(key("x", 1, 2, 3), key("x", 3, 2, 1))
self.assertNotEqual(key("x", 1, 2, 3), key("x", 1, 2, 3, x=None))
self.assertNotEqual(key("x", 1, 2, 3, x=0), key("x", 1, 2, 3, x=None))
self.assertNotEqual(key("x", 1, 2, 3, x=0), key("x", 1, 2, 3, y=0))
with self.assertRaises(TypeError):
hash(key("x", {}))
# typed keys compare unequal
self.assertNotEqual(key("x", 1, 2, 3), key("x", 1.0, 2.0, 3.0))
def test_addkeys(self, key=cachetools.keys.hashkey):
self.assertIsInstance(key(), tuple)
self.assertIsInstance(key(1, 2, 3) + key(4, 5, 6), type(key()))
self.assertIsInstance(key(1, 2, 3) + (4, 5, 6), type(key()))
self.assertIsInstance((1, 2, 3) + key(4, 5, 6), type(key()))
def test_pickle(self, key=cachetools.keys.hashkey):
import pickle
for k in [key(), key("abc"), key("abc", 123), key("abc", q="abc")]:
# white-box test: assert cached hash value is not pickled
self.assertEqual(len(k.__dict__), 0)
h = hash(k)
self.assertEqual(len(k.__dict__), 1)
pickled = pickle.loads(pickle.dumps(k))
self.assertEqual(len(pickled.__dict__), 0)
self.assertEqual(k, pickled)
self.assertEqual(h, hash(pickled))

View File

@@ -0,0 +1,49 @@
import unittest
from cachetools import LFUCache
from . import CacheTestMixin
class LFUCacheTest(unittest.TestCase, CacheTestMixin):
Cache = LFUCache
def test_lfu(self):
cache = LFUCache(maxsize=2)
cache[1] = 1
cache[1]
cache[2] = 2
cache[3] = 3
self.assertEqual(len(cache), 2)
self.assertEqual(cache[1], 1)
self.assertTrue(2 in cache or 3 in cache)
self.assertTrue(2 not in cache or 3 not in cache)
cache[4] = 4
self.assertEqual(len(cache), 2)
self.assertEqual(cache[4], 4)
self.assertEqual(cache[1], 1)
def test_lfu_getsizeof(self):
cache = LFUCache(maxsize=3, getsizeof=lambda x: x)
cache[1] = 1
cache[2] = 2
self.assertEqual(len(cache), 2)
self.assertEqual(cache[1], 1)
self.assertEqual(cache[2], 2)
cache[3] = 3
self.assertEqual(len(cache), 1)
self.assertEqual(cache[3], 3)
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
with self.assertRaises(ValueError):
cache[4] = 4
self.assertEqual(len(cache), 1)
self.assertEqual(cache[3], 3)

View File

@@ -0,0 +1,56 @@
import unittest
from cachetools import LRUCache
from . import CacheTestMixin
class LRUCacheTest(unittest.TestCase, CacheTestMixin):
Cache = LRUCache
def test_lru(self):
cache = LRUCache(maxsize=2)
cache[1] = 1
cache[2] = 2
cache[3] = 3
self.assertEqual(len(cache), 2)
self.assertEqual(cache[2], 2)
self.assertEqual(cache[3], 3)
self.assertNotIn(1, cache)
cache[2]
cache[4] = 4
self.assertEqual(len(cache), 2)
self.assertEqual(cache[2], 2)
self.assertEqual(cache[4], 4)
self.assertNotIn(3, cache)
cache[5] = 5
self.assertEqual(len(cache), 2)
self.assertEqual(cache[4], 4)
self.assertEqual(cache[5], 5)
self.assertNotIn(2, cache)
def test_lru_getsizeof(self):
cache = LRUCache(maxsize=3, getsizeof=lambda x: x)
cache[1] = 1
cache[2] = 2
self.assertEqual(len(cache), 2)
self.assertEqual(cache[1], 1)
self.assertEqual(cache[2], 2)
cache[3] = 3
self.assertEqual(len(cache), 1)
self.assertEqual(cache[3], 3)
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
with self.assertRaises(ValueError):
cache[4] = 4
self.assertEqual(len(cache), 1)
self.assertEqual(cache[3], 3)

View File

@@ -0,0 +1,63 @@
import unittest
import warnings
from cachetools import MRUCache
from . import CacheTestMixin
class MRUCacheTest(unittest.TestCase, CacheTestMixin):
# TODO: method to create cache that can be overridden
Cache = MRUCache
def test_evict__writes_only(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
cache = MRUCache(maxsize=2)
self.assertEqual(len(w), 1)
self.assertIs(w[0].category, DeprecationWarning)
cache[1] = 1
cache[2] = 2
cache[3] = 3 # Evicts 1 because nothing's been used yet
assert len(cache) == 2
assert 1 not in cache, "Wrong key was evicted. Should have been '1'."
assert 2 in cache
assert 3 in cache
def test_evict__with_access(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
cache = MRUCache(maxsize=2)
self.assertEqual(len(w), 1)
self.assertIs(w[0].category, DeprecationWarning)
cache[1] = 1
cache[2] = 2
cache[1]
cache[2]
cache[3] = 3 # Evicts 2
assert 2 not in cache, "Wrong key was evicted. Should have been '2'."
assert 1 in cache
assert 3 in cache
def test_evict__with_delete(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
cache = MRUCache(maxsize=2)
self.assertEqual(len(w), 1)
self.assertIs(w[0].category, DeprecationWarning)
cache[1] = 1
cache[2] = 2
del cache[2]
cache[3] = 3 # Doesn't evict anything because we just deleted 2
assert 2 not in cache
assert 1 in cache
cache[4] = 4 # Should evict 1 as we just accessed it with __contains__
assert 1 not in cache
assert 3 in cache
assert 4 in cache

View File

@@ -0,0 +1,34 @@
import unittest
from cachetools import RRCache
from . import CacheTestMixin
class RRCacheTest(unittest.TestCase, CacheTestMixin):
Cache = RRCache
def test_rr(self):
cache = RRCache(maxsize=2, choice=min)
self.assertEqual(min, cache.choice)
cache[1] = 1
cache[2] = 2
cache[3] = 3
self.assertEqual(2, len(cache))
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
self.assertNotIn(1, cache)
cache[0] = 0
self.assertEqual(2, len(cache))
self.assertEqual(0, cache[0])
self.assertEqual(3, cache[3])
self.assertNotIn(2, cache)
cache[4] = 4
self.assertEqual(2, len(cache))
self.assertEqual(3, cache[3])
self.assertEqual(4, cache[4])
self.assertNotIn(0, cache)

View File

@@ -0,0 +1,271 @@
import math
import unittest
from cachetools import TLRUCache
from . import CacheTestMixin
class Timer:
def __init__(self, auto=False):
self.auto = auto
self.time = 0
def __call__(self):
if self.auto:
self.time += 1
return self.time
def tick(self):
self.time += 1
class TLRUTestCache(TLRUCache):
def default_ttu(_key, _value, _time):
return math.inf
def __init__(self, maxsize, ttu=default_ttu, **kwargs):
TLRUCache.__init__(self, maxsize, ttu, timer=Timer(), **kwargs)
class TLRUCacheTest(unittest.TestCase, CacheTestMixin):
Cache = TLRUTestCache
def test_ttu(self):
cache = TLRUCache(maxsize=6, ttu=lambda _, v, t: t + v + 1, timer=Timer())
self.assertEqual(0, cache.timer())
self.assertEqual(3, cache.ttu(None, 1, 1))
cache[1] = 1
self.assertEqual(1, cache[1])
self.assertEqual(1, len(cache))
self.assertEqual({1}, set(cache))
cache.timer.tick()
self.assertEqual(1, cache[1])
self.assertEqual(1, len(cache))
self.assertEqual({1}, set(cache))
cache[2] = 2
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
self.assertEqual(2, len(cache))
self.assertEqual({1, 2}, set(cache))
cache.timer.tick()
self.assertNotIn(1, cache)
self.assertEqual(2, cache[2])
self.assertEqual(1, len(cache))
self.assertEqual({2}, set(cache))
cache[3] = 3
self.assertNotIn(1, cache)
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
self.assertEqual(2, len(cache))
self.assertEqual({2, 3}, set(cache))
cache.timer.tick()
self.assertNotIn(1, cache)
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
self.assertEqual(2, len(cache))
self.assertEqual({2, 3}, set(cache))
cache[1] = 1
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
self.assertEqual(3, len(cache))
self.assertEqual({1, 2, 3}, set(cache))
cache.timer.tick()
self.assertEqual(1, cache[1])
self.assertNotIn(2, cache)
self.assertEqual(3, cache[3])
self.assertEqual(2, len(cache))
self.assertEqual({1, 3}, set(cache))
cache.timer.tick()
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertEqual(3, cache[3])
self.assertEqual(1, len(cache))
self.assertEqual({3}, set(cache))
cache.timer.tick()
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertNotIn(3, cache)
with self.assertRaises(KeyError):
del cache[1]
with self.assertRaises(KeyError):
cache.pop(2)
with self.assertRaises(KeyError):
del cache[3]
self.assertEqual(0, len(cache))
self.assertEqual(set(), set(cache))
def test_ttu_lru(self):
cache = TLRUCache(maxsize=2, ttu=lambda k, v, t: t + 1, timer=Timer())
self.assertEqual(0, cache.timer())
self.assertEqual(2, cache.ttu(None, None, 1))
cache[1] = 1
cache[2] = 2
cache[3] = 3
self.assertEqual(len(cache), 2)
self.assertNotIn(1, cache)
self.assertEqual(cache[2], 2)
self.assertEqual(cache[3], 3)
cache[2]
cache[4] = 4
self.assertEqual(len(cache), 2)
self.assertNotIn(1, cache)
self.assertEqual(cache[2], 2)
self.assertNotIn(3, cache)
self.assertEqual(cache[4], 4)
cache[5] = 5
self.assertEqual(len(cache), 2)
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertNotIn(3, cache)
self.assertEqual(cache[4], 4)
self.assertEqual(cache[5], 5)
def test_ttu_expire(self):
cache = TLRUCache(maxsize=3, ttu=lambda k, v, t: t + 3, timer=Timer())
with cache.timer as time:
self.assertEqual(time, cache.timer())
cache[1] = 1
cache.timer.tick()
cache[2] = 2
cache.timer.tick()
cache[3] = 3
self.assertEqual(2, cache.timer())
self.assertEqual({1, 2, 3}, set(cache))
self.assertEqual(3, len(cache))
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
items = cache.expire()
self.assertEqual(set(), set(items))
self.assertEqual({1, 2, 3}, set(cache))
self.assertEqual(3, len(cache))
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
items = cache.expire(3)
self.assertEqual({(1, 1)}, set(items))
self.assertEqual({2, 3}, set(cache))
self.assertEqual(2, len(cache))
self.assertNotIn(1, cache)
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
items = cache.expire(4)
self.assertEqual({(2, 2)}, set(items))
self.assertEqual({3}, set(cache))
self.assertEqual(1, len(cache))
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertEqual(3, cache[3])
items = cache.expire(5)
self.assertEqual({(3, 3)}, set(items))
self.assertEqual(set(), set(cache))
self.assertEqual(0, len(cache))
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertNotIn(3, cache)
def test_ttu_expired(self):
cache = TLRUCache(maxsize=1, ttu=lambda k, _, t: t + k, timer=Timer())
cache[1] = None
self.assertEqual(cache[1], None)
self.assertEqual(1, len(cache))
cache[0] = None
self.assertNotIn(0, cache)
self.assertEqual(cache[1], None)
self.assertEqual(1, len(cache))
cache[-1] = None
self.assertNotIn(-1, cache)
self.assertNotIn(0, cache)
self.assertEqual(cache[1], None)
self.assertEqual(1, len(cache))
def test_ttu_atomic(self):
cache = TLRUCache(maxsize=1, ttu=lambda k, v, t: t + 2, timer=Timer(auto=True))
cache[1] = 1
self.assertEqual(1, cache[1])
cache[1] = 1
self.assertEqual(1, cache.get(1))
cache[1] = 1
self.assertEqual(1, cache.pop(1))
cache[1] = 1
self.assertEqual(1, cache.setdefault(1))
cache[1] = 1
cache.clear()
self.assertEqual(0, len(cache))
def test_ttu_tuple_key(self):
cache = TLRUCache(maxsize=1, ttu=lambda k, v, t: t + 1, timer=Timer())
cache[(1, 2, 3)] = 42
self.assertEqual(42, cache[(1, 2, 3)])
cache.timer.tick()
with self.assertRaises(KeyError):
cache[(1, 2, 3)]
self.assertNotIn((1, 2, 3), cache)
def test_ttu_reverse_insert(self):
cache = TLRUCache(maxsize=4, ttu=lambda k, v, t: t + v, timer=Timer())
self.assertEqual(0, cache.timer())
cache[3] = 3
cache[2] = 2
cache[1] = 1
cache[0] = 0
self.assertEqual({1, 2, 3}, set(cache))
self.assertEqual(3, len(cache))
self.assertNotIn(0, cache)
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
cache.timer.tick()
self.assertEqual({2, 3}, set(cache))
self.assertEqual(2, len(cache))
self.assertNotIn(0, cache)
self.assertNotIn(1, cache)
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
cache.timer.tick()
self.assertEqual({3}, set(cache))
self.assertEqual(1, len(cache))
self.assertNotIn(0, cache)
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertEqual(3, cache[3])
cache.timer.tick()
self.assertEqual(set(), set(cache))
self.assertEqual(0, len(cache))
self.assertNotIn(0, cache)
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertNotIn(3, cache)

View File

@@ -0,0 +1,203 @@
import math
import unittest
from cachetools import TTLCache
from . import CacheTestMixin
class Timer:
def __init__(self, auto=False):
self.auto = auto
self.time = 0
def __call__(self):
if self.auto:
self.time += 1
return self.time
def tick(self):
self.time += 1
class TTLTestCache(TTLCache):
def __init__(self, maxsize, ttl=math.inf, **kwargs):
TTLCache.__init__(self, maxsize, ttl=ttl, timer=Timer(), **kwargs)
class TTLCacheTest(unittest.TestCase, CacheTestMixin):
Cache = TTLTestCache
def test_ttl(self):
cache = TTLCache(maxsize=2, ttl=2, timer=Timer())
self.assertEqual(0, cache.timer())
self.assertEqual(2, cache.ttl)
cache[1] = 1
self.assertEqual(1, cache[1])
self.assertEqual(1, len(cache))
self.assertEqual({1}, set(cache))
cache.timer.tick()
self.assertEqual(1, cache[1])
self.assertEqual(1, len(cache))
self.assertEqual({1}, set(cache))
cache[2] = 2
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
self.assertEqual(2, len(cache))
self.assertEqual({1, 2}, set(cache))
cache.timer.tick()
self.assertNotIn(1, cache)
self.assertEqual(2, cache[2])
self.assertEqual(1, len(cache))
self.assertEqual({2}, set(cache))
cache[3] = 3
self.assertNotIn(1, cache)
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
self.assertEqual(2, len(cache))
self.assertEqual({2, 3}, set(cache))
cache.timer.tick()
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertEqual(3, cache[3])
self.assertEqual(1, len(cache))
self.assertEqual({3}, set(cache))
cache.timer.tick()
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertNotIn(3, cache)
with self.assertRaises(KeyError):
del cache[1]
with self.assertRaises(KeyError):
cache.pop(2)
with self.assertRaises(KeyError):
del cache[3]
self.assertEqual(0, len(cache))
self.assertEqual(set(), set(cache))
def test_ttl_lru(self):
cache = TTLCache(maxsize=2, ttl=1, timer=Timer())
cache[1] = 1
cache[2] = 2
cache[3] = 3
self.assertEqual(len(cache), 2)
self.assertNotIn(1, cache)
self.assertEqual(cache[2], 2)
self.assertEqual(cache[3], 3)
cache[2]
cache[4] = 4
self.assertEqual(len(cache), 2)
self.assertNotIn(1, cache)
self.assertEqual(cache[2], 2)
self.assertNotIn(3, cache)
self.assertEqual(cache[4], 4)
cache[5] = 5
self.assertEqual(len(cache), 2)
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertNotIn(3, cache)
self.assertEqual(cache[4], 4)
self.assertEqual(cache[5], 5)
def test_ttl_expire(self):
cache = TTLCache(maxsize=3, ttl=3, timer=Timer())
with cache.timer as time:
self.assertEqual(time, cache.timer())
self.assertEqual(3, cache.ttl)
cache[1] = 1
cache.timer.tick()
cache[2] = 2
cache.timer.tick()
cache[3] = 3
self.assertEqual(2, cache.timer())
self.assertEqual({1, 2, 3}, set(cache))
self.assertEqual(3, len(cache))
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
items = cache.expire()
self.assertEqual(set(), set(items))
self.assertEqual({1, 2, 3}, set(cache))
self.assertEqual(3, len(cache))
self.assertEqual(1, cache[1])
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
items = cache.expire(3)
self.assertEqual({(1, 1)}, set(items))
self.assertEqual({2, 3}, set(cache))
self.assertEqual(2, len(cache))
self.assertNotIn(1, cache)
self.assertEqual(2, cache[2])
self.assertEqual(3, cache[3])
items = cache.expire(4)
self.assertEqual({(2, 2)}, set(items))
self.assertEqual({3}, set(cache))
self.assertEqual(1, len(cache))
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertEqual(3, cache[3])
items = cache.expire(5)
self.assertEqual({(3, 3)}, set(items))
self.assertEqual(set(), set(cache))
self.assertEqual(0, len(cache))
self.assertNotIn(1, cache)
self.assertNotIn(2, cache)
self.assertNotIn(3, cache)
def test_ttl_atomic(self):
cache = TTLCache(maxsize=1, ttl=2, timer=Timer(auto=True))
cache[1] = 1
self.assertEqual(1, cache[1])
cache[1] = 1
self.assertEqual(1, cache.get(1))
cache[1] = 1
self.assertEqual(1, cache.pop(1))
cache[1] = 1
self.assertEqual(1, cache.setdefault(1))
cache[1] = 1
cache.clear()
self.assertEqual(0, len(cache))
def test_ttl_tuple_key(self):
cache = TTLCache(maxsize=1, ttl=1, timer=Timer())
self.assertEqual(1, cache.ttl)
cache[(1, 2, 3)] = 42
self.assertEqual(42, cache[(1, 2, 3)])
cache.timer.tick()
with self.assertRaises(KeyError):
cache[(1, 2, 3)]
self.assertNotIn((1, 2, 3), cache)
def test_ttl_datetime(self):
from datetime import datetime, timedelta
cache = TTLCache(maxsize=1, ttl=timedelta(days=1), timer=datetime.now)
cache[1] = 1
self.assertEqual(1, len(cache))
items = cache.expire(datetime.now())
self.assertEqual([], list(items))
self.assertEqual(1, len(cache))
items = cache.expire(datetime.now() + timedelta(days=1))
self.assertEqual([(1, 1)], list(items))
self.assertEqual(0, len(cache))