luxia--
[Photo.git] / cache.py
1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Photo is a part of Plinn - http://plinn.org #
4 # Copyright (C) 2008 BenoƮt PIN <benoit.pin@ensmp.fr> #
5 # #
6 # This program is free software; you can redistribute it and/or #
7 # modify it under the terms of the GNU General Public License #
8 # as published by the Free Software Foundation; either version 2 #
9 # of the License, or (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program; if not, write to the Free Software #
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
19 #######################################################################################
20 """ Memoization utils
21
22
23
24 """
25
26 import inspect
27 from BTrees.OOBTree import OOBTree
28
29 def memoizedmethod(*indexes, **options) :
30 """ Used as decorator, this function stores result
31 of method m inside self._methodResultsCache or
32 self._v__methodResultsCache if volatile.
33 This decorator may be used inside a class which provides
34 a mapping object named _methodResultsCache and / or
35 _v__methodResultsCache.
36
37 example :
38
39 1 - simple metdhod memoization
40
41 @memoizedmethod()
42 def methodWithNoParameters(self): pass
43
44 2 - indexed memoisation:
45 Parameters names are passed to memoizedmethod are
46 evaluated to construct an indexed cache.
47 Names must be a subset of the memoized method signature.
48
49 @memoizedmethod('arg1', 'arg2')
50 def methodWithParameters(self, arg1, arg2=None): pass
51 """
52 volatile = options.get('volatile', False)
53 cacheVarName = '_methodResultsCache'
54 if volatile==True :
55 cacheVarName = '_v_%s' % cacheVarName
56
57 def makeMemoizedMethod(m) :
58 methodname = m.__name__
59
60 if not indexes :
61 def memoizedMethod(self) :
62 if not hasattr(self, cacheVarName) :
63 setattr(self, cacheVarName, OOBTree())
64 cache = getattr(self, cacheVarName)
65 if cache.has_key(methodname) :
66 return cache[methodname]
67 else :
68 res = m(self)
69 cache[methodname] = res
70 return res
71
72 memoizedMethod.__name__ = methodname
73 memoizedMethod.__doc__ = m.__doc__
74 return memoizedMethod
75
76 else :
77 args, varargs, varkw, defaults = inspect.getargspec(m)
78 args = list(args)
79 if defaults is None :
80 defaults = []
81 mandatoryargs = args[1:-len(defaults)]
82 optargs = args[-len(defaults):]
83 defaultValues = dict(zip([name for name in args[-len(defaults):]], [val for val in defaults]))
84
85 indexPositions = []
86 for index in indexes :
87 try :
88 indexPositions.append((index, args.index(index)))
89 except ValueError :
90 raise ValueError("%r argument is not in signature of %r" % (index, methodname))
91
92 if indexPositions :
93 indexPositions.sort(lambda a, b : cmp(a[1], b[1]))
94
95 indexPositions = tuple(indexPositions)
96
97
98 def memoizedMethod(self, *args, **kw) :
99 # test if m if called by ZPublished
100 if len(args) < len(mandatoryargs) and hasattr(self, 'REQUEST') :
101 assert not kw
102 args = list(args)
103 get = lambda name : self.REQUEST[name]
104 for name in mandatoryargs :
105 try :
106 args.append(get(name))
107 except KeyError :
108 exactOrAtLeast = defaults and 'exactly' or 'at least'
109 raise TypeError('%(methodname)s takes %(exactOrAtLeast)s %(mandatoryArgsLength)d argument (%(givenArgsLength)s given)' % \
110 { 'methodname': methodname
111 , 'exactOrAtLeast': exactOrAtLeast
112 , 'mandatoryArgsLength': len(mandatoryargs)
113 , 'givenArgsLength': len(args)})
114
115 for name in optargs :
116 get = self.REQUEST.get
117 args.append(get(name, defaultValues[name]))
118
119 args = tuple(args)
120
121 if not hasattr(self, cacheVarName) :
122 setattr(self, cacheVarName, OOBTree())
123 cache = getattr(self, cacheVarName)
124 if not cache.has_key(methodname) :
125 cache[methodname] = OOBTree()
126
127 cache = cache[methodname]
128 index = aggregateIndex(indexPositions, args)
129
130 if cache.has_key(index) :
131 return cache[index]
132 else :
133 res = m(self, *args, **kw)
134 cache[index] = res
135 return res
136
137 memoizedMethod.__name__ = methodname
138 memoizedMethod.__doc__ = m.__doc__
139 return memoizedMethod
140
141 return makeMemoizedMethod
142
143 def aggregateIndex(indexPositions, args):
144 '''
145 Returns the index to be used when looking for or inserting
146 a cache entry.
147 view_name is a string.
148 local_keys is a mapping or None.
149 '''
150
151 agg_index = []
152
153 for name, pos in indexPositions :
154 val = args[pos-1]
155 agg_index.append((name, str(val)))
156
157 return tuple(agg_index)