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