From 70d36671e8180d842e7a4237b9afa90294201667 Mon Sep 17 00:00:00 2001 From: Stefan Nordhausen Date: Wed, 18 Jan 2012 17:08:06 +0100 Subject: Add "expiry" feature to the decorator - Decorator now accepts "timeout" parameter. - Unit tests check that decorator with timeout really forgets. - Fix incorrect import in ExpiringLRUCacheTests._getTargetClass(). --- repoze/lru/__init__.py | 15 ++++++++++++--- repoze/lru/tests.py | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/repoze/lru/__init__.py b/repoze/lru/__init__.py index 938f5ba..d7bcfef 100644 --- a/repoze/lru/__init__.py +++ b/repoze/lru/__init__.py @@ -9,6 +9,8 @@ except NameError: # pragma: no cover pass _MARKER = object() +# By default, expire items after 2**60 seconds. This fits into 64 bit +# integers and is close enough to "never" for practical purposes. _DEFAULT_TIMEOUT = 2 ** 60 class LRUCache(object): @@ -249,10 +251,17 @@ class ExpiringLRUCache(object): # else: key was not in cache. Nothing to do. class lru_cache(object): - """ Decorator for LRU-cached function """ - def __init__(self, maxsize, cache=None): # cache is an arg to serve tests + """ Decorator for LRU-cached function + + timeout parameter specifies after how many seconds a cached entry should + be considered invalid. + """ + def __init__(self, maxsize, cache=None, timeout=None): # cache is an arg to serve tests if cache is None: - cache = LRUCache(maxsize) + if timeout is None: + cache = LRUCache(maxsize) + else: + cache = ExpiringLRUCache(maxsize, default_timeout=timeout) self.cache = cache def __call__(self, f): diff --git a/repoze/lru/tests.py b/repoze/lru/tests.py index a6572fb..bac2407 100755 --- a/repoze/lru/tests.py +++ b/repoze/lru/tests.py @@ -266,7 +266,7 @@ class LRUCacheTests(unittest.TestCase): class ExpiringLRUCacheTests(LRUCacheTests): def _getTargetClass(self): - from repoze.lru import LRUCache + from repoze.lru import ExpiringLRUCache return ExpiringLRUCache def _makeOne(self, size, default_timeout=None): @@ -455,8 +455,8 @@ class DecoratorTests(unittest.TestCase): from repoze.lru import lru_cache return lru_cache - def _makeOne(self, maxsize, cache): - return self._getTargetClass()(maxsize, cache) + def _makeOne(self, maxsize, cache, timeout=None): + return self._getTargetClass()(maxsize, timeout=timeout, cache=cache) def test_ctor_nocache(self): decorator = self._makeOne(10, None) @@ -492,6 +492,34 @@ class DecoratorTests(unittest.TestCase): self.assertEqual(result, (3, 4, 5)) self.assertEqual(len(cache), 1) + def test_expiry(self): + """When timeout is given, decorator must eventually forget entries""" + @self._makeOne(1, None, timeout=0.1) + def sleep_a_bit(param): + time.sleep(0.1) + return 2 * param + + # First call must take at least 0.1 seconds + start = time.time() + result1 = sleep_a_bit("hello") + stop = time.time() + self.assertEqual(result1, 2 * "hello") + self.assertGreater(stop - start, 0.1) + + # Second call must take less than 0.1 seconds. + start = time.time() + result2 = sleep_a_bit("hello") + stop = time.time() + self.assertEqual(result2, 2 * "hello") + self.assertLess(stop - start, 0.1) + + time.sleep(0.1) + # This one must calculate again and take at least 0.1 seconds + start = time.time() + result3 = sleep_a_bit("hello") + stop = time.time() + self.assertEqual(result3, 2 * "hello") + self.assertGreater(stop - start, 0.1) class DummyLRUCache(dict): def put(self, k, v): -- cgit v1.2.1