[IMP] module.py: deprecate openerp

[PEP-594] is deprecating the `imp` module, that module is used in
`module.py` in order to dynamically import addons using any of the
`odoo.addons` or `openerp.addons` import anchor.

We are deprecating `openerp` module/addons imports in v13 in order to
remove the support in v14 and greatly simplify how modules/addons are
loaded. If you are still using the old `import openerp` or `import
openerp.addons`, `import odoo` and `import odoo.addons` are drop-in
replacements.

The `odoo.modules.module.ad_paths` addon paths list has been deprecated
too. The list is now accessible on `odoo.addons.__path__` where they
are now directly loaded [2].

See also:

[PEP-594]: https://python.org/dev/peps/pep-0594/
[2]: https://packaging.python.org/guides/packaging-namespace-packages/

closes odoo/odoo#36597

Task: 2003936
Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
This commit is contained in:
Julien Castiaux
2019-09-16 09:31:38 +00:00
parent 760ab74f83
commit 7c47eb1854
10 changed files with 55 additions and 68 deletions
@@ -8,6 +8,7 @@ import sys
import zipfile
from os.path import join as opj
import odoo
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.modules import load_information_from_description_file
@@ -125,7 +126,7 @@ class IrModule(models.Model):
with tempdir() as module_dir:
import odoo.modules.module as module
try:
module.ad_paths.append(module_dir)
odoo.addons.__path__.append(module_dir)
z.extractall(module_dir)
dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))]
for mod_name in dirs:
@@ -139,7 +140,7 @@ class IrModule(models.Model):
_logger.exception('Error while importing module')
errors[mod_name] = exception_to_unicode(e)
finally:
module.ad_paths.remove(module_dir)
odoo.addons.__path__.remove(module_dir)
r = ["Successfully imported module '%s'" % mod for mod in success]
for mod, error in errors.items():
r.append("Error while importing module '%s'.\n\n %s \n Make sure those modules are installed and try again." % (mod, error))
+2 -2
View File
@@ -10,8 +10,8 @@ from os.path import join as opj
import os
import werkzeug
import odoo
from odoo import models, fields
from odoo.modules.module import ad_paths
class IrTranslation(models.Model):
@@ -27,7 +27,7 @@ class IrTranslation(models.Model):
tx_config_file = ConfigParser()
tx_sections = []
for addon_path in ad_paths:
for addon_path in odoo.addons.__path__:
tx_path = opj(addon_path, '.tx', 'config')
if os.path.isfile(tx_path):
tx_config_file.read(tx_path)
+2 -1
View File
@@ -1155,7 +1155,8 @@ class View(http.Controller):
class Binary(http.Controller):
def placeholder(self, image='placeholder.png'):
return tools.file_open(get_resource_path('web', 'static/src/img', image), 'rb').read()
with tools.file_open(get_resource_path('web', 'static/src/img', image), 'rb') as fd:
return fd.read()
@http.route(['/web/content',
'/web/content/<string:xmlid>',
+2 -1
View File
@@ -100,7 +100,8 @@ class IrAttachment(models.Model):
if bin_size:
r = human_size(os.path.getsize(full_path))
else:
r = base64.b64encode(open(full_path,'rb').read())
with open(full_path,'rb') as fd:
r = base64.b64encode(fd.read())
except (IOError, OSError):
_logger.info("_read_file reading %s", full_path, exc_info=True)
return r
+1 -1
View File
@@ -59,7 +59,7 @@ def report_configuration():
_logger.info("Odoo version %s", __version__)
if os.path.isfile(config.rcfile):
_logger.info("Using configuration file at " + config.rcfile)
_logger.info('addons paths: %s', odoo.modules.module.ad_paths)
_logger.info('addons paths: %s', odoo.addons.__path__)
if config.get('upgrades_paths'):
_logger.info('upgrades path: %s', config['upgrades_paths'])
host = config['db_host'] or os.environ.get('PGHOST', 'default')
+3 -2
View File
@@ -1299,14 +1299,15 @@ class Root(object):
controllers and configure them. """
# TODO should we move this to ir.http so that only configured modules are served ?
statics = {}
for addons_path in odoo.modules.module.ad_paths:
for addons_path in odoo.addons.__path__:
for module in sorted(os.listdir(str(addons_path))):
if module not in addons_manifest:
mod_path = opj(addons_path, module)
manifest_path = module_manifest(mod_path)
path_static = opj(addons_path, module, 'static')
if manifest_path and os.path.isdir(path_static):
manifest_data = open(manifest_path, 'rb').read()
with open(manifest_path, 'rb') as fd:
manifest_data = fd.read()
manifest = ast.literal_eval(pycompat.to_text(manifest_data))
if not manifest.get('installable', True):
continue
+37 -54
View File
@@ -31,60 +31,45 @@ README = ['README.rst', 'README.md', 'README.txt']
_logger = logging.getLogger(__name__)
# addons path as a list
ad_paths = []
# ad_paths is a deprecated alias, please use odoo.addons.__path__
@tools.lazy
def ad_paths():
_logger.warning('"odoo.modules.module.ad_paths" is a deprecated '
'proxy to "odoo.addons.__path__". Please consider '
'using the latter as the former is going to be '
'removed in the next version.',
exc_info=DeprecationWarning(), stack_info=True)
return odoo.addons.__path__
hooked = False
# Modules already loaded
loaded = []
class AddonsHook(object):
""" Makes modules accessible through openerp.addons.* and odoo.addons.* """
""" Makes modules accessible through openerp.addons.* """
def find_module(self, name, path=None):
if name.startswith(('odoo.addons.', 'openerp.addons.'))\
and name.count('.') == 2:
if name.startswith('openerp.addons.') and name.count('.') == 2:
_logger.warning('"openerp.addons" is a deprecated alias to '
'"odoo.addons". Please consider using the '
'latter as the former is going to be removed '
'in the next version.',
exc_info=DeprecationWarning(), stack_info=True)
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
odoo_module = sys.modules.get(odoo_name)
if not odoo_module:
odoo_module = importlib.import_module(odoo_name)
# 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()
sys.modules[name] = odoo_module
# TODO: fetch existing module from sys.modules if reloads permitted
# create empty odoo.addons.* module, set name
new_mod = types.ModuleType(odoo_name)
new_mod.__loader__ = self
return odoo_module
# 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__ = odoo_name
# both base and alias should be in sys.modules to handle recursive and
# corecursive situations
sys.modules[odoo_name] = sys.modules[openerp_name] = new_mod
# execute source in context of module *after* putting everything in
# sys.modules, so recursive import works
exec(open(modfile, 'rb').read(), new_mod.__dict__)
# people import openerp.addons and expect openerp.addons.<module> to work
setattr(odoo.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)
@@ -96,6 +81,11 @@ class OdooHook(object):
# openerp.addons.<identifier> should already be matched by AddonsHook,
# only framework and subdirectories of modules should match
if re.match(r'^openerp\b', name):
_logger.warning('openerp is a deprecated alias to odoo. '
'Please consider using the latter as the '
'former is going to be removed in the next '
'version.',
exc_info=DeprecationWarning(), stack_info=True)
return self
def load_module(self, name):
@@ -125,28 +115,21 @@ def initialize_sys_path():
``import odoo.addons.crm``) works even if the addons are not in the
PYTHONPATH.
"""
global ad_paths
global hooked
dd = os.path.normcase(tools.config.addons_data_dir)
if os.access(dd, os.R_OK) and dd not in ad_paths:
ad_paths.append(dd)
if os.access(dd, os.R_OK) and dd not in odoo.addons.__path__:
odoo.addons.__path__.append(dd)
for ad in tools.config['addons_path'].split(','):
ad = os.path.normcase(os.path.abspath(tools.ustr(ad.strip())))
if ad not in ad_paths:
ad_paths.append(ad)
if ad not in odoo.addons.__path__:
odoo.addons.__path__.append(ad)
# add base module path
base_path = os.path.normcase(os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons')))
if base_path not in ad_paths and os.path.isdir(base_path):
ad_paths.append(base_path)
# add odoo.addons.__path__
for ad in __import__('odoo.addons').addons.__path__:
ad = os.path.abspath(ad)
if ad not in ad_paths and os.path.isdir(ad):
ad_paths.append(ad)
if base_path not in odoo.addons.__path__ and os.path.isdir(base_path):
odoo.addons.__path__.append(base_path)
if not hooked:
sys.meta_path.insert(0, OdooHook())
@@ -162,7 +145,7 @@ def get_module_path(module, downloaded=False, display_warning=True):
"""
initialize_sys_path()
for adp in ad_paths:
for adp in odoo.addons.__path__:
files = [opj(adp, module, manifest) for manifest in MANIFEST_NAMES] +\
[opj(adp, module + '.zip')]
if any(os.path.exists(f) for f in files):
@@ -241,7 +224,7 @@ def get_resource_from_path(path):
:return: tuple(module_name, relative_path, os_relative_path) if possible, else None
"""
resource = False
for adpath in ad_paths:
for adpath in odoo.addons.__path__:
# force trailing separator
adpath = os.path.join(adpath, "")
if os.path.commonprefix([adpath, path]) == adpath:
@@ -338,8 +321,8 @@ def load_information_from_description_file(module, mod_path=None):
readme_path = [opj(mod_path, x) for x in README
if os.path.isfile(opj(mod_path, x))]
if readme_path:
readme_text = tools.file_open(readme_path[0]).read()
info['description'] = readme_text
with tools.file_open(readme_path[0]) as fd:
info['description'] = fd.read()
# auto_install is set to `False` if disabled, and a set of
# auto_install dependencies otherwise. That way, we can set
@@ -418,7 +401,7 @@ def get_modules():
plist = []
initialize_sys_path()
for ad in ad_paths:
for ad in odoo.addons.__path__:
plist.extend(listdir(ad))
return list(set(plist))
+2 -2
View File
@@ -210,7 +210,7 @@ class FSWatcherBase(object):
class FSWatcherWatchdog(FSWatcherBase):
def __init__(self):
self.observer = Observer()
for path in odoo.modules.module.ad_paths:
for path in odoo.addons.__path__:
_logger.info('Watching addons folder %s', path)
self.observer.schedule(self, path, recursive=True)
@@ -236,7 +236,7 @@ class FSWatcherInotify(FSWatcherBase):
inotify.adapters._LOGGER.setLevel(logging.ERROR)
# recreate a list as InotifyTrees' __init__ deletes the list's items
paths_to_watch = []
for path in odoo.modules.module.ad_paths:
for path in odoo.addons.__path__:
paths_to_watch.append(path)
_logger.info('Watching addons folder %s', path)
self.watcher = InotifyTrees(paths_to_watch, mask=INOTIFY_LISTEN_EVENTS, block_duration_s=.5)
+2 -2
View File
@@ -157,7 +157,7 @@ def file_open(name, mode="r", subdir='addons', pathinfo=False):
@return fileobject if pathinfo is False else (fileobject, filepath)
"""
import odoo.modules as addons
adps = addons.module.ad_paths
adps = odoo.addons.__path__
rtp = os.path.normcase(os.path.abspath(config['root_path']))
basename = name
@@ -205,7 +205,7 @@ def _fileopen(path, mode, basedir, pathinfo, basename=None):
name = os.path.normpath(os.path.normcase(os.path.join(basedir, path)))
import odoo.modules as addons
paths = addons.module.ad_paths + [config['root_path']]
paths = odoo.addons.__path__ + [config['root_path']]
for addons_path in paths:
addons_path = os.path.normpath(os.path.normcase(addons_path)) + os.sep
if name.startswith(addons_path):
+1 -1
View File
@@ -954,7 +954,7 @@ def trans_generate(lang, modules, cr):
for m in env['ir.module.module'].search_read([('state', '=', 'installed')], fields=['name'])
]
path_list = [(path, True) for path in odoo.modules.module.ad_paths]
path_list = [(path, True) for path in odoo.addons.__path__]
# Also scan these non-addon paths
for bin_path in ['osv', 'report', 'modules', 'service', 'tools']:
path_list.append((os.path.join(config['root_path'], bin_path), True))