summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjulien tayon <jtayon@gmail.com>2012-07-04 14:30:32 +0200
committerjulien tayon <jtayon@gmail.com>2012-07-04 14:30:32 +0200
commit933b3c58828bb85422d650e9ab3b9115585900c5 (patch)
treecabfb005c1639724699bb99be96322bff1f7dd72
parent5323152e4d6d082367c77972ad24cadd71fca818 (diff)
downloadrepoze-lru-933b3c58828bb85422d650e9ab3b9115585900c5.tar.gz
Adding CacheMaker with(tests + docs)
Code coverage can't reach L324
-rw-r--r--CONTRIBUTORS.txt1
-rw-r--r--docs/api.rst4
-rw-r--r--docs/narr.rst30
-rw-r--r--repoze/lru/__init__.py70
-rw-r--r--repoze/lru/tests.py73
5 files changed, 178 insertions, 0 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 27cb8c6..925d2d1 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -105,3 +105,4 @@ Contributors
- Tres Seaver, 2011/02/22
- Joel Bohman, 2011/08/16
+- Julien Tayon, 2012/07/04
diff --git a/docs/api.rst b/docs/api.rst
index ff2716a..4f24bba 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -17,3 +17,7 @@ Module: :mod:`repoze.lru`
.. autoclass:: lru_cache
:members:
:member-order: bysource
+
+ .. autoclass:: CacheMaker
+ :members:
+ :member-order: bysource
diff --git a/docs/narr.rst b/docs/narr.rst
index 6c46325..f819698 100644
--- a/docs/narr.rst
+++ b/docs/narr.rst
@@ -63,3 +63,33 @@ decorated function must be hashable. It does not support keyword arguments:
Each function decorated with the lru_cache decorator uses its own
cache related to that function.
+
+Cleaning cache of decorated function
+------------------------------------
+
+:mod:`repoze.lru` provides a :class:`~repoze.lru.CacheMaker`, which generates
+decorators. This way, you can later clear your cache if needed.
+
+.. doctest::
+
+ >>> from repoze.lru import CacheMaker
+ >>> cache_maker=CacheMaker()
+ >>> @cache_maker.lrucache(maxsize=300, name="adder")
+ ... def yet_another_exepensive_function(*arg):#*
+ ... pass
+
+ >>> @cache_maker.expiring_lrucache(maxsize=300,timeout=30)
+ ... def another_exepensive_function(*arg):#*
+ ... pass
+
+This way, when you need it you can choose to either clear all cache:
+
+.. doctest::
+
+ >>> cache_maker.clear()
+
+or clear a specific cache
+
+.. doctest::
+
+ >>> cache_maker.clear("adder")
diff --git a/repoze/lru/__init__.py b/repoze/lru/__init__.py
index 1d8ae94..7d9e50a 100644
--- a/repoze/lru/__init__.py
+++ b/repoze/lru/__init__.py
@@ -3,6 +3,7 @@ from __future__ import with_statement
import threading
import time
+import uuid
try:
range = xrange
@@ -292,3 +293,72 @@ class lru_cache(object):
lru_cached.__name__ = f.__name__
lru_cached.__doc__ = f.__doc__
return lru_cached
+
+_SENTINEL=_MARKER
+
+class CacheMaker(object):
+ """Generates decorators that can be cleared later
+ """
+ def __init__(self,**default):
+ """
+ constructor is accepting named arguments :
+ * maxsize : the default size for the cache
+ * timeout : the defaut size for the cache if using expriring cache
+ """
+ self._maxsize=default.get("maxsize",_SENTINEL)
+ self._timeout=default.get("timeout",_DEFAULT_TIMEOUT)
+ self._cache=dict()
+
+ def _resolve_setting(self,option):
+ name = option.get("name",_SENTINEL)
+ maxsize = option.get("maxsize",_SENTINEL)
+ maxsize = self._maxsize if maxsize is _SENTINEL else maxsize
+ if maxsize is _SENTINEL:
+ raise ValueError("Cache must have a maxsize set")
+ timeout = option.get("timeout",_SENTINEL)
+ timeout = self._timeout if timeout is _SENTINEL else timeout
+ if name is _SENTINEL:
+ _name= str(uuid.uuid4())
+ ## the probability of collision is so low ....
+ while _name in self._cache.keys():
+ _name=str(uuid.uuid4())
+ else:
+ if name in self._cache:
+ raise KeyError("cache %s already in use" % name)
+ _name=name
+
+ return dict( name=_name,timeout=timeout, maxsize=maxsize)
+
+ def lrucache(self,**option):
+ """named argument:
+ * name (optional) is a string, and should be unique amongst all
+ cache
+ * maxsize : if given will override any default value given at
+ the constructor"""
+ option=self._resolve_setting(option)
+ cache = self._cache[option["name"]] = LRUCache(option['maxsize'])
+ return lru_cache(option['maxsize'], cache)
+
+ def expiring_lrucache(self, **option):
+ """named argument:
+ * name (optional) is a string, and should be unique amongst all
+ cache
+ * maxsize : if given will override any default value given at
+ the constructor
+ * timeout : if given will override any default value given at
+ constructur or the default value (%d seconds)
+ """ % _DEFAULT_TIMEOUT
+ option=self._resolve_setting(option)
+ cache = self._cache[option['name']]=ExpiringLRUCache(
+ option["maxsize"],option["timeout"]
+ )
+ return lru_cache(option["maxsize"], cache, option["timeout"])
+
+ def clear(self, name = _SENTINEL):
+ """
+ clear all cache if no arguments, else clear cache with the given name"""
+ to_clear = self._cache.keys() if name is _SENTINEL else [ name ]
+ for cache_name in to_clear:
+ self._cache[cache_name].clear()
+
+
diff --git a/repoze/lru/tests.py b/repoze/lru/tests.py
index eae1d47..1b6fae5 100644
--- a/repoze/lru/tests.py
+++ b/repoze/lru/tests.py
@@ -560,3 +560,76 @@ class DecoratorTests(unittest.TestCase):
class DummyLRUCache(dict):
def put(self, k, v):
return self.__setitem__(k, v)
+
+class CacherMaker(unittest.TestCase):
+ def setUp(self):
+ self.adder=lambda x : x+10
+ from ..lru import CacheMaker
+ self.cache_maker=CacheMaker
+
+
+
+ def test_named_cache(self):
+ cache=self.cache_maker()
+ size=10
+ name="name"
+ decorated=cache.lrucache(maxsize=size, name=name)(self.adder)
+ self.assertEqual( cache._cache.keys() , [ name ])
+ self.assertEqual( cache._cache[name].size,size)
+ decorated(10)
+ decorated(11)
+ self.assertEqual(len(cache._cache[name].data),2)
+
+ def test_excpetion(self):
+ cache=self.cache_maker()
+ size=10
+ name="name"
+ decorated=cache.lrucache(maxsize=size, name=name)(self.adder)
+ with self.assertRaises(KeyError):
+ cache.lrucache(maxsize=size,name= name)
+ with self.assertRaises(ValueError):
+ cache.lrucache()
+
+
+ def test_defaultvalue_and_clear(self):
+ size=10
+ cache=self.cache_maker(maxsize=size)
+ for i in range(100):
+ decorated=cache.lrucache()(self.adder)
+ decorated(10)
+
+ self.assertEqual( len(cache._cache) , 100)
+ for _cache in cache._cache.values():
+ self.assertEqual( _cache.size,size)
+ self.assertEqual(len(_cache.data),1)
+ ## and test clear cache
+ cache.clear()
+ for _cache in cache._cache.values():
+ self.assertEqual( _cache.size,size)
+ self.assertEqual(len(_cache.data),0)
+
+ def test_expiring(self):
+ size=10
+ timeout=10
+ name="name"
+ cache=self.cache_maker(maxsize=size,timeout=timeout)
+ for i in range(100):
+ if not i:
+ decorated=cache.expiring_lrucache(name=name)(self.adder)
+ self.assertEqual( cache._cache[name].size,size)
+ else:
+ decorated=cache.expiring_lrucache()(self.adder)
+ decorated(10)
+
+ self.assertEqual( len(cache._cache) , 100)
+ for _cache in cache._cache.values():
+ self.assertEqual( _cache.size,size)
+ self.assertEqual( _cache.default_timeout,timeout)
+ self.assertEqual(len(_cache.data),1)
+ ## and test clear cache
+ cache.clear()
+ for _cache in cache._cache.values():
+ self.assertEqual( _cache.size,size)
+ self.assertEqual(len(_cache.data),0)
+
+