1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
22 """ Models registries.
25 from contextlib import contextmanager
30 import openerp.osv.orm
32 import openerp.modules.db
33 import openerp.tools.config
34 from openerp.tools import assertion_report
36 _logger = logging.getLogger(__name__)
38 class Registry(object):
39 """ Model registry for a particular database.
41 The registry is essentially a mapping between model names and model
42 instances. There is one registry instance per database.
46 def __init__(self, db_name):
47 self.models = {} # model name/model instance mapping
49 self._store_function = {}
51 self._init_parent = {}
52 self._assertion_report = assertion_report.assertion_report()
54 # modules fully loaded (maintained during init phase by `loading` module)
55 self._init_modules = set()
57 self.db_name = db_name
58 self.db = openerp.sql_db.db_connect(db_name)
60 # In monoprocess cron jobs flag (pooljobs)
63 # Inter-process signaling (used only when openerp.multi_process is True):
64 # The `base_registry_signaling` sequence indicates the whole registry
66 # The `base_cache_signaling sequence` indicates all caches must be
67 # invalidated (i.e. cleared).
68 self.base_registry_signaling_sequence = 1
69 self.base_cache_signaling_sequence = 1
71 # Flag indicating if at least one model cache has been cleared.
72 # Useful only in a multi-process context.
73 self._any_cache_cleared = False
76 has_unaccent = openerp.modules.db.has_unaccent(cr)
77 if openerp.tools.config['unaccent'] and not has_unaccent:
78 _logger.warning("The option --unaccent was given but no unaccent() function was found in database.")
79 self.has_unaccent = openerp.tools.config['unaccent'] and has_unaccent
82 def do_parent_store(self, cr):
83 for o in self._init_parent:
84 self.get(o)._parent_store_compute(cr)
88 """ Return the list of model names in this registry."""
89 return self.models.keys()
91 def add(self, model_name, model):
92 """ Add or replace a model in the registry."""
93 self.models[model_name] = model
95 def get(self, model_name):
96 """ Return a model for a given name or None if it doesn't exist."""
97 return self.models.get(model_name)
99 def __getitem__(self, model_name):
100 """ Return a model for a given name or raise KeyError if it doesn't exist."""
101 return self.models[model_name]
103 def load(self, cr, module):
104 """ Load a given module in the registry.
106 At the Python level, the modules are already loaded, but not yet on a
107 per-registry level. This method populates a registry with the given
108 modules, i.e. it instanciates all the classes of a the given module
109 and registers them in the registry.
115 # Instantiate registered classes (via the MetaModel automatic discovery
116 # or via explicit constructor call), and add them to the pool.
117 for cls in openerp.osv.orm.MetaModel.module_to_models.get(module.name, []):
118 res.append(cls.create_instance(self, cr))
122 def schedule_cron_jobs(self):
123 """ Make the cron thread care about this registry/database jobs.
124 This will initiate the cron thread to check for any pending jobs for
125 this registry/database as soon as possible. Then it will continuously
126 monitor the ir.cron model for future jobs. See openerp.cron for
131 def clear_caches(self):
133 This clears the caches associated to methods decorated with
134 ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models.
136 for model in self.models.itervalues():
138 # Special case for ir_ui_menu which does not use openerp.tools.ormcache.
139 ir_ui_menu = self.models.get('ir.ui.menu')
141 ir_ui_menu.clear_cache()
144 # Useful only in a multi-process context.
145 def reset_any_cache_cleared(self):
146 self._any_cache_cleared = False
148 # Useful only in a multi-process context.
149 def any_cache_cleared(self):
150 return self._any_cache_cleared
153 def setup_multi_process_signaling(cls, cr):
154 if not openerp.multi_process:
157 # Inter-process signaling:
158 # The `base_registry_signaling` sequence indicates the whole registry
160 # The `base_cache_signaling sequence` indicates all caches must be
161 # invalidated (i.e. cleared).
162 cr.execute("""SELECT sequence_name FROM information_schema.sequences WHERE sequence_name='base_registry_signaling'""")
163 if not cr.fetchall():
164 cr.execute("""CREATE SEQUENCE base_registry_signaling INCREMENT BY 1 START WITH 1""")
165 cr.execute("""SELECT nextval('base_registry_signaling')""")
166 cr.execute("""CREATE SEQUENCE base_cache_signaling INCREMENT BY 1 START WITH 1""")
167 cr.execute("""SELECT nextval('base_cache_signaling')""")
170 def cursor(self, auto_commit=True):
171 cr = self.db.cursor()
180 class RegistryManager(object):
181 """ Model registries manager.
183 The manager is responsible for creation and deletion of model
184 registries (essentially database connection/model registry pairs).
187 # Mapping between db name and model registry.
188 # Accessed through the methods below.
190 registries_lock = threading.RLock()
193 def get(cls, db_name, force_demo=False, status=None, update_module=False,
195 """ Return a registry for a given database name."""
197 return cls.registries[db_name]
199 return cls.new(db_name, force_demo, status,
200 update_module, pooljobs)
203 def new(cls, db_name, force_demo=False, status=None,
204 update_module=False, pooljobs=True):
205 """ Create and return a new registry for a given database name.
207 The (possibly) previous registry for that database name is discarded.
210 import openerp.modules
211 with cls.registries_lock:
212 registry = Registry(db_name)
214 # Initializing a registry will call general code which will in turn
215 # call registries.get (this object) to obtain the registry being
216 # initialized. Make it available in the registries dictionary then
217 # remove it if an exception is raised.
219 cls.registries[db_name] = registry
221 # This should be a method on Registry
222 openerp.modules.load_modules(registry.db, force_demo, status, update_module)
224 del cls.registries[db_name]
227 cr = registry.db.cursor()
229 Registry.setup_multi_process_signaling(cr)
230 registry.do_parent_store(cr)
231 registry.get('ir.actions.report.xml').register_all(cr)
237 registry.schedule_cron_jobs()
242 def delete(cls, db_name):
243 """Delete the registry linked to a given database.
245 This also cleans the associated caches. For good measure this also
246 cancels the associated cron job. But please note that the cron job can
247 be running and take some time before ending, and that you should not
248 remove a registry if it can still be used by some thread. So it might
249 be necessary to call yourself openerp.cron.Agent.cancel(db_name) and
250 and join (i.e. wait for) the thread.
252 with cls.registries_lock:
253 if db_name in cls.registries:
254 cls.registries[db_name].clear_caches()
255 del cls.registries[db_name]
256 openerp.cron.cancel(db_name)
260 """Delete all the registries. """
261 with cls.registries_lock:
262 for db_name in cls.registries.keys():
266 def clear_caches(cls, db_name):
269 This clears the caches associated to methods decorated with
270 ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models
271 of the given database name.
273 This method is given to spare you a ``RegistryManager.get(db_name)``
274 that would loads the given database if it was not already loaded.
276 with cls.registries_lock:
277 if db_name in cls.registries:
278 cls.registries[db_name].clear_caches()
281 def check_registry_signaling(cls, db_name):
282 if openerp.multi_process and db_name in cls.registries:
283 registry = cls.get(db_name, pooljobs=False)
284 cr = registry.db.cursor()
287 SELECT base_registry_signaling.last_value,
288 base_cache_signaling.last_value
289 FROM base_registry_signaling, base_cache_signaling""")
291 # Check if the model registry must be reloaded (e.g. after the
292 # database has been updated by another process).
293 if registry.base_registry_signaling_sequence != r:
294 _logger.info("Reloading the model registry after database signaling.")
295 # Don't run the cron in the Gunicorn worker.
296 registry = cls.new(db_name, pooljobs=False)
297 registry.base_registry_signaling_sequence = r
298 # Check if the model caches must be invalidated (e.g. after a write
299 # occured on another process). Don't clear right after a registry
301 elif registry.base_cache_signaling_sequence != c:
302 _logger.info("Invalidating all model caches after database signaling.")
303 registry.base_cache_signaling_sequence = c
304 registry.clear_caches()
305 registry.reset_any_cache_cleared()
306 # One possible reason caches have been invalidated is the
307 # use of decimal_precision.write(), in which case we need
308 # to refresh fields.float columns.
309 for model in registry.models.values():
310 for column in model._columns.values():
311 if hasattr(column, 'digits_change'):
312 column.digits_change(cr)
317 def signal_caches_change(cls, db_name):
318 if openerp.multi_process and db_name in cls.registries:
319 # Check the registries if any cache has been cleared and signal it
320 # through the database to other processes.
321 registry = cls.get(db_name, pooljobs=False)
322 if registry.any_cache_cleared():
323 _logger.info("At least one model cache has been cleared, signaling through the database.")
324 cr = registry.db.cursor()
327 cr.execute("select nextval('base_cache_signaling')")
331 registry.base_cache_signaling_sequence = r
332 registry.reset_any_cache_cleared()
335 def signal_registry_change(cls, db_name):
336 if openerp.multi_process and db_name in cls.registries:
337 registry = cls.get(db_name, pooljobs=False)
338 cr = registry.db.cursor()
341 cr.execute("select nextval('base_registry_signaling')")
345 registry.base_registry_signaling_sequence = r
347 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: