Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/IPython/core/oinspect.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
1"""Tools for inspecting Python objects.
3Uses syntax highlighting for presenting the various information elements.
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"""
9# Copyright (c) IPython Development Team.
10# Distributed under the terms of the Modified BSD License.
12__all__ = ['Inspector','InspectColors']
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
28from typing import (
29 cast,
30 Any,
31 Optional,
32 Dict,
33 Union,
34 List,
35 TypedDict,
36 TypeAlias,
37 Tuple,
38)
40import traitlets
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
55from pygments import highlight
56from pygments.lexers import PythonLexer
57from pygments.formatters import HtmlFormatter
59HOOK_NAME = "__custom_documentations__"
62UnformattedBundle: TypeAlias = Dict[str, List[Tuple[str, str]]] # List of (title, body)
63Bundle: TypeAlias = Dict[str, str]
66@dataclass
67class OInfo:
68 ismagic: bool
69 isalias: bool
70 found: bool
71 namespace: Optional[str]
72 parent: Any
73 obj: Any
75 def get(self, field):
76 """Get a field from the object for backward compatibility with before 8.12
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)
92def pylight(code):
93 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
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}
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
108Colors = TermColors # just a shorthand
110InspectColors = PyColorize.ANSICodeColors
112#****************************************************************************
113# Auxiliary functions and objects
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
141_info_fields = list(InfoDict.__annotations__.keys())
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
153 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
156@dataclass
157class InspectorHookData:
158 """Data passed to the mime hook"""
160 obj: Any
161 info: Optional[OInfo]
162 info_dict: InfoDict
163 detail_level: int
164 omit_sections: list[str]
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
186 return InfoDict(**infodict) # type:ignore
189def get_encoding(obj):
190 """Get encoding for python source file defining obj
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
213def getdoc(obj) -> Union[str, None]:
214 """Stable wrapper around inspect.getdoc.
216 This can't crash because of attribute problems.
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
234def getsource(obj, oname='') -> Union[str,None]:
235 """Wrapper around inspect.getsource.
237 This can be modified by other projects to provide customized source
238 extraction.
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
247 Returns
248 -------
249 src : unicode or None
251 """
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
277 else:
278 # Get source for non-property objects.
280 obj = _get_wrapped(obj)
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
294 return src
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))
302@undoc
303def getargspec(obj):
304 """Wrapper around :func:`inspect.getfullargspec`
306 In addition to functions and methods, this can also handle objects with a
307 ``__call__`` attribute.
309 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
310 """
312 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
313 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
315 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
316 obj = obj.__call__
318 return inspect.getfullargspec(obj)
320@undoc
321def format_argspec(argspec):
322 """Format argspect, convenience wrapper around inspect's.
324 This takes a dict instead of ordered arguments and calls
325 inspect.format_argspec with the arguments in the necessary order.
327 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
328 """
330 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
331 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
334 return inspect.formatargspec(argspec['args'], argspec['varargs'],
335 argspec['varkw'], argspec['defaults'])
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:]
362 call_line = oinfo['name']+format_argspec(argspec)
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','')
372 return call_line, doc
375def _get_wrapped(obj):
376 """Get the original object if wrapped in one or more @decorators
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
393def find_file(obj) -> Optional[str]:
394 """Find the absolute path to the file where an object was defined.
396 This is essentially a robust wrapper around `inspect.getabsfile`.
398 Returns None if no file can be found.
400 Parameters
401 ----------
402 obj : any Python object
404 Returns
405 -------
406 fname : str
407 The absolute path to the file where the object was defined.
408 """
409 obj = _get_wrapped(obj)
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
425 return fname
428def find_source_lines(obj):
429 """Find the line number in a file where an object was defined.
431 This is essentially a robust wrapper around `inspect.getsourcelines`.
433 Returns None if no file can be found.
435 Parameters
436 ----------
437 obj : any Python object
439 Returns
440 -------
441 lineno : int
442 The line number where the object definition starts.
443 """
444 obj = _get_wrapped(obj)
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
457 return lineno
459class Inspector(Colorable):
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)
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)
482 def _getdef(self,obj,oname='') -> Union[str,None]:
483 """Return the call signature for any callable object.
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
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)
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)
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()
512 def pdef(self, obj, oname=''):
513 """Print the call signature for any callable object.
515 If the object is a class, print the constructor information."""
517 if not callable(obj):
518 print('Object is not callable.')
519 return
521 header = ''
523 if inspect.isclass(obj):
524 header = self.__head('Class constructor information:\n')
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=' ')
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.
538 Optional:
539 -formatter: a function to run the docstring through for specially
540 formatted docstrings.
542 Examples
543 --------
544 In [1]: class NoInit:
545 ...: pass
547 In [2]: class NoDoc:
548 ...: def __init__(self):
549 ...: pass
551 In [3]: %pdoc NoDoc
552 No documentation found for NoDoc
554 In [4]: %pdoc NoInit
555 No documentation found for NoInit
557 In [5]: obj = NoInit()
559 In [6]: %pdoc obj
560 No documentation found for obj
562 In [5]: obj2 = NoDoc()
564 In [6]: %pdoc obj2
565 No documentation found for obj2
566 """
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))
587 if not lines:
588 self.noinfo('documentation',oname)
589 else:
590 page.page('\n'.join(lines))
592 def psource(self, obj, oname=''):
593 """Print the source code for an object."""
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
602 if src is None:
603 self.noinfo('source', oname)
604 else:
605 page.page(self.format(src))
607 def pfile(self, obj, oname=''):
608 """Show the whole file where an object was defined."""
610 lineno = find_source_lines(obj)
611 if lineno is None:
612 self.noinfo('file', oname)
613 return
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)
632 def _mime_format(self, text:str, formatter=None) -> dict:
633 """Return a mime bundle representation of the input text.
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.
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.
643 Note:
645 Formatters returning strings are supported but this behavior is deprecated.
647 """
648 defaults = {
649 "text/plain": text,
650 "text/html": f"<pre>{html.escape(text)}</pre>",
651 }
653 if formatter is None:
654 return defaults
655 else:
656 formatted = formatter(text)
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>"}
663 else:
664 return dict(defaults, **formatted)
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)
673 new_b: Bundle = {}
674 lines = []
675 _len = max(len(h) for h, _ in bundle["text/plain"])
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 )
684 new_b["text/plain"] = "\n".join(lines)
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 )
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
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"]))
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 }
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 )
744 def code_formatter(text) -> Bundle:
745 return {
746 'text/plain': self.format(text),
747 'text/html': pylight(text)
748 }
750 if info["isalias"]:
751 append_field(bundle, "Repr", "string_form")
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")
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)
770 append_field(bundle, "File", "file")
771 append_field(bundle, "Type", "type_name")
772 append_field(bundle, "Subclasses", "subclasses")
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")
781 # Namespace
782 if info["namespace"] != "Interactive":
783 append_field(bundle, "Namespace", "namespace")
785 append_field(bundle, "Length", "length")
786 append_field(bundle, "File", "file")
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)
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
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.
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 """
827 info_dict = self.info(obj, oname=oname, info=info, detail_level=detail_level)
828 omit_sections = list(omit_sections)
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)
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.
878 Optional arguments:
880 - oname: name of the variable pointing to the object.
882 - formatter: callable (optional)
883 A special formatter for docstrings.
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.
889 Although the support of custom formatter returning a string
890 instead of a mime type bundle is deprecated.
892 - info: a structure with some information fields which may have been
893 precomputed already.
895 - detail_level: if set to 1, more information is given.
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)
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 """
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)
922 def info(self, obj, oname="", info=None, detail_level=0) -> InfoDict:
923 """Compute a dict with detailed information about an object.
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.
937 Returns
938 -------
939 An object info dict with known fields from `info_fields` (see `InfoDict`).
940 """
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
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 )
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
991 ds = prelude + ds
993 # store output in a dict, we initialize it here and fill it as we go
995 string_max = 200 # max size of strings to show (snipped if longer)
996 shalf = int((string_max - 5) / 2)
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__
1005 try:
1006 bclass = obj.__class__
1007 out['base_class'] = str(bclass)
1008 except:
1009 pass
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
1026 if ospace:
1027 out['namespace'] = ospace
1029 # Length (for strings and lists)
1030 try:
1031 out['length'] = str(len(obj))
1032 except Exception:
1033 pass
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)
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
1061 except Exception:
1062 pass
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
1068 # Constructor docstring for classes
1069 if inspect.isclass(obj):
1070 out['isclass'] = True
1072 # get the init signature:
1073 try:
1074 init_def = self._getdef(obj, oname)
1075 except AttributeError:
1076 init_def = None
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
1096 if init_def:
1097 out['init_definition'] = init_def
1099 if init_ds:
1100 out['init_docstring'] = init_ds
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
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
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
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
1157 return out
1159 @staticmethod
1160 def _source_contains_docstring(src, doc):
1161 """
1162 Check whether the source *src* contains the docstring *doc*.
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
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.
1180 Arguments:
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.
1186 - ns_table: dict of name->namespaces for search.
1188 Optional arguments:
1190 - ns_search: list of namespace names to include in search.
1192 - ignore_case(False): make the search case-insensitive.
1194 - show_all(False): show all names, including those starting with
1195 underscores.
1197 - list_types(False): list all available object types for object matching.
1198 """
1199 # print('ps pattern:<%r>' % pattern) # dbg
1201 # defaults
1202 type_pattern = 'all'
1203 filter = ''
1205 # list all object types
1206 if list_types:
1207 page.page('\n'.join(sorted(typestr2type)))
1208 return
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)
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()))
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)
1240 page.page('\n'.join(sorted(search_result)))
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
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
1265 result.append(str(param))
1267 if pos_only:
1268 result.append('/')
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))
1279 if obj_signature.return_annotation is not inspect._empty:
1280 anno = inspect.formatannotation(obj_signature.return_annotation)
1281 rendered += ' -> {}'.format(anno)
1283 return rendered