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 collections import Mapping
31 from .. import SUPERUSER_ID
32 from openerp.tools import assertion_report, lazy_property, classproperty, config
33 from openerp.tools.lru import LRU
35 _logger = logging.getLogger(__name__)
37 class Registry(Mapping):
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 super(Registry, self).__init__()
47 self.models = {} # model name/model instance mapping
49 self._store_function = {}
50 self._pure_function_fields = {} # {model: [field, ...], ...}
52 self._init_parent = {}
53 self._assertion_report = assertion_report.assertion_report()
54 self.fields_by_model = None
56 # modules fully loaded (maintained during init phase by `loading` module)
57 self._init_modules = set()
59 self.db_name = db_name
60 self._db = openerp.sql_db.db_connect(db_name)
62 # special cursor for test mode; None means "normal" mode
65 # Indicates that the registry is
68 # Inter-process signaling (used only when openerp.multi_process is True):
69 # The `base_registry_signaling` sequence indicates the whole registry
71 # The `base_cache_signaling sequence` indicates all caches must be
72 # invalidated (i.e. cleared).
73 self.base_registry_signaling_sequence = None
74 self.base_cache_signaling_sequence = None
76 # Flag indicating if at least one model cache has been cleared.
77 # Useful only in a multi-process context.
78 self._any_cache_cleared = False
81 has_unaccent = openerp.modules.db.has_unaccent(cr)
82 if openerp.tools.config['unaccent'] and not has_unaccent:
83 _logger.warning("The option --unaccent was given but no unaccent() function was found in database.")
84 self.has_unaccent = openerp.tools.config['unaccent'] and has_unaccent
88 # Mapping abstract methods implementation
89 # => mixin provides methods keys, items, values, get, __eq__, and __ne__
92 """ Return the size of the registry. """
93 return len(self.models)
96 """ Return an iterator over all model names. """
97 return iter(self.models)
99 def __getitem__(self, model_name):
100 """ Return the model with the given name or raise KeyError if it doesn't exist."""
101 return self.models[model_name]
103 def __call__(self, model_name):
104 """ Same as ``self[model_name]``. """
105 return self.models[model_name]
108 def pure_function_fields(self):
109 """ Return the list of pure function fields (field objects) """
111 for mname, fnames in self._pure_function_fields.iteritems():
112 model_fields = self[mname]._fields
114 fields.append(model_fields[fname])
117 def do_parent_store(self, cr):
118 for o in self._init_parent:
119 self.get(o)._parent_store_compute(cr)
123 """ Return the list of model names in this registry."""
126 def add(self, model_name, model):
127 """ Add or replace a model in the registry."""
128 self.models[model_name] = model
130 def load(self, cr, module):
131 """ Load a given module in the registry.
133 At the Python level, the modules are already loaded, but not yet on a
134 per-registry level. This method populates a registry with the given
135 modules, i.e. it instanciates all the classes of a the given module
136 and registers them in the registry.
139 from .. import models
141 models_to_load = [] # need to preserve loading order
142 lazy_property.reset_all(self)
144 # Instantiate registered classes (via the MetaModel automatic discovery
145 # or via explicit constructor call), and add them to the pool.
146 for cls in models.MetaModel.module_to_models.get(module.name, []):
147 # models register themselves in self.models
148 model = cls._build_model(self, cr)
149 if model._name not in models_to_load:
150 # avoid double-loading models whose declaration is split
151 models_to_load.append(model._name)
153 return [self.models[m] for m in models_to_load]
155 def setup_models(self, cr, partial=False):
156 """ Complete the setup of models.
157 This must be called after loading modules and before using the ORM.
159 :param partial: ``True`` if all models have not been loaded yet.
161 # prepare the setup on all models
162 for model in self.models.itervalues():
163 model._prepare_setup_fields(cr, SUPERUSER_ID)
165 # do the actual setup from a clean state
167 context = {'_setup_fields_partial': partial}
168 for model in self.models.itervalues():
169 model._setup_fields(cr, SUPERUSER_ID, context=context)
171 def clear_caches(self):
173 This clears the caches associated to methods decorated with
174 ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models.
176 for model in self.models.itervalues():
178 # Special case for ir_ui_menu which does not use openerp.tools.ormcache.
179 ir_ui_menu = self.models.get('ir.ui.menu')
180 if ir_ui_menu is not None:
181 ir_ui_menu.clear_cache()
184 # Useful only in a multi-process context.
185 def reset_any_cache_cleared(self):
186 self._any_cache_cleared = False
188 # Useful only in a multi-process context.
189 def any_cache_cleared(self):
190 return self._any_cache_cleared
193 def setup_multi_process_signaling(cls, cr):
194 if not openerp.multi_process:
197 # Inter-process signaling:
198 # The `base_registry_signaling` sequence indicates the whole registry
200 # The `base_cache_signaling sequence` indicates all caches must be
201 # invalidated (i.e. cleared).
202 cr.execute("""SELECT sequence_name FROM information_schema.sequences WHERE sequence_name='base_registry_signaling'""")
203 if not cr.fetchall():
204 cr.execute("""CREATE SEQUENCE base_registry_signaling INCREMENT BY 1 START WITH 1""")
205 cr.execute("""SELECT nextval('base_registry_signaling')""")
206 cr.execute("""CREATE SEQUENCE base_cache_signaling INCREMENT BY 1 START WITH 1""")
207 cr.execute("""SELECT nextval('base_cache_signaling')""")
210 SELECT base_registry_signaling.last_value,
211 base_cache_signaling.last_value
212 FROM base_registry_signaling, base_cache_signaling""")
214 _logger.debug("Multiprocess load registry signaling: [Registry: # %s] "\
219 def enter_test_mode(self):
220 """ Enter the 'test' mode, where one cursor serves several requests. """
221 assert self.test_cr is None
222 self.test_cr = self._db.test_cursor()
223 RegistryManager.enter_test_mode()
225 def leave_test_mode(self):
226 """ Leave the test mode. """
227 assert self.test_cr is not None
228 self.test_cr.force_close()
230 RegistryManager.leave_test_mode()
233 """ Return a new cursor for the database. The cursor itself may be used
234 as a context manager to commit/rollback and close automatically.
238 # While in test mode, we use one special cursor across requests. The
239 # test cursor uses a reentrant lock to serialize accesses. The lock
240 # is granted here by cursor(), and automatically released by the
241 # cursor itself in its method close().
244 return self._db.cursor()
246 class DummyRLock(object):
247 """ Dummy reentrant lock, to be used while running rpc and js tests """
254 def __exit__(self, type, value, traceback):
257 class RegistryManager(object):
258 """ Model registries manager.
260 The manager is responsible for creation and deletion of model
261 registries (essentially database connection/model registry pairs).
265 _lock = threading.RLock()
270 if cls._registries is None:
271 size = config.get('registry_lru_size', None)
273 # Size the LRU depending of the memory limits
274 if os.name != 'posix':
275 # cannot specify the memory limit soft on windows...
278 # On average, a clean registry take 25MB of memory + cache
279 avgsz = 30 * 1024 * 1024
280 size = int(config['limit_memory_soft'] / avgsz)
282 cls._registries = LRU(size)
283 return cls._registries
287 """ Return the current registry lock. """
291 def enter_test_mode(cls):
292 """ Enter the 'test' mode, where the registry is no longer locked. """
293 assert cls._saved_lock is None
294 cls._lock, cls._saved_lock = DummyRLock(), cls._lock
297 def leave_test_mode(cls):
298 """ Leave the 'test' mode. """
299 assert cls._saved_lock is not None
300 cls._lock, cls._saved_lock = cls._saved_lock, None
303 def get(cls, db_name, force_demo=False, status=None, update_module=False):
304 """ Return a registry for a given database name."""
307 return cls.registries[db_name]
309 return cls.new(db_name, force_demo, status,
312 # set db tracker - cleaned up at the WSGI
313 # dispatching phase in openerp.service.wsgi_server.application
314 threading.current_thread().dbname = db_name
317 def new(cls, db_name, force_demo=False, status=None,
318 update_module=False):
319 """ Create and return a new registry for a given database name.
321 The (possibly) previous registry for that database name is discarded.
324 import openerp.modules
326 with openerp.api.Environment.manage():
327 registry = Registry(db_name)
329 # Initializing a registry will call general code which will in
330 # turn call registries.get (this object) to obtain the registry
331 # being initialized. Make it available in the registries
332 # dictionary then remove it if an exception is raised.
334 cls.registries[db_name] = registry
336 with registry.cursor() as cr:
337 seq_registry, seq_cache = Registry.setup_multi_process_signaling(cr)
338 registry.base_registry_signaling_sequence = seq_registry
339 registry.base_cache_signaling_sequence = seq_cache
340 # This should be a method on Registry
341 openerp.modules.load_modules(registry._db, force_demo, status, update_module)
343 del cls.registries[db_name]
346 # load_modules() above can replace the registry by calling
347 # indirectly new() again (when modules have to be uninstalled).
349 registry = cls.registries[db_name]
351 cr = registry.cursor()
353 registry.do_parent_store(cr)
358 registry.ready = True
361 # only in case of update, otherwise we'll have an infinite reload loop!
362 cls.signal_registry_change(db_name)
366 def delete(cls, db_name):
367 """Delete the registry linked to a given database. """
369 if db_name in cls.registries:
370 cls.registries[db_name].clear_caches()
371 del cls.registries[db_name]
375 """Delete all the registries. """
377 for db_name in cls.registries.keys():
381 def clear_caches(cls, db_name):
384 This clears the caches associated to methods decorated with
385 ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models
386 of the given database name.
388 This method is given to spare you a ``RegistryManager.get(db_name)``
389 that would loads the given database if it was not already loaded.
392 if db_name in cls.registries:
393 cls.registries[db_name].clear_caches()
396 def check_registry_signaling(cls, db_name):
398 Check if the modules have changed and performs all necessary operations to update
399 the registry of the corresponding database.
402 :returns: True if changes has been detected in the database and False otherwise.
405 if openerp.multi_process and db_name in cls.registries:
406 registry = cls.get(db_name)
407 cr = registry.cursor()
410 SELECT base_registry_signaling.last_value,
411 base_cache_signaling.last_value
412 FROM base_registry_signaling, base_cache_signaling""")
414 _logger.debug("Multiprocess signaling check: [Registry - old# %s new# %s] "\
415 "[Cache - old# %s new# %s]",
416 registry.base_registry_signaling_sequence, r,
417 registry.base_cache_signaling_sequence, c)
418 # Check if the model registry must be reloaded (e.g. after the
419 # database has been updated by another process).
420 if registry.base_registry_signaling_sequence is not None and registry.base_registry_signaling_sequence != r:
422 _logger.info("Reloading the model registry after database signaling.")
423 registry = cls.new(db_name)
424 # Check if the model caches must be invalidated (e.g. after a write
425 # occured on another process). Don't clear right after a registry
427 elif registry.base_cache_signaling_sequence is not None and registry.base_cache_signaling_sequence != c:
429 _logger.info("Invalidating all model caches after database signaling.")
430 registry.clear_caches()
431 registry.reset_any_cache_cleared()
432 registry.base_registry_signaling_sequence = r
433 registry.base_cache_signaling_sequence = c
439 def signal_caches_change(cls, db_name):
440 if openerp.multi_process and db_name in cls.registries:
441 # Check the registries if any cache has been cleared and signal it
442 # through the database to other processes.
443 registry = cls.get(db_name)
444 if registry.any_cache_cleared():
445 _logger.info("At least one model cache has been cleared, signaling through the database.")
446 cr = registry.cursor()
449 cr.execute("select nextval('base_cache_signaling')")
453 registry.base_cache_signaling_sequence = r
454 registry.reset_any_cache_cleared()
457 def signal_registry_change(cls, db_name):
458 if openerp.multi_process and db_name in cls.registries:
459 _logger.info("Registry changed, signaling through the database")
460 registry = cls.get(db_name)
461 cr = registry.cursor()
464 cr.execute("select nextval('base_registry_signaling')")
468 registry.base_registry_signaling_sequence = r
470 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: