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

38 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-06 03:35 -0700

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 

12from __future__ import annotations 

13 

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

15 

16import importlib 

17import types 

18 

19 

20def doImport(importable: str) -> types.ModuleType | type: 

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

22 

23 Parameters 

24 ---------- 

25 importable : `str` 

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

27 or member function. 

28 

29 Returns 

30 ------- 

31 type : `type` 

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

33 

34 Raises 

35 ------ 

36 TypeError 

37 ``importable`` is not a `str`. 

38 ModuleNotFoundError 

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

40 ImportError 

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

42 item could not be retrieved from the imported module. 

43 """ 

44 if not isinstance(importable, str): 

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

46 

47 def tryImport(module: str, fromlist: list[str], previousError: str | None) -> types.ModuleType | type: 

48 pytype = importlib.import_module(module) 

49 # Can have functions inside classes inside modules 

50 for f in fromlist: 

51 try: 

52 pytype = getattr(pytype, f) 

53 except AttributeError as e: 

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

55 raise ImportError( 

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

57 ) from e 

58 return pytype 

59 

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

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

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

63 moduleComponents = importable.split(".") 

64 infileComponents: list[str] = [] 

65 previousError = None 

66 

67 while moduleComponents: 

68 try: 

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

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

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

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

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

74 # return the attribute, not the module. 

75 try: 

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

77 except ModuleNotFoundError: 

78 pass 

79 return pytype 

80 except ModuleNotFoundError as e: 

81 previousError = str(e) 

82 # Move element from module to file and try again 

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

84 

85 # Fell through without success. 

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

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

88 

89 

90def doImportType(importable: str) -> type: 

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

92 

93 Parameters 

94 ---------- 

95 importable : `str` 

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

97 or member function. 

98 

99 Returns 

100 ------- 

101 type : `type` 

102 Type object. Can not return a module. 

103 

104 Raises 

105 ------ 

106 TypeError 

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

108 ModuleNotFoundError 

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

110 ImportError 

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

112 item could not be retrieved from the imported module. 

113 """ 

114 imported = doImport(importable) 

115 if isinstance(imported, types.ModuleType): 

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

117 return imported