summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Nordhausen <nordhausen@linux-z4v3.site>2012-01-18 17:08:06 +0100
committerStefan Nordhausen <nordhausen@linux-z4v3.site>2012-01-18 17:08:06 +0100
commit70d36671e8180d842e7a4237b9afa90294201667 (patch)
treee1f05d0ad1daef6929ac0a74b796293bd0719adb
parent4406c85fad0f7345953c624fbf5089f38ee9f7b2 (diff)
downloadrepoze-lru-70d36671e8180d842e7a4237b9afa90294201667.tar.gz
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().
-rw-r--r--repoze/lru/__init__.py15
-rwxr-xr-xrepoze/lru/tests.py34
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):