Coverage for physioblocks / utils / dynamic_import_utils.py: 87%

46 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-09 16:40 +0100

1# SPDX-FileCopyrightText: Copyright INRIA 

2# 

3# SPDX-License-Identifier: LGPL-3.0-only 

4# 

5# Copyright INRIA 

6# 

7# This file is part of PhysioBlocks, a library mostly developed by the 

8# [Ananke project-team](https://team.inria.fr/ananke) at INRIA. 

9# 

10# Authors: 

11# - Colin Drieu 

12# - Dominique Chapelle 

13# - François Kimmig 

14# - Philippe Moireau 

15# 

16# PhysioBlocks is free software: you can redistribute it and/or modify it under the 

17# terms of the GNU Lesser General Public License as published by the Free Software 

18# Foundation, version 3 of the License. 

19# 

20# PhysioBlocks is distributed in the hope that it will be useful, but WITHOUT ANY 

21# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 

22# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 

23# 

24# You should have received a copy of the GNU Lesser General Public License along with 

25# PhysioBlocks. If not, see <https://www.gnu.org/licenses/>. 

26 

27""" 

28Defines methods to dynamically import modules while in a script or python 

29application. 

30""" 

31 

32import importlib 

33import logging 

34import pkgutil 

35import sys 

36from pathlib import Path 

37 

38from physioblocks.utils.exceptions_utils import log_exception 

39 

40_logger = logging.getLogger(__name__) 

41 

42 

43def import_libraries(libraries_folder_paths: list[Path]) -> None: 

44 """ 

45 Dynamically import all the modules at the given paths. 

46 

47 .. note:: The libraries folders must be in a site to be able to 

48 load theirs modules. 

49 

50 :param libraries_folder_path: the paths 

51 :type libraries_folder_path: list[Path] 

52 

53 Example 

54 ^^^^^^^ 

55 

56 .. code:: python 

57 

58 # Add site for the library to load 

59 site.addsitedir(ABSOLUTE_PATH_TO_LIBRARY) 

60 

61 lib_path = Path(ABSOLUTE_PATH_TO_LIBRARY) 

62 import_libraries([lib_path]) # dynamically import the library 

63 

64 """ 

65 packages_absolute_paths: list[tuple[Path, str]] = [] 

66 

67 for library_path in libraries_folder_paths: 

68 if library_path.exists() is True: 

69 absolute_path = library_path.absolute() 

70 full_package_name = _get_full_package_name(absolute_path) 

71 packages_absolute_paths.append((absolute_path, full_package_name)) 

72 else: 

73 _logger.error( 

74 str.format( 

75 "There is no library folder at path {0}. Path skipped.", 

76 str(library_path), 

77 ) 

78 ) 

79 

80 for package_path, full_package_name in packages_absolute_paths: 

81 _import_modules_recursivly_at_path(package_path, full_package_name) 

82 

83 

84def _get_full_package_name(package_path: Path) -> str: 

85 if _is_package(package_path) is False: 

86 raise ImportError(str.format("{0} is not a package", package_path.name)) 

87 

88 full_package_name = package_path.name 

89 parent = package_path.parent 

90 check_parent = True 

91 while check_parent is True: 

92 check_parent = False 

93 if _is_package(parent) is True: 

94 full_package_name = ".".join([parent.name, full_package_name]) 

95 parent = parent.parent 

96 check_parent = True 

97 

98 return full_package_name 

99 

100 

101def _is_package(dir_path: Path) -> bool: 

102 return ( 

103 any( 

104 path.is_file() and path.name == "__init__.py" for path in dir_path.iterdir() 

105 ) 

106 is True 

107 ) 

108 

109 

110def _import_modules_recursivly_at_path( 

111 package_path: Path, full_package_name: str 

112) -> None: 

113 for module_info in pkgutil.walk_packages([str(package_path)]): 

114 full_module_name = module_info.name 

115 if full_package_name is not None: 

116 full_module_name = ".".join([full_package_name, module_info.name]) 

117 if full_module_name in sys.modules: 

118 # already loaded module 

119 _logger.warning( 

120 str.format( 

121 "Module {0} at {1} already loaded. Module skipped ", 

122 full_module_name, 

123 str(package_path), 

124 ) 

125 ) 

126 else: 

127 try: 

128 importlib.import_module(full_module_name) 

129 except ImportError as import_exception: 

130 # Error while loading the module, log and skip the module 

131 log_exception( 

132 _logger, 

133 ImportError, 

134 import_exception, 

135 import_exception.__traceback__, 

136 logging.WARNING, 

137 ) 

138 _logger.warning( 

139 str.format( 

140 "Import Error while loading {0} from {1}. Module skipped ", 

141 full_module_name, 

142 str(package_path), 

143 ) 

144 ) 

145 

146 if module_info.ispkg is True: 

147 # recursivle load packa submodules 

148 _import_modules_recursivly_at_path( 

149 package_path / module_info.name, full_module_name 

150 )