Hide keyboard shortcuts

Hot-keys 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

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 

13from __future__ import annotations 

14 

15"""Utilities relating to introspection in python.""" 

16 

17__all__ = ["get_class_of", "get_full_type_name", "get_instance_of", "get_caller_name"] 

18 

19import builtins 

20import inspect 

21import types 

22from typing import ( 

23 Any, 

24 Union, 

25 Type, 

26) 

27 

28from .doImport import doImportType, doImport 

29 

30 

31def get_full_type_name(cls: Any) -> str: 

32 """Return full type name of the supplied entity. 

33 

34 Parameters 

35 ---------- 

36 cls : `type` or `object` 

37 Entity from which to obtain the full name. Can be an instance 

38 or a `type`. 

39 

40 Returns 

41 ------- 

42 name : `str` 

43 Full name of type. 

44 

45 Notes 

46 ----- 

47 Builtins are returned without the ``builtins`` specifier included. This 

48 allows `str` to be returned as "str" rather than "builtins.str". Any 

49 parts of the path that start with a leading underscore are removed 

50 on the assumption that they are an implementation detail and the 

51 entity will be hoisted into the parent namespace. 

52 """ 

53 # If we have a module that needs to be converted directly 

54 # to a name. 

55 if isinstance(cls, types.ModuleType): 

56 return cls.__name__ 

57 # If we have an instance we need to convert to a type 

58 if not hasattr(cls, "__qualname__"): 

59 cls = type(cls) 

60 if hasattr(builtins, cls.__qualname__): 

61 # Special case builtins such as str and dict 

62 return cls.__qualname__ 

63 

64 real_name = cls.__module__ + "." + cls.__qualname__ 

65 

66 # Remove components with leading underscores 

67 cleaned_name = ".".join(c for c in real_name.split(".") if not c.startswith("_")) 

68 

69 # Consistency check 

70 if real_name != cleaned_name: 

71 try: 

72 test = doImport(cleaned_name) 

73 except Exception: 

74 # Could not import anything so return the real name 

75 return real_name 

76 

77 # The thing we imported should match the class we started with 

78 # despite the clean up. If it does not we return the real name 

79 if test is not cls: 

80 return real_name 

81 

82 return cleaned_name 

83 

84 

85def get_class_of(typeOrName: Union[Type, str]) -> Type: 

86 """Given the type name or a type, return the python type. 

87 

88 If a type name is given, an attempt will be made to import the type. 

89 

90 Parameters 

91 ---------- 

92 typeOrName : `str` or Python class 

93 A string describing the Python class to load or a Python type. 

94 

95 Returns 

96 ------- 

97 type_ : `type` 

98 Directly returns the Python type if a type was provided, else 

99 tries to import the given string and returns the resulting type. 

100 

101 Notes 

102 ----- 

103 This is a thin wrapper around `~lsst.utils.doImport`. 

104 

105 Raises 

106 ------ 

107 TypeError 

108 Raised if a module is imported rather than a type. 

109 """ 

110 if isinstance(typeOrName, str): 

111 cls = doImportType(typeOrName) 

112 else: 

113 cls = typeOrName 

114 if isinstance(cls, types.ModuleType): 

115 raise TypeError(f"Can not get class of module {get_full_type_name(typeOrName)}") 

116 return cls 

117 

118 

119def get_instance_of(typeOrName: Union[Type, str], *args: Any, **kwargs: Any) -> Any: 

120 """Given the type name or a type, instantiate an object of that type. 

121 

122 If a type name is given, an attempt will be made to import the type. 

123 

124 Parameters 

125 ---------- 

126 typeOrName : `str` or Python class 

127 A string describing the Python class to load or a Python type. 

128 args : `tuple` 

129 Positional arguments to use pass to the object constructor. 

130 **kwargs 

131 Keyword arguments to pass to object constructor. 

132 

133 Returns 

134 ------- 

135 instance : `object` 

136 Instance of the requested type, instantiated with the provided 

137 parameters. 

138 

139 Raises 

140 ------ 

141 TypeError 

142 Raised if a module is imported rather than a type. 

143 """ 

144 cls = get_class_of(typeOrName) 

145 return cls(*args, **kwargs) 

146 

147 

148def get_caller_name(stacklevel: int = 2) -> str: 

149 """Get the name of the caller method. 

150 

151 Any item that cannot be determined (or is not relevant, e.g. a free 

152 function has no class) is silently omitted, along with an 

153 associated separator. 

154 

155 Parameters 

156 ---------- 

157 stacklevel : `int` 

158 How many levels of stack to skip while getting caller name; 

159 1 means "who calls me", 2 means "who calls my caller", etc. 

160 

161 Returns 

162 ------- 

163 name : `str` 

164 Name of the caller as a string in the form ``module.class.method``. 

165 An empty string is returned if ``stacklevel`` exceeds the stack height. 

166 

167 Notes 

168 ----- 

169 Adapted from http://stackoverflow.com/a/9812105 

170 by adding support to get the class from ``parentframe.f_locals['cls']`` 

171 """ 

172 stack = inspect.stack() 

173 start = 0 + stacklevel 

174 if len(stack) < start + 1: 

175 return '' 

176 parentframe = stack[start][0] 

177 

178 name = [] 

179 module = inspect.getmodule(parentframe) 

180 if module: 

181 name.append(module.__name__) 

182 # add class name, if any 

183 if 'self' in parentframe.f_locals: 

184 name.append(type(parentframe.f_locals['self']).__name__) 

185 elif 'cls' in parentframe.f_locals: 

186 name.append(parentframe.f_locals['cls'].__name__) 

187 codename = parentframe.f_code.co_name 

188 if codename != '<module>': # top level usually 

189 name.append(codename) # function or a method 

190 return ".".join(name)