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
31 import openerp.modules.db
32 import openerp.tools.config
34 _logger = logging.getLogger(__name__)
36 class Registry(object):
37 """ Model registry for a particular database.
39 The registry is essentially a mapping between model names and model
40 instances. There is one registry instance per database.
44 def __init__(self, db_name):
45 self.models = {} # model name/model instance mapping
47 self._store_function = {}
49 self._init_parent = {}
50 self.db_name = db_name
51 self.db = openerp.sql_db.db_connect(db_name)
53 # Inter-process signaling (used only when openerp.multi_process is True):
54 # The `base_registry_signaling` sequence indicates the whole registry
56 # The `base_cache_signaling sequence` indicates all caches must be
57 # invalidated (i.e. cleared).
58 self.base_registry_signaling_sequence = 1
59 self.base_cache_signaling_sequence = 1
61 # Flag indicating if at least one model cache has been cleared.
62 # Useful only in a multi-process context.
63 self._any_cache_cleared = False
66 has_unaccent = openerp.modules.db.has_unaccent(cr)
67 if openerp.tools.config['unaccent'] and not has_unaccent:
68 _logger.warning("The option --unaccent was given but no unaccent() function was found in database.")
69 self.has_unaccent = openerp.tools.config['unaccent'] and has_unaccent
72 def do_parent_store(self, cr):
73 for o in self._init_parent:
74 self.get(o)._parent_store_compute(cr)
78 """ Return the list of model names in this registry."""
79 return self.models.keys()
81 def add(self, model_name, model):
82 """ Add or replace a model in the registry."""
83 self.models[model_name] = model
85 def get(self, model_name):
86 """ Return a model for a given name or None if it doesn't exist."""
87 return self.models.get(model_name)
89 def __getitem__(self, model_name):
90 """ Return a model for a given name or raise KeyError if it doesn't exist."""
91 return self.models[model_name]
93 def load(self, cr, module):
94 """ Load a given module in the registry.
96 At the Python level, the modules are already loaded, but not yet on a
97 per-registry level. This method populates a registry with the given
98 modules, i.e. it instanciates all the classes of a the given module
99 and registers them in the registry.
105 # Instantiate registered classes (via the MetaModel automatic discovery
106 # or via explicit constructor call), and add them to the pool.
107 for cls in openerp.osv.orm.MetaModel.module_to_models.get(module.name, []):
108 res.append(cls.create_instance(self, cr))
112 def clear_caches(self):
114 This clears the caches associated to methods decorated with
115 ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models.
117 for model in self.models.itervalues():
119 # Special case for ir_ui_menu which does not use openerp.tools.ormcache.
120 ir_ui_menu = self.models.get('ir.ui.menu')
122 ir_ui_menu.clear_cache()
125 # Useful only in a multi-process context.
126 def reset_any_cache_cleared(self):
127 self._any_cache_cleared = False
129 # Useful only in a multi-process context.
130 def any_cache_cleared(self):
131 return self._any_cache_cleared
134 def setup_multi_process_signaling(cls, cr):
135 if not openerp.multi_process:
138 # Inter-process signaling:
139 # The `base_registry_signaling` sequence indicates the whole registry
141 # The `base_cache_signaling sequence` indicates all caches must be
142 # invalidated (i.e. cleared).
143 cr.execute("""SELECT sequence_name FROM information_schema.sequences WHERE sequence_name='base_registry_signaling'""")
144 if not cr.fetchall():
145 cr.execute("""CREATE SEQUENCE base_registry_signaling INCREMENT BY 1 START WITH 1""")
146 cr.execute("""SELECT nextval('base_registry_signaling')""")
147 cr.execute("""CREATE SEQUENCE base_cache_signaling INCREMENT BY 1 START WITH 1""")
148 cr.execute("""SELECT nextval('base_cache_signaling')""")
150 class RegistryManager(object):
151 """ Model registries manager.
153 The manager is responsible for creation and deletion of model
154 registries (essentially database connection/model registry pairs).
157 # Mapping between db name and model registry.
158 # Accessed through the methods below.
160 registries_lock = threading.RLock()
163 def get(cls, db_name, force_demo=False, status=None, update_module=False,
165 """ Return a registry for a given database name."""
167 return cls.registries[db_name]
169 return cls.new(db_name, force_demo, status,
170 update_module, pooljobs)
173 def new(cls, db_name, force_demo=False, status=None,
174 update_module=False, pooljobs=True):
175 """ Create and return a new registry for a given database name.
177 The (possibly) previous registry for that database name is discarded.
180 import openerp.modules
181 with cls.registries_lock:
182 registry = Registry(db_name)
184 # Initializing a registry will call general code which will in turn
185 # call registries.get (this object) to obtain the registry being
186 # initialized. Make it available in the registries dictionary then
187 # remove it if an exception is raised.
189 cls.registries[db_name] = registry
191 # This should be a method on Registry
192 openerp.modules.load_modules(registry.db, force_demo, status, update_module)
194 del cls.registries[db_name]
197 cr = registry.db.cursor()
199 Registry.setup_multi_process_signaling(cr)
200 registry.do_parent_store(cr)
201 registry.get('ir.actions.report.xml').register_all(cr)
209 def delete(cls, db_name):
210 """Delete the registry linked to a given database.
212 This also cleans the associated caches. For good measure this also
213 cancels the associated cron job. But please note that the cron job can
214 be running and take some time before ending, and that you should not
215 remove a registry if it can still be used by some thread. So it might
216 be necessary to call yourself openerp.cron.Agent.cancel(db_name) and
217 and join (i.e. wait for) the thread.
219 with cls.registries_lock:
220 if db_name in cls.registries:
221 cls.registries[db_name].clear_caches()
222 del cls.registries[db_name]
226 """Delete all the registries. """
227 with cls.registries_lock:
228 for db_name in cls.registries.keys():
232 def clear_caches(cls, db_name):
235 This clears the caches associated to methods decorated with
236 ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models
237 of the given database name.
239 This method is given to spare you a ``RegistryManager.get(db_name)``
240 that would loads the given database if it was not already loaded.
242 with cls.registries_lock:
243 if db_name in cls.registries:
244 cls.registries[db_name].clear_caches()
247 def check_registry_signaling(cls, db_name):
248 if openerp.multi_process and db_name in cls.registries:
249 registry = cls.get(db_name, pooljobs=False)
250 cr = registry.db.cursor()
253 SELECT base_registry_signaling.last_value,
254 base_cache_signaling.last_value
255 FROM base_registry_signaling, base_cache_signaling""")
257 # Check if the model registry must be reloaded (e.g. after the
258 # database has been updated by another process).
259 if registry.base_registry_signaling_sequence != r:
260 _logger.info("Reloading the model registry after database signaling.")
261 # Don't run the cron in the Gunicorn worker.
262 registry = cls.new(db_name, pooljobs=False)
263 registry.base_registry_signaling_sequence = r
264 # Check if the model caches must be invalidated (e.g. after a write
265 # occured on another process). Don't clear right after a registry
267 elif registry.base_cache_signaling_sequence != c:
268 _logger.info("Invalidating all model caches after database signaling.")
269 registry.base_cache_signaling_sequence = c
270 registry.clear_caches()
271 registry.reset_any_cache_cleared()
272 # One possible reason caches have been invalidated is the
273 # use of decimal_precision.write(), in which case we need
274 # to refresh fields.float columns.
275 for model in registry.models.values():
276 for column in model._columns.values():
277 if hasattr(column, 'digits_change'):
278 column.digits_change(cr)
283 def signal_caches_change(cls, db_name):
284 if openerp.multi_process and db_name in cls.registries:
285 # Check the registries if any cache has been cleared and signal it
286 # through the database to other processes.
287 registry = cls.get(db_name, pooljobs=False)
288 if registry.any_cache_cleared():
289 _logger.info("At least one model cache has been cleared, signaling through the database.")
290 cr = registry.db.cursor()
293 cr.execute("select nextval('base_cache_signaling')")
297 registry.base_cache_signaling_sequence = r
298 registry.reset_any_cache_cleared()
301 def signal_registry_change(cls, db_name):
302 if openerp.multi_process and db_name in cls.registries:
303 registry = cls.get(db_name, pooljobs=False)
304 cr = registry.db.cursor()
307 cr.execute("select nextval('base_registry_signaling')")
311 registry.base_registry_signaling_sequence = r
313 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: