[IMP] translations: simplify condition in qweb terms extraction
[odoo/odoo.git] / openerp / tools / cache.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2013 OpenERP (<http://www.openerp.com>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 # decorator makes wrappers that have the same API as their wrapped function;
23 # this is important for the openerp.api.guess() that relies on signatures
24 from decorator import decorator
25 from inspect import getargspec
26
27 import lru
28 import logging
29
30 logger = logging.getLogger(__name__)
31
32
33 class ormcache(object):
34     """ LRU cache decorator for orm methods. """
35
36     def __init__(self, skiparg=2, size=8192, multi=None, timeout=None):
37         self.skiparg = skiparg
38         self.size = size
39         self.stat_miss = 0
40         self.stat_hit = 0
41         self.stat_err = 0
42
43     def __call__(self, method):
44         self.method = method
45         lookup = decorator(self.lookup, method)
46         lookup.clear_cache = self.clear
47         return lookup
48
49     def stat(self):
50         return "lookup-stats hit=%s miss=%s err=%s ratio=%.1f" % \
51             (self.stat_hit, self.stat_miss, self.stat_err,
52                 (100*float(self.stat_hit))/(self.stat_miss+self.stat_hit))
53
54     def lru(self, model):
55         ormcache = model._ormcache
56         try:
57             d = ormcache[self.method]
58         except KeyError:
59             d = ormcache[self.method] = lru.LRU(self.size)
60         return d
61
62     def lookup(self, method, *args, **kwargs):
63         d = self.lru(args[0])
64         key = args[self.skiparg:]
65         try:
66             r = d[key]
67             self.stat_hit += 1
68             return r
69         except KeyError:
70             self.stat_miss += 1
71             value = d[key] = self.method(*args, **kwargs)
72             return value
73         except TypeError:
74             self.stat_err += 1
75             return self.method(*args, **kwargs)
76
77     def clear(self, model, *args):
78         """ Remove *args entry from the cache or all keys if *args is undefined """
79         d = self.lru(model)
80         if args:
81             logger.warn("ormcache.clear arguments are deprecated and ignored "
82                         "(while clearing caches on (%s).%s)",
83                         model._name, self.method.__name__)
84         d.clear()
85         model.pool._any_cache_cleared = True
86
87
88 class ormcache_context(ormcache):
89     def __init__(self, skiparg=2, size=8192, accepted_keys=()):
90         super(ormcache_context,self).__init__(skiparg,size)
91         self.accepted_keys = accepted_keys
92
93     def __call__(self, method):
94         # remember which argument is context
95         args = getargspec(method)[0]
96         self.context_pos = args.index('context')
97         return super(ormcache_context, self).__call__(method)
98
99     def lookup(self, method, *args, **kwargs):
100         d = self.lru(args[0])
101
102         # Note. The decorator() wrapper (used in __call__ above) will resolve
103         # arguments, and pass them positionally to lookup(). This is why context
104         # is not passed through kwargs!
105         if self.context_pos < len(args):
106             context = args[self.context_pos]
107         else:
108             context = kwargs.get('context') or {}
109         ckey = [(k, context[k]) for k in self.accepted_keys if k in context]
110
111         # Beware: do not take the context from args!
112         key = args[self.skiparg:self.context_pos] + tuple(ckey)
113         try:
114             r = d[key]
115             self.stat_hit += 1
116             return r
117         except KeyError:
118             self.stat_miss += 1
119             value = d[key] = self.method(*args, **kwargs)
120             return value
121         except TypeError:
122             self.stat_err += 1
123             return self.method(*args, **kwargs)
124
125
126 class ormcache_multi(ormcache):
127     def __init__(self, skiparg=2, size=8192, multi=3):
128         assert skiparg <= multi
129         super(ormcache_multi, self).__init__(skiparg, size)
130         self.multi = multi
131
132     def lookup(self, method, *args, **kwargs):
133         d = self.lru(args[0])
134         base_key = args[self.skiparg:self.multi] + args[self.multi+1:]
135         ids = args[self.multi]
136         result = {}
137         missed = []
138
139         # first take what is available in the cache
140         for i in ids:
141             key = base_key + (i,)
142             try:
143                 result[i] = d[key]
144                 self.stat_hit += 1
145             except Exception:
146                 self.stat_miss += 1
147                 missed.append(i)
148
149         if missed:
150             # call the method for the ids that were not in the cache
151             args = list(args)
152             args[self.multi] = missed
153             result.update(method(*args, **kwargs))
154
155             # store those new results back in the cache
156             for i in missed:
157                 key = base_key + (i,)
158                 d[key] = result[i]
159
160         return result
161
162
163 class dummy_cache(object):
164     """ Cache decorator replacement to actually do no caching. """
165     def __init__(self, *l, **kw):
166         pass
167
168     def __call__(self, fn):
169         fn.clear_cache = self.clear
170         return fn
171
172     def clear(self, *l, **kw):
173         pass
174
175
176 # For backward compatibility
177 cache = ormcache
178
179 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: