diff --git a/trytond/bin/trytond b/trytond/bin/trytond index ead75aa34f..df4169e039 100755 --- a/trytond/bin/trytond +++ b/trytond/bin/trytond @@ -33,7 +33,7 @@ if options.coroutine: from gevent import monkey monkey.patch_all() -from trytond.modules import get_module_info, get_module_list +from trytond.modules import get_module_info, get_modules from trytond.pool import Pool # Import trytond things after it is configured from trytond.wsgi import app @@ -67,7 +67,7 @@ with commandline.pidfile(options): else: ssl_args = {} if options.dev and not options.coroutine: - for module in get_module_list(): + for module in get_modules(): info = get_module_info(module) for ext in ['xml', 'fodt', 'odt', 'fodp', 'odp', 'fods', 'ods', 'fodg', 'odg', diff --git a/trytond/trytond/commandline.py b/trytond/trytond/commandline.py index 0e6a39afe9..c95517a088 100644 --- a/trytond/trytond/commandline.py +++ b/trytond/trytond/commandline.py @@ -25,8 +25,8 @@ def database_completer(parsed_args, **kwargs): def module_completer(**kwargs): - from trytond.modules import get_module_list - return get_module_list() + from trytond.modules import get_modules + return get_modules() def language_completer(**kwargs): diff --git a/trytond/trytond/ir/lang.py b/trytond/trytond/ir/lang.py index fe9b968645..ae2a07bb76 100644 --- a/trytond/trytond/ir/lang.py +++ b/trytond/trytond/ir/lang.py @@ -239,7 +239,7 @@ class Lang(DeactivableMixin, ModelSQL, ModelView): modules = Module.search([ ('state', '=', 'activated'), ]) - modules = [m.name for m in modules] + modules = {m.name for m in modules} for node in create_graph(modules): load_translations(pool, node, codes) diff --git a/trytond/trytond/ir/module.py b/trytond/trytond/ir/module.py index 9a43ffe727..c9de521dbe 100644 --- a/trytond/trytond/ir/module.py +++ b/trytond/trytond/ir/module.py @@ -10,7 +10,7 @@ from trytond.exceptions import UserError from trytond.i18n import gettext from trytond.model import ModelSQL, ModelView, Unique, fields, sequence_ordered from trytond.model.exceptions import AccessError -from trytond.modules import get_module_info, get_module_list +from trytond.modules import get_module_info, get_modules from trytond.pool import Pool from trytond.pyson import Eval from trytond.rpc import RPC @@ -243,7 +243,7 @@ class Module(ModelSQL, ModelView): "Update the list of available modules" pool = Pool() Dependency = pool.get('ir.module.dependency') - module_names = get_module_list(with_test=Pool.test) + module_names = get_modules(with_test=Pool.test) for sub_module_names in grouped_slice(module_names): cls.delete(cls.search([ ('state', '!=', 'activated'), diff --git a/trytond/trytond/ir/ui/view.py b/trytond/trytond/ir/ui/view.py index 267858373b..eaeba0070f 100644 --- a/trytond/trytond/ir/ui/view.py +++ b/trytond/trytond/ir/ui/view.py @@ -221,9 +221,9 @@ class View(ModelSQL, ModelView): @property def _module_index(self): - from trytond.modules import create_graph, get_module_list + from trytond.modules import create_graph, get_modules if self.__class__.__module_index is None: - graph = create_graph(get_module_list(with_test=Pool.test)) + graph = create_graph(get_modules(with_test=Pool.test)) modules = [m.name for m in graph] self.__class__.__module_index = { m: i for i, m in enumerate(reversed(modules))} diff --git a/trytond/trytond/modules/__init__.py b/trytond/trytond/modules/__init__.py index 3d7a2e4ca5..d987565d0d 100644 --- a/trytond/trytond/modules/__init__.py +++ b/trytond/trytond/modules/__init__.py @@ -5,10 +5,13 @@ import importlib import itertools import logging import os -import sys from collections import defaultdict from glob import iglob -from importlib.machinery import SOURCE_SUFFIXES, FileFinder, SourceFileLoader + +try: + import pkg_resources +except ImportError: + pkg_resources = None from sql import Table from sql.functions import CurrentTimestamp @@ -45,40 +48,6 @@ def update_egg_modules(): update_egg_modules() -def import_module(name, fullname=None): - if fullname is None: - fullname = 'trytond.modules.' + name - try: - module = importlib.import_module(fullname) - except ImportError: - if name not in EGG_MODULES: - raise - ep = EGG_MODULES[name] - # Can not use ep.load because modules are declared in an importable - # path and it can not import submodule. - path = os.path.join( - ep.dist.location, *ep.module_name.split('.')[:-1]) - if not os.path.isdir(path): - # Find module in path - for path in sys.path: - path = os.path.join( - path, *ep.module_name.split('.')[:-1]) - if os.path.isdir(os.path.join(path, name)): - break - else: - # When testing modules from setuptools location is the - # module directory - path = os.path.dirname(ep.dist.location) - spec = FileFinder( - path, (SourceFileLoader, SOURCE_SUFFIXES) - ).find_spec(fullname) - if spec.loader: - module = spec.loader.load_module() - else: - raise - return module - - def get_module_info(name): "Return the content of the tryton.cfg" module_config = configparser.ConfigParser() @@ -139,20 +108,19 @@ class Node(list): super(Node, self).append(node) -def create_graph(module_list): - module_list = set(module_list) +def create_graph(modules): all_deps = set() graph = Graph() - for module in module_list: + for module in modules: info = get_module_info(module) deps = info.get('depends', []) + [ - d for d in info.get('extras_depend', []) if d in module_list] + d for d in info.get('extras_depend', []) if d in modules] node = graph.add(module, deps) assert node.info is None node.info = info all_deps.update(deps) - missing = all_deps - module_list + missing = all_deps - modules if missing: raise MissingDependenciesException(list(missing)) return graph @@ -323,8 +291,8 @@ def load_module_graph(graph, pool, update=None, lang=None): logger.info('all modules loaded') -def get_module_list(with_test=False): - module_list = set() +def get_modules(with_test=False): + modules = {'ir', 'res'} if os.path.exists(MODULES_PATH) and os.path.isdir(MODULES_PATH): for file in os.listdir(MODULES_PATH): if file.startswith('.'): @@ -332,14 +300,13 @@ def get_module_list(with_test=False): if file == '__pycache__': continue if os.path.isdir(OPJ(MODULES_PATH, file)): - module_list.add(file) - update_egg_modules() - module_list.update(EGG_MODULES.keys()) - module_list.add('ir') - module_list.add('res') + modules.add(file) + if pkg_resources: + for ep in pkg_resources.iter_entry_points('trytond.modules'): + modules.add(ep.name) if with_test: - module_list.add('tests') - return list(module_list) + modules.add('tests') + return modules def register_classes(with_test=False): @@ -354,19 +321,19 @@ def register_classes(with_test=False): import trytond.tests trytond.tests.register() - for node in create_graph(get_module_list(with_test=with_test)): - module = node.name - logger.info('%s:registering classes', module) + for node in create_graph(get_modules(with_test=with_test)): + module_name = node.name + logger.info('%s:registering classes', module_name) - if module in ('ir', 'res', 'tests'): - MODULES.append(module) + if module_name in ('ir', 'res', 'tests'): + MODULES.append(module_name) continue - the_module = import_module(module) + module = importlib.import_module(f'trytond.modules.{module_name}') # Some modules register nothing in the Pool - if hasattr(the_module, 'register'): - the_module.register() - MODULES.append(module) + if hasattr(module, 'register'): + module.register() + MODULES.append(module_name) def load_modules( @@ -392,12 +359,12 @@ def load_modules( cursor.execute(*ir_module.select(ir_module.name, where=ir_module.state.in_(('activated', 'to upgrade', 'to remove')))) - module_list = [name for (name,) in cursor] + modules = {name for (name,) in cursor} graph = None while graph is None: - module_list += update + modules.update(update) try: - graph = create_graph(module_list) + graph = create_graph(modules) except MissingDependenciesException as e: if not activatedeps: raise diff --git a/trytond/trytond/tools/misc.py b/trytond/trytond/tools/misc.py index 3ef04b42c7..ee83a34e66 100644 --- a/trytond/trytond/tools/misc.py +++ b/trytond/trytond/tools/misc.py @@ -8,7 +8,6 @@ import importlib import io import os import re -import sys import types import unicodedata import warnings @@ -32,7 +31,6 @@ def file_open(name, mode="r", subdir='modules', encoding=None): def find_path(name, subdir='modules', _test=os.path.isfile): "Return path from the root directory, using subdir folder" - from trytond.modules import EGG_MODULES root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) def secure_join(root, *paths): @@ -43,45 +41,31 @@ def find_path(name, subdir='modules', _test=os.path.isfile): raise IOError("Permission denied: %s" % name) return path - egg_name = False - if subdir == 'modules': - module_name = name.split(os.sep)[0] - if module_name in EGG_MODULES: - epoint = EGG_MODULES[module_name] - location = os.path.dirname(epoint.load().__file__) - mod_path = os.path.join(location, - *epoint.module_name.split('.')[:-1]) - mod_path = os.path.abspath(mod_path) - egg_name = secure_join(mod_path, name) - if not _test(egg_name): - # Find module in path - for path in sys.path: - mod_path = os.path.join(path, - *epoint.module_name.split('.')[:-1]) - mod_path = os.path.abspath(mod_path) - egg_name = secure_join(mod_path, name) - if _test(egg_name): - break - if not _test(egg_name): - # When testing modules from setuptools location is the - # module directory - egg_name = secure_join( - os.path.dirname(location), name) - if subdir: - if (subdir == 'modules' - and (name.startswith('ir' + os.sep) - or name.startswith('res' + os.sep) - or name.startswith('tests' + os.sep))): - name = secure_join(root_path, name) + if subdir == 'modules': + try: + module_name, module_path = name.split(os.sep, 1) + except ValueError: + module_name, module_path = name, None + if module_name in {'ir', 'res', 'tests'}: + path = secure_join(root_path, module_name, module_path) + else: + try: + module = importlib.import_module( + f'trytond.modules.{module_name}') + except ModuleNotFoundError: + path = secure_join(root_path, subdir, name) + else: + path = os.path.dirname(module.__file__) + if module_path: + path = secure_join(path, module_path) else: - name = secure_join(root_path, subdir, name) + path = secure_join(root_path, subdir, name) else: - name = secure_join(root_path, name) + path = secure_join(root_path, name) - for path in [name, egg_name]: - if path and _test(path): - return path + if _test(path): + return path else: raise FileNotFoundError("No such file or directory: %r" % name)