[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:
+80
-26
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user