summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCosmin Basca <cosmin.basca@gmail.com>2014-08-12 11:35:39 +0200
committerCosmin Basca <cosmin.basca@gmail.com>2014-08-12 11:35:39 +0200
commitfdc077c384e800259755d079828683a9af59bb53 (patch)
tree1a2e067e15361860681a0395d73a27bc87d96d78
parentef418de0d98f01098a5922dc2ded7dc4a7f574c6 (diff)
downloadrepoze-lru-fdc077c384e800259755d079828683a9af59bb53.tar.gz
included UnbondedCache (a simple unbounded and unexpiring cache)
added new abstract base class (interface): Cache LRUCache, ExpiringLRUCache and UnboundedCache inherit from it modified lru_cache decorator to support *args and **kwargs, not just *args added memoized member to CacheMaker
-rw-r--r--CONTRIBUTORS.txt1
-rw-r--r--repoze/lru/__init__.py97
2 files changed, 78 insertions, 20 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 925d2d1..5af5f5b 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -106,3 +106,4 @@ Contributors
- Tres Seaver, 2011/02/22
- Joel Bohman, 2011/08/16
- Julien Tayon, 2012/07/04
+- Cosmin Basca, 2014/07/12
diff --git a/repoze/lru/__init__.py b/repoze/lru/__init__.py
index e5c3280..c761b8e 100644
--- a/repoze/lru/__init__.py
+++ b/repoze/lru/__init__.py
@@ -1,5 +1,6 @@
""" LRU caching class and decorator """
from __future__ import with_statement
+from abc import ABCMeta, abstractmethod
import threading
import time
@@ -12,12 +13,54 @@ _MARKER = object()
_DEFAULT_TIMEOUT = 2 ** 60
-class LRUCache(object):
+class Cache(object):
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def clear(self):
+ pass
+
+ @abstractmethod
+ def get(self, key, default=None):
+ pass
+
+ @abstractmethod
+ def put(self, key, val):
+ pass
+
+ @abstractmethod
+ def invalidate(self, key):
+ pass
+
+
+class UnboundedCache(Cache):
+ """
+ a simple unbounded cache backed by a dictionary
+ """
+
+ def __init__(self):
+ self._data = dict()
+
+ def get(self, key, default=None):
+ return self._data.get(key, default)
+
+ def clear(self):
+ self._data.clear()
+
+ def invalidate(self, key):
+ pass
+
+ def put(self, key, val):
+ self._data[key] = val
+
+
+class LRUCache(Cache):
""" Implements a pseudo-LRU algorithm (CLOCK)
The Clock algorithm is not kept strictly to improve performance, e.g. to
allow get() and invalidate() to work without acquiring the lock.
"""
+
def __init__(self, size):
size = int(size)
if size < 1:
@@ -127,15 +170,16 @@ class LRUCache(object):
# We have no lock, but worst thing that can happen is that we
# set another key's entry to False.
self.clock_refs[entry[0]] = False
- # else: key was not in cache. Nothing to do.
+ # else: key was not in cache. Nothing to do.
-class ExpiringLRUCache(object):
+class ExpiringLRUCache(Cache):
""" Implements a pseudo-LRU algorithm (CLOCK) with expiration times
The Clock algorithm is not kept strictly to improve performance, e.g. to
allow get() and invalidate() to work without acquiring the lock.
"""
+
def __init__(self, size, default_timeout=_DEFAULT_TIMEOUT):
self.default_timeout = default_timeout
size = int(size)
@@ -261,7 +305,7 @@ class ExpiringLRUCache(object):
# We have no lock, but worst thing that can happen is that we
# set another key's entry to False.
self.clock_refs[entry[0]] = False
- # else: key was not in cache. Nothing to do.
+ # else: key was not in cache. Nothing to do.
class lru_cache(object):
@@ -270,37 +314,45 @@ class lru_cache(object):
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
+
+ def __init__(self, maxsize, cache=None, timeout=None): # cache is an arg to serve tests
if cache is None:
- if timeout is None:
+ if maxsize is None:
+ cache = UnboundedCache()
+ elif timeout is None:
cache = LRUCache(maxsize)
else:
cache = ExpiringLRUCache(maxsize, default_timeout=timeout)
self.cache = cache
- def __call__(self, f):
+ def __call__(self, func):
cache = self.cache
marker = _MARKER
- def lru_cached(*arg):
- val = cache.get(arg, marker)
+
+ def cached_wrapper(*args, **kwargs):
+ key = (args, frozenset(kwargs.items()))
+ val = cache.get(key, marker)
if val is marker:
- val = f(*arg)
- cache.put(arg, val)
+ val = func(*args, **kwargs)
+ cache.put(key, val)
return val
+
def _maybe_copy(source, target, attr):
value = getattr(source, attr, source)
if value is not source:
setattr(target, attr, value)
- _maybe_copy(f, lru_cached, '__module__')
- _maybe_copy(f, lru_cached, '__name__')
- _maybe_copy(f, lru_cached, '__doc__')
- lru_cached._cache = cache
- return lru_cached
+
+ _maybe_copy(func, cached_wrapper, '__module__')
+ _maybe_copy(func, cached_wrapper, '__name__')
+ _maybe_copy(func, cached_wrapper, '__doc__')
+ cached_wrapper._cache = cache
+ return cached_wrapper
class CacheMaker(object):
"""Generates decorators that can be cleared later
"""
+
def __init__(self, maxsize=None, timeout=_DEFAULT_TIMEOUT):
"""Create cache decorator factory.
@@ -316,7 +368,7 @@ class CacheMaker(object):
if name is None:
while True:
name = str(uuid.uuid4())
- ## the probability of collision is so low ....
+ # # the probability of collision is so low ....
if name not in self._cache:
break
@@ -333,7 +385,12 @@ class CacheMaker(object):
timeout = self._timeout
return name, maxsize, timeout
-
+
+ def memoized(self, name=None):
+ name, maxsize, _ = self._resolve_setting(name, 0)
+ cache = self._cache[name] = UnboundedCache()
+ return lru_cache(None, cache)
+
def lrucache(self, name=None, maxsize=None):
"""Named arguments:
@@ -355,12 +412,12 @@ class CacheMaker(object):
the constructor
- timeout (optional) is an int, overriding any default value set by
- the constructor or the default value (%d seconds)
+ the constructor or the default value (%d seconds)
""" % _DEFAULT_TIMEOUT
name, maxsize, timeout = self._resolve_setting(name, maxsize, timeout)
cache = self._cache[name] = ExpiringLRUCache(maxsize, timeout)
return lru_cache(maxsize, cache, timeout)
-
+
def clear(self, *names):
"""Clear the given cache(s).