summaryrefslogtreecommitdiff
path: root/openerp/tools/cache.py
diff options
context:
space:
mode:
authorRaphael Collet <rco@openerp.com>2014-07-06 16:44:26 +0200
committerAntony Lesuisse <al@openerp.com>2014-07-06 17:05:41 +0200
commitcbe2dbb672a6c55d86f2e515f9d1cdfee426ef80 (patch)
treed6ff62b719fce32392397783c961b734b1e4c50e /openerp/tools/cache.py
parentd78192c489d3be66cd6487b39f5ce7ba4733a187 (diff)
[MERGE] new v8 api by rco
A squashed merge is required as the conversion of the apiculture branch from bzr to git was not correctly done. The git history contains irrelevant blobs and commits. This branch brings a lot of changes and fixes, too many to list exhaustively. - New orm api, objects are now used instead of ids - Environements to encapsulates cr uid context while maintaining backward compatibility - Field compute attribute is a new object oriented way to define function fields - Shared browse record cache - New onchange protocol - Optional copy flag on fields - Documentation update - Dead code cleanup - Lots of fixes
Diffstat (limited to 'openerp/tools/cache.py')
-rw-r--r--openerp/tools/cache.py213
1 files changed, 112 insertions, 101 deletions
diff --git a/openerp/tools/cache.py b/openerp/tools/cache.py
index a23641a9f4b..57dea0888c1 100644
--- a/openerp/tools/cache.py
+++ b/openerp/tools/cache.py
@@ -1,166 +1,177 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 OpenERP (<http://www.openerp.com>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+# decorator makes wrappers that have the same API as their wrapped function;
+# this is important for the openerp.api.guess() that relies on signatures
+from decorator import decorator
+from inspect import getargspec
+
import lru
import logging
logger = logging.getLogger(__name__)
+
class ormcache(object):
- """ LRU cache decorator for orm methods,
- """
+ """ LRU cache decorator for orm methods. """
def __init__(self, skiparg=2, size=8192, multi=None, timeout=None):
self.skiparg = skiparg
self.size = size
- self.method = None
self.stat_miss = 0
self.stat_hit = 0
self.stat_err = 0
- def __call__(self,m):
- self.method = m
- def lookup(self2, cr, *args, **argv):
- r = self.lookup(self2, cr, *args, **argv)
- return r
+ def __call__(self, method):
+ self.method = method
+ lookup = decorator(self.lookup, method)
lookup.clear_cache = self.clear
return lookup
def stat(self):
- return "lookup-stats hit=%s miss=%s err=%s ratio=%.1f" % (self.stat_hit,self.stat_miss,self.stat_err, (100*float(self.stat_hit))/(self.stat_miss+self.stat_hit) )
+ return "lookup-stats hit=%s miss=%s err=%s ratio=%.1f" % \
+ (self.stat_hit, self.stat_miss, self.stat_err,
+ (100*float(self.stat_hit))/(self.stat_miss+self.stat_hit))
- def lru(self, self2):
- try:
- ormcache = getattr(self2, '_ormcache')
- except AttributeError:
- ormcache = self2._ormcache = {}
+ def lru(self, model):
+ ormcache = model._ormcache
try:
d = ormcache[self.method]
except KeyError:
d = ormcache[self.method] = lru.LRU(self.size)
return d
- def lookup(self, self2, cr, *args, **argv):
- d = self.lru(self2)
- key = args[self.skiparg-2:]
+ def lookup(self, method, *args, **kwargs):
+ d = self.lru(args[0])
+ key = args[self.skiparg:]
try:
- r = d[key]
- self.stat_hit += 1
- return r
+ r = d[key]
+ self.stat_hit += 1
+ return r
except KeyError:
- self.stat_miss += 1
- value = d[key] = self.method(self2, cr, *args)
- return value
+ self.stat_miss += 1
+ value = d[key] = self.method(*args, **kwargs)
+ return value
except TypeError:
- self.stat_err += 1
- return self.method(self2, cr, *args)
+ self.stat_err += 1
+ return self.method(*args, **kwargs)
- def clear(self, self2, *args):
- """ Remove *args entry from the cache or all keys if *args is undefined
- """
- d = self.lru(self2)
+ def clear(self, model, *args):
+ """ Remove *args entry from the cache or all keys if *args is undefined """
+ d = self.lru(model)
if args:
logger.warn("ormcache.clear arguments are deprecated and ignored "
"(while clearing caches on (%s).%s)",
- self2._name, self.method.__name__)
+ model._name, self.method.__name__)
d.clear()
- self2.pool._any_cache_cleared = True
+ model.pool._any_cache_cleared = True
+
class ormcache_context(ormcache):
def __init__(self, skiparg=2, size=8192, accepted_keys=()):
super(ormcache_context,self).__init__(skiparg,size)
self.accepted_keys = accepted_keys
- def lookup(self, self2, cr, *args, **argv):
- d = self.lru(self2)
-
- context = argv.get('context', {})
- ckey = filter(lambda x: x[0] in self.accepted_keys, context.items())
- ckey.sort()
-
- d = self.lru(self2)
- key = args[self.skiparg-2:]+tuple(ckey)
+ def __call__(self, method):
+ # remember which argument is context
+ args = getargspec(method)[0]
+ self.context_pos = args.index('context')
+ return super(ormcache_context, self).__call__(method)
+
+ def lookup(self, method, *args, **kwargs):
+ d = self.lru(args[0])
+
+ # Note. The decorator() wrapper (used in __call__ above) will resolve
+ # arguments, and pass them positionally to lookup(). This is why context
+ # is not passed through kwargs!
+ if self.context_pos < len(args):
+ context = args[self.context_pos]
+ else:
+ context = kwargs.get('context') or {}
+ ckey = [(k, context[k]) for k in self.accepted_keys if k in context]
+
+ # Beware: do not take the context from args!
+ key = args[self.skiparg:self.context_pos] + tuple(ckey)
try:
- r = d[key]
- self.stat_hit += 1
- return r
+ r = d[key]
+ self.stat_hit += 1
+ return r
except KeyError:
- self.stat_miss += 1
- value = d[key] = self.method(self2, cr, *args, **argv)
- return value
+ self.stat_miss += 1
+ value = d[key] = self.method(*args, **kwargs)
+ return value
except TypeError:
- self.stat_err += 1
- return self.method(self2, cr, *args, **argv)
+ self.stat_err += 1
+ return self.method(*args, **kwargs)
class ormcache_multi(ormcache):
def __init__(self, skiparg=2, size=8192, multi=3):
- super(ormcache_multi,self).__init__(skiparg,size)
- self.multi = multi - 2
-
- def lookup(self, self2, cr, *args, **argv):
- d = self.lru(self2)
- args = list(args)
- multi = self.multi
- ids = args[multi]
- r = {}
- miss = []
-
+ assert skiparg <= multi
+ super(ormcache_multi, self).__init__(skiparg, size)
+ self.multi = multi
+
+ def lookup(self, method, *args, **kwargs):
+ d = self.lru(args[0])
+ base_key = args[self.skiparg:self.multi] + args[self.multi+1:]
+ ids = args[self.multi]
+ result = {}
+ missed = []
+
+ # first take what is available in the cache
for i in ids:
- args[multi] = i
- key = tuple(args[self.skiparg-2:])
+ key = base_key + (i,)
try:
- r[i] = d[key]
- self.stat_hit += 1
+ result[i] = d[key]
+ self.stat_hit += 1
except Exception:
- self.stat_miss += 1
- miss.append(i)
+ self.stat_miss += 1
+ missed.append(i)
- if miss:
- args[multi] = miss
- r.update(self.method(self2, cr, *args))
+ if missed:
+ # call the method for the ids that were not in the cache
+ args = list(args)
+ args[self.multi] = missed
+ result.update(method(*args, **kwargs))
- for i in miss:
- args[multi] = i
- key = tuple(args[self.skiparg-2:])
- d[key] = r[i]
+ # store those new results back in the cache
+ for i in missed:
+ key = base_key + (i,)
+ d[key] = result[i]
+
+ return result
- return r
class dummy_cache(object):
- """ Cache decorator replacement to actually do no caching.
- """
+ """ Cache decorator replacement to actually do no caching. """
def __init__(self, *l, **kw):
pass
+
def __call__(self, fn):
fn.clear_cache = self.clear
return fn
+
def clear(self, *l, **kw):
pass
-if __name__ == '__main__':
-
- class A():
- @ormcache()
- def m(self,a,b):
- print "A::m(", self,a,b
- return 1
-
- @ormcache_multi(multi=3)
- def n(self,cr,uid,ids):
- print "m", self,cr,uid,ids
- return dict([(i,i) for i in ids])
-
- a=A()
- r=a.m(1,2)
- r=a.m(1,2)
- r=a.n("cr",1,[1,2,3,4])
- r=a.n("cr",1,[1,2])
- print r
- for i in a._ormcache:
- print a._ormcache[i].d
- a.m.clear_cache()
- a.n.clear_cache(a,1,1)
- r=a.n("cr",1,[1,2])
- print r
- r=a.n("cr",1,[1,2])
# For backward compatibility
cache = ormcache