summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Morel <xmo@odoo.com>2015-10-07 14:21:07 +0200
committerXavier Morel <xmo@odoo.com>2015-10-07 15:10:36 +0200
commit9b83dd5894fda3b8fb6a81bd20dd8ea8d6a5b1a4 (patch)
treebfdc5e11d17f5295b2f56c82b6c9a91b69ecaa0e
parent72dee6d52368ae53de793bf42c63f7a7b93fd909 (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.py108
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():