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.
29 import openerp.osv.orm
32 import openerp.modules.db
33 import openerp.tools.config
35 _logger = logging.getLogger(__name__)
37 class Registry(object):
38 """ Model registry for a particular database.
40 The registry is essentially a mapping between model names and model
41 instances. There is one registry instance per database.
45 def __init__(self, db_name):
46 self.models = {} # model name/model instance mapping
48 self._store_function = {}
50 self._init_parent = {}
51 self.db_name = db_name
52 self.db = openerp.sql_db.db_connect(db_name)
54 # Inter-process signaling (used only when openerp.multi_process is True):
55 # The `base_registry_signaling` sequence indicates the whole registry
57 # The `base_cache_signaling sequence` indicates all caches must be
58 # invalidated (i.e. cleared).
59 self.base_registry_signaling_sequence = 1
60 self.base_cache_signaling_sequence = 1
62 # Flag indicating if at least one model cache has been cleared.
63 # Useful only in a multi-process context.
64 self._any_cache_cleared = False
67 has_unaccent = openerp.modules.db.has_unaccent(cr)
68 if openerp.tools.config['unaccent'] and not has_unaccent:
69 _logger.warning("The option --unaccent was given but no unaccent() function was found in database.")
70 self.has_unaccent = openerp.tools.config['unaccent'] and has_unaccent
73 def do_parent_store(self, cr):
74 for o in self._init_parent:
75 self.get(o)._parent_store_compute(cr)
79 """ Return the list of model names in this registry."""
80 return self.models.keys()
82 def add(self, model_name, model):
83 """ Add or replace a model in the registry."""
84 self.models[model_name] = model
86 def get(self, model_name):
87 """ Return a model for a given name or None if it doesn't exist."""
88 return self.models.get(model_name)
90 def __getitem__(self, model_name):
91 """ Return a model for a given name or raise KeyError if it doesn't exist."""
92 return self.models[model_name]
94 def load(self, cr, module):
95 """ Load a given module in the registry.
97 At the Python level, the modules are already loaded, but not yet on a
98 per-registry level. This method populates a registry with the given
99 modules, i.e. it instanciates all the classes of a the given module
100 and registers them in the registry.
106 # Instantiate registered classes (via the MetaModel automatic discovery
107 # or via explicit constructor call), and add them to the pool.
108 for cls in openerp.osv.orm.MetaModel.module_to_models.get(module.name, []):
109 res.append(cls.create_instance(self, cr))
113 def schedule_cron_jobs(self):
114 """ Make the cron thread care about this registry/database jobs.
115 This will initiate the cron thread to check for any pending jobs for
116 this registry/database as soon as possible. Then it will continuously
117 monitor the ir.cron model for future jobs. See openerp.cron for
120 openerp.cron.schedule_wakeup(openerp.cron.WAKE_UP_NOW, self.db.dbname)
122 def clear_caches(self):
124 This clears the caches associated to methods decorated with
125 ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models.
127 for model in self.models.itervalues():
129 # Special case for ir_ui_menu which does not use openerp.tools.ormcache.
130 ir_ui_menu = self.models.get('ir.ui.menu')
132 ir_ui_menu.clear_cache()
135 # Useful only in a multi-process context.
136 def reset_any_cache_cleared(self):
137 self._any_cache_cleared = False
139 # Useful only in a multi-process context.
140 def any_cache_cleared(self):
141 return self._any_cache_cleared
144 def setup_multi_process_signaling(cls, cr):
145 if not openerp.multi_process:
148 # Inter-process signaling:
149 # The `base_registry_signaling` sequence indicates the whole registry
151 # The `base_cache_signaling sequence` indicates all caches must be
152 # invalidated (i.e. cleared).
153 cr.execute("""SELECT sequence_name FROM information_schema.sequences WHERE sequence_name='base_registry_signaling'""")
154 if not cr.fetchall():
155 cr.execute("""CREATE SEQUENCE base_registry_signaling INCREMENT BY 1 START WITH 1""")
156 cr.execute("""SELECT nextval('base_registry_signaling')""")
157 cr.execute("""CREATE SEQUENCE base_cache_signaling INCREMENT BY 1 START WITH 1""")
158 cr.execute("""SELECT nextval('base_cache_signaling')""")
160 class RegistryManager(object):
161 """ Model registries manager.
163 The manager is responsible for creation and deletion of model
164 registries (essentially database connection/model registry pairs).
167 # Mapping between db name and model registry.
168 # Accessed through the methods below.
170 registries_lock = threading.RLock()
173 def get(cls, db_name, force_demo=False, status=None, update_module=False,
175 """ Return a registry for a given database name."""
177 return cls.registries[db_name]
179 return cls.new(db_name, force_demo, status,
180 update_module, pooljobs)
183 def new(cls, db_name, force_demo=False, status=None,
184 update_module=False, pooljobs=True):
185 """ Create and return a new registry for a given database name.
187 The (possibly) previous registry for that database name is discarded.
190 import openerp.modules
191 with cls.registries_lock:
192 registry = Registry(db_name)
194 # Initializing a registry will call general code which will in turn
195 # call registries.get (this object) to obtain the registry being
196 # initialized. Make it available in the registries dictionary then
197 # remove it if an exception is raised.
199 cls.registries[db_name] = registry
201 # This should be a method on Registry
202 openerp.modules.load_modules(registry.db, force_demo, status, update_module)
204 del cls.registries[db_name]
207 cr = registry.db.cursor()
209 Registry.setup_multi_process_signaling(cr)
210 registry.do_parent_store(cr)
211 registry.get('ir.actions.report.xml').register_all(cr)
217 registry.schedule_cron_jobs()
222 def delete(cls, db_name):
223 """Delete the registry linked to a given database.
225 This also cleans the associated caches. For good measure this also
226 cancels the associated cron job. But please note that the cron job can
227 be running and take some time before ending, and that you should not
228 remove a registry if it can still be used by some thread. So it might
229 be necessary to call yourself openerp.cron.Agent.cancel(db_name) and
230 and join (i.e. wait for) the thread.
232 with cls.registries_lock:
233 if db_name in cls.registries:
234 cls.registries[db_name].clear_caches()
235 del cls.registries[db_name]
236 openerp.cron.cancel(db_name)
241 """Delete all the registries. """
242 with cls.registries_lock:
243 for db_name in cls.registries.keys():
247 def clear_caches(cls, db_name):
250 This clears the caches associated to methods decorated with
251 ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models
252 of the given database name.
254 This method is given to spare you a ``RegistryManager.get(db_name)``
255 that would loads the given database if it was not already loaded.
257 with cls.registries_lock:
258 if db_name in cls.registries:
259 cls.registries[db_name].clear_caches()
262 def check_registry_signaling(cls, db_name):
263 if openerp.multi_process and db_name in cls.registries:
264 registry = cls.get(db_name, pooljobs=False)
265 cr = registry.db.cursor()
268 SELECT base_registry_signaling.last_value,
269 base_cache_signaling.last_value
270 FROM base_registry_signaling, base_cache_signaling""")
272 # Check if the model registry must be reloaded (e.g. after the
273 # database has been updated by another process).
274 if registry.base_registry_signaling_sequence != r:
275 _logger.info("Reloading the model registry after database signaling.")
276 # Don't run the cron in the Gunicorn worker.
277 registry = cls.new(db_name, pooljobs=False)
278 registry.base_registry_signaling_sequence = r
279 # Check if the model caches must be invalidated (e.g. after a write
280 # occured on another process). Don't clear right after a registry
282 elif registry.base_cache_signaling_sequence != c:
283 _logger.info("Invalidating all model caches after database signaling.")
284 registry.base_cache_signaling_sequence = c
285 registry.clear_caches()
286 registry.reset_any_cache_cleared()
287 # One possible reason caches have been invalidated is the
288 # use of decimal_precision.write(), in which case we need
289 # to refresh fields.float columns.
290 for model in registry.models.values():
291 for column in model._columns.values():
292 if hasattr(column, 'digits_change'):
293 column.digits_change(cr)
298 def signal_caches_change(cls, db_name):
299 if openerp.multi_process and db_name in cls.registries:
300 # Check the registries if any cache has been cleared and signal it
301 # through the database to other processes.
302 registry = cls.get(db_name, pooljobs=False)
303 if registry.any_cache_cleared():
304 _logger.info("At least one model cache has been cleared, signaling through the database.")
305 cr = registry.db.cursor()
308 cr.execute("select nextval('base_cache_signaling')")
312 registry.base_cache_signaling_sequence = r
313 registry.reset_any_cache_cleared()
316 def signal_registry_change(cls, db_name):
317 if openerp.multi_process and db_name in cls.registries:
318 registry = cls.get(db_name, pooljobs=False)
319 cr = registry.db.cursor()
322 cr.execute("select nextval('base_registry_signaling')")
326 registry.base_registry_signaling_sequence = r
328 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: