Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rich/_inspect.py: 3%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

146 statements  

1import inspect 

2from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature 

3from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union 

4 

5from .console import Group, RenderableType 

6from .control import escape_control_codes 

7from .highlighter import ReprHighlighter 

8from .jupyter import JupyterMixin 

9from .panel import Panel 

10from .pretty import Pretty 

11from .table import Table 

12from .text import Text, TextType 

13 

14 

15def _first_paragraph(doc: str) -> str: 

16 """Get the first paragraph from a docstring.""" 

17 paragraph, _, _ = doc.partition("\n\n") 

18 return paragraph 

19 

20 

21class Inspect(JupyterMixin): 

22 """A renderable to inspect any Python Object. 

23 

24 Args: 

25 obj (Any): An object to inspect. 

26 title (str, optional): Title to display over inspect result, or None use type. Defaults to None. 

27 help (bool, optional): Show full help text rather than just first paragraph. Defaults to False. 

28 methods (bool, optional): Enable inspection of callables. Defaults to False. 

29 docs (bool, optional): Also render doc strings. Defaults to True. 

30 private (bool, optional): Show private attributes (beginning with underscore). Defaults to False. 

31 dunder (bool, optional): Show attributes starting with double underscore. Defaults to False. 

32 sort (bool, optional): Sort attributes alphabetically, callables at the top, leading and trailing underscores ignored. Defaults to True. 

33 all (bool, optional): Show all attributes. Defaults to False. 

34 value (bool, optional): Pretty print value of object. Defaults to True. 

35 """ 

36 

37 def __init__( 

38 self, 

39 obj: Any, 

40 *, 

41 title: Optional[TextType] = None, 

42 help: bool = False, 

43 methods: bool = False, 

44 docs: bool = True, 

45 private: bool = False, 

46 dunder: bool = False, 

47 sort: bool = True, 

48 all: bool = True, 

49 value: bool = True, 

50 ) -> None: 

51 self.highlighter = ReprHighlighter() 

52 self.obj = obj 

53 self.title = title or self._make_title(obj) 

54 if all: 

55 methods = private = dunder = True 

56 self.help = help 

57 self.methods = methods 

58 self.docs = docs or help 

59 self.private = private or dunder 

60 self.dunder = dunder 

61 self.sort = sort 

62 self.value = value 

63 

64 def _make_title(self, obj: Any) -> Text: 

65 """Make a default title.""" 

66 title_str = ( 

67 str(obj) 

68 if (isclass(obj) or callable(obj) or ismodule(obj)) 

69 else str(type(obj)) 

70 ) 

71 title_text = self.highlighter(title_str) 

72 return title_text 

73 

74 def __rich__(self) -> Panel: 

75 return Panel.fit( 

76 Group(*self._render()), 

77 title=self.title, 

78 border_style="scope.border", 

79 padding=(0, 1), 

80 ) 

81 

82 def _get_signature(self, name: str, obj: Any) -> Optional[Text]: 

83 """Get a signature for a callable.""" 

84 try: 

85 _signature = str(signature(obj)) + ":" 

86 except ValueError: 

87 _signature = "(...)" 

88 except TypeError: 

89 return None 

90 

91 source_filename: Optional[str] = None 

92 try: 

93 source_filename = getfile(obj) 

94 except (OSError, TypeError): 

95 # OSError is raised if obj has no source file, e.g. when defined in REPL. 

96 pass 

97 

98 callable_name = Text(name, style="inspect.callable") 

99 if source_filename: 

100 callable_name.stylize(f"link file://{source_filename}") 

101 signature_text = self.highlighter(_signature) 

102 

103 qualname = name or getattr(obj, "__qualname__", name) 

104 if not isinstance(qualname, str): 

105 qualname = getattr(obj, "__name__", name) 

106 if not isinstance(qualname, str): 

107 qualname = name 

108 

109 # If obj is a module, there may be classes (which are callable) to display 

110 if inspect.isclass(obj): 

111 prefix = "class" 

112 elif inspect.iscoroutinefunction(obj): 

113 prefix = "async def" 

114 else: 

115 prefix = "def" 

116 

117 qual_signature = Text.assemble( 

118 (f"{prefix} ", f"inspect.{prefix.replace(' ', '_')}"), 

119 (qualname, "inspect.callable"), 

120 signature_text, 

121 ) 

122 

123 return qual_signature 

124 

125 def _render(self) -> Iterable[RenderableType]: 

126 """Render object.""" 

127 

128 def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]: 

129 key, (_error, value) = item 

130 return (callable(value), key.strip("_").lower()) 

131 

132 def safe_getattr(attr_name: str) -> Tuple[Any, Any]: 

133 """Get attribute or any exception.""" 

134 try: 

135 return (None, getattr(obj, attr_name)) 

136 except Exception as error: 

137 return (error, None) 

138 

139 obj = self.obj 

140 keys = dir(obj) 

141 total_items = len(keys) 

142 if not self.dunder: 

143 keys = [key for key in keys if not key.startswith("__")] 

144 if not self.private: 

145 keys = [key for key in keys if not key.startswith("_")] 

146 not_shown_count = total_items - len(keys) 

147 items = [(key, safe_getattr(key)) for key in keys] 

148 if self.sort: 

149 items.sort(key=sort_items) 

150 

151 items_table = Table.grid(padding=(0, 1), expand=False) 

152 items_table.add_column(justify="right") 

153 add_row = items_table.add_row 

154 highlighter = self.highlighter 

155 

156 if callable(obj): 

157 signature = self._get_signature("", obj) 

158 if signature is not None: 

159 yield signature 

160 yield "" 

161 

162 if self.docs: 

163 _doc = self._get_formatted_doc(obj) 

164 if _doc is not None: 

165 doc_text = Text(_doc, style="inspect.help") 

166 doc_text = highlighter(doc_text) 

167 yield doc_text 

168 yield "" 

169 

170 if self.value and not (isclass(obj) or callable(obj) or ismodule(obj)): 

171 yield Panel( 

172 Pretty(obj, indent_guides=True, max_length=10, max_string=60), 

173 border_style="inspect.value.border", 

174 ) 

175 yield "" 

176 

177 for key, (error, value) in items: 

178 key_text = Text.assemble( 

179 ( 

180 key, 

181 "inspect.attr.dunder" if key.startswith("__") else "inspect.attr", 

182 ), 

183 (" =", "inspect.equals"), 

184 ) 

185 if error is not None: 

186 warning = key_text.copy() 

187 warning.stylize("inspect.error") 

188 add_row(warning, highlighter(repr(error))) 

189 continue 

190 

191 if callable(value): 

192 if not self.methods: 

193 continue 

194 

195 _signature_text = self._get_signature(key, value) 

196 if _signature_text is None: 

197 add_row(key_text, Pretty(value, highlighter=highlighter)) 

198 else: 

199 if self.docs: 

200 docs = self._get_formatted_doc(value) 

201 if docs is not None: 

202 _signature_text.append("\n" if "\n" in docs else " ") 

203 doc = highlighter(docs) 

204 doc.stylize("inspect.doc") 

205 _signature_text.append(doc) 

206 

207 add_row(key_text, _signature_text) 

208 else: 

209 add_row(key_text, Pretty(value, highlighter=highlighter)) 

210 if items_table.row_count: 

211 yield items_table 

212 elif not_shown_count: 

213 yield Text.from_markup( 

214 f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] " 

215 f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options." 

216 ) 

217 

218 def _get_formatted_doc(self, object_: Any) -> Optional[str]: 

219 """ 

220 Extract the docstring of an object, process it and returns it. 

221 The processing consists in cleaning up the docstring's indentation, 

222 taking only its 1st paragraph if `self.help` is not True, 

223 and escape its control codes. 

224 

225 Args: 

226 object_ (Any): the object to get the docstring from. 

227 

228 Returns: 

229 Optional[str]: the processed docstring, or None if no docstring was found. 

230 """ 

231 docs = getdoc(object_) 

232 if docs is None: 

233 return None 

234 docs = cleandoc(docs).strip() 

235 if not self.help: 

236 docs = _first_paragraph(docs) 

237 return escape_control_codes(docs) 

238 

239 

240def get_object_types_mro(obj: Union[object, Type[Any]]) -> Tuple[type, ...]: 

241 """Returns the MRO of an object's class, or of the object itself if it's a class.""" 

242 if not hasattr(obj, "__mro__"): 

243 # N.B. we cannot use `if type(obj) is type` here because it doesn't work with 

244 # some types of classes, such as the ones that use abc.ABCMeta. 

245 obj = type(obj) 

246 return getattr(obj, "__mro__", ()) 

247 

248 

249def get_object_types_mro_as_strings(obj: object) -> Collection[str]: 

250 """ 

251 Returns the MRO of an object's class as full qualified names, or of the object itself if it's a class. 

252 

253 Examples: 

254 `object_types_mro_as_strings(JSONDecoder)` will return `['json.decoder.JSONDecoder', 'builtins.object']` 

255 """ 

256 return [ 

257 f'{getattr(type_, "__module__", "")}.{getattr(type_, "__qualname__", "")}' 

258 for type_ in get_object_types_mro(obj) 

259 ] 

260 

261 

262def is_object_one_of_types( 

263 obj: object, fully_qualified_types_names: Collection[str] 

264) -> bool: 

265 """ 

266 Returns `True` if the given object's class (or the object itself, if it's a class) has one of the 

267 fully qualified names in its MRO. 

268 """ 

269 for type_name in get_object_types_mro_as_strings(obj): 

270 if type_name in fully_qualified_types_names: 

271 return True 

272 return False