summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-01-22 08:30:29 +0000
committerChris McDonough <chrism@agendaless.com>2009-01-22 08:30:29 +0000
commit72c5baff65bc82e010a6f0701324e5e09c05969c (patch)
treedd7430cee11ba433af8a10c8bfde958e83ea2bad
parentf662908175abb6ac9c638dbdeeea2635956ac80c (diff)
downloadpyramid-72c5baff65bc82e010a6f0701324e5e09c05969c.tar.gz
pyramid-72c5baff65bc82e010a6f0701324e5e09c05969c.tar.bz2
pyramid-72c5baff65bc82e010a6f0701324e5e09c05969c.zip
- ``repoze.bfg.lru`` implements an LRU cache class and a decorator for
internal use.
-rw-r--r--repoze/bfg/lru.py94
-rw-r--r--repoze/bfg/tests/test_lru.py75
2 files changed, 169 insertions, 0 deletions
diff --git a/repoze/bfg/lru.py b/repoze/bfg/lru.py
new file mode 100644
index 000000000..059750d15
--- /dev/null
+++ b/repoze/bfg/lru.py
@@ -0,0 +1,94 @@
+""" LRU caching class and decorator """
+
+import threading
+
+_marker = object()
+
+class Node(object):
+ def __init__(self, key):
+ self.key = key
+ self.ref = False
+
+class LRUCache(object):
+ def __init__(self, size):
+ """ Implements a psueudo-LRU algorithm (CLOCK) """
+ if size < 1:
+ raise ValueError('size must be >1')
+ self.clock = []
+ for i in xrange(0, size):
+ self.clock.append({'key':_marker, 'ref':False})
+ self.size = size
+ self.maxpos = size - 1
+ self.hand = 0
+ self.data = {}
+ self.lock = threading.Lock()
+
+ def get(self, key, default=None, _marker=_marker):
+ datum = self.data.get(key, _marker)
+ if datum is _marker:
+ return default
+ pos, val = datum
+ self.clock[pos]['ref'] = True
+ hand = pos + 1
+ if hand > self.maxpos:
+ hand = 0
+ self.hand = hand
+ return val
+
+ def put(self, key, val, _marker=_marker):
+ hand = self.hand
+ maxpos = self.maxpos
+ clock = self.clock
+ data = self.data
+ lock = self.lock
+
+ end = hand - 1
+ if end < 0:
+ end = maxpos
+
+ while 1:
+ current = clock[hand]
+ ref = current['ref']
+ if ref is True:
+ current['ref'] = False
+ hand = hand + 1
+ if hand > maxpos:
+ hand = 0
+ elif ref is False or hand == end:
+ lock.acquire()
+ try:
+ oldkey = current['key']
+ if oldkey is not _marker:
+ del data[oldkey]
+ current['key'] = key
+ current['ref'] = True
+ data[key] = (hand, val)
+ hand += 1
+ if hand > maxpos:
+ hand = 0
+ self.hand = hand
+ finally:
+ lock.release()
+ break
+
+class lru_cache(object):
+ """ Decorator for LRU-cached function """
+ def __init__(self, maxsize, cache=None): # cache is an arg to serve tests
+ if cache is None:
+ cache = LRUCache(maxsize)
+ self.cache = cache
+
+ def __call__(self, f):
+ cache = self.cache
+ marker = _marker
+ def lru_cached(key):
+ val = cache.get(key, marker)
+ if val is marker:
+ val = f(key)
+ cache.put(key, val)
+ return val
+ return lru_cached
+
+
+
+
diff --git a/repoze/bfg/tests/test_lru.py b/repoze/bfg/tests/test_lru.py
new file mode 100644
index 000000000..02bee3052
--- /dev/null
+++ b/repoze/bfg/tests/test_lru.py
@@ -0,0 +1,75 @@
+import unittest
+
+class LRUCacheTests(unittest.TestCase):
+ def _getTargetClass(self):
+ from repoze.bfg.lru import LRUCache
+ return LRUCache
+
+ def _makeOne(self, size):
+ return self._getTargetClass()(size)
+
+ def test_it(self):
+ cache = self._makeOne(3)
+ self.assertEqual(cache.get('a'), None)
+ cache.put('a', '1')
+ pos, value = cache.data.get('a')
+ self.assertEqual(cache.clock[pos]['ref'], True)
+ self.assertEqual(cache.clock[pos]['key'], 'a')
+ self.assertEqual(value, '1')
+ self.assertEqual(cache.get('a'), '1')
+ self.assertEqual(cache.hand, pos+1)
+ pos, value = cache.data.get('a')
+ self.assertEqual(cache.clock[pos]['ref'], True)
+ self.assertEqual(cache.hand, pos+1)
+ self.assertEqual(len(cache.data), 1)
+ cache.put('b', '2')
+ pos, value = cache.data.get('b')
+ self.assertEqual(cache.clock[pos]['ref'], True)
+ self.assertEqual(cache.clock[pos]['key'], 'b')
+ self.assertEqual(len(cache.data), 2)
+ cache.put('c', '3')
+ pos, value = cache.data.get('c')
+ self.assertEqual(cache.clock[pos]['ref'], True)
+ self.assertEqual(cache.clock[pos]['key'], 'c')
+ self.assertEqual(len(cache.data), 3)
+ pos, value = cache.data.get('a')
+ self.assertEqual(cache.clock[pos]['ref'], True)
+ cache.get('a')
+ cache.put('d', '4')
+ self.assertEqual(len(cache.data), 3)
+ self.assertEqual(cache.data.get('b'), None)
+ cache.put('e', '5')
+ self.assertEqual(len(cache.data), 3)
+ self.assertEqual(cache.data.get('c'), None)
+
+class DecoratorTests(unittest.TestCase):
+ def _getTargetClass(self):
+ from repoze.bfg.lru import lru_cache
+ return lru_cache
+
+ def _makeOne(self, cache):
+ return self._getTargetClass()(0, cache)
+
+ def test_it(self):
+ cache = DummyLRUCache()
+ decorator = self._makeOne(cache)
+ def wrapped(k):
+ return k
+ decorated = decorator(wrapped)
+ result = decorated(1)
+ self.assertEqual(cache[1], 1)
+ self.assertEqual(result, 1)
+ self.assertEqual(len(cache), 1)
+ result = decorated(2)
+ self.assertEqual(cache[2], 2)
+ self.assertEqual(result, 2)
+ self.assertEqual(len(cache), 2)
+ result = decorated(2)
+ self.assertEqual(cache[2], 2)
+ self.assertEqual(result, 2)
+ self.assertEqual(len(cache), 2)
+
+class DummyLRUCache(dict):
+ def put(self, k, v):
+ return self.__setitem__(k, v)
+