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 mime_hooks = traitlets.Dict(
389 config=True,
390 help="dictionary of mime to callable to add information into help mimebundle dict",
391 ).tag(config=True)
392
393 _theme_name: str
394
395 def __init__(
396 self,
397 *,
398 theme_name: str,
399 str_detail_level=0,
400 parent=None,
401 config=None,
402 ):
403 if theme_name in ["Linux", "LightBG", "Neutral", "NoColor"]:
404 warnings.warn(
405 f"Theme names and color schemes are lowercase in IPython 9.0 use {theme_name.lower()} instead",
406 DeprecationWarning,
407 stacklevel=2,
408 )
409 theme_name = theme_name.lower()
410 self._theme_name = theme_name
411 super(Inspector, self).__init__(parent=parent, config=config)
412 self.parser = PyColorize.Parser(out="str", theme_name=theme_name)
413 self.str_detail_level = str_detail_level
414 self.set_theme_name(theme_name)
415
416 def format(self, *args, **kwargs):
417 return self.parser.format(*args, **kwargs)
418
419 def _getdef(self,obj,oname='') -> Union[str,None]:
420 """Return the call signature for any callable object.
421
422 If any exception is generated, None is returned instead and the
423 exception is suppressed."""
424 if not callable(obj):
425 return None
426 try:
427 return _render_signature(signature(obj), oname)
428 except:
429 return None
430
431 def __head(self, h: str) -> str:
432 """Return a header string with proper colors."""
433 return PyColorize.theme_table[self._theme_name].format([(Token.Header, h)])
434
435 def set_theme_name(self, name: str):
436 assert name == name.lower()
437 assert name in PyColorize.theme_table.keys()
438 self._theme_name = name
439 self.parser.theme_name = name
440
441 def set_active_scheme(self, scheme: str):
442 warnings.warn(
443 "set_active_scheme is deprecated and replaced by set_theme_name as of IPython 9.0",
444 DeprecationWarning,
445 stacklevel=2,
446 )
447 assert scheme == scheme.lower()
448 if scheme is not None and self._theme_name != scheme:
449 self._theme_name = scheme
450 self.parser.theme_name = scheme
451
452 def noinfo(self, msg, oname):
453 """Generic message when no information is found."""
454 print('No %s found' % msg, end=' ')
455 if oname:
456 print('for %s' % oname)
457 else:
458 print()
459
460 def pdef(self, obj, oname=''):
461 """Print the call signature for any callable object.
462
463 If the object is a class, print the constructor information."""
464
465 if not callable(obj):
466 print('Object is not callable.')
467 return
468
469 header = ''
470
471 if inspect.isclass(obj):
472 header = self.__head('Class constructor information:\n')
473
474
475 output = self._getdef(obj,oname)
476 if output is None:
477 self.noinfo('definition header',oname)
478 else:
479 print(header,self.format(output), end=' ')
480
481 # In Python 3, all classes are new-style, so they all have __init__.
482 @skip_doctest
483 def pdoc(self, obj, oname='', formatter=None):
484 """Print the docstring for any object.
485
486 Optional:
487 -formatter: a function to run the docstring through for specially
488 formatted docstrings.
489
490 Examples
491 --------
492 In [1]: class NoInit:
493 ...: pass
494
495 In [2]: class NoDoc:
496 ...: def __init__(self):
497 ...: pass
498
499 In [3]: %pdoc NoDoc
500 No documentation found for NoDoc
501
502 In [4]: %pdoc NoInit
503 No documentation found for NoInit
504
505 In [5]: obj = NoInit()
506
507 In [6]: %pdoc obj
508 No documentation found for obj
509
510 In [5]: obj2 = NoDoc()
511
512 In [6]: %pdoc obj2
513 No documentation found for obj2
514 """
515
516 lines = []
517 ds = getdoc(obj)
518 if formatter:
519 ds = formatter(ds).get('plain/text', ds)
520 if ds:
521 lines.append(self.__head("Class docstring:"))
522 lines.append(indent(ds))
523 if inspect.isclass(obj) and hasattr(obj, '__init__'):
524 init_ds = getdoc(obj.__init__)
525 if init_ds is not None:
526 lines.append(self.__head("Init docstring:"))
527 lines.append(indent(init_ds))
528 elif hasattr(obj,'__call__'):
529 call_ds = getdoc(obj.__call__)
530 if call_ds:
531 lines.append(self.__head("Call docstring:"))
532 lines.append(indent(call_ds))
533
534 if not lines:
535 self.noinfo('documentation',oname)
536 else:
537 page.page('\n'.join(lines))
538
539 def psource(self, obj, oname=''):
540 """Print the source code for an object."""
541
542 # Flush the source cache because inspect can return out-of-date source
543 linecache.checkcache()
544 try:
545 src = getsource(obj, oname=oname)
546 except Exception:
547 src = None
548
549 if src is None:
550 self.noinfo('source', oname)
551 else:
552 page.page(self.format(src))
553
554 def pfile(self, obj, oname=''):
555 """Show the whole file where an object was defined."""
556
557 lineno = find_source_lines(obj)
558 if lineno is None:
559 self.noinfo('file', oname)
560 return
561
562 ofile = find_file(obj)
563 # run contents of file through pager starting at line where the object
564 # is defined, as long as the file isn't binary and is actually on the
565 # filesystem.
566 if ofile is None:
567 print("Could not find file for object")
568 elif ofile.endswith((".so", ".dll", ".pyd")):
569 print("File %r is binary, not printing." % ofile)
570 elif not os.path.isfile(ofile):
571 print('File %r does not exist, not printing.' % ofile)
572 else:
573 # Print only text files, not extension binaries. Note that
574 # getsourcelines returns lineno with 1-offset and page() uses
575 # 0-offset, so we must adjust.
576 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
577
578
579 def _mime_format(self, text:str, formatter=None) -> dict:
580 """Return a mime bundle representation of the input text.
581
582 - if `formatter` is None, the returned mime bundle has
583 a ``text/plain`` field, with the input text.
584 a ``text/html`` field with a ``<pre>`` tag containing the input text.
585
586 - if ``formatter`` is not None, it must be a callable transforming the
587 input text into a mime bundle. Default values for ``text/plain`` and
588 ``text/html`` representations are the ones described above.
589
590 Note:
591
592 Formatters returning strings are supported but this behavior is deprecated.
593
594 """
595 defaults = {
596 "text/plain": text,
597 "text/html": f"<pre>{html.escape(text)}</pre>",
598 }
599
600 if formatter is None:
601 return defaults
602 else:
603 formatted = formatter(text)
604
605 if not isinstance(formatted, dict):
606 # Handle the deprecated behavior of a formatter returning
607 # a string instead of a mime bundle.
608 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
609
610 else:
611 return dict(defaults, **formatted)
612
613 def format_mime(self, bundle: UnformattedBundle) -> Bundle:
614 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
615 # Format text/plain mimetype
616 assert isinstance(bundle["text/plain"], list)
617 for item in bundle["text/plain"]:
618 assert isinstance(item, tuple)
619
620 new_b: Bundle = {}
621 lines = []
622 _len = max(len(h) for h, _ in bundle["text/plain"])
623
624 for head, body in bundle["text/plain"]:
625 body = body.strip("\n")
626 delim = "\n" if "\n" in body else " "
627 lines.append(
628 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
629 )
630
631 new_b["text/plain"] = "\n".join(lines)
632
633 if "text/html" in bundle:
634 assert isinstance(bundle["text/html"], list)
635 for item in bundle["text/html"]:
636 assert isinstance(item, tuple)
637 # Format the text/html mimetype
638 if isinstance(bundle["text/html"], (list, tuple)):
639 # bundle['text/html'] is a list of (head, formatted body) pairs
640 new_b["text/html"] = "\n".join(
641 f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"]
642 )
643
644 for k in bundle.keys():
645 if k in ("text/html", "text/plain"):
646 continue
647 else:
648 new_b[k] = bundle[k] # type:ignore
649 return new_b
650
651 def _append_info_field(
652 self,
653 bundle: UnformattedBundle,
654 title: str,
655 key: str,
656 info,
657 omit_sections: List[str],
658 formatter,
659 ):
660 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
661 if title in omit_sections or key in omit_sections:
662 return
663 field = info[key]
664 if field is not None:
665 formatted_field = self._mime_format(field, formatter)
666 bundle["text/plain"].append((title, formatted_field["text/plain"]))
667 bundle["text/html"].append((title, formatted_field["text/html"]))
668
669 def _make_info_unformatted(
670 self, obj, info, formatter, detail_level, omit_sections
671 ) -> UnformattedBundle:
672 """Assemble the mimebundle as unformatted lists of information"""
673 bundle: UnformattedBundle = {
674 "text/plain": [],
675 "text/html": [],
676 }
677
678 # A convenience function to simplify calls below
679 def append_field(
680 bundle: UnformattedBundle, title: str, key: str, formatter=None
681 ):
682 self._append_info_field(
683 bundle,
684 title=title,
685 key=key,
686 info=info,
687 omit_sections=omit_sections,
688 formatter=formatter,
689 )
690
691 def code_formatter(text) -> Bundle:
692 return {
693 'text/plain': self.format(text),
694 'text/html': pylight(text)
695 }
696
697 if info["isalias"]:
698 append_field(bundle, "Repr", "string_form")
699
700 elif info['ismagic']:
701 if detail_level > 0:
702 append_field(bundle, "Source", "source", code_formatter)
703 else:
704 append_field(bundle, "Docstring", "docstring", formatter)
705 append_field(bundle, "File", "file")
706
707 elif info['isclass'] or is_simple_callable(obj):
708 # Functions, methods, classes
709 append_field(bundle, "Signature", "definition", code_formatter)
710 append_field(bundle, "Init signature", "init_definition", code_formatter)
711 append_field(bundle, "Docstring", "docstring", formatter)
712 if detail_level > 0 and info["source"]:
713 append_field(bundle, "Source", "source", code_formatter)
714 else:
715 append_field(bundle, "Init docstring", "init_docstring", formatter)
716
717 append_field(bundle, "File", "file")
718 append_field(bundle, "Type", "type_name")
719 append_field(bundle, "Subclasses", "subclasses")
720
721 else:
722 # General Python objects
723 append_field(bundle, "Signature", "definition", code_formatter)
724 append_field(bundle, "Call signature", "call_def", code_formatter)
725 append_field(bundle, "Type", "type_name")
726 append_field(bundle, "String form", "string_form")
727
728 # Namespace
729 if info["namespace"] != "Interactive":
730 append_field(bundle, "Namespace", "namespace")
731
732 append_field(bundle, "Length", "length")
733 append_field(bundle, "File", "file")
734
735 # Source or docstring, depending on detail level and whether
736 # source found.
737 if detail_level > 0 and info["source"]:
738 append_field(bundle, "Source", "source", code_formatter)
739 else:
740 append_field(bundle, "Docstring", "docstring", formatter)
741
742 append_field(bundle, "Class docstring", "class_docstring", formatter)
743 append_field(bundle, "Init docstring", "init_docstring", formatter)
744 append_field(bundle, "Call docstring", "call_docstring", formatter)
745 return bundle
746
747
748 def _get_info(
749 self,
750 obj: Any,
751 oname: str = "",
752 formatter=None,
753 info: Optional[OInfo] = None,
754 detail_level: int = 0,
755 omit_sections: Union[List[str], Tuple[()]] = (),
756 ) -> Bundle:
757 """Retrieve an info dict and format it.
758
759 Parameters
760 ----------
761 obj : any
762 Object to inspect and return info from
763 oname : str (default: ''):
764 Name of the variable pointing to `obj`.
765 formatter : callable
766 info
767 already computed information
768 detail_level : integer
769 Granularity of detail level, if set to 1, give more information.
770 omit_sections : list[str]
771 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
772 """
773
774 info_dict = self.info(obj, oname=oname, info=info, detail_level=detail_level)
775 omit_sections = list(omit_sections)
776
777 bundle = self._make_info_unformatted(
778 obj,
779 info_dict,
780 formatter,
781 detail_level=detail_level,
782 omit_sections=omit_sections,
783 )
784 if self.mime_hooks:
785 hook_data = InspectorHookData(
786 obj=obj,
787 info=info,
788 info_dict=info_dict,
789 detail_level=detail_level,
790 omit_sections=omit_sections,
791 )
792 for key, hook in self.mime_hooks.items(): # type:ignore
793 required_parameters = [
794 parameter
795 for parameter in inspect.signature(hook).parameters.values()
796 if parameter.default != inspect.Parameter.default
797 ]
798 if len(required_parameters) == 1:
799 res = hook(hook_data)
800 else:
801 warnings.warn(
802 "MIME hook format changed in IPython 8.22; hooks should now accept"
803 " a single parameter (InspectorHookData); support for hooks requiring"
804 " two-parameters (obj and info) will be removed in a future version",
805 DeprecationWarning,
806 stacklevel=2,
807 )
808 res = hook(obj, info)
809 if res is not None:
810 bundle[key] = res
811 return self.format_mime(bundle)
812
813 def pinfo(
814 self,
815 obj,
816 oname="",
817 formatter=None,
818 info: Optional[OInfo] = None,
819 detail_level=0,
820 enable_html_pager=True,
821 omit_sections=(),
822 ):
823 """Show detailed information about an object.
824
825 Optional arguments:
826
827 - oname: name of the variable pointing to the object.
828
829 - formatter: callable (optional)
830 A special formatter for docstrings.
831
832 The formatter is a callable that takes a string as an input
833 and returns either a formatted string or a mime type bundle
834 in the form of a dictionary.
835
836 Although the support of custom formatter returning a string
837 instead of a mime type bundle is deprecated.
838
839 - info: a structure with some information fields which may have been
840 precomputed already.
841
842 - detail_level: if set to 1, more information is given.
843
844 - omit_sections: set of section keys and titles to omit
845 """
846 assert info is not None
847 info_b: Bundle = self._get_info(
848 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
849 )
850 if not enable_html_pager:
851 del info_b["text/html"]
852 page.page(info_b)
853
854 def info(self, obj, oname="", info=None, detail_level=0) -> InfoDict:
855 """Compute a dict with detailed information about an object.
856
857 Parameters
858 ----------
859 obj : any
860 An object to find information about
861 oname : str (default: '')
862 Name of the variable pointing to `obj`.
863 info : (default: None)
864 A struct (dict like with attr access) with some information fields
865 which may have been precomputed already.
866 detail_level : int (default:0)
867 If set to 1, more information is given.
868
869 Returns
870 -------
871 An object info dict with known fields from `info_fields` (see `InfoDict`).
872 """
873
874 if info is None:
875 ismagic = False
876 isalias = False
877 ospace = ''
878 else:
879 ismagic = info.ismagic
880 isalias = info.isalias
881 ospace = info.namespace
882
883 # Get docstring, special-casing aliases:
884 att_name = oname.split(".")[-1]
885 parents_docs = None
886 prelude = ""
887 if info and info.parent is not None and hasattr(info.parent, HOOK_NAME):
888 parents_docs_dict = getattr(info.parent, HOOK_NAME)
889 parents_docs = parents_docs_dict.get(att_name, None)
890 out: InfoDict = cast(
891 InfoDict,
892 {
893 **{field: None for field in _info_fields},
894 **{
895 "name": oname,
896 "found": True,
897 "isalias": isalias,
898 "ismagic": ismagic,
899 "subclasses": None,
900 },
901 },
902 )
903
904 if parents_docs:
905 ds = parents_docs
906 elif isalias:
907 if not callable(obj):
908 try:
909 ds = "Alias to the system command:\n %s" % obj[1]
910 except:
911 ds = "Alias: " + str(obj)
912 else:
913 ds = "Alias to " + str(obj)
914 if obj.__doc__:
915 ds += "\nDocstring:\n" + obj.__doc__
916 else:
917 ds_or_None = getdoc(obj)
918 if ds_or_None is None:
919 ds = '<no docstring>'
920 else:
921 ds = ds_or_None
922
923 ds = prelude + ds
924
925 # store output in a dict, we initialize it here and fill it as we go
926
927 string_max = 200 # max size of strings to show (snipped if longer)
928 shalf = int((string_max - 5) / 2)
929
930 if ismagic:
931 out['type_name'] = 'Magic function'
932 elif isalias:
933 out['type_name'] = 'System alias'
934 else:
935 out['type_name'] = type(obj).__name__
936
937 try:
938 bclass = obj.__class__
939 out['base_class'] = str(bclass)
940 except:
941 pass
942
943 # String form, but snip if too long in ? form (full in ??)
944 if detail_level >= self.str_detail_level:
945 try:
946 ostr = str(obj)
947 if not detail_level and len(ostr) > string_max:
948 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
949 # TODO: `'string_form'.expandtabs()` seems wrong, but
950 # it was (nearly) like this since the first commit ever.
951 ostr = ("\n" + " " * len("string_form".expandtabs())).join(
952 q.strip() for q in ostr.split("\n")
953 )
954 out["string_form"] = ostr
955 except:
956 pass
957
958 if ospace:
959 out['namespace'] = ospace
960
961 # Length (for strings and lists)
962 try:
963 out['length'] = str(len(obj))
964 except Exception:
965 pass
966
967 # Filename where object was defined
968 binary_file = False
969 fname = find_file(obj)
970 if fname is None:
971 # if anything goes wrong, we don't want to show source, so it's as
972 # if the file was binary
973 binary_file = True
974 else:
975 if fname.endswith(('.so', '.dll', '.pyd')):
976 binary_file = True
977 elif fname.endswith('<string>'):
978 fname = 'Dynamically generated function. No source code available.'
979 out['file'] = compress_user(fname)
980
981 # Original source code for a callable, class or property.
982 if detail_level:
983 # Flush the source cache because inspect can return out-of-date
984 # source
985 linecache.checkcache()
986 try:
987 if isinstance(obj, property) or not binary_file:
988 src = getsource(obj, oname)
989 if src is not None:
990 src = src.rstrip()
991 out['source'] = src
992
993 except Exception:
994 pass
995
996 # Add docstring only if no source is to be shown (avoid repetitions).
997 if ds and not self._source_contains_docstring(out.get('source'), ds):
998 out['docstring'] = ds
999
1000 # Constructor docstring for classes
1001 if inspect.isclass(obj):
1002 out['isclass'] = True
1003
1004 # get the init signature:
1005 try:
1006 init_def = self._getdef(obj, oname)
1007 except AttributeError:
1008 init_def = None
1009
1010 # get the __init__ docstring
1011 try:
1012 obj_init = obj.__init__
1013 except AttributeError:
1014 init_ds = None
1015 else:
1016 if init_def is None:
1017 # Get signature from init if top-level sig failed.
1018 # Can happen for built-in types (list, etc.).
1019 try:
1020 init_def = self._getdef(obj_init, oname)
1021 except AttributeError:
1022 pass
1023 init_ds = getdoc(obj_init)
1024 # Skip Python's auto-generated docstrings
1025 if init_ds == _object_init_docstring:
1026 init_ds = None
1027
1028 if init_def:
1029 out['init_definition'] = init_def
1030
1031 if init_ds:
1032 out['init_docstring'] = init_ds
1033
1034 names = [sub.__name__ for sub in type.__subclasses__(obj)]
1035 if len(names) < 10:
1036 all_names = ', '.join(names)
1037 else:
1038 all_names = ', '.join(names[:10]+['...'])
1039 out['subclasses'] = all_names
1040 # and class docstring for instances:
1041 else:
1042 # reconstruct the function definition and print it:
1043 defln = self._getdef(obj, oname)
1044 if defln:
1045 out['definition'] = defln
1046
1047 # First, check whether the instance docstring is identical to the
1048 # class one, and print it separately if they don't coincide. In
1049 # most cases they will, but it's nice to print all the info for
1050 # objects which use instance-customized docstrings.
1051 if ds:
1052 try:
1053 cls = getattr(obj,'__class__')
1054 except:
1055 class_ds = None
1056 else:
1057 class_ds = getdoc(cls)
1058 # Skip Python's auto-generated docstrings
1059 if class_ds in _builtin_type_docstrings:
1060 class_ds = None
1061 if class_ds and ds != class_ds:
1062 out['class_docstring'] = class_ds
1063
1064 # Next, try to show constructor docstrings
1065 try:
1066 init_ds = getdoc(obj.__init__)
1067 # Skip Python's auto-generated docstrings
1068 if init_ds == _object_init_docstring:
1069 init_ds = None
1070 except AttributeError:
1071 init_ds = None
1072 if init_ds:
1073 out['init_docstring'] = init_ds
1074
1075 # Call form docstring for callable instances
1076 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
1077 call_def = self._getdef(obj.__call__, oname)
1078 if call_def and (call_def != out.get('definition')):
1079 # it may never be the case that call def and definition differ,
1080 # but don't include the same signature twice
1081 out['call_def'] = call_def
1082 call_ds = getdoc(obj.__call__)
1083 # Skip Python's auto-generated docstrings
1084 if call_ds == _func_call_docstring:
1085 call_ds = None
1086 if call_ds:
1087 out['call_docstring'] = call_ds
1088
1089 return out
1090
1091 @staticmethod
1092 def _source_contains_docstring(src, doc):
1093 """
1094 Check whether the source *src* contains the docstring *doc*.
1095
1096 This is is helper function to skip displaying the docstring if the
1097 source already contains it, avoiding repetition of information.
1098 """
1099 try:
1100 (def_node,) = ast.parse(dedent(src)).body
1101 return ast.get_docstring(def_node) == doc # type: ignore[arg-type]
1102 except Exception:
1103 # The source can become invalid or even non-existent (because it
1104 # is re-fetched from the source file) so the above code fail in
1105 # arbitrary ways.
1106 return False
1107
1108 def psearch(self,pattern,ns_table,ns_search=[],
1109 ignore_case=False,show_all=False, *, list_types=False):
1110 """Search namespaces with wildcards for objects.
1111
1112 Arguments:
1113
1114 - pattern: string containing shell-like wildcards to use in namespace
1115 searches and optionally a type specification to narrow the search to
1116 objects of that type.
1117
1118 - ns_table: dict of name->namespaces for search.
1119
1120 Optional arguments:
1121
1122 - ns_search: list of namespace names to include in search.
1123
1124 - ignore_case(False): make the search case-insensitive.
1125
1126 - show_all(False): show all names, including those starting with
1127 underscores.
1128
1129 - list_types(False): list all available object types for object matching.
1130 """
1131 # print('ps pattern:<%r>' % pattern) # dbg
1132
1133 # defaults
1134 type_pattern = 'all'
1135 filter = ''
1136
1137 # list all object types
1138 if list_types:
1139 page.page('\n'.join(sorted(typestr2type)))
1140 return
1141
1142 cmds = pattern.split()
1143 len_cmds = len(cmds)
1144 if len_cmds == 1:
1145 # Only filter pattern given
1146 filter = cmds[0]
1147 elif len_cmds == 2:
1148 # Both filter and type specified
1149 filter,type_pattern = cmds
1150 else:
1151 raise ValueError('invalid argument string for psearch: <%s>' %
1152 pattern)
1153
1154 # filter search namespaces
1155 for name in ns_search:
1156 if name not in ns_table:
1157 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1158 (name,ns_table.keys()))
1159
1160 # print('type_pattern:',type_pattern) # dbg
1161 search_result, namespaces_seen = set(), set()
1162 for ns_name in ns_search:
1163 ns = ns_table[ns_name]
1164 # Normally, locals and globals are the same, so we just check one.
1165 if id(ns) in namespaces_seen:
1166 continue
1167 namespaces_seen.add(id(ns))
1168 tmp_res = list_namespace(ns, type_pattern, filter,
1169 ignore_case=ignore_case, show_all=show_all)
1170 search_result.update(tmp_res)
1171
1172 page.page('\n'.join(sorted(search_result)))
1173
1174
1175def _render_signature(obj_signature, obj_name) -> str:
1176 """
1177 This was mostly taken from inspect.Signature.__str__.
1178 Look there for the comments.
1179 The only change is to add linebreaks when this gets too long.
1180 """
1181 result = []
1182 pos_only = False
1183 kw_only = True
1184 for param in obj_signature.parameters.values():
1185 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1186 pos_only = True
1187 elif pos_only:
1188 result.append('/')
1189 pos_only = False
1190
1191 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1192 kw_only = False
1193 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1194 result.append('*')
1195 kw_only = False
1196
1197 result.append(str(param))
1198
1199 if pos_only:
1200 result.append('/')
1201
1202 # add up name, parameters, braces (2), and commas
1203 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1204 # This doesn’t fit behind “Signature: ” in an inspect window.
1205 rendered = '{}(\n{})'.format(obj_name, ''.join(
1206 ' {},\n'.format(r) for r in result)
1207 )
1208 else:
1209 rendered = '{}({})'.format(obj_name, ', '.join(result))
1210
1211 if obj_signature.return_annotation is not inspect._empty:
1212 anno = inspect.formatannotation(obj_signature.return_annotation)
1213 rendered += ' -> {}'.format(anno)
1214
1215 return rendered