Vo Minh Thu (OpenERP) has proposed merging
lp:~openerp-dev/openobject-server/trunk-import-hook-vmt into
lp:openobject-server.
Requested reviews:
OpenERP Core Team (openerp)
For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-import-hook-vmt/+merge/88664
This replaces sys.path mutation with an import hook. It removes the clash
between our own 'resource' addons and the standard Python resource module. It
provides a transition between our current practice and a cleaner
'openerp.addons' namespace.
The import hook makes it possible to either 'import openerp.addons.hr' or
'import hr' in the addons (ie. both paths are set in sys.modules). When trying
to import an already existing (non-openerp) Python module (e.g. resource), it
will indeed load the Python module and print a warning (to invite you to use
openerp.addons.resource if this is the one you want).
Unfortunately, the import hook doesn't support self-referential imports. E.g.
hr.wizard.xxx can't use 'import hr.yyy' or 'from hr import yyy'. Python will
fail to load hr with cryptic error messages or even a segmentation fault.
Beginning with this patch, 'import openerp.addons.xxx' is the way to go,
'import xxx' is deprecated. Once 'import xxx' support can be removed, we will
probably support pkgutil or setuptool namespaces.
Similar branches for openerp-web and openobject-addons are provided.
--
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-import-hook-vmt/+merge/88664
Your team OpenERP R&D Team is subscribed to branch
lp:~openerp-dev/openobject-server/trunk-import-hook-vmt.
=== modified file 'openerp-server'
--- openerp-server 2012-01-09 14:09:58 +0000
+++ openerp-server 2012-01-16 11:39:25 +0000
@@ -42,6 +42,9 @@
__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 @@
check_root_user()
openerp.tools.config.parse_config(sys.argv[1:])
+
check_postgres_user()
openerp.netsvc.init_logger()
report_configuration()
=== modified file 'openerp/addons/__init__.py'
--- openerp/addons/__init__.py 2011-09-02 13:31:36 +0000
+++ openerp/addons/__init__.py 2012-01-16 11:39:25 +0000
@@ -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.
=== modified file 'openerp/modules/module.py'
--- openerp/modules/module.py 2012-01-07 04:17:45 +0000
+++ openerp/modules/module.py 2012-01-16 11:39:25 +0000
@@ -56,31 +56,109 @@
logger = netsvc.Logger()
+class AddonsImportHook(object):
+ """
+ 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`.
+
+ 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>.`.""" % (module_name, path))
+ return
+ except ImportError, e:
+ # Using `import <module_name>` instead of
+ # `import openerp.addons.<module_name>` 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 <module_name>` instead of
+ # `import openerp.addons.<module_name>` 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():
- """ Add all addons paths in sys.path.
+ """
+ Setup an import-hook to be able to import OpenERP addons from the different
+ addons paths.
- This ensures something like ``import crm`` works even if the addons are
- not in the PYTHONPATH.
+ 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(','))
-
- 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):
+ ad_paths.append(_ad) # for get_module_path
+ sys.meta_path.append(AddonsImportHook())
+
+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 @@
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 @@
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 @@
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)
=== modified file 'openerp/osv/orm.py'
--- openerp/osv/orm.py 2012-01-10 15:43:33 +0000
+++ openerp/osv/orm.py 2012-01-16 11:39:25 +0000
@@ -608,7 +608,16 @@
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
=== modified file 'openerp/tools/config.py'
--- openerp/tools/config.py 2011-12-23 15:22:11 +0000
+++ openerp/tools/config.py 2012-01-16 11:39:25 +0000
@@ -461,9 +461,6 @@
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):
"""
_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help : https://help.launchpad.net/ListHelp