eggification
[Photo.git] / Products / Photo / cache.py
diff --git a/Products/Photo/cache.py b/Products/Photo/cache.py
new file mode 100755 (executable)
index 0000000..ab41afd
--- /dev/null
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+#######################################################################################
+#   Photo is a part of Plinn - http://plinn.org                                       #
+#   Copyright (C) 2008  BenoĆ®t PIN <benoit.pin@ensmp.fr>                              #
+#                                                                                     #
+#   This program is free software; you can redistribute it and/or                     #
+#   modify it under the terms of the GNU General Public License                       #
+#   as published by the Free Software Foundation; either version 2                    #
+#   of the License, or (at your option) any later version.                            #
+#                                                                                     #
+#   This program is distributed in the hope that it will be useful,                   #
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of                    #
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                     #
+#   GNU General Public License for more details.                                      #
+#                                                                                     #
+#   You should have received a copy of the GNU General Public License                 #
+#   along with this program; if not, write to the Free Software                       #
+#   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.   #
+#######################################################################################
+""" Memoization utils
+
+
+
+"""
+
+import inspect
+from BTrees.OOBTree import OOBTree
+
+def memoizedmethod(*indexes, **options) :
+       """ Used as decorator, this function stores result
+               of method m inside self._methodResultsCache or
+               self._v__methodResultsCache if volatile.
+               This decorator may be used inside a class which provides
+               a mapping object named _methodResultsCache and / or
+               _v__methodResultsCache.
+               
+               example :
+               
+               1 - simple metdhod memoization
+               
+               @memoizedmethod()
+               def methodWithNoParameters(self): pass
+               
+               2 - indexed memoisation:
+               Parameters names are passed to memoizedmethod are
+               evaluated to construct an indexed cache.
+               Names must be a subset of the memoized method signature.
+               
+               @memoizedmethod('arg1', 'arg2')
+               def methodWithParameters(self, arg1, arg2=None): pass
+       """
+       volatile = options.get('volatile', False)
+       cacheVarName = '_methodResultsCache'
+       if volatile==True :
+               cacheVarName = '_v_%s' % cacheVarName
+       
+       def makeMemoizedMethod(m) :
+               methodname = m.__name__
+               
+               if not indexes :
+                       def memoizedMethod(self) :
+                               if not hasattr(self, cacheVarName) :
+                                       setattr(self, cacheVarName, OOBTree())
+                               cache = getattr(self, cacheVarName)
+                               if cache.has_key(methodname) :
+                                       return cache[methodname]
+                               else :
+                                       res = m(self)
+                                       cache[methodname] = res
+                                       return res
+
+                       memoizedMethod.__name__ = methodname
+                       memoizedMethod.__doc__ = m.__doc__
+                       return memoizedMethod
+               
+               else :
+                       args, varargs, varkw, defaults = inspect.getargspec(m)
+                       args = list(args)
+                       if defaults is None :
+                               defaults = []
+                       mandatoryargs = args[1:-len(defaults)]
+                       optargs = args[-len(defaults):]
+                       defaultValues = dict(zip([name for name in args[-len(defaults):]], [val for val in defaults]))
+                       
+                       indexPositions = []
+                       for index in indexes :
+                               try :
+                                       indexPositions.append((index, args.index(index)))
+                               except ValueError :
+                                       raise ValueError("%r argument is not in signature of %r" % (index, methodname))
+                       
+                       if indexPositions :
+                               indexPositions.sort(lambda a, b : cmp(a[1], b[1]))
+                       
+                       indexPositions = tuple(indexPositions)
+                               
+                       
+                       def memoizedMethod(self, *args, **kw) :
+                               # test if m if called by ZPublished
+                               if len(args) < len(mandatoryargs) and hasattr(self, 'REQUEST') :
+                                       assert not kw
+                                       args = list(args)
+                                       get = lambda name : self.REQUEST[name]
+                                       for name in mandatoryargs :
+                                               try :
+                                                       args.append(get(name))
+                                               except KeyError :
+                                                       exactOrAtLeast = defaults and 'exactly' or 'at least'
+                                                       raise TypeError('%(methodname)s takes %(exactOrAtLeast)s %(mandatoryArgsLength)d argument (%(givenArgsLength)s given)' % \
+                                                                                       { 'methodname': methodname
+                                                                                       , 'exactOrAtLeast': exactOrAtLeast
+                                                                                       , 'mandatoryArgsLength': len(mandatoryargs)
+                                                                                       , 'givenArgsLength': len(args)})
+                                       
+                                       for name in optargs :
+                                               get = self.REQUEST.get
+                                               args.append(get(name, defaultValues[name]))
+
+                                       args = tuple(args)
+                               
+                               if not hasattr(self, cacheVarName) :
+                                       setattr(self, cacheVarName, OOBTree())
+                               cache = getattr(self, cacheVarName)
+                               if not cache.has_key(methodname) :
+                                       cache[methodname] = OOBTree()
+                               
+                               cache = cache[methodname]
+                               index = aggregateIndex(indexPositions, args)
+                               
+                               if cache.has_key(index) :
+                                       return cache[index]
+                               else :
+                                       res = m(self, *args, **kw)
+                                       cache[index] = res
+                                       return res
+
+                       memoizedMethod.__name__ = methodname
+                       memoizedMethod.__doc__ = m.__doc__
+                       return memoizedMethod
+
+       return makeMemoizedMethod
+
+def aggregateIndex(indexPositions, args):
+       '''
+       Returns the index to be used when looking for or inserting
+       a cache entry.
+       view_name is a string.
+       local_keys is a mapping or None.
+       '''
+       
+       agg_index = []
+       
+       for name, pos in indexPositions :
+               val = args[pos-1]
+               agg_index.append((name, str(val)))
+       
+       return tuple(agg_index)