Merge pull request #218 from odoo-dev/master-ormcache_new_api-rco
[MERGE] ormcache: improve API to enable the decoration of new-api methods The new signature of `ormcache()` is more intuitive: simply list the names of the method arguments that compose the cache key. The decorators `ormcache_context()` and `ormcache_multi()` have been adapted accordingly.
This commit is contained in:
@@ -39,30 +39,29 @@ class decimal_precision(orm.Model):
|
||||
('name_uniq', 'unique (name)', """Only one value can be defined for each given usage!"""),
|
||||
]
|
||||
|
||||
@tools.ormcache(skiparg=3)
|
||||
@tools.ormcache('application')
|
||||
def precision_get(self, cr, uid, application):
|
||||
cr.execute('select digits from decimal_precision where name=%s', (application,))
|
||||
res = cr.fetchone()
|
||||
return res[0] if res else 2
|
||||
|
||||
def clear_cache(self, cr):
|
||||
"""clear cache and update models. Notify other workers to restart their registry."""
|
||||
self.precision_get.clear_cache(self)
|
||||
RegistryManager.signal_registry_change(cr.dbname)
|
||||
""" Deprecated, use `clear_caches` instead. """
|
||||
self.clear_caches()
|
||||
|
||||
def create(self, cr, uid, data, context=None):
|
||||
res = super(decimal_precision, self).create(cr, uid, data, context=context)
|
||||
self.clear_cache(cr)
|
||||
self.clear_caches()
|
||||
return res
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
res = super(decimal_precision, self).unlink(cr, uid, ids, context=context)
|
||||
self.clear_cache(cr)
|
||||
self.clear_caches()
|
||||
return res
|
||||
|
||||
def write(self, cr, uid, ids, data, *args, **argv):
|
||||
res = super(decimal_precision, self).write(cr, uid, ids, data, *args, **argv)
|
||||
self.clear_cache(cr)
|
||||
self.clear_caches()
|
||||
return res
|
||||
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ class view(osv.osv):
|
||||
|
||||
return arch
|
||||
|
||||
@tools.ormcache_context(accepted_keys=('website_id',))
|
||||
@tools.ormcache_context('uid', 'xml_id', keys=('website_id',))
|
||||
def get_view_id(self, cr, uid, xml_id, context=None):
|
||||
if context and 'website_id' in context and not isinstance(xml_id, (int, long)):
|
||||
domain = [('key', '=', xml_id), '|', ('website_id', '=', context['website_id']), ('website_id', '=', False)]
|
||||
|
||||
@@ -333,8 +333,8 @@ class website(osv.osv):
|
||||
except:
|
||||
return False
|
||||
|
||||
@openerp.tools.ormcache(skiparg=3)
|
||||
def _get_languages(self, cr, uid, id):
|
||||
@openerp.tools.ormcache('id')
|
||||
def _get_languages(self, cr, uid, id, context=None):
|
||||
website = self.browse(cr, uid, id)
|
||||
return [(lg.code, lg.name) for lg in website.language_ids]
|
||||
|
||||
@@ -375,9 +375,9 @@ class website(osv.osv):
|
||||
lang['hreflang'] = lang['short']
|
||||
return langs
|
||||
|
||||
@openerp.tools.ormcache(skiparg=3)
|
||||
def _get_current_website_id(self, cr, uid, domain_name):
|
||||
ids = self.search(cr, uid, [('name', '=', domain_name)], limit=1)
|
||||
@openerp.tools.ormcache('domain_name')
|
||||
def _get_current_website_id(self, cr, uid, domain_name, context=None):
|
||||
ids = self.search(cr, uid, [('name', '=', domain_name)], limit=1, context=context)
|
||||
if ids:
|
||||
return ids[0]
|
||||
else:
|
||||
|
||||
@@ -135,8 +135,7 @@ class website(orm.Model):
|
||||
string='Price list available for this Ecommerce/Website'),
|
||||
}
|
||||
|
||||
@tools.ormcache()
|
||||
# No context, else no cache
|
||||
@tools.ormcache('uid', 'country_code', 'show_visible', 'website_pl', 'current_pl', 'all_pl')
|
||||
def _get_pl(self, cr, uid, country_code, show_visible, website_pl, current_pl, all_pl):
|
||||
""" Return the list of pricelists that can be used on website for the current user.
|
||||
|
||||
@@ -362,10 +361,12 @@ class website_pricelist(osv.Model):
|
||||
'website_pricelist_id', 'res_country_group_id', string='Country Groups'),
|
||||
}
|
||||
|
||||
# _get_pl is cached to avoid to recompute at each request the list of pricelists availables.
|
||||
# So, we need to invalidate the cache when we change the config of website price list to force to recompute.
|
||||
def clear_cache(self):
|
||||
self.pool['website']._get_pl.clear_cache(self.pool['website'])
|
||||
# website._get_pl() is cached to avoid to recompute at each request the
|
||||
# list of available pricelists. So, we need to invalidate the cache when
|
||||
# we change the config of website price list to force to recompute.
|
||||
website = self.pool['website']
|
||||
website._get_pl.clear_cache(website)
|
||||
|
||||
def create(self, cr, uid, data, context=None):
|
||||
res = super(website_pricelist, self).create(cr, uid, data, context=context)
|
||||
|
||||
@@ -62,7 +62,8 @@ class view(models.Model):
|
||||
xml_id = super(view, self).get_view_id(xml_id)
|
||||
return xml_id
|
||||
|
||||
@tools.ormcache_context(**dict(accepted_keys=('lang', 'inherit_branding', 'editable', 'translatable', 'website_id', 'version_id')))
|
||||
@tools.ormcache_context('uid', 'view_id',
|
||||
keys=('lang', 'inherit_branding', 'editable', 'translatable', 'website_id', 'version_id'))
|
||||
def _read_template(self, cr, uid, view_id, context=None):
|
||||
arch = self.read_combined(cr, uid, view_id, fields=['arch'], context=context)['arch']
|
||||
arch_tree = etree.fromstring(arch)
|
||||
|
||||
@@ -81,7 +81,7 @@ class ir_config_parameter(osv.osv):
|
||||
return default
|
||||
return result
|
||||
|
||||
@ormcache(skiparg=2) # cache on (uid, key)
|
||||
@ormcache('uid', 'key')
|
||||
def _get_param(self, cr, uid, key):
|
||||
params = self.search_read(cr, uid, [('key', '=', key)], fields=['value'], limit=1)
|
||||
if not params:
|
||||
|
||||
@@ -733,7 +733,7 @@ class ir_model_access(osv.osv):
|
||||
# But as the method raises an exception in that case, the key 'lang' might
|
||||
# not be really necessary as a cache key, unless the `ormcache_context`
|
||||
# decorator catches the exception (it does not at the moment.)
|
||||
@tools.ormcache_context(accepted_keys=('lang',))
|
||||
@tools.ormcache_context('uid', 'model', 'mode', 'raise_exception', keys=('lang',))
|
||||
def check(self, cr, uid, model, mode='read', raise_exception=True, context=None):
|
||||
if uid==1:
|
||||
# User root have all accesses
|
||||
@@ -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
|
||||
|
||||
@@ -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,))
|
||||
@@ -144,7 +144,8 @@ class ir_rule(osv.osv):
|
||||
return []
|
||||
|
||||
def clear_cache(self, cr, uid):
|
||||
self._compute_domain.clear_cache(self)
|
||||
""" Deprecated, use `clear_caches` instead. """
|
||||
self.clear_caches()
|
||||
|
||||
def domain_get(self, cr, uid, model_name, mode='read', context=None):
|
||||
dom = self._compute_domain(cr, uid, model_name, mode)
|
||||
@@ -159,15 +160,15 @@ class ir_rule(osv.osv):
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
res = super(ir_rule, self).unlink(cr, uid, ids, context=context)
|
||||
self.clear_cache(cr, uid)
|
||||
self.clear_caches()
|
||||
return res
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
res = super(ir_rule, self).create(cr, uid, vals, context=context)
|
||||
self.clear_cache(cr, uid)
|
||||
self.clear_caches()
|
||||
return res
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
res = super(ir_rule, self).write(cr, uid, ids, vals, context=context)
|
||||
self.clear_cache(cr,uid)
|
||||
self.clear_caches()
|
||||
return res
|
||||
|
||||
@@ -269,7 +269,7 @@ class ir_translation(osv.osv):
|
||||
return
|
||||
return super(ir_translation, self)._check_selection_field_value(cr, uid, field, value, context=context)
|
||||
|
||||
@tools.ormcache_multi(skiparg=3, multi=6)
|
||||
@tools.ormcache_multi('name', 'tt', 'lang', multi='ids')
|
||||
def _get_ids(self, cr, uid, name, tt, lang, ids):
|
||||
translations = dict.fromkeys(ids, False)
|
||||
if ids:
|
||||
@@ -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)
|
||||
|
||||
@@ -38,23 +38,51 @@ class ir_ui_menu(osv.osv):
|
||||
_name = 'ir.ui.menu'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
cls = type(self)
|
||||
# by design, self._menu_cache is specific to the database
|
||||
cls._menu_cache_lock = threading.RLock()
|
||||
cls._menu_cache = {}
|
||||
super(ir_ui_menu, self).__init__(*args, **kwargs)
|
||||
self.pool.get('ir.model.access').register_cache_clearing_method(self._name, 'clear_cache')
|
||||
self.pool['ir.model.access'].register_cache_clearing_method(self._name, 'clear_caches')
|
||||
|
||||
def clear_cache(self):
|
||||
with self._menu_cache_lock:
|
||||
# radical but this doesn't frequently happen
|
||||
if self._menu_cache:
|
||||
# Normally this is done by openerp.tools.ormcache
|
||||
# but since we do not use it, set it by ourself.
|
||||
self.pool._any_cache_cleared = True
|
||||
self._menu_cache.clear()
|
||||
self.load_menus_root._orig.clear_cache(self)
|
||||
self.load_menus._orig.clear_cache(self)
|
||||
""" Deprecated, use `clear_caches` instead. """
|
||||
self.clear_caches()
|
||||
|
||||
@api.model
|
||||
@tools.ormcache('frozenset(self.env.user.groups_id.ids)')
|
||||
def _visible_menu_ids(self):
|
||||
""" Return the ids of the menu items visible to the user. """
|
||||
# retrieve all menus, and determine which ones are visible
|
||||
context = {'ir.ui.menu.full_list': True}
|
||||
menus = self.with_context(context).search([])
|
||||
|
||||
# first discard all menus with groups the user does not have
|
||||
groups = self.env.user.groups_id
|
||||
menus = menus.filtered(
|
||||
lambda menu: not menu.groups_id or menu.groups_id & groups)
|
||||
|
||||
# take apart menus that have an action
|
||||
action_menus = menus.filtered('action')
|
||||
folder_menus = menus - action_menus
|
||||
visible = self.browse()
|
||||
|
||||
# process action menus, check whether their action is allowed
|
||||
access = self.env['ir.model.access']
|
||||
model_fname = {
|
||||
'ir.actions.act_window': 'res_model',
|
||||
'ir.actions.report.xml': 'model',
|
||||
'ir.actions.wizard': 'model',
|
||||
'ir.actions.server': 'model_id',
|
||||
}
|
||||
for menu in action_menus:
|
||||
fname = model_fname.get(menu.action._name)
|
||||
if not fname or not menu.action[fname] or \
|
||||
access.check(menu.action[fname], 'read', False):
|
||||
# make menu visible, and its folder ancestors, too
|
||||
visible += menu
|
||||
menu = menu.parent_id
|
||||
while menu and menu in folder_menus and menu not in visible:
|
||||
visible += menu
|
||||
menu = menu.parent_id
|
||||
|
||||
return set(visible.ids)
|
||||
|
||||
@api.multi
|
||||
@api.returns('self')
|
||||
@@ -63,51 +91,8 @@ class ir_ui_menu(osv.osv):
|
||||
the menu hierarchy of the current user.
|
||||
Uses a cache for speeding up the computation.
|
||||
"""
|
||||
with self._menu_cache_lock:
|
||||
groups = self.env.user.groups_id
|
||||
|
||||
# visibility is entirely based on the user's groups;
|
||||
# self._menu_cache[key] gives the ids of all visible menus
|
||||
key = frozenset(groups._ids)
|
||||
if key in self._menu_cache:
|
||||
visible = self.browse(self._menu_cache[key])
|
||||
|
||||
else:
|
||||
# retrieve all menus, and determine which ones are visible
|
||||
context = {'ir.ui.menu.full_list': True}
|
||||
menus = self.with_context(context).search([])
|
||||
|
||||
# first discard all menus with groups the user does not have
|
||||
menus = menus.filtered(
|
||||
lambda menu: not menu.groups_id or menu.groups_id & groups)
|
||||
|
||||
# take apart menus that have an action
|
||||
action_menus = menus.filtered('action')
|
||||
folder_menus = menus - action_menus
|
||||
visible = self.browse()
|
||||
|
||||
# process action menus, check whether their action is allowed
|
||||
access = self.env['ir.model.access']
|
||||
model_fname = {
|
||||
'ir.actions.act_window': 'res_model',
|
||||
'ir.actions.report.xml': 'model',
|
||||
'ir.actions.wizard': 'model',
|
||||
'ir.actions.server': 'model_id',
|
||||
}
|
||||
for menu in action_menus:
|
||||
fname = model_fname.get(menu.action._name)
|
||||
if not fname or not menu.action[fname] or \
|
||||
access.check(menu.action[fname], 'read', False):
|
||||
# make menu visible, and its folder ancestors, too
|
||||
visible += menu
|
||||
menu = menu.parent_id
|
||||
while menu and menu in folder_menus and menu not in visible:
|
||||
visible += menu
|
||||
menu = menu.parent_id
|
||||
|
||||
self._menu_cache[key] = visible._ids
|
||||
|
||||
return self.filtered(lambda menu: menu in visible)
|
||||
visible_ids = self._visible_menu_ids()
|
||||
return self.filtered(lambda menu: menu.id in visible_ids)
|
||||
|
||||
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
|
||||
if context is None:
|
||||
@@ -161,11 +146,11 @@ class ir_ui_menu(osv.osv):
|
||||
return parent_path + elmt.name
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
self.clear_cache()
|
||||
self.clear_caches()
|
||||
return super(ir_ui_menu, self).create(cr, uid, values, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, values, context=None):
|
||||
self.clear_cache()
|
||||
self.clear_caches()
|
||||
return super(ir_ui_menu, self).write(cr, uid, ids, values, context=context)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
@@ -182,7 +167,7 @@ class ir_ui_menu(osv.osv):
|
||||
self.write(cr, uid, direct_children_ids, {'parent_id': False})
|
||||
|
||||
result = super(ir_ui_menu, self).unlink(cr, uid, ids, context=context)
|
||||
self.clear_cache()
|
||||
self.clear_caches()
|
||||
return result
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
@@ -356,7 +341,7 @@ class ir_ui_menu(osv.osv):
|
||||
return self.search(cr, uid, menu_domain, context=context)
|
||||
|
||||
@api.cr_uid_context
|
||||
@tools.ormcache_context(accepted_keys=('lang',))
|
||||
@tools.ormcache_context('uid', keys=('lang',))
|
||||
def load_menus_root(self, cr, uid, context=None):
|
||||
fields = ['name', 'sequence', 'parent_id', 'action', 'icon']
|
||||
menu_root_ids = self.get_user_roots(cr, uid, context=context)
|
||||
@@ -371,7 +356,7 @@ class ir_ui_menu(osv.osv):
|
||||
|
||||
|
||||
@api.cr_uid_context
|
||||
@tools.ormcache_context(accepted_keys=('lang',))
|
||||
@tools.ormcache_context('uid', keys=('lang',))
|
||||
def load_menus(self, cr, uid, context=None):
|
||||
""" Loads all menu items (all applications and their sub-menus).
|
||||
|
||||
|
||||
@@ -320,7 +320,7 @@ class view(osv.osv):
|
||||
if not values.get('name'):
|
||||
values['name'] = "%s %s" % (values.get('model'), values['type'])
|
||||
|
||||
self.clear_cache()
|
||||
self.clear_caches()
|
||||
return super(view, self).create(
|
||||
cr, uid,
|
||||
self._compute_defaults(cr, uid, values, context=context),
|
||||
@@ -343,7 +343,7 @@ class view(osv.osv):
|
||||
if custom_view_ids:
|
||||
self.pool.get('ir.ui.view.custom').unlink(cr, uid, custom_view_ids)
|
||||
|
||||
self.clear_cache()
|
||||
self.clear_caches()
|
||||
ret = super(view, self).write(
|
||||
cr, uid, ids,
|
||||
self._compute_defaults(cr, uid, vals, context=context),
|
||||
@@ -925,11 +925,11 @@ class view(osv.osv):
|
||||
#------------------------------------------------------
|
||||
# QWeb template views
|
||||
#------------------------------------------------------
|
||||
_read_template_cache = dict(accepted_keys=('lang','inherit_branding', 'editable', 'translatable'))
|
||||
if config['dev_mode']:
|
||||
_read_template_cache['size'] = 0
|
||||
|
||||
@tools.conditional(not tools.config['dev_mode'], tools.ormcache_context(**_read_template_cache))
|
||||
# apply ormcache_context decorator unless in dev mode...
|
||||
@tools.conditional(not config['dev_mode'],
|
||||
tools.ormcache_context('uid', 'view_id',
|
||||
keys=('lang', 'inherit_branding', 'editable', 'translatable')))
|
||||
def _read_template(self, cr, uid, view_id, context=None):
|
||||
arch = self.read_combined(cr, uid, view_id, fields=['arch'], context=context)['arch']
|
||||
arch_tree = etree.fromstring(arch)
|
||||
@@ -956,7 +956,8 @@ class view(osv.osv):
|
||||
return self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, xml_id, raise_if_not_found=True)
|
||||
|
||||
def clear_cache(self):
|
||||
self._read_template.clear_cache(self)
|
||||
""" Deprecated, use `clear_caches` instead. """
|
||||
self.clear_caches()
|
||||
|
||||
def _contains_branded(self, node):
|
||||
return node.tag == 't'\
|
||||
@@ -1070,7 +1071,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)]
|
||||
|
||||
@@ -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
|
||||
@@ -387,7 +387,7 @@ class ir_values(osv.osv):
|
||||
'value': action,
|
||||
})
|
||||
|
||||
@tools.ormcache_context(accepted_keys=('lang',))
|
||||
@tools.ormcache_context('uid', 'action_slot', 'model', 'res_id', keys=('lang',))
|
||||
def get_actions(self, cr, uid, action_slot, model, res_id=False, context=None):
|
||||
"""Retrieves the list of actions bound to the given model's action slot.
|
||||
See the class description for more details about the various action
|
||||
|
||||
@@ -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 []
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import test_ir_values
|
||||
import test_mail
|
||||
import test_menu
|
||||
import test_orm
|
||||
import test_ormcache
|
||||
import test_osv
|
||||
import test_qweb
|
||||
import test_res_config
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
from openerp.tests import common
|
||||
from openerp.tools import get_cache_key_counter
|
||||
|
||||
class TestOrmcache(common.TransactionCase):
|
||||
def test_ormcache(self):
|
||||
""" Test the effectiveness of the ormcache() decorator. """
|
||||
IMD = self.env['ir.model.data']
|
||||
XMLID = 'base.group_no_one'
|
||||
|
||||
# retrieve the cache, its key and stat counter
|
||||
cache, key, counter = get_cache_key_counter(IMD.xmlid_lookup, self.cr, self.uid, XMLID)
|
||||
hit = counter.hit
|
||||
miss = counter.miss
|
||||
|
||||
# clear the cache of ir.model.data.xmlid_lookup, retrieve its key and
|
||||
IMD.xmlid_lookup.clear_cache(IMD)
|
||||
self.assertNotIn(key, cache)
|
||||
|
||||
# lookup some reference
|
||||
self.env.ref(XMLID)
|
||||
self.assertEqual(counter.hit, hit)
|
||||
self.assertEqual(counter.miss, miss + 1)
|
||||
self.assertIn(key, cache)
|
||||
|
||||
# lookup again
|
||||
self.env.ref(XMLID)
|
||||
self.assertEqual(counter.hit, hit + 1)
|
||||
self.assertEqual(counter.miss, miss + 1)
|
||||
self.assertIn(key, cache)
|
||||
|
||||
# lookup again
|
||||
self.env.ref(XMLID)
|
||||
self.assertEqual(counter.hit, hit + 2)
|
||||
self.assertEqual(counter.miss, miss + 1)
|
||||
self.assertIn(key, cache)
|
||||
@@ -199,13 +199,9 @@ class Registry(Mapping):
|
||||
This clears the caches associated to methods decorated with
|
||||
``tools.ormcache`` or ``tools.ormcache_multi`` for all the models.
|
||||
"""
|
||||
self.cache.clear()
|
||||
for model in self.models.itervalues():
|
||||
model.clear_caches()
|
||||
# Special case for ir_ui_menu which does not use openerp.tools.ormcache.
|
||||
ir_ui_menu = self.models.get('ir.ui.menu')
|
||||
if ir_ui_menu is not None:
|
||||
ir_ui_menu.clear_cache()
|
||||
|
||||
|
||||
# Useful only in a multi-process context.
|
||||
def reset_any_cache_cleared(self):
|
||||
|
||||
+93
-46
@@ -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
|
||||
@@ -89,53 +118,60 @@ class ormcache(object):
|
||||
|
||||
|
||||
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
|
||||
""" This LRU cache decorator is a variant of :class:`ormcache`, with an
|
||||
extra parameter ``keys`` that defines a sequence of dictionary keys. Those
|
||||
keys are looked up in the ``context`` parameter and combined to the cache
|
||||
key made by :class:`ormcache`.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ormcache_context, self).__init__(*args, **kwargs)
|
||||
self.keys = kwargs['keys']
|
||||
|
||||
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, key0, counter = 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] or {}
|
||||
def determine_key(self):
|
||||
""" Determine the function that computes a cache key from arguments. """
|
||||
assert self.skiparg is None, "ormcache_context() no longer supports skiparg"
|
||||
# build a string that represents function code and evaluate it
|
||||
spec = getargspec(self.method)
|
||||
args = formatargspec(*spec)[1:-1]
|
||||
cont_expr = "(context or {})" if 'context' in spec.args else "self._context"
|
||||
keys_expr = "tuple(map(%s.get, %r))" % (cont_expr, self.keys)
|
||||
if self.args:
|
||||
code = "lambda %s: (%s, %s)" % (args, ", ".join(self.args), keys_expr)
|
||||
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 = key0 + args[self.skiparg:self.context_pos] + tuple(ckey)
|
||||
try:
|
||||
r = d[key]
|
||||
counter.hit += 1
|
||||
return r
|
||||
except KeyError:
|
||||
counter.miss += 1
|
||||
value = d[key] = self.method(*args, **kwargs)
|
||||
return value
|
||||
except TypeError:
|
||||
counter.err += 1
|
||||
return self.method(*args, **kwargs)
|
||||
code = "lambda %s: (%s,)" % (args, keys_expr)
|
||||
self.key = eval(code)
|
||||
|
||||
|
||||
class ormcache_multi(ormcache):
|
||||
def __init__(self, skiparg=2, size=8192, multi=3):
|
||||
assert skiparg <= multi
|
||||
super(ormcache_multi, self).__init__(skiparg, size)
|
||||
self.multi = multi
|
||||
""" This LRU cache decorator is a variant of :class:`ormcache`, with an
|
||||
extra parameter ``multi`` that gives the name of a parameter. Upon call, the
|
||||
corresponding argument is iterated on, and every value leads to a cache
|
||||
entry under its own key.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ormcache_multi, self).__init__(*args, **kwargs)
|
||||
self.multi = kwargs['multi']
|
||||
|
||||
def determine_key(self):
|
||||
""" Determine the function that computes a cache key from arguments. """
|
||||
assert self.skiparg is None, "ormcache_multi() no longer supports skiparg"
|
||||
assert isinstance(self.multi, basestring), "ormcache_multi() parameter multi must be an argument name"
|
||||
|
||||
super(ormcache_multi, self).determine_key()
|
||||
|
||||
# key_multi computes the extra element added to the key
|
||||
spec = getargspec(self.method)
|
||||
args = formatargspec(*spec)[1:-1]
|
||||
code_multi = "lambda %s: %s" % (args, self.multi)
|
||||
self.key_multi = eval(code_multi)
|
||||
|
||||
# self.multi_pos is the position of self.multi in args
|
||||
self.multi_pos = spec.args.index(self.multi)
|
||||
|
||||
def lookup(self, method, *args, **kwargs):
|
||||
d, key0, counter = self.lru(args[0])
|
||||
base_key = key0 + args[self.skiparg:self.multi] + args[self.multi+1:]
|
||||
ids = args[self.multi]
|
||||
base_key = key0 + self.key(*args, **kwargs)
|
||||
ids = self.key_multi(*args, **kwargs)
|
||||
result = {}
|
||||
missed = []
|
||||
|
||||
@@ -150,9 +186,11 @@ class ormcache_multi(ormcache):
|
||||
missed.append(i)
|
||||
|
||||
if missed:
|
||||
# call the method for the ids that were not in the cache
|
||||
# call the method for the ids that were not in the cache; note that
|
||||
# thanks to decorator(), the multi argument will be bound and passed
|
||||
# positionally in args.
|
||||
args = list(args)
|
||||
args[self.multi] = missed
|
||||
args[self.multi_pos] = missed
|
||||
result.update(method(*args, **kwargs))
|
||||
|
||||
# store those new results back in the cache
|
||||
@@ -196,5 +234,14 @@ def log_ormcache_stats(sig=None, frame=None):
|
||||
|
||||
me.dbname = me_dbname
|
||||
|
||||
|
||||
def get_cache_key_counter(bound_method, *args, **kwargs):
|
||||
""" Return the cache, key and stat counter for the given call. """
|
||||
model = bound_method.im_self
|
||||
ormcache = bound_method.clear_cache.im_self
|
||||
cache, key0, counter = ormcache.lru(model)
|
||||
key = key0 + ormcache.key(model, *args, **kwargs)
|
||||
return cache, key, counter
|
||||
|
||||
# For backward compatibility
|
||||
cache = ormcache
|
||||
|
||||
Reference in New Issue
Block a user