From 933b3c58828bb85422d650e9ab3b9115585900c5 Mon Sep 17 00:00:00 2001 From: julien tayon Date: Wed, 4 Jul 2012 14:30:32 +0200 Subject: Adding CacheMaker with(tests + docs) Code coverage can't reach L324 --- CONTRIBUTORS.txt | 1 + docs/api.rst | 4 +++ docs/narr.rst | 30 +++++++++++++++++++++ repoze/lru/__init__.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++ repoze/lru/tests.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 178 insertions(+) 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) + + -- cgit v1.2.1