diff --git a/monai/__init__.py b/monai/__init__.py index 46f7510915..f6fc8b0646 100644 --- a/monai/__init__.py +++ b/monai/__init__.py @@ -80,25 +80,29 @@ def filter(self, record): ) -from . import ( # noqa: E402 - apps, - auto3dseg, - bundle, - config, - data, - engines, - fl, - handlers, - inferers, - losses, - metrics, - networks, - optimizers, - transforms, - utils, - visualize, +from .utils.module import load_submodules # noqa: E402 + +# handlers_* have some external decorators the users may not have installed +# *.so files and folder "_C" may not exist when the cpp extensions are not compiled +excludes = "|".join( + [ + "(^(monai.handlers))", + "(^(monai.bundle))", + "(^(monai.fl))", + "((\\.so)$)", + "(^(monai._C))", + "(.*(__main__)$)", + "(.*(video_dataset)$)", + "(.*(nnunet).*$)", + ] ) +# load directory modules only, skip loading individual files +load_submodules(sys.modules[__name__], False, exclude_pattern=excludes) + +# load all modules, this will trigger all export decorations +load_submodules(sys.modules[__name__], True, exclude_pattern=excludes) + __all__ = [ "apps", "auto3dseg", diff --git a/monai/utils/__init__.py b/monai/utils/__init__.py index b5b2d7a525..916c1a6c70 100644 --- a/monai/utils/__init__.py +++ b/monai/utils/__init__.py @@ -112,6 +112,7 @@ get_package_version, get_torch_version_tuple, instantiate, + load_submodules, look_up_option, min_version, optional_import, diff --git a/monai/utils/module.py b/monai/utils/module.py index 3f07dd9e91..df5fe873ae 100644 --- a/monai/utils/module.py +++ b/monai/utils/module.py @@ -16,13 +16,15 @@ import os import pdb import re +import sys import warnings from collections.abc import Callable, Collection, Hashable, Mapping from functools import partial, wraps from importlib import import_module +from pkgutil import walk_packages from pydoc import locate from re import match -from types import FunctionType +from types import FunctionType, ModuleType from typing import Any, Iterable, cast import torch @@ -168,6 +170,38 @@ def damerau_levenshtein_distance(s1: str, s2: str) -> int: return d[string_1_length - 1, string_2_length - 1] +def load_submodules( + basemod: ModuleType, load_all: bool = True, exclude_pattern: str = "(.*[tT]est.*)|(_.*)" +) -> tuple[list[ModuleType], list[str]]: + """ + Traverse the source of the module structure starting with module `basemod`, loading all packages plus all files if + `load_all` is True, excluding anything whose name matches `exclude_pattern`. + """ + submodules = [] + err_mod: list[str] = [] + for importer, name, is_pkg in walk_packages( + basemod.__path__, prefix=basemod.__name__ + ".", onerror=err_mod.append + ): + if (is_pkg or load_all) and name not in sys.modules and match(exclude_pattern, name) is None: + try: + mod = import_module(name) + mod_spec = importer.find_spec(name) # type: ignore + if mod_spec and mod_spec.loader: + loader = mod_spec.loader + loader.exec_module(mod) + submodules.append(mod) + except OptionalImportError: + pass # could not import the optional deps., they are ignored + except ImportError as e: + msg = ( + "\nMultiple versions of MONAI may have been installed?\n" + "Please see the installation guide: https://docs.monai.io/en/stable/installation.html\n" + ) # issue project-monai/monai#5193 + raise type(e)(f"{e}\n{msg}").with_traceback(e.__traceback__) from e # raise with modified message + + return submodules, err_mod + + def instantiate(__path: str, __mode: str, **kwargs: Any) -> Any: """ Create an object instance or call a callable object from a class or function represented by ``_path``. diff --git a/tests/min_tests.py b/tests/min_tests.py index f5b715e979..f39d3f9843 100644 --- a/tests/min_tests.py +++ b/tests/min_tests.py @@ -232,6 +232,12 @@ def run_testsuit(): if __name__ == "__main__": + # testing import submodules + from monai.utils.module import load_submodules + + _, err_mod = load_submodules(sys.modules["monai"], True) + assert not err_mod, f"err_mod={err_mod} not empty" + # testing all modules test_runner = unittest.TextTestRunner(stream=sys.stdout, verbosity=2) result = test_runner.run(run_testsuit())