Coverage for python/lsst/utils/doImport.py: 12%

38 statements  

« prev     ^ index     » next       coverage.py v7.2.4, created at 2023-04-29 09:54 +0000

1# This file is part of utils. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# Use of this source code is governed by a 3-clause BSD-style 

10# license that can be found in the LICENSE file. 

11 

12__all__ = ("doImport", "doImportType") 

13 

14import importlib 

15import types 

16from typing import List, Optional, Type, Union 

17 

18 

19def doImport(importable: str) -> Union[types.ModuleType, Type]: 

20 """Import a python object given an importable string and return it. 

21 

22 Parameters 

23 ---------- 

24 importable : `str` 

25 String containing dot-separated path of a Python class, module, 

26 or member function. 

27 

28 Returns 

29 ------- 

30 type : `type` 

31 Type object. Either a module or class or a function. 

32 

33 Raises 

34 ------ 

35 TypeError 

36 ``importable`` is not a `str`. 

37 ModuleNotFoundError 

38 No module in the supplied import string could be found. 

39 ImportError 

40 ``importable`` is found but can not be imported or the requested 

41 item could not be retrieved from the imported module. 

42 """ 

43 if not isinstance(importable, str): 

44 raise TypeError(f"Unhandled type of importable, val: {importable}") 

45 

46 def tryImport( 

47 module: str, fromlist: List[str], previousError: Optional[str] 

48 ) -> Union[types.ModuleType, Type]: 

49 pytype = importlib.import_module(module) 

50 # Can have functions inside classes inside modules 

51 for f in fromlist: 

52 try: 

53 pytype = getattr(pytype, f) 

54 except AttributeError: 

55 extra = f"({previousError})" if previousError is not None else "" 

56 raise ImportError( 

57 f"Could not get attribute '{f}' from '{module}' when importing '{importable}' {extra}" 

58 ) 

59 return pytype 

60 

61 # Go through the import path attempting to load the module 

62 # and retrieve the class or function as an attribute. Shift components 

63 # from the module list to the attribute list until something works. 

64 moduleComponents = importable.split(".") 

65 infileComponents: List[str] = [] 

66 previousError = None 

67 

68 while moduleComponents: 

69 try: 

70 pytype = tryImport(".".join(moduleComponents), infileComponents, previousError) 

71 if not infileComponents and hasattr(pytype, moduleComponents[-1]): 

72 # This module has an attribute with the same name as the 

73 # module itself (like doImport.doImport, actually!). 

74 # If that attribute was lifted to the package, we should 

75 # return the attribute, not the module. 

76 try: 

77 return tryImport(".".join(moduleComponents[:-1]), moduleComponents[-1:], previousError) 

78 except ModuleNotFoundError: 

79 pass 

80 return pytype 

81 except ModuleNotFoundError as e: 

82 previousError = str(e) 

83 # Move element from module to file and try again 

84 infileComponents.insert(0, moduleComponents.pop()) 

85 

86 # Fell through without success. 

87 extra = f"({previousError})" if previousError is not None else "" 

88 raise ModuleNotFoundError(f"Unable to import {importable!r} {extra}") 

89 

90 

91def doImportType(importable: str) -> Type: 

92 """Import a python type given an importable string and return it. 

93 

94 Parameters 

95 ---------- 

96 importable : `str` 

97 String containing dot-separated path of a Python class, 

98 or member function. 

99 

100 Returns 

101 ------- 

102 type : `type` 

103 Type object. Can not return a module. 

104 

105 Raises 

106 ------ 

107 TypeError 

108 ``importable`` is not a `str` or the imported type is a module. 

109 ModuleNotFoundError 

110 No module in the supplied import string could be found. 

111 ImportError 

112 ``importable`` is found but can not be imported or the requested 

113 item could not be retrieved from the imported module. 

114 """ 

115 imported = doImport(importable) 

116 if isinstance(imported, types.ModuleType): 

117 raise TypeError(f"Import of {importable} returned a module and not a type.") 

118 return imported