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