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
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
1import inspect
2from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature
3from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union
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
15def _first_paragraph(doc: str) -> str:
16 """Get the first paragraph from a docstring."""
17 paragraph, _, _ = doc.partition("\n\n")
18 return paragraph
21class Inspect(JupyterMixin):
22 """A renderable to inspect any Python Object.
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 """
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
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
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 )
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
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
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)
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
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"
117 qual_signature = Text.assemble(
118 (f"{prefix} ", f"inspect.{prefix.replace(' ', '_')}"),
119 (qualname, "inspect.callable"),
120 signature_text,
121 )
123 return qual_signature
125 def _render(self) -> Iterable[RenderableType]:
126 """Render object."""
128 def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]:
129 key, (_error, value) = item
130 return (callable(value), key.strip("_").lower())
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)
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)
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
156 if callable(obj):
157 signature = self._get_signature("", obj)
158 if signature is not None:
159 yield signature
160 yield ""
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 ""
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 ""
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
191 if callable(value):
192 if not self.methods:
193 continue
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)
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 )
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.
225 Args:
226 object_ (Any): the object to get the docstring from.
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)
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__", ())
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.
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 ]
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