Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/_inspect.py: 3%
143 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1from __future__ import absolute_import
3import inspect
4from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature
5from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union
7from .console import Group, RenderableType
8from .control import escape_control_codes
9from .highlighter import ReprHighlighter
10from .jupyter import JupyterMixin
11from .panel import Panel
12from .pretty import Pretty
13from .table import Table
14from .text import Text, TextType
17def _first_paragraph(doc: str) -> str:
18 """Get the first paragraph from a docstring."""
19 paragraph, _, _ = doc.partition("\n\n")
20 return paragraph
23class Inspect(JupyterMixin):
24 """A renderable to inspect any Python Object.
26 Args:
27 obj (Any): An object to inspect.
28 title (str, optional): Title to display over inspect result, or None use type. Defaults to None.
29 help (bool, optional): Show full help text rather than just first paragraph. Defaults to False.
30 methods (bool, optional): Enable inspection of callables. Defaults to False.
31 docs (bool, optional): Also render doc strings. Defaults to True.
32 private (bool, optional): Show private attributes (beginning with underscore). Defaults to False.
33 dunder (bool, optional): Show attributes starting with double underscore. Defaults to False.
34 sort (bool, optional): Sort attributes alphabetically. Defaults to True.
35 all (bool, optional): Show all attributes. Defaults to False.
36 value (bool, optional): Pretty print value of object. Defaults to True.
37 """
39 def __init__(
40 self,
41 obj: Any,
42 *,
43 title: Optional[TextType] = None,
44 help: bool = False,
45 methods: bool = False,
46 docs: bool = True,
47 private: bool = False,
48 dunder: bool = False,
49 sort: bool = True,
50 all: bool = True,
51 value: bool = True,
52 ) -> None:
53 self.highlighter = ReprHighlighter()
54 self.obj = obj
55 self.title = title or self._make_title(obj)
56 if all:
57 methods = private = dunder = True
58 self.help = help
59 self.methods = methods
60 self.docs = docs or help
61 self.private = private or dunder
62 self.dunder = dunder
63 self.sort = sort
64 self.value = value
66 def _make_title(self, obj: Any) -> Text:
67 """Make a default title."""
68 title_str = (
69 str(obj)
70 if (isclass(obj) or callable(obj) or ismodule(obj))
71 else str(type(obj))
72 )
73 title_text = self.highlighter(title_str)
74 return title_text
76 def __rich__(self) -> Panel:
77 return Panel.fit(
78 Group(*self._render()),
79 title=self.title,
80 border_style="scope.border",
81 padding=(0, 1),
82 )
84 def _get_signature(self, name: str, obj: Any) -> Optional[Text]:
85 """Get a signature for a callable."""
86 try:
87 _signature = str(signature(obj)) + ":"
88 except ValueError:
89 _signature = "(...)"
90 except TypeError:
91 return None
93 source_filename: Optional[str] = None
94 try:
95 source_filename = getfile(obj)
96 except (OSError, TypeError):
97 # OSError is raised if obj has no source file, e.g. when defined in REPL.
98 pass
100 callable_name = Text(name, style="inspect.callable")
101 if source_filename:
102 callable_name.stylize(f"link file://{source_filename}")
103 signature_text = self.highlighter(_signature)
105 qualname = name or getattr(obj, "__qualname__", name)
107 # If obj is a module, there may be classes (which are callable) to display
108 if inspect.isclass(obj):
109 prefix = "class"
110 elif inspect.iscoroutinefunction(obj):
111 prefix = "async def"
112 else:
113 prefix = "def"
115 qual_signature = Text.assemble(
116 (f"{prefix} ", f"inspect.{prefix.replace(' ', '_')}"),
117 (qualname, "inspect.callable"),
118 signature_text,
119 )
121 return qual_signature
123 def _render(self) -> Iterable[RenderableType]:
124 """Render object."""
126 def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]:
127 key, (_error, value) = item
128 return (callable(value), key.strip("_").lower())
130 def safe_getattr(attr_name: str) -> Tuple[Any, Any]:
131 """Get attribute or any exception."""
132 try:
133 return (None, getattr(obj, attr_name))
134 except Exception as error:
135 return (error, None)
137 obj = self.obj
138 keys = dir(obj)
139 total_items = len(keys)
140 if not self.dunder:
141 keys = [key for key in keys if not key.startswith("__")]
142 if not self.private:
143 keys = [key for key in keys if not key.startswith("_")]
144 not_shown_count = total_items - len(keys)
145 items = [(key, safe_getattr(key)) for key in keys]
146 if self.sort:
147 items.sort(key=sort_items)
149 items_table = Table.grid(padding=(0, 1), expand=False)
150 items_table.add_column(justify="right")
151 add_row = items_table.add_row
152 highlighter = self.highlighter
154 if callable(obj):
155 signature = self._get_signature("", obj)
156 if signature is not None:
157 yield signature
158 yield ""
160 if self.docs:
161 _doc = self._get_formatted_doc(obj)
162 if _doc is not None:
163 doc_text = Text(_doc, style="inspect.help")
164 doc_text = highlighter(doc_text)
165 yield doc_text
166 yield ""
168 if self.value and not (isclass(obj) or callable(obj) or ismodule(obj)):
169 yield Panel(
170 Pretty(obj, indent_guides=True, max_length=10, max_string=60),
171 border_style="inspect.value.border",
172 )
173 yield ""
175 for key, (error, value) in items:
176 key_text = Text.assemble(
177 (
178 key,
179 "inspect.attr.dunder" if key.startswith("__") else "inspect.attr",
180 ),
181 (" =", "inspect.equals"),
182 )
183 if error is not None:
184 warning = key_text.copy()
185 warning.stylize("inspect.error")
186 add_row(warning, highlighter(repr(error)))
187 continue
189 if callable(value):
190 if not self.methods:
191 continue
193 _signature_text = self._get_signature(key, value)
194 if _signature_text is None:
195 add_row(key_text, Pretty(value, highlighter=highlighter))
196 else:
197 if self.docs:
198 docs = self._get_formatted_doc(value)
199 if docs is not None:
200 _signature_text.append("\n" if "\n" in docs else " ")
201 doc = highlighter(docs)
202 doc.stylize("inspect.doc")
203 _signature_text.append(doc)
205 add_row(key_text, _signature_text)
206 else:
207 add_row(key_text, Pretty(value, highlighter=highlighter))
208 if items_table.row_count:
209 yield items_table
210 elif not_shown_count:
211 yield Text.from_markup(
212 f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] "
213 f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options."
214 )
216 def _get_formatted_doc(self, object_: Any) -> Optional[str]:
217 """
218 Extract the docstring of an object, process it and returns it.
219 The processing consists in cleaning up the doctring's indentation,
220 taking only its 1st paragraph if `self.help` is not True,
221 and escape its control codes.
223 Args:
224 object_ (Any): the object to get the docstring from.
226 Returns:
227 Optional[str]: the processed docstring, or None if no docstring was found.
228 """
229 docs = getdoc(object_)
230 if docs is None:
231 return None
232 docs = cleandoc(docs).strip()
233 if not self.help:
234 docs = _first_paragraph(docs)
235 return escape_control_codes(docs)
238def get_object_types_mro(obj: Union[object, Type[Any]]) -> Tuple[type, ...]:
239 """Returns the MRO of an object's class, or of the object itself if it's a class."""
240 if not hasattr(obj, "__mro__"):
241 # N.B. we cannot use `if type(obj) is type` here because it doesn't work with
242 # some types of classes, such as the ones that use abc.ABCMeta.
243 obj = type(obj)
244 return getattr(obj, "__mro__", ())
247def get_object_types_mro_as_strings(obj: object) -> Collection[str]:
248 """
249 Returns the MRO of an object's class as full qualified names, or of the object itself if it's a class.
251 Examples:
252 `object_types_mro_as_strings(JSONDecoder)` will return `['json.decoder.JSONDecoder', 'builtins.object']`
253 """
254 return [
255 f'{getattr(type_, "__module__", "")}.{getattr(type_, "__qualname__", "")}'
256 for type_ in get_object_types_mro(obj)
257 ]
260def is_object_one_of_types(
261 obj: object, fully_qualified_types_names: Collection[str]
262) -> bool:
263 """
264 Returns `True` if the given object's class (or the object itself, if it's a class) has one of the
265 fully qualified names in its MRO.
266 """
267 for type_name in get_object_types_mro_as_strings(obj):
268 if type_name in fully_qualified_types_names:
269 return True
270 return False