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(f"Could not get attribute '{f}' from '{module}' {extra}") 

57 return pytype 

58 

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

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

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

62 moduleComponents = importable.split(".") 

63 infileComponents: List[str] = [] 

64 previousError = None 

65 

66 while moduleComponents: 

67 try: 

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

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

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

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

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

73 # return the attribute, not the module. 

74 try: 

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

76 except ModuleNotFoundError: 

77 pass 

78 return pytype 

79 except ModuleNotFoundError as e: 

80 previousError = str(e) 

81 # Move element from module to file and try again 

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

83 

84 raise ModuleNotFoundError(f"Unable to import {importable}") 

85 

86 

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

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

89 

90 Parameters 

91 ---------- 

92 importable : `str` 

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

94 or member function. 

95 

96 Returns 

97 ------- 

98 type : `type` 

99 Type object. Can not return a module. 

100 

101 Raises 

102 ------ 

103 TypeError 

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

105 ModuleNotFoundError 

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

107 ImportError 

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

109 item could not be retrieved from the imported module. 

110 """ 

111 imported = doImport(importable) 

112 if isinstance(imported, types.ModuleType): 

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

114 return imported