From 0ae59c779f45c4aa51265dd244c85cff74702e8a Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 25 Mar 2015 17:12:46 +0100 Subject: [PATCH] [IMP] ormcache: improve API to enable the decoration of new-api methods The new signature of ormcache() is way more intuitive: simply list the names of the method arguments that compose the cache key (see documentation and existing code). A backward-compatible mode is available by using the keyword argument `skiparg`. --- openerp/addons/base/ir/ir_model.py | 2 +- openerp/addons/base/ir/ir_rule.py | 2 +- openerp/addons/base/ir/ir_translation.py | 2 +- openerp/addons/base/ir/ir_ui_view.py | 2 +- openerp/addons/base/ir/ir_values.py | 2 +- openerp/addons/base/res/res_company.py | 2 +- openerp/addons/base/res/res_lang.py | 4 +-- openerp/addons/base/res/res_users.py | 13 +++----- openerp/tools/cache.py | 39 +++++++++++++++++++++--- 9 files changed, 47 insertions(+), 21 deletions(-) diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index bf9c35bdaec..d0a65d12e56 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -913,7 +913,7 @@ class ir_model_data(osv.osv): cr.execute('CREATE INDEX ir_model_data_module_name_index ON ir_model_data (module, name)') # NEW V8 API - @tools.ormcache(skiparg=3) + @tools.ormcache('xmlid') def xmlid_lookup(self, cr, uid, xmlid): """Low level xmlid lookup Return (id, res_model, res_id) or raise ValueError if not found diff --git a/openerp/addons/base/ir/ir_rule.py b/openerp/addons/base/ir/ir_rule.py index c54bb5c28ed..c774b35d06f 100644 --- a/openerp/addons/base/ir/ir_rule.py +++ b/openerp/addons/base/ir/ir_rule.py @@ -103,7 +103,7 @@ class ir_rule(osv.osv): (_check_model_name, 'Rules can not be applied on the Record Rules model.', ['model_id']), ] - @tools.ormcache() + @tools.ormcache('uid', 'model_name', 'mode') def _compute_domain(self, cr, uid, model_name, mode="read"): if mode not in self._MODES: raise ValueError('Invalid mode: %r' % (mode,)) diff --git a/openerp/addons/base/ir/ir_translation.py b/openerp/addons/base/ir/ir_translation.py index 090e31dbc15..c41cf45181e 100644 --- a/openerp/addons/base/ir/ir_translation.py +++ b/openerp/addons/base/ir/ir_translation.py @@ -333,7 +333,7 @@ class ir_translation(osv.osv): return (query, params) - @tools.ormcache(skiparg=3) + @tools.ormcache('name', 'types', 'lang', 'source', 'res_id') def __get_source(self, cr, uid, name, types, lang, source, res_id): # res_id is a tuple or None, otherwise ormcache cannot cache it! query, params = self._get_source_query(cr, uid, name, types, lang, source, res_id) diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index 25e2d4cfec4..a4bcdf0c2c7 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -1070,7 +1070,7 @@ class view(osv.osv): self._translate_qweb(cr, uid, arch, translate_func, context=context) return arch - @openerp.tools.ormcache() + @openerp.tools.ormcache('uid', 'id') def get_view_xmlid(self, cr, uid, id): imd = self.pool['ir.model.data'] domain = [('model', '=', 'ir.ui.view'), ('res_id', '=', id)] diff --git a/openerp/addons/base/ir/ir_values.py b/openerp/addons/base/ir/ir_values.py index c6cdd4f6233..726360057c0 100644 --- a/openerp/addons/base/ir/ir_values.py +++ b/openerp/addons/base/ir/ir_values.py @@ -337,7 +337,7 @@ class ir_values(osv.osv): return defaults.values() # use ormcache: this is called a lot by BaseModel.default_get()! - @tools.ormcache(skiparg=2) + @tools.ormcache('uid', 'model', 'condition') def get_defaults_dict(self, cr, uid, model, condition=False): """ Returns a dictionary mapping field names with their corresponding default value. This method simply improves the returned value of diff --git a/openerp/addons/base/res/res_company.py b/openerp/addons/base/res/res_company.py index 7bda8cd53bb..2bc57e2bcad 100644 --- a/openerp/addons/base/res/res_company.py +++ b/openerp/addons/base/res/res_company.py @@ -240,7 +240,7 @@ class res_company(osv.osv): return rule.company_dest_id.id return user.company_id.id - @tools.ormcache() + @tools.ormcache('uid', 'company') def _get_company_children(self, cr, uid=None, company=None): if not company: return [] diff --git a/openerp/addons/base/res/res_lang.py b/openerp/addons/base/res/res_lang.py index b9dacf1c2d8..8f6c780ad8d 100644 --- a/openerp/addons/base/res/res_lang.py +++ b/openerp/addons/base/res/res_lang.py @@ -176,13 +176,13 @@ class lang(osv.osv): (_check_grouping, "The Separator Format should be like [,n] where 0 < n :starting from Unit digit.-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. Provided ',' as the thousand separator in each case.", ['grouping']) ] - @tools.ormcache(skiparg=3) + @tools.ormcache('lang') def _lang_get(self, cr, uid, lang): lang_ids = self.search(cr, uid, [('code', '=', lang)]) or \ self.search(cr, uid, [('code', '=', 'en_US')]) return lang_ids[0] - @tools.ormcache(skiparg=3) + @tools.ormcache('lang', 'monetary') def _lang_data_get(self, cr, uid, lang, monetary=False): if type(lang) in (str, unicode): lang = self._lang_get(cr, uid, lang) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 0f4d4ad2cc4..7e4b651e7eb 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -344,7 +344,7 @@ class res_users(osv.osv): for id in ids: if id in self._uid_cache[db]: del self._uid_cache[db][id] - self._context_get.clear_cache(self) + self.context_get.clear_cache(self) self.has_group.clear_cache(self) return res @@ -379,9 +379,9 @@ class res_users(osv.osv): default['login'] = _("%s (copy)") % user2copy['login'] return super(res_users, self).copy(cr, uid, id, default, context) - @tools.ormcache(skiparg=2) - def _context_get(self, cr, uid): - user = self.browse(cr, SUPERUSER_ID, uid) + @tools.ormcache('uid') + def context_get(self, cr, uid, context=None): + user = self.browse(cr, SUPERUSER_ID, uid, context) result = {} for k in self._fields: if k.startswith('context_'): @@ -397,9 +397,6 @@ class res_users(osv.osv): result[context_key] = res or False return result - def context_get(self, cr, uid, context=None): - return self._context_get(cr, uid) - def action_get(self, cr, uid, context=None): dataobj = self.pool['ir.model.data'] data_id = dataobj._get_id(cr, SUPERUSER_ID, 'base', 'action_res_users_my') @@ -533,7 +530,7 @@ class res_users(osv.osv): 'target': 'new', } - @tools.ormcache(skiparg=2) + @tools.ormcache('uid', 'group_ext_id') def has_group(self, cr, uid, group_ext_id): """Checks whether user belongs to given group. diff --git a/openerp/tools/cache.py b/openerp/tools/cache.py index 7070d437c2d..f64d356428c 100644 --- a/openerp/tools/cache.py +++ b/openerp/tools/cache.py @@ -23,7 +23,7 @@ # this is important for the openerp.api.guess() that relies on signatures from collections import defaultdict from decorator import decorator -from inspect import getargspec +from inspect import formatargspec, getargspec import logging _logger = logging.getLogger(__name__) @@ -47,24 +47,53 @@ STAT = defaultdict(ormcache_counter) class ormcache(object): - """ LRU cache decorator for orm methods. """ + """ LRU cache decorator for model methods. + The parameters are strings that represent expressions referring to the + signature of the decorated method, and are used to compute a cache key:: - def __init__(self, skiparg=2, size=8192, multi=None, timeout=None): - self.skiparg = skiparg + @ormcache('model_name', 'mode') + def _compute_domain(self, cr, uid, model_name, mode="read"): + ... + + For the sake of backward compatibility, the decorator supports the named + parameter `skiparg`:: + + @ormcache(skiparg=3) + def _compute_domain(self, cr, uid, model_name, mode="read"): + ... + """ + def __init__(self, *args, **kwargs): + self.args = args + self.skiparg = kwargs.get('skiparg') def __call__(self, method): self.method = method + self.determine_key() lookup = decorator(self.lookup, method) lookup.clear_cache = self.clear return lookup + def determine_key(self): + """ Determine the function that computes a cache key from arguments. """ + if self.skiparg is None: + # build a string that represents function code and evaluate it + args = formatargspec(*getargspec(self.method))[1:-1] + if self.args: + code = "lambda %s: (%s,)" % (args, ", ".join(self.args)) + else: + code = "lambda %s: ()" % (args,) + self.key = eval(code) + else: + # backward-compatible function that uses self.skiparg + self.key = lambda *args, **kwargs: args[self.skiparg:] + def lru(self, model): counter = STAT[(model.pool.db_name, model._name, self.method)] return model.pool.cache, (model._name, self.method), counter def lookup(self, method, *args, **kwargs): d, key0, counter = self.lru(args[0]) - key = key0 + args[self.skiparg:] + key = key0 + self.key(*args, **kwargs) try: r = d[key] counter.hit += 1