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

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

37 statements  

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 raise ModuleNotFoundError(f"Unable to import {importable}") 

87 

88 

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

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

91 

92 Parameters 

93 ---------- 

94 importable : `str` 

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

96 or member function. 

97 

98 Returns 

99 ------- 

100 type : `type` 

101 Type object. Can not return a module. 

102 

103 Raises 

104 ------ 

105 TypeError 

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

107 ModuleNotFoundError 

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

109 ImportError 

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

111 item could not be retrieved from the imported module. 

112 """ 

113 imported = doImport(importable) 

114 if isinstance(imported, types.ModuleType): 

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

116 return imported