diff --git a/openerp-server b/openerp-server index b5275ec9859..334c1790526 100755 --- a/openerp-server +++ b/openerp-server @@ -42,6 +42,9 @@ import openerp __author__ = openerp.release.author __version__ = openerp.release.version +import sys +import imp + def check_root_user(): """ Exit if the process's user is 'root' (on POSIX system).""" if os.name == 'posix': @@ -212,6 +215,7 @@ if __name__ == "__main__": check_root_user() openerp.tools.config.parse_config(sys.argv[1:]) + check_postgres_user() openerp.netsvc.init_logger() report_configuration() diff --git a/openerp/addons/__init__.py b/openerp/addons/__init__.py index 1f863eca509..9554e9bd850 100644 --- a/openerp/addons/__init__.py +++ b/openerp/addons/__init__.py @@ -25,9 +25,9 @@ This module serves to contain all OpenERP addons, across all configured addons paths. For the code to manage those addons, see openerp.modules. -Addons are made available here (i.e. under openerp.addons) after -openerp.tools.config.parse_config() is called (so that the addons paths -are known). +Addons are made available under `openerp.addons` after +openerp.tools.config.parse_config() is called (so that the addons paths are +known). This module also conveniently reexports some symbols from openerp.modules. Importing them from here is deprecated. diff --git a/openerp/modules/module.py b/openerp/modules/module.py index ad56bf896a6..b82597d656d 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -56,31 +56,109 @@ loaded = [] logger = netsvc.Logger() -def initialize_sys_path(): - """ Add all addons paths in sys.path. +class AddonsImportHook(object): + """ + Import hook to load OpenERP addons from multiple paths. - This ensures something like ``import crm`` works even if the addons are - not in the PYTHONPATH. + OpenERP implements its own import-hook to load its addons. OpenERP + addons are Python modules. Originally, they were each living in their + own top-level namespace, e.g. the sale module, or the hr module. For + backward compatibility, `import ` is still supported. Now they + are living in `openerp.addons`. The good way to import such modules is + thus `import openerp.addons.module`. + + For backward compatibility, loading an addons puts it in `sys.modules` + under both the legacy (short) name, and the new (longer) name. This + ensures that + import hr + import openerp.addons.hr + loads the hr addons only once. + + When an OpenERP addons name clashes with some other installed Python + module (for instance this is the case of the `resource` addons), + obtaining the OpenERP addons is only possible with the long name. The + short name will give the expected Python module. + + Instead of relying on some addons path, an alternative approach would be + to use pkg_resources entry points from already installed Python libraries + (and install our addons as such). Even when implemented, we would still + have to support the addons path approach for backward compatibility. + """ + + def find_module(self, module_name, package_path): + module_parts = module_name.split('.') + if len(module_parts) == 3 and module_name.startswith('openerp.addons.'): + return self # We act as a loader too. + + # TODO list of loadable modules can be cached instead of always + # calling get_module_path(). + if len(module_parts) == 1 and \ + get_module_path(module_parts[0], + display_warning=False): + try: + # Check if the bare module name clashes with another module. + f, path, descr = imp.find_module(module_parts[0]) + logger = logging.getLogger('init') + logger.warning(""" +Ambiguous import: the OpenERP module `%s` is shadowed by another +module (available at %s). +To import it, use `import openerp.addons..`.""" % (module_name, path)) + return + except ImportError, e: + # Using `import ` instead of + # `import openerp.addons.` is ugly but not harmful + # and kept for backward compatibility. + return self # We act as a loader too. + + def load_module(self, module_name): + + module_parts = module_name.split('.') + if len(module_parts) == 3 and module_name.startswith('openerp.addons.'): + module_part = module_parts[2] + if module_name in sys.modules: + return sys.modules[module_name] + + if len(module_parts) == 1: + module_part = module_parts[0] + if module_part in sys.modules: + return sys.modules[module_part] + + try: + # Check if the bare module name shadows another module. + f, path, descr = imp.find_module(module_part) + is_shadowing = True + except ImportError, e: + # Using `import ` instead of + # `import openerp.addons.` is ugly but not harmful + # and kept for backward compatibility. + is_shadowing = False + + # Note: we don't support circular import. + f, path, descr = imp.find_module(module_part, ad_paths) + mod = imp.load_module(module_name, f, path, descr) + if not is_shadowing: + sys.modules[module_part] = mod + sys.modules['openerp.addons.' + module_part] = mod + return mod + +def initialize_sys_path(): + """ + Setup an import-hook to be able to import OpenERP addons from the different + addons paths. + + This ensures something like ``import crm`` (or even + ``import openerp.addons.crm``) works even if the addons are not in the + PYTHONPATH. """ global ad_paths - if ad_paths: return ad_paths = map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(',')) + ad_paths.append(_ad) # for get_module_path + sys.meta_path.append(AddonsImportHook()) - sys.path.insert(1, _ad) - - ad_cnt=1 - for adp in ad_paths: - if adp != _ad: - sys.path.insert(ad_cnt, adp) - ad_cnt+=1 - - ad_paths.append(_ad) # for get_module_path - - -def get_module_path(module, downloaded=False): +def get_module_path(module, downloaded=False, display_warning=True): """Return the path of the given module. Search the addons paths and return the first path where the given @@ -95,7 +173,8 @@ def get_module_path(module, downloaded=False): if downloaded: return opj(_ad, module) - logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,)) + if display_warning: + logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,)) return False @@ -299,17 +378,6 @@ def init_module_models(cr, module_name, obj_list): t[1](cr, *t[2]) cr.commit() -# Import hook to write a addon m in both sys.modules['m'] and -# sys.modules['openerp.addons.m']. Otherwise it could be loaded twice -# if imported twice using different names. -#class MyImportHook(object): -# def find_module(self, module_name, package_path): -# print ">>>", module_name, package_path -# def load_module(self, module_name): -# raise ImportError("Restricted") - -#sys.meta_path.append(MyImportHook()) - def register_module_classes(m): """ Register module named m, if not already registered. @@ -334,7 +402,7 @@ def register_module_classes(m): try: zip_mod_path = mod_path + '.zip' if not os.path.isfile(zip_mod_path): - __import__(m) + __import__('openerp.addons.' + m) else: zimp = zipimport.zipimporter(zip_mod_path) zimp.load_module(m) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 9a72523796a..1c12fab27c9 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -608,7 +608,16 @@ class MetaModel(type): super(MetaModel, self).__init__(name, bases, attrs) return - module_name = self.__module__.split('.')[0] + # The (OpenERP) module name can be in the `openerp.addons` namespace + # or not. For instance module `sale` can be imported as + # `openerp.addons.sale` (the good way) or `sale` (for backward + # compatibility). + module_parts = self.__module__.split('.') + if len(module_parts) > 2 and module_parts[0] == 'openerp' and \ + module_parts[1] == 'addons': + module_name = self.__module__.split('.')[2] + else: + module_name = self.__module__.split('.')[0] if not hasattr(self, '_module'): self._module = module_name diff --git a/openerp/tools/config.py b/openerp/tools/config.py index 00815ed2055..8c980447f52 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -461,9 +461,6 @@ class configmanager(object): if complete: openerp.modules.module.initialize_sys_path() openerp.modules.loading.open_openerp_namespace() - # openerp.addons.__path__.extend(openerp.conf.addons_paths) # This - # is not compatible with initialize_sys_path(): import crm and - # import openerp.addons.crm load twice the module. def _generate_pgpassfile(self): """