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