1"""Tools for inspecting Python objects.
2
3Uses syntax highlighting for presenting the various information elements.
4
5Similar in spirit to the inspect module, but all calls take a name argument to
6reference the name under which an object is being read.
7"""
8
9# Copyright (c) IPython Development Team.
10# Distributed under the terms of the Modified BSD License.
11
12__all__ = ['Inspector','InspectColors']
13
14# stdlib modules
15from dataclasses import dataclass
16from inspect import signature
17from textwrap import dedent
18import ast
19import html
20import inspect
21import io as stdlib_io
22import linecache
23import os
24import types
25import warnings
26
27
28from typing import (
29 cast,
30 Any,
31 Optional,
32 Dict,
33 Union,
34 List,
35 TypedDict,
36 TypeAlias,
37 Tuple,
38)
39
40import traitlets
41
42# IPython's own
43from IPython.core import page
44from IPython.lib.pretty import pretty
45from IPython.testing.skipdoctest import skip_doctest
46from IPython.utils import PyColorize, openpy
47from IPython.utils.dir2 import safe_hasattr
48from IPython.utils.path import compress_user
49from IPython.utils.text import indent
50from IPython.utils.wildcard import list_namespace, typestr2type
51from IPython.utils.coloransi import TermColors
52from IPython.utils.colorable import Colorable
53from IPython.utils.decorators import undoc
54
55from pygments import highlight
56from pygments.lexers import PythonLexer
57from pygments.formatters import HtmlFormatter
58
59HOOK_NAME = "__custom_documentations__"
60
61
62UnformattedBundle: TypeAlias = Dict[str, List[Tuple[str, str]]] # List of (title, body)
63Bundle: TypeAlias = Dict[str, str]
64
65
66@dataclass
67class OInfo:
68 ismagic: bool
69 isalias: bool
70 found: bool
71 namespace: Optional[str]
72 parent: Any
73 obj: Any
74
75 def get(self, field):
76 """Get a field from the object for backward compatibility with before 8.12
77
78 see https://github.com/h5py/h5py/issues/2253
79 """
80 # We need to deprecate this at some point, but the warning will show in completion.
81 # Let's comment this for now and uncomment end of 2023 ish
82 # warnings.warn(
83 # f"OInfo dataclass with fields access since IPython 8.12 please use OInfo.{field} instead."
84 # "OInfo used to be a dict but a dataclass provide static fields verification with mypy."
85 # "This warning and backward compatibility `get()` method were added in 8.13.",
86 # DeprecationWarning,
87 # stacklevel=2,
88 # )
89 return getattr(self, field)
90
91
92def pylight(code):
93 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
94
95# builtin docstrings to ignore
96_func_call_docstring = types.FunctionType.__call__.__doc__
97_object_init_docstring = object.__init__.__doc__
98_builtin_type_docstrings = {
99 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
100 types.FunctionType, property)
101}
102
103_builtin_func_type = type(all)
104_builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
105#****************************************************************************
106# Builtin color schemes
107
108Colors = TermColors # just a shorthand
109
110InspectColors = PyColorize.ANSICodeColors
111
112#****************************************************************************
113# Auxiliary functions and objects
114
115
116class InfoDict(TypedDict):
117 type_name: Optional[str]
118 base_class: Optional[str]
119 string_form: Optional[str]
120 namespace: Optional[str]
121 length: Optional[str]
122 file: Optional[str]
123 definition: Optional[str]
124 docstring: Optional[str]
125 source: Optional[str]
126 init_definition: Optional[str]
127 class_docstring: Optional[str]
128 init_docstring: Optional[str]
129 call_def: Optional[str]
130 call_docstring: Optional[str]
131 subclasses: Optional[str]
132 # These won't be printed but will be used to determine how to
133 # format the object
134 ismagic: bool
135 isalias: bool
136 isclass: bool
137 found: bool
138 name: str
139
140
141_info_fields = list(InfoDict.__annotations__.keys())
142
143
144def __getattr__(name):
145 if name == "info_fields":
146 warnings.warn(
147 "IPython.core.oinspect's `info_fields` is considered for deprecation and may be removed in the Future. ",
148 DeprecationWarning,
149 stacklevel=2,
150 )
151 return _info_fields
152
153 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
154
155
156@dataclass
157class InspectorHookData:
158 """Data passed to the mime hook"""
159
160 obj: Any
161 info: Optional[OInfo]
162 info_dict: InfoDict
163 detail_level: int
164 omit_sections: list[str]
165
166
167@undoc
168def object_info(
169 *,
170 name: str,
171 found: bool,
172 isclass: bool = False,
173 isalias: bool = False,
174 ismagic: bool = False,
175 **kw,
176) -> InfoDict:
177 """Make an object info dict with all fields present."""
178 infodict = kw
179 infodict = {k: None for k in _info_fields if k not in infodict}
180 infodict["name"] = name # type: ignore
181 infodict["found"] = found # type: ignore
182 infodict["isclass"] = isclass # type: ignore
183 infodict["isalias"] = isalias # type: ignore
184 infodict["ismagic"] = ismagic # type: ignore
185
186 return InfoDict(**infodict) # type:ignore
187
188
189def get_encoding(obj):
190 """Get encoding for python source file defining obj
191
192 Returns None if obj is not defined in a sourcefile.
193 """
194 ofile = find_file(obj)
195 # run contents of file through pager starting at line where the object
196 # is defined, as long as the file isn't binary and is actually on the
197 # filesystem.
198 if ofile is None:
199 return None
200 elif ofile.endswith(('.so', '.dll', '.pyd')):
201 return None
202 elif not os.path.isfile(ofile):
203 return None
204 else:
205 # Print only text files, not extension binaries. Note that
206 # getsourcelines returns lineno with 1-offset and page() uses
207 # 0-offset, so we must adjust.
208 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
209 encoding, _lines = openpy.detect_encoding(buffer.readline)
210 return encoding
211
212
213def getdoc(obj) -> Union[str, None]:
214 """Stable wrapper around inspect.getdoc.
215
216 This can't crash because of attribute problems.
217
218 It also attempts to call a getdoc() method on the given object. This
219 allows objects which provide their docstrings via non-standard mechanisms
220 (like Pyro proxies) to still be inspected by ipython's ? system.
221 """
222 # Allow objects to offer customized documentation via a getdoc method:
223 try:
224 ds = obj.getdoc()
225 except Exception:
226 pass
227 else:
228 if isinstance(ds, str):
229 return inspect.cleandoc(ds)
230 docstr = inspect.getdoc(obj)
231 return docstr
232
233
234def getsource(obj, oname='') -> Union[str,None]:
235 """Wrapper around inspect.getsource.
236
237 This can be modified by other projects to provide customized source
238 extraction.
239
240 Parameters
241 ----------
242 obj : object
243 an object whose source code we will attempt to extract
244 oname : str
245 (optional) a name under which the object is known
246
247 Returns
248 -------
249 src : unicode or None
250
251 """
252
253 if isinstance(obj, property):
254 sources = []
255 for attrname in ['fget', 'fset', 'fdel']:
256 fn = getattr(obj, attrname)
257 if fn is not None:
258 encoding = get_encoding(fn)
259 oname_prefix = ('%s.' % oname) if oname else ''
260 sources.append(''.join(('# ', oname_prefix, attrname)))
261 if inspect.isfunction(fn):
262 _src = getsource(fn)
263 if _src:
264 # assert _src is not None, "please mypy"
265 sources.append(dedent(_src))
266 else:
267 # Default str/repr only prints function name,
268 # pretty.pretty prints module name too.
269 sources.append(
270 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
271 )
272 if sources:
273 return '\n'.join(sources)
274 else:
275 return None
276
277 else:
278 # Get source for non-property objects.
279
280 obj = _get_wrapped(obj)
281
282 try:
283 src = inspect.getsource(obj)
284 except TypeError:
285 # The object itself provided no meaningful source, try looking for
286 # its class definition instead.
287 try:
288 src = inspect.getsource(obj.__class__)
289 except (OSError, TypeError):
290 return None
291 except OSError:
292 return None
293
294 return src
295
296
297def is_simple_callable(obj):
298 """True if obj is a function ()"""
299 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
300 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
301
302@undoc
303def getargspec(obj):
304 """Wrapper around :func:`inspect.getfullargspec`
305
306 In addition to functions and methods, this can also handle objects with a
307 ``__call__`` attribute.
308
309 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
310 """
311
312 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
313 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
314
315 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
316 obj = obj.__call__
317
318 return inspect.getfullargspec(obj)
319
320@undoc
321def format_argspec(argspec):
322 """Format argspect, convenience wrapper around inspect's.
323
324 This takes a dict instead of ordered arguments and calls
325 inspect.format_argspec with the arguments in the necessary order.
326
327 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
328 """
329
330 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
331 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
332
333
334 return inspect.formatargspec(argspec['args'], argspec['varargs'],
335 argspec['varkw'], argspec['defaults'])
336
337@undoc
338def call_tip(oinfo, format_call=True):
339 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
340 warnings.warn(
341 "`call_tip` function is deprecated as of IPython 6.0"
342 "and will be removed in future versions.",
343 DeprecationWarning,
344 stacklevel=2,
345 )
346 # Get call definition
347 argspec = oinfo.get('argspec')
348 if argspec is None:
349 call_line = None
350 else:
351 # Callable objects will have 'self' as their first argument, prune
352 # it out if it's there for clarity (since users do *not* pass an
353 # extra first argument explicitly).
354 try:
355 has_self = argspec['args'][0] == 'self'
356 except (KeyError, IndexError):
357 pass
358 else:
359 if has_self:
360 argspec['args'] = argspec['args'][1:]
361
362 call_line = oinfo['name']+format_argspec(argspec)
363
364 # Now get docstring.
365 # The priority is: call docstring, constructor docstring, main one.
366 doc = oinfo.get('call_docstring')
367 if doc is None:
368 doc = oinfo.get('init_docstring')
369 if doc is None:
370 doc = oinfo.get('docstring','')
371
372 return call_line, doc
373
374
375def _get_wrapped(obj):
376 """Get the original object if wrapped in one or more @decorators
377
378 Some objects automatically construct similar objects on any unrecognised
379 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
380 this will arbitrarily cut off after 100 levels of obj.__wrapped__
381 attribute access. --TK, Jan 2016
382 """
383 orig_obj = obj
384 i = 0
385 while safe_hasattr(obj, '__wrapped__'):
386 obj = obj.__wrapped__
387 i += 1
388 if i > 100:
389 # __wrapped__ is probably a lie, so return the thing we started with
390 return orig_obj
391 return obj
392
393def find_file(obj) -> Optional[str]:
394 """Find the absolute path to the file where an object was defined.
395
396 This is essentially a robust wrapper around `inspect.getabsfile`.
397
398 Returns None if no file can be found.
399
400 Parameters
401 ----------
402 obj : any Python object
403
404 Returns
405 -------
406 fname : str
407 The absolute path to the file where the object was defined.
408 """
409 obj = _get_wrapped(obj)
410
411 fname: Optional[str] = None
412 try:
413 fname = inspect.getabsfile(obj)
414 except TypeError:
415 # For an instance, the file that matters is where its class was
416 # declared.
417 try:
418 fname = inspect.getabsfile(obj.__class__)
419 except (OSError, TypeError):
420 # Can happen for builtins
421 pass
422 except OSError:
423 pass
424
425 return fname
426
427
428def find_source_lines(obj):
429 """Find the line number in a file where an object was defined.
430
431 This is essentially a robust wrapper around `inspect.getsourcelines`.
432
433 Returns None if no file can be found.
434
435 Parameters
436 ----------
437 obj : any Python object
438
439 Returns
440 -------
441 lineno : int
442 The line number where the object definition starts.
443 """
444 obj = _get_wrapped(obj)
445
446 try:
447 lineno = inspect.getsourcelines(obj)[1]
448 except TypeError:
449 # For instances, try the class object like getsource() does
450 try:
451 lineno = inspect.getsourcelines(obj.__class__)[1]
452 except (OSError, TypeError):
453 return None
454 except OSError:
455 return None
456
457 return lineno
458
459class Inspector(Colorable):
460
461 mime_hooks = traitlets.Dict(
462 config=True,
463 help="dictionary of mime to callable to add information into help mimebundle dict",
464 ).tag(config=True)
465
466 def __init__(
467 self,
468 color_table=InspectColors,
469 code_color_table=PyColorize.ANSICodeColors,
470 scheme=None,
471 str_detail_level=0,
472 parent=None,
473 config=None,
474 ):
475 super(Inspector, self).__init__(parent=parent, config=config)
476 self.color_table = color_table
477 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
478 self.format = self.parser.format
479 self.str_detail_level = str_detail_level
480 self.set_active_scheme(scheme)
481
482 def _getdef(self,obj,oname='') -> Union[str,None]:
483 """Return the call signature for any callable object.
484
485 If any exception is generated, None is returned instead and the
486 exception is suppressed."""
487 if not callable(obj):
488 return None
489 try:
490 return _render_signature(signature(obj), oname)
491 except:
492 return None
493
494 def __head(self,h) -> str:
495 """Return a header string with proper colors."""
496 return '%s%s%s' % (self.color_table.active_colors.header,h,
497 self.color_table.active_colors.normal)
498
499 def set_active_scheme(self, scheme):
500 if scheme is not None:
501 self.color_table.set_active_scheme(scheme)
502 self.parser.color_table.set_active_scheme(scheme)
503
504 def noinfo(self, msg, oname):
505 """Generic message when no information is found."""
506 print('No %s found' % msg, end=' ')
507 if oname:
508 print('for %s' % oname)
509 else:
510 print()
511
512 def pdef(self, obj, oname=''):
513 """Print the call signature for any callable object.
514
515 If the object is a class, print the constructor information."""
516
517 if not callable(obj):
518 print('Object is not callable.')
519 return
520
521 header = ''
522
523 if inspect.isclass(obj):
524 header = self.__head('Class constructor information:\n')
525
526
527 output = self._getdef(obj,oname)
528 if output is None:
529 self.noinfo('definition header',oname)
530 else:
531 print(header,self.format(output), end=' ')
532
533 # In Python 3, all classes are new-style, so they all have __init__.
534 @skip_doctest
535 def pdoc(self, obj, oname='', formatter=None):
536 """Print the docstring for any object.
537
538 Optional:
539 -formatter: a function to run the docstring through for specially
540 formatted docstrings.
541
542 Examples
543 --------
544 In [1]: class NoInit:
545 ...: pass
546
547 In [2]: class NoDoc:
548 ...: def __init__(self):
549 ...: pass
550
551 In [3]: %pdoc NoDoc
552 No documentation found for NoDoc
553
554 In [4]: %pdoc NoInit
555 No documentation found for NoInit
556
557 In [5]: obj = NoInit()
558
559 In [6]: %pdoc obj
560 No documentation found for obj
561
562 In [5]: obj2 = NoDoc()
563
564 In [6]: %pdoc obj2
565 No documentation found for obj2
566 """
567
568 head = self.__head # For convenience
569 lines = []
570 ds = getdoc(obj)
571 if formatter:
572 ds = formatter(ds).get('plain/text', ds)
573 if ds:
574 lines.append(head("Class docstring:"))
575 lines.append(indent(ds))
576 if inspect.isclass(obj) and hasattr(obj, '__init__'):
577 init_ds = getdoc(obj.__init__)
578 if init_ds is not None:
579 lines.append(head("Init docstring:"))
580 lines.append(indent(init_ds))
581 elif hasattr(obj,'__call__'):
582 call_ds = getdoc(obj.__call__)
583 if call_ds:
584 lines.append(head("Call docstring:"))
585 lines.append(indent(call_ds))
586
587 if not lines:
588 self.noinfo('documentation',oname)
589 else:
590 page.page('\n'.join(lines))
591
592 def psource(self, obj, oname=''):
593 """Print the source code for an object."""
594
595 # Flush the source cache because inspect can return out-of-date source
596 linecache.checkcache()
597 try:
598 src = getsource(obj, oname=oname)
599 except Exception:
600 src = None
601
602 if src is None:
603 self.noinfo('source', oname)
604 else:
605 page.page(self.format(src))
606
607 def pfile(self, obj, oname=''):
608 """Show the whole file where an object was defined."""
609
610 lineno = find_source_lines(obj)
611 if lineno is None:
612 self.noinfo('file', oname)
613 return
614
615 ofile = find_file(obj)
616 # run contents of file through pager starting at line where the object
617 # is defined, as long as the file isn't binary and is actually on the
618 # filesystem.
619 if ofile is None:
620 print("Could not find file for object")
621 elif ofile.endswith((".so", ".dll", ".pyd")):
622 print("File %r is binary, not printing." % ofile)
623 elif not os.path.isfile(ofile):
624 print('File %r does not exist, not printing.' % ofile)
625 else:
626 # Print only text files, not extension binaries. Note that
627 # getsourcelines returns lineno with 1-offset and page() uses
628 # 0-offset, so we must adjust.
629 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
630
631
632 def _mime_format(self, text:str, formatter=None) -> dict:
633 """Return a mime bundle representation of the input text.
634
635 - if `formatter` is None, the returned mime bundle has
636 a ``text/plain`` field, with the input text.
637 a ``text/html`` field with a ``<pre>`` tag containing the input text.
638
639 - if ``formatter`` is not None, it must be a callable transforming the
640 input text into a mime bundle. Default values for ``text/plain`` and
641 ``text/html`` representations are the ones described above.
642
643 Note:
644
645 Formatters returning strings are supported but this behavior is deprecated.
646
647 """
648 defaults = {
649 "text/plain": text,
650 "text/html": f"<pre>{html.escape(text)}</pre>",
651 }
652
653 if formatter is None:
654 return defaults
655 else:
656 formatted = formatter(text)
657
658 if not isinstance(formatted, dict):
659 # Handle the deprecated behavior of a formatter returning
660 # a string instead of a mime bundle.
661 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
662
663 else:
664 return dict(defaults, **formatted)
665
666 def format_mime(self, bundle: UnformattedBundle) -> Bundle:
667 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
668 # Format text/plain mimetype
669 assert isinstance(bundle["text/plain"], list)
670 for item in bundle["text/plain"]:
671 assert isinstance(item, tuple)
672
673 new_b: Bundle = {}
674 lines = []
675 _len = max(len(h) for h, _ in bundle["text/plain"])
676
677 for head, body in bundle["text/plain"]:
678 body = body.strip("\n")
679 delim = "\n" if "\n" in body else " "
680 lines.append(
681 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
682 )
683
684 new_b["text/plain"] = "\n".join(lines)
685
686 if "text/html" in bundle:
687 assert isinstance(bundle["text/html"], list)
688 for item in bundle["text/html"]:
689 assert isinstance(item, tuple)
690 # Format the text/html mimetype
691 if isinstance(bundle["text/html"], (list, tuple)):
692 # bundle['text/html'] is a list of (head, formatted body) pairs
693 new_b["text/html"] = "\n".join(
694 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
695 )
696
697 for k in bundle.keys():
698 if k in ("text/html", "text/plain"):
699 continue
700 else:
701 new_b[k] = bundle[k] # type:ignore
702 return new_b
703
704 def _append_info_field(
705 self,
706 bundle: UnformattedBundle,
707 title: str,
708 key: str,
709 info,
710 omit_sections: List[str],
711 formatter,
712 ):
713 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
714 if title in omit_sections or key in omit_sections:
715 return
716 field = info[key]
717 if field is not None:
718 formatted_field = self._mime_format(field, formatter)
719 bundle["text/plain"].append((title, formatted_field["text/plain"]))
720 bundle["text/html"].append((title, formatted_field["text/html"]))
721
722 def _make_info_unformatted(
723 self, obj, info, formatter, detail_level, omit_sections
724 ) -> UnformattedBundle:
725 """Assemble the mimebundle as unformatted lists of information"""
726 bundle: UnformattedBundle = {
727 "text/plain": [],
728 "text/html": [],
729 }
730
731 # A convenience function to simplify calls below
732 def append_field(
733 bundle: UnformattedBundle, title: str, key: str, formatter=None
734 ):
735 self._append_info_field(
736 bundle,
737 title=title,
738 key=key,
739 info=info,
740 omit_sections=omit_sections,
741 formatter=formatter,
742 )
743
744 def code_formatter(text) -> Bundle:
745 return {
746 'text/plain': self.format(text),
747 'text/html': pylight(text)
748 }
749
750 if info["isalias"]:
751 append_field(bundle, "Repr", "string_form")
752
753 elif info['ismagic']:
754 if detail_level > 0:
755 append_field(bundle, "Source", "source", code_formatter)
756 else:
757 append_field(bundle, "Docstring", "docstring", formatter)
758 append_field(bundle, "File", "file")
759
760 elif info['isclass'] or is_simple_callable(obj):
761 # Functions, methods, classes
762 append_field(bundle, "Signature", "definition", code_formatter)
763 append_field(bundle, "Init signature", "init_definition", code_formatter)
764 append_field(bundle, "Docstring", "docstring", formatter)
765 if detail_level > 0 and info["source"]:
766 append_field(bundle, "Source", "source", code_formatter)
767 else:
768 append_field(bundle, "Init docstring", "init_docstring", formatter)
769
770 append_field(bundle, "File", "file")
771 append_field(bundle, "Type", "type_name")
772 append_field(bundle, "Subclasses", "subclasses")
773
774 else:
775 # General Python objects
776 append_field(bundle, "Signature", "definition", code_formatter)
777 append_field(bundle, "Call signature", "call_def", code_formatter)
778 append_field(bundle, "Type", "type_name")
779 append_field(bundle, "String form", "string_form")
780
781 # Namespace
782 if info["namespace"] != "Interactive":
783 append_field(bundle, "Namespace", "namespace")
784
785 append_field(bundle, "Length", "length")
786 append_field(bundle, "File", "file")
787
788 # Source or docstring, depending on detail level and whether
789 # source found.
790 if detail_level > 0 and info["source"]:
791 append_field(bundle, "Source", "source", code_formatter)
792 else:
793 append_field(bundle, "Docstring", "docstring", formatter)
794
795 append_field(bundle, "Class docstring", "class_docstring", formatter)
796 append_field(bundle, "Init docstring", "init_docstring", formatter)
797 append_field(bundle, "Call docstring", "call_docstring", formatter)
798 return bundle
799
800
801 def _get_info(
802 self,
803 obj: Any,
804 oname: str = "",
805 formatter=None,
806 info: Optional[OInfo] = None,
807 detail_level: int = 0,
808 omit_sections: Union[List[str], Tuple[()]] = (),
809 ) -> Bundle:
810 """Retrieve an info dict and format it.
811
812 Parameters
813 ----------
814 obj : any
815 Object to inspect and return info from
816 oname : str (default: ''):
817 Name of the variable pointing to `obj`.
818 formatter : callable
819 info
820 already computed information
821 detail_level : integer
822 Granularity of detail level, if set to 1, give more information.
823 omit_sections : list[str]
824 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
825 """
826
827 info_dict = self.info(obj, oname=oname, info=info, detail_level=detail_level)
828 omit_sections = list(omit_sections)
829
830 bundle = self._make_info_unformatted(
831 obj,
832 info_dict,
833 formatter,
834 detail_level=detail_level,
835 omit_sections=omit_sections,
836 )
837 if self.mime_hooks:
838 hook_data = InspectorHookData(
839 obj=obj,
840 info=info,
841 info_dict=info_dict,
842 detail_level=detail_level,
843 omit_sections=omit_sections,
844 )
845 for key, hook in self.mime_hooks.items(): # type:ignore
846 required_parameters = [
847 parameter
848 for parameter in inspect.signature(hook).parameters.values()
849 if parameter.default != inspect.Parameter.default
850 ]
851 if len(required_parameters) == 1:
852 res = hook(hook_data)
853 else:
854 warnings.warn(
855 "MIME hook format changed in IPython 8.22; hooks should now accept"
856 " a single parameter (InspectorHookData); support for hooks requiring"
857 " two-parameters (obj and info) will be removed in a future version",
858 DeprecationWarning,
859 stacklevel=2,
860 )
861 res = hook(obj, info)
862 if res is not None:
863 bundle[key] = res
864 return self.format_mime(bundle)
865
866 def pinfo(
867 self,
868 obj,
869 oname="",
870 formatter=None,
871 info: Optional[OInfo] = None,
872 detail_level=0,
873 enable_html_pager=True,
874 omit_sections=(),
875 ):
876 """Show detailed information about an object.
877
878 Optional arguments:
879
880 - oname: name of the variable pointing to the object.
881
882 - formatter: callable (optional)
883 A special formatter for docstrings.
884
885 The formatter is a callable that takes a string as an input
886 and returns either a formatted string or a mime type bundle
887 in the form of a dictionary.
888
889 Although the support of custom formatter returning a string
890 instead of a mime type bundle is deprecated.
891
892 - info: a structure with some information fields which may have been
893 precomputed already.
894
895 - detail_level: if set to 1, more information is given.
896
897 - omit_sections: set of section keys and titles to omit
898 """
899 assert info is not None
900 info_b: Bundle = self._get_info(
901 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
902 )
903 if not enable_html_pager:
904 del info_b["text/html"]
905 page.page(info_b)
906
907 def _info(self, obj, oname="", info=None, detail_level=0):
908 """
909 Inspector.info() was likely improperly marked as deprecated
910 while only a parameter was deprecated. We "un-deprecate" it.
911 """
912
913 warnings.warn(
914 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
915 "and the `formatter=` keyword removed. `Inspector._info` is now "
916 "an alias, and you can just call `.info()` directly.",
917 DeprecationWarning,
918 stacklevel=2,
919 )
920 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
921
922 def info(self, obj, oname="", info=None, detail_level=0) -> InfoDict:
923 """Compute a dict with detailed information about an object.
924
925 Parameters
926 ----------
927 obj : any
928 An object to find information about
929 oname : str (default: '')
930 Name of the variable pointing to `obj`.
931 info : (default: None)
932 A struct (dict like with attr access) with some information fields
933 which may have been precomputed already.
934 detail_level : int (default:0)
935 If set to 1, more information is given.
936
937 Returns
938 -------
939 An object info dict with known fields from `info_fields` (see `InfoDict`).
940 """
941
942 if info is None:
943 ismagic = False
944 isalias = False
945 ospace = ''
946 else:
947 ismagic = info.ismagic
948 isalias = info.isalias
949 ospace = info.namespace
950
951 # Get docstring, special-casing aliases:
952 att_name = oname.split(".")[-1]
953 parents_docs = None
954 prelude = ""
955 if info and info.parent is not None and hasattr(info.parent, HOOK_NAME):
956 parents_docs_dict = getattr(info.parent, HOOK_NAME)
957 parents_docs = parents_docs_dict.get(att_name, None)
958 out: InfoDict = cast(
959 InfoDict,
960 {
961 **{field: None for field in _info_fields},
962 **{
963 "name": oname,
964 "found": True,
965 "isalias": isalias,
966 "ismagic": ismagic,
967 "subclasses": None,
968 },
969 },
970 )
971
972 if parents_docs:
973 ds = parents_docs
974 elif isalias:
975 if not callable(obj):
976 try:
977 ds = "Alias to the system command:\n %s" % obj[1]
978 except:
979 ds = "Alias: " + str(obj)
980 else:
981 ds = "Alias to " + str(obj)
982 if obj.__doc__:
983 ds += "\nDocstring:\n" + obj.__doc__
984 else:
985 ds_or_None = getdoc(obj)
986 if ds_or_None is None:
987 ds = '<no docstring>'
988 else:
989 ds = ds_or_None
990
991 ds = prelude + ds
992
993 # store output in a dict, we initialize it here and fill it as we go
994
995 string_max = 200 # max size of strings to show (snipped if longer)
996 shalf = int((string_max - 5) / 2)
997
998 if ismagic:
999 out['type_name'] = 'Magic function'
1000 elif isalias:
1001 out['type_name'] = 'System alias'
1002 else:
1003 out['type_name'] = type(obj).__name__
1004
1005 try:
1006 bclass = obj.__class__
1007 out['base_class'] = str(bclass)
1008 except:
1009 pass
1010
1011 # String form, but snip if too long in ? form (full in ??)
1012 if detail_level >= self.str_detail_level:
1013 try:
1014 ostr = str(obj)
1015 if not detail_level and len(ostr) > string_max:
1016 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
1017 # TODO: `'string_form'.expandtabs()` seems wrong, but
1018 # it was (nearly) like this since the first commit ever.
1019 ostr = ("\n" + " " * len("string_form".expandtabs())).join(
1020 q.strip() for q in ostr.split("\n")
1021 )
1022 out["string_form"] = ostr
1023 except:
1024 pass
1025
1026 if ospace:
1027 out['namespace'] = ospace
1028
1029 # Length (for strings and lists)
1030 try:
1031 out['length'] = str(len(obj))
1032 except Exception:
1033 pass
1034
1035 # Filename where object was defined
1036 binary_file = False
1037 fname = find_file(obj)
1038 if fname is None:
1039 # if anything goes wrong, we don't want to show source, so it's as
1040 # if the file was binary
1041 binary_file = True
1042 else:
1043 if fname.endswith(('.so', '.dll', '.pyd')):
1044 binary_file = True
1045 elif fname.endswith('<string>'):
1046 fname = 'Dynamically generated function. No source code available.'
1047 out['file'] = compress_user(fname)
1048
1049 # Original source code for a callable, class or property.
1050 if detail_level:
1051 # Flush the source cache because inspect can return out-of-date
1052 # source
1053 linecache.checkcache()
1054 try:
1055 if isinstance(obj, property) or not binary_file:
1056 src = getsource(obj, oname)
1057 if src is not None:
1058 src = src.rstrip()
1059 out['source'] = src
1060
1061 except Exception:
1062 pass
1063
1064 # Add docstring only if no source is to be shown (avoid repetitions).
1065 if ds and not self._source_contains_docstring(out.get('source'), ds):
1066 out['docstring'] = ds
1067
1068 # Constructor docstring for classes
1069 if inspect.isclass(obj):
1070 out['isclass'] = True
1071
1072 # get the init signature:
1073 try:
1074 init_def = self._getdef(obj, oname)
1075 except AttributeError:
1076 init_def = None
1077
1078 # get the __init__ docstring
1079 try:
1080 obj_init = obj.__init__
1081 except AttributeError:
1082 init_ds = None
1083 else:
1084 if init_def is None:
1085 # Get signature from init if top-level sig failed.
1086 # Can happen for built-in types (list, etc.).
1087 try:
1088 init_def = self._getdef(obj_init, oname)
1089 except AttributeError:
1090 pass
1091 init_ds = getdoc(obj_init)
1092 # Skip Python's auto-generated docstrings
1093 if init_ds == _object_init_docstring:
1094 init_ds = None
1095
1096 if init_def:
1097 out['init_definition'] = init_def
1098
1099 if init_ds:
1100 out['init_docstring'] = init_ds
1101
1102 names = [sub.__name__ for sub in type.__subclasses__(obj)]
1103 if len(names) < 10:
1104 all_names = ', '.join(names)
1105 else:
1106 all_names = ', '.join(names[:10]+['...'])
1107 out['subclasses'] = all_names
1108 # and class docstring for instances:
1109 else:
1110 # reconstruct the function definition and print it:
1111 defln = self._getdef(obj, oname)
1112 if defln:
1113 out['definition'] = defln
1114
1115 # First, check whether the instance docstring is identical to the
1116 # class one, and print it separately if they don't coincide. In
1117 # most cases they will, but it's nice to print all the info for
1118 # objects which use instance-customized docstrings.
1119 if ds:
1120 try:
1121 cls = getattr(obj,'__class__')
1122 except:
1123 class_ds = None
1124 else:
1125 class_ds = getdoc(cls)
1126 # Skip Python's auto-generated docstrings
1127 if class_ds in _builtin_type_docstrings:
1128 class_ds = None
1129 if class_ds and ds != class_ds:
1130 out['class_docstring'] = class_ds
1131
1132 # Next, try to show constructor docstrings
1133 try:
1134 init_ds = getdoc(obj.__init__)
1135 # Skip Python's auto-generated docstrings
1136 if init_ds == _object_init_docstring:
1137 init_ds = None
1138 except AttributeError:
1139 init_ds = None
1140 if init_ds:
1141 out['init_docstring'] = init_ds
1142
1143 # Call form docstring for callable instances
1144 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
1145 call_def = self._getdef(obj.__call__, oname)
1146 if call_def and (call_def != out.get('definition')):
1147 # it may never be the case that call def and definition differ,
1148 # but don't include the same signature twice
1149 out['call_def'] = call_def
1150 call_ds = getdoc(obj.__call__)
1151 # Skip Python's auto-generated docstrings
1152 if call_ds == _func_call_docstring:
1153 call_ds = None
1154 if call_ds:
1155 out['call_docstring'] = call_ds
1156
1157 return out
1158
1159 @staticmethod
1160 def _source_contains_docstring(src, doc):
1161 """
1162 Check whether the source *src* contains the docstring *doc*.
1163
1164 This is is helper function to skip displaying the docstring if the
1165 source already contains it, avoiding repetition of information.
1166 """
1167 try:
1168 (def_node,) = ast.parse(dedent(src)).body
1169 return ast.get_docstring(def_node) == doc # type: ignore[arg-type]
1170 except Exception:
1171 # The source can become invalid or even non-existent (because it
1172 # is re-fetched from the source file) so the above code fail in
1173 # arbitrary ways.
1174 return False
1175
1176 def psearch(self,pattern,ns_table,ns_search=[],
1177 ignore_case=False,show_all=False, *, list_types=False):
1178 """Search namespaces with wildcards for objects.
1179
1180 Arguments:
1181
1182 - pattern: string containing shell-like wildcards to use in namespace
1183 searches and optionally a type specification to narrow the search to
1184 objects of that type.
1185
1186 - ns_table: dict of name->namespaces for search.
1187
1188 Optional arguments:
1189
1190 - ns_search: list of namespace names to include in search.
1191
1192 - ignore_case(False): make the search case-insensitive.
1193
1194 - show_all(False): show all names, including those starting with
1195 underscores.
1196
1197 - list_types(False): list all available object types for object matching.
1198 """
1199 # print('ps pattern:<%r>' % pattern) # dbg
1200
1201 # defaults
1202 type_pattern = 'all'
1203 filter = ''
1204
1205 # list all object types
1206 if list_types:
1207 page.page('\n'.join(sorted(typestr2type)))
1208 return
1209
1210 cmds = pattern.split()
1211 len_cmds = len(cmds)
1212 if len_cmds == 1:
1213 # Only filter pattern given
1214 filter = cmds[0]
1215 elif len_cmds == 2:
1216 # Both filter and type specified
1217 filter,type_pattern = cmds
1218 else:
1219 raise ValueError('invalid argument string for psearch: <%s>' %
1220 pattern)
1221
1222 # filter search namespaces
1223 for name in ns_search:
1224 if name not in ns_table:
1225 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1226 (name,ns_table.keys()))
1227
1228 # print('type_pattern:',type_pattern) # dbg
1229 search_result, namespaces_seen = set(), set()
1230 for ns_name in ns_search:
1231 ns = ns_table[ns_name]
1232 # Normally, locals and globals are the same, so we just check one.
1233 if id(ns) in namespaces_seen:
1234 continue
1235 namespaces_seen.add(id(ns))
1236 tmp_res = list_namespace(ns, type_pattern, filter,
1237 ignore_case=ignore_case, show_all=show_all)
1238 search_result.update(tmp_res)
1239
1240 page.page('\n'.join(sorted(search_result)))
1241
1242
1243def _render_signature(obj_signature, obj_name) -> str:
1244 """
1245 This was mostly taken from inspect.Signature.__str__.
1246 Look there for the comments.
1247 The only change is to add linebreaks when this gets too long.
1248 """
1249 result = []
1250 pos_only = False
1251 kw_only = True
1252 for param in obj_signature.parameters.values():
1253 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1254 pos_only = True
1255 elif pos_only:
1256 result.append('/')
1257 pos_only = False
1258
1259 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1260 kw_only = False
1261 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1262 result.append('*')
1263 kw_only = False
1264
1265 result.append(str(param))
1266
1267 if pos_only:
1268 result.append('/')
1269
1270 # add up name, parameters, braces (2), and commas
1271 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1272 # This doesn’t fit behind “Signature: ” in an inspect window.
1273 rendered = '{}(\n{})'.format(obj_name, ''.join(
1274 ' {},\n'.format(r) for r in result)
1275 )
1276 else:
1277 rendered = '{}({})'.format(obj_name, ', '.join(result))
1278
1279 if obj_signature.return_annotation is not inspect._empty:
1280 anno = inspect.formatannotation(obj_signature.return_annotation)
1281 rendered += ' -> {}'.format(anno)
1282
1283 return rendered