diff options
| author | Raphael Collet <rco@openerp.com> | 2014-07-06 16:44:26 +0200 |
|---|---|---|
| committer | Antony Lesuisse <al@openerp.com> | 2014-07-06 17:05:41 +0200 |
| commit | cbe2dbb672a6c55d86f2e515f9d1cdfee426ef80 (patch) | |
| tree | d6ff62b719fce32392397783c961b734b1e4c50e /openerp/tools/cache.py | |
| parent | d78192c489d3be66cd6487b39f5ce7ba4733a187 (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.py | 213 |
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 |
