diff options
| author | Xavier Morel <xmo@odoo.com> | 2015-10-07 14:21:07 +0200 |
|---|---|---|
| committer | Xavier Morel <xmo@odoo.com> | 2015-10-07 15:10:36 +0200 |
| commit | 9b83dd5894fda3b8fb6a81bd20dd8ea8d6a5b1a4 (patch) | |
| tree | bfdc5e11d17f5295b2f56c82b6c9a91b69ecaa0e | |
| parent | 72dee6d52368ae53de793bf42c63f7a7b93fd909 (diff) | |
[ADD] Import hook to translate odoo imports
Allows importing the core ``openerp`` as ``odoo`` (e.g. ``from
odoo.tools import cache``) and importing modules as
e.g. ``odoo.addons.purchase`` **from modules**. From the openerp core,
imports *must* remain to ``openerp``, as those run before import hooks
can be installed.
* AddonsHook allows Odoo imports to be imported as both odoo.addons.* or
openerp.addons.* and will load addons to both names simultanously
* OdooHook makes the Odoo core accessible as odoo as well as openerp
| -rw-r--r-- | openerp/modules/module.py | 108 |
1 files changed, 81 insertions, 27 deletions
diff --git a/openerp/modules/module.py b/openerp/modules/module.py index b03e31b3aa5..fb5067d3b5d 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -8,14 +8,15 @@ import inspect import itertools import logging import os +import pkg_resources import re import sys import time +import types import unittest import threading from os.path import join as opj -import unittest2 import openerp import openerp.tools as tools @@ -34,33 +35,85 @@ hooked = False # Modules already loaded loaded = [] -class AddonsImportHook(object): +class AddonsHook(object): + """ Makes modules accessible through openerp.addons.* and odoo.addons.* """ - Import hook to load OpenERP addons from multiple paths. - - 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 <module>` is still supported. Now they - are living in `openerp.addons`. The good way to import such modules is - thus `import openerp.addons.module`. + def find_module(self, name, path): + if name.startswith(('odoo.addons.', 'openerp.addons.'))\ + and name.count('.') == 2: + return self + + def load_module(self, name): + assert name not in sys.modules + + # get canonical names + odoo_name = re.sub(r'^openerp.addons.(\w+)$', r'odoo.addons.\g<1>', name) + openerp_name = re.sub(r'^odoo.addons.(\w+)$', r'openerp.addons.\g<1>', odoo_name) + + assert odoo_name not in sys.modules + assert openerp_name not in sys.modules + + # get module name in addons paths + _1, _2, addon_name = name.split('.') + # load module + f, path, (_suffix, _mode, type_) = imp.find_module(addon_name, ad_paths) + if f: f.close() + + # TODO: fetch existing module from sys.modules if reloads permitted + # create empty openerp.addons.* module, set name + new_mod = types.ModuleType(openerp_name) + new_mod.__loader__ = self + + # module top-level can only be a package + assert type_ == imp.PKG_DIRECTORY, "Odoo addon top-level must be a package" + modfile = opj(path, '__init__.py') + new_mod.__file__ = modfile + new_mod.__path__ = [path] + new_mod.__package__ = openerp_name + + # both base and alias should be in sys.modules to handle recursive and + # corecursive situations + sys.modules[openerp_name] = sys.modules[odoo_name] = new_mod + + # execute source in context of module *after* putting everything in + # sys.modules, so recursive import works + execfile(modfile, new_mod.__dict__) + + # people import openerp.addons and expect openerp.addons.<module> to work + setattr(openerp.addons, addon_name, new_mod) + + return sys.modules[name] +# need to register loader with setuptools as Jinja relies on it when using +# PackageLoader +pkg_resources.register_loader_type(AddonsHook, pkg_resources.DefaultProvider) + +class OdooHook(object): + """ Makes odoo package also available as openerp """ - 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. + def find_module(self, name, path): + # openerp.addons.<identifier> should already be matched by AddonsHook, + # only framework and subdirectories of modules should match + if re.match(r'^odoo\b', name): + return self - def load_module(self, module_name): - if module_name in sys.modules: - return sys.modules[module_name] + def load_module(self, name): + assert name not in sys.modules - _1, _2, module_part = module_name.split('.') - # Note: we don't support circular import. - f, path, descr = imp.find_module(module_part, ad_paths) - mod = imp.load_module('openerp.addons.' + module_part, f, path, descr) - sys.modules['openerp.addons.' + module_part] = mod - return mod + canonical = re.sub(r'^odoo(.*)', r'openerp\g<1>', name) + + if canonical in sys.modules: + mod = sys.modules[canonical] + else: + # probable failure: canonical execution calling old naming -> corecursion + mod = importlib.import_module(canonical) + + # just set the original module at the new location. Don't proxy, + # it breaks *-import (unless you can find how `from a import *` lists + # what's supposed to be imported by `*`, and manage to override it) + sys.modules[name] = mod + + return sys.modules[name] def initialize_sys_path(): """ @@ -89,7 +142,8 @@ def initialize_sys_path(): ad_paths.append(base_path) if not hooked: - sys.meta_path.append(AddonsImportHook()) + sys.meta_path.append(AddonsHook()) + sys.meta_path.append(OdooHook()) hooked = True def get_module_path(module, downloaded=False, display_warning=True): @@ -446,14 +500,14 @@ def run_unit_tests(module_name, dbname, position=runs_at_install): threading.currentThread().testing = True r = True for m in mods: - tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m)) - suite = unittest2.TestSuite(itertools.ifilter(position, tests)) + tests = unwrap_suite(unittest.TestLoader().loadTestsFromModule(m)) + suite = unittest.TestSuite(itertools.ifilter(position, tests)) if suite.countTestCases(): t0 = time.time() t0_sql = openerp.sql_db.sql_counter _logger.info('%s running tests.', m.__name__) - result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite) + result = unittest.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite) if time.time() - t0 > 5: _logger.log(25, "%s tested in %.2fs, %s queries", m.__name__, time.time() - t0, openerp.sql_db.sql_counter - t0_sql) if not result.wasSuccessful(): |
