Back to index

plone3  3.1.7
ram.py
Go to the documentation of this file.
00001 """A cache decorator that uses RAMCache by default.
00002 
00003 See README.txt and the `volatile` module for more details.
00004 
00005   >>> def cache_key(fun, first, second):
00006   ...     return (first, second)
00007   >>> @cache(cache_key)
00008   ... def pow(first, second):
00009   ...     print 'Someone or something called me'
00010   ...     return first ** second
00011 
00012   >>> pow(3, 2)
00013   Someone or something called me
00014   9
00015   >>> pow(3, 2)
00016   9
00017 
00018 Let's cache another function:
00019 
00020   >>> @cache(cache_key)
00021   ... def add(first, second):
00022   ...     print 'Someone or something called me'
00023   ...     return first + second
00024 
00025   >>> add(3, 2)
00026   Someone or something called me
00027   5
00028   >>> add(3, 2)
00029   5
00030 
00031 Now invalidate the cache for the `pow` function:
00032 
00033   >>> pow(3, 2)
00034   9
00035   >>> global_cache.invalidate('plone.memoize.ram.pow')
00036   >>> pow(3, 2)
00037   Someone or something called me
00038   9
00039 
00040 Make sure that we only invalidated the cache for the `pow` function:
00041 
00042   >>> add(3, 2)
00043   5
00044 
00045   >>> global_cache.invalidateAll()
00046 
00047 You can register an ICacheChooser utility to override the cache used
00048 based on the function that is cached.  To do this, we'll first
00049 unregister the already registered global `choose_cache` function:
00050 
00051   >>> sm = component.getGlobalSiteManager()
00052   >>> sm.unregisterUtility(choose_cache)
00053   True
00054 
00055 This customized cache chooser will use the `my_cache` for the `pow`
00056 function, and use the `global_cache` for all other functions:
00057 
00058   >>> my_cache = ram.RAMCache()
00059   >>> def my_choose_cache(fun_name):
00060   ...     if fun_name.endswith('.pow'):
00061   ...         return RAMCacheAdapter(my_cache)
00062   ...     else:
00063   ...         return RAMCacheAdapter(global_cache)
00064   >>> interface.directlyProvides(my_choose_cache, ICacheChooser)
00065   >>> sm.registerUtility(my_choose_cache)
00066 
00067 Both caches are empty at this point:
00068 
00069   >>> len(global_cache.getStatistics())
00070   0
00071   >>> len(my_cache.getStatistics())
00072   0
00073 
00074 Let's fill them:
00075 
00076   >>> pow(3, 2)
00077   Someone or something called me
00078   9
00079   >>> pow(3, 2)
00080   9
00081   >>> len(global_cache.getStatistics())
00082   0
00083   >>> len(my_cache.getStatistics())
00084   1
00085 
00086   >>> add(3, 2)
00087   Someone or something called me
00088   5
00089   >>> add(3, 2)
00090   5
00091   >>> len(global_cache.getStatistics())
00092   1
00093   >>> len(my_cache.getStatistics())
00094   1
00095 """
00096 
00097 import md5
00098 import cPickle
00099 
00100 from zope import interface
00101 from zope import component
00102 from zope.app.cache.interfaces.ram import IRAMCache
00103 from zope.app.cache import ram
00104 
00105 from plone.memoize.interfaces import ICacheChooser
00106 from plone.memoize import volatile
00107 
00108 global_cache = ram.RAMCache()
00109 global_cache.update(maxAge=86400)
00110 
00111 DontCache = volatile.DontCache
00112 MARKER = object()
00113 
00114 class AbstractDict:
00115     def get(self, key, default=None):
00116         try:
00117             return self.__getitem__(key)
00118         except KeyError:
00119             return default
00120 
00121 class MemcacheAdapter(AbstractDict):
00122     def __init__(self, client, globalkey=''):
00123         self.client = client
00124         self.globalkey = globalkey and '%s:' % globalkey
00125 
00126     def _make_key(self, source):
00127         return md5.new(source).hexdigest()
00128 
00129     def __getitem__(self, key):
00130         cached_value = self.client.get(self.globalkey + self._make_key(key))
00131         if cached_value is None:
00132             raise KeyError(key)
00133         else:
00134             return cPickle.loads(cached_value)
00135 
00136     def __setitem__(self, key, value):
00137         cached_value = cPickle.dumps(value)
00138         self.client.set(self.globalkey + self._make_key(key), cached_value)
00139 
00140 class RAMCacheAdapter(AbstractDict):
00141     def __init__(self, ramcache, globalkey=''):
00142         self.ramcache = ramcache
00143         self.globalkey = globalkey
00144 
00145     def _make_key(self, source):
00146         return md5.new(source).digest()
00147 
00148     def __getitem__(self, key):
00149         value = self.ramcache.query(self.globalkey,
00150                                     dict(key=self._make_key(key)),
00151                                     MARKER)
00152         if value is MARKER:
00153             raise KeyError(key)
00154         else:
00155             return value
00156 
00157     def __setitem__(self, key, value):
00158         self.ramcache.set(value,
00159                           self.globalkey,
00160                           dict(key=self._make_key(key)))
00161 
00162 def choose_cache(fun_name):
00163     return RAMCacheAdapter(component.queryUtility(IRAMCache),
00164                            globalkey=fun_name)
00165 interface.directlyProvides(choose_cache, ICacheChooser)
00166 
00167 def store_in_cache(fun, *args, **kwargs):
00168     key = '%s.%s' % (fun.__module__, fun.__name__)
00169     cache_chooser = component.queryUtility(ICacheChooser)
00170     if cache_chooser is not None:
00171         return cache_chooser(key)
00172     else:
00173         return RAMCacheAdapter(global_cache, globalkey=key)
00174 
00175 def cache(get_key):
00176     return volatile.cache(get_key, get_cache=store_in_cache)