[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
This commit is contained in:
Xavier Morel
2015-10-07 15:10:36 +02:00
parent 72dee6d523
commit 9b83dd5894
+80 -26
View File
@@ -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.
def find_module(self, name, path):
if name.startswith(('odoo.addons.', 'openerp.addons.'))\
and name.count('.') == 2:
return self
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 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():