Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/oinspect.py: 1%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Tools for inspecting Python objects.
3Uses syntax highlighting for presenting the various information elements.
5Similar in spirit to the inspect module, but all calls take a name argument to
6reference the name under which an object is being read.
7"""
9# Copyright (c) IPython Development Team.
10# Distributed under the terms of the Modified BSD License.
12__all__ = ["Inspector"]
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
29from typing import (
30 cast,
31 Any,
32 Optional,
33 Dict,
34 Union,
35 List,
36 TypedDict,
37 TypeAlias,
38 Tuple,
39)
41import traitlets
42from traitlets.config import Configurable
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
55from pygments import highlight
56from pygments.lexers import PythonLexer
57from pygments.formatters import HtmlFormatter
59HOOK_NAME = "__custom_documentations__"
62UnformattedBundle: TypeAlias = Dict[str, List[Tuple[str, str]]] # List of (title, body)
63Bundle: TypeAlias = Dict[str, str]
66@dataclass
67class OInfo:
68 ismagic: bool
69 isalias: bool
70 found: bool
71 namespace: Optional[str]
72 parent: Any
73 obj: Any
75 def get(self, field):
76 """Get a field from the object for backward compatibility with before 8.12
78 see https://github.com/h5py/h5py/issues/2253
79 """
80 # We need to deprecate this at some point, but the warning will show in completion.
81 # Let's comment this for now and uncomment end of 2023 ish
82 # 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)
93def pylight(code):
94 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
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}
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
110#****************************************************************************
111# Auxiliary functions and objects
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
139_info_fields = list(InfoDict.__annotations__.keys())
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
151 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
154@dataclass
155class InspectorHookData:
156 """Data passed to the mime hook"""
158 obj: Any
159 info: Optional[OInfo]
160 info_dict: InfoDict
161 detail_level: int
162 omit_sections: list[str]
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
184 return InfoDict(**infodict) # type:ignore
187def get_encoding(obj):
188 """Get encoding for python source file defining obj
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
211def getdoc(obj) -> Union[str, None]:
212 """Stable wrapper around inspect.getdoc.
214 This can't crash because of attribute problems.
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
232def getsource(obj, oname='') -> Union[str,None]:
233 """Wrapper around inspect.getsource.
235 This can be modified by other projects to provide customized source
236 extraction.
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
245 Returns
246 -------
247 src : unicode or None
249 """
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
274 else:
275 # Get source for non-property objects.
277 obj = _get_wrapped(obj)
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
291 return src
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))
299def _get_wrapped(obj):
300 """Get the original object if wrapped in one or more @decorators
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
317def find_file(obj) -> Optional[str]:
318 """Find the absolute path to the file where an object was defined.
320 This is essentially a robust wrapper around `inspect.getabsfile`.
322 Returns None if no file can be found.
324 Parameters
325 ----------
326 obj : any Python object
328 Returns
329 -------
330 fname : str
331 The absolute path to the file where the object was defined.
332 """
333 obj = _get_wrapped(obj)
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
349 return fname
352def find_source_lines(obj):
353 """Find the line number in a file where an object was defined.
355 This is essentially a robust wrapper around `inspect.getsourcelines`.
357 Returns None if no file can be found.
359 Parameters
360 ----------
361 obj : any Python object
363 Returns
364 -------
365 lineno : int
366 The line number where the object definition starts.
367 """
368 obj = _get_wrapped(obj)
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
381 return lineno
384_sentinel = object()
387class Inspector(Configurable):
389 mime_hooks = traitlets.Dict(
390 config=True,
391 help="dictionary of mime to callable to add information into help mimebundle dict",
392 ).tag(config=True)
394 _theme_name: str
396 def __init__(
397 self,
398 *,
399 theme_name: str,
400 str_detail_level=0,
401 parent=None,
402 config=None,
403 ):
404 if theme_name in ["Linux", "LightBG", "Neutral", "NoColor"]:
405 warnings.warn(
406 f"Theme names and color schemes are lowercase in IPython 9.0 use {theme_name.lower()} instead",
407 DeprecationWarning,
408 stacklevel=2,
409 )
410 theme_name = theme_name.lower()
411 self._theme_name = theme_name
412 super(Inspector, self).__init__(parent=parent, config=config)
413 self.parser = PyColorize.Parser(out="str", theme_name=theme_name)
414 self.str_detail_level = str_detail_level
415 self.set_theme_name(theme_name)
417 def format(self, *args, **kwargs):
418 return self.parser.format(*args, **kwargs)
420 def _getdef(self,obj,oname='') -> Union[str,None]:
421 """Return the call signature for any callable object.
423 If any exception is generated, None is returned instead and the
424 exception is suppressed."""
425 if not callable(obj):
426 return None
427 try:
428 return _render_signature(signature(obj), oname)
429 except:
430 return None
432 def __head(self, h: str) -> str:
433 """Return a header string with proper colors."""
434 return PyColorize.theme_table[self._theme_name].format([(Token.Header, h)])
436 def set_theme_name(self, name: str):
437 assert name == name.lower()
438 assert name in PyColorize.theme_table.keys()
439 self._theme_name = name
440 self.parser.theme_name = name
442 def set_active_scheme(self, scheme: str):
443 warnings.warn(
444 "set_active_scheme is deprecated and replaced by set_theme_name as of IPython 9.0",
445 DeprecationWarning,
446 stacklevel=2,
447 )
448 assert scheme == scheme.lower()
449 if scheme is not None and self._theme_name != scheme:
450 self._theme_name = scheme
451 self.parser.theme_name = scheme
453 def noinfo(self, msg, oname):
454 """Generic message when no information is found."""
455 print('No %s found' % msg, end=' ')
456 if oname:
457 print('for %s' % oname)
458 else:
459 print()
461 def pdef(self, obj, oname=''):
462 """Print the call signature for any callable object.
464 If the object is a class, print the constructor information."""
466 if not callable(obj):
467 print('Object is not callable.')
468 return
470 header = ''
472 if inspect.isclass(obj):
473 header = self.__head('Class constructor information:\n')
476 output = self._getdef(obj,oname)
477 if output is None:
478 self.noinfo('definition header',oname)
479 else:
480 print(header,self.format(output), end=' ')
482 # In Python 3, all classes are new-style, so they all have __init__.
483 @skip_doctest
484 def pdoc(self, obj, oname='', formatter=None):
485 """Print the docstring for any object.
487 Optional:
488 -formatter: a function to run the docstring through for specially
489 formatted docstrings.
491 Examples
492 --------
493 In [1]: class NoInit:
494 ...: pass
496 In [2]: class NoDoc:
497 ...: def __init__(self):
498 ...: pass
500 In [3]: %pdoc NoDoc
501 No documentation found for NoDoc
503 In [4]: %pdoc NoInit
504 No documentation found for NoInit
506 In [5]: obj = NoInit()
508 In [6]: %pdoc obj
509 No documentation found for obj
511 In [5]: obj2 = NoDoc()
513 In [6]: %pdoc obj2
514 No documentation found for obj2
515 """
517 lines = []
518 ds = getdoc(obj)
519 if formatter:
520 ds = formatter(ds).get('plain/text', ds)
521 if ds:
522 lines.append(self.__head("Class docstring:"))
523 lines.append(indent(ds))
524 if inspect.isclass(obj) and hasattr(obj, '__init__'):
525 init_ds = getdoc(obj.__init__)
526 if init_ds is not None:
527 lines.append(self.__head("Init docstring:"))
528 lines.append(indent(init_ds))
529 elif hasattr(obj,'__call__'):
530 call_ds = getdoc(obj.__call__)
531 if call_ds:
532 lines.append(self.__head("Call docstring:"))
533 lines.append(indent(call_ds))
535 if not lines:
536 self.noinfo('documentation',oname)
537 else:
538 page.page('\n'.join(lines))
540 def psource(self, obj, oname=''):
541 """Print the source code for an object."""
543 # Flush the source cache because inspect can return out-of-date source
544 linecache.checkcache()
545 try:
546 src = getsource(obj, oname=oname)
547 except Exception:
548 src = None
550 if src is None:
551 self.noinfo('source', oname)
552 else:
553 page.page(self.format(src))
555 def pfile(self, obj, oname=''):
556 """Show the whole file where an object was defined."""
558 lineno = find_source_lines(obj)
559 if lineno is None:
560 self.noinfo('file', oname)
561 return
563 ofile = find_file(obj)
564 # run contents of file through pager starting at line where the object
565 # is defined, as long as the file isn't binary and is actually on the
566 # filesystem.
567 if ofile is None:
568 print("Could not find file for object")
569 elif ofile.endswith((".so", ".dll", ".pyd")):
570 print("File %r is binary, not printing." % ofile)
571 elif not os.path.isfile(ofile):
572 print('File %r does not exist, not printing.' % ofile)
573 else:
574 # Print only text files, not extension binaries. Note that
575 # getsourcelines returns lineno with 1-offset and page() uses
576 # 0-offset, so we must adjust.
577 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
580 def _mime_format(self, text:str, formatter=None) -> dict:
581 """Return a mime bundle representation of the input text.
583 - if `formatter` is None, the returned mime bundle has
584 a ``text/plain`` field, with the input text.
585 a ``text/html`` field with a ``<pre>`` tag containing the input text.
587 - if ``formatter`` is not None, it must be a callable transforming the
588 input text into a mime bundle. Default values for ``text/plain`` and
589 ``text/html`` representations are the ones described above.
591 Note:
593 Formatters returning strings are supported but this behavior is deprecated.
595 """
596 defaults = {
597 "text/plain": text,
598 "text/html": f"<pre>{html.escape(text)}</pre>",
599 }
601 if formatter is None:
602 return defaults
603 else:
604 formatted = formatter(text)
606 if not isinstance(formatted, dict):
607 # Handle the deprecated behavior of a formatter returning
608 # a string instead of a mime bundle.
609 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
611 else:
612 return dict(defaults, **formatted)
614 def format_mime(self, bundle: UnformattedBundle) -> Bundle:
615 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
616 # Format text/plain mimetype
617 assert isinstance(bundle["text/plain"], list)
618 for item in bundle["text/plain"]:
619 assert isinstance(item, tuple)
621 new_b: Bundle = {}
622 lines = []
623 _len = max(len(h) for h, _ in bundle["text/plain"])
625 for head, body in bundle["text/plain"]:
626 body = body.strip("\n")
627 delim = "\n" if "\n" in body else " "
628 lines.append(
629 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
630 )
632 new_b["text/plain"] = "\n".join(lines)
634 if "text/html" in bundle:
635 assert isinstance(bundle["text/html"], list)
636 for item in bundle["text/html"]:
637 assert isinstance(item, tuple)
638 # Format the text/html mimetype
639 if isinstance(bundle["text/html"], (list, tuple)):
640 # bundle['text/html'] is a list of (head, formatted body) pairs
641 new_b["text/html"] = "\n".join(
642 f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"]
643 )
645 for k in bundle.keys():
646 if k in ("text/html", "text/plain"):
647 continue
648 else:
649 new_b[k] = bundle[k] # type:ignore
650 return new_b
652 def _append_info_field(
653 self,
654 bundle: UnformattedBundle,
655 title: str,
656 key: str,
657 info,
658 omit_sections: List[str],
659 formatter,
660 ):
661 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
662 if title in omit_sections or key in omit_sections:
663 return
664 field = info[key]
665 if field is not None:
666 formatted_field = self._mime_format(field, formatter)
667 bundle["text/plain"].append((title, formatted_field["text/plain"]))
668 bundle["text/html"].append((title, formatted_field["text/html"]))
670 def _make_info_unformatted(
671 self, obj, info, formatter, detail_level, omit_sections
672 ) -> UnformattedBundle:
673 """Assemble the mimebundle as unformatted lists of information"""
674 bundle: UnformattedBundle = {
675 "text/plain": [],
676 "text/html": [],
677 }
679 # A convenience function to simplify calls below
680 def append_field(
681 bundle: UnformattedBundle, title: str, key: str, formatter=None
682 ):
683 self._append_info_field(
684 bundle,
685 title=title,
686 key=key,
687 info=info,
688 omit_sections=omit_sections,
689 formatter=formatter,
690 )
692 def code_formatter(text) -> Bundle:
693 return {
694 'text/plain': self.format(text),
695 'text/html': pylight(text)
696 }
698 if info["isalias"]:
699 append_field(bundle, "Repr", "string_form")
701 elif info['ismagic']:
702 if detail_level > 0:
703 append_field(bundle, "Source", "source", code_formatter)
704 else:
705 append_field(bundle, "Docstring", "docstring", formatter)
706 append_field(bundle, "File", "file")
708 elif info['isclass'] or is_simple_callable(obj):
709 # Functions, methods, classes
710 append_field(bundle, "Signature", "definition", code_formatter)
711 append_field(bundle, "Init signature", "init_definition", code_formatter)
712 append_field(bundle, "Docstring", "docstring", formatter)
713 if detail_level > 0 and info["source"]:
714 append_field(bundle, "Source", "source", code_formatter)
715 else:
716 append_field(bundle, "Init docstring", "init_docstring", formatter)
718 append_field(bundle, "File", "file")
719 append_field(bundle, "Type", "type_name")
720 append_field(bundle, "Subclasses", "subclasses")
722 else:
723 # General Python objects
724 append_field(bundle, "Signature", "definition", code_formatter)
725 append_field(bundle, "Call signature", "call_def", code_formatter)
726 append_field(bundle, "Type", "type_name")
727 append_field(bundle, "String form", "string_form")
729 # Namespace
730 if info["namespace"] != "Interactive":
731 append_field(bundle, "Namespace", "namespace")
733 append_field(bundle, "Length", "length")
734 append_field(bundle, "File", "file")
736 # Source or docstring, depending on detail level and whether
737 # source found.
738 if detail_level > 0 and info["source"]:
739 append_field(bundle, "Source", "source", code_formatter)
740 else:
741 append_field(bundle, "Docstring", "docstring", formatter)
743 append_field(bundle, "Class docstring", "class_docstring", formatter)
744 append_field(bundle, "Init docstring", "init_docstring", formatter)
745 append_field(bundle, "Call docstring", "call_docstring", formatter)
746 return bundle
749 def _get_info(
750 self,
751 obj: Any,
752 oname: str = "",
753 formatter=None,
754 info: Optional[OInfo] = None,
755 detail_level: int = 0,
756 omit_sections: Union[List[str], Tuple[()]] = (),
757 ) -> Bundle:
758 """Retrieve an info dict and format it.
760 Parameters
761 ----------
762 obj : any
763 Object to inspect and return info from
764 oname : str (default: ''):
765 Name of the variable pointing to `obj`.
766 formatter : callable
767 info
768 already computed information
769 detail_level : integer
770 Granularity of detail level, if set to 1, give more information.
771 omit_sections : list[str]
772 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
773 """
775 info_dict = self.info(obj, oname=oname, info=info, detail_level=detail_level)
776 omit_sections = list(omit_sections)
778 bundle = self._make_info_unformatted(
779 obj,
780 info_dict,
781 formatter,
782 detail_level=detail_level,
783 omit_sections=omit_sections,
784 )
785 if self.mime_hooks:
786 hook_data = InspectorHookData(
787 obj=obj,
788 info=info,
789 info_dict=info_dict,
790 detail_level=detail_level,
791 omit_sections=omit_sections,
792 )
793 for key, hook in self.mime_hooks.items(): # type:ignore
794 required_parameters = [
795 parameter
796 for parameter in inspect.signature(hook).parameters.values()
797 if parameter.default != inspect.Parameter.default
798 ]
799 if len(required_parameters) == 1:
800 res = hook(hook_data)
801 else:
802 warnings.warn(
803 "MIME hook format changed in IPython 8.22; hooks should now accept"
804 " a single parameter (InspectorHookData); support for hooks requiring"
805 " two-parameters (obj and info) will be removed in a future version",
806 DeprecationWarning,
807 stacklevel=2,
808 )
809 res = hook(obj, info)
810 if res is not None:
811 bundle[key] = res
812 return self.format_mime(bundle)
814 def pinfo(
815 self,
816 obj,
817 oname="",
818 formatter=None,
819 info: Optional[OInfo] = None,
820 detail_level=0,
821 enable_html_pager=True,
822 omit_sections=(),
823 ):
824 """Show detailed information about an object.
826 Optional arguments:
828 - oname: name of the variable pointing to the object.
830 - formatter: callable (optional)
831 A special formatter for docstrings.
833 The formatter is a callable that takes a string as an input
834 and returns either a formatted string or a mime type bundle
835 in the form of a dictionary.
837 Although the support of custom formatter returning a string
838 instead of a mime type bundle is deprecated.
840 - info: a structure with some information fields which may have been
841 precomputed already.
843 - detail_level: if set to 1, more information is given.
845 - omit_sections: set of section keys and titles to omit
846 """
847 assert info is not None
848 info_b: Bundle = self._get_info(
849 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
850 )
851 if not enable_html_pager:
852 del info_b["text/html"]
853 page.page(info_b)
855 def info(self, obj, oname="", info=None, detail_level=0) -> InfoDict:
856 """Compute a dict with detailed information about an object.
858 Parameters
859 ----------
860 obj : any
861 An object to find information about
862 oname : str (default: '')
863 Name of the variable pointing to `obj`.
864 info : (default: None)
865 A struct (dict like with attr access) with some information fields
866 which may have been precomputed already.
867 detail_level : int (default:0)
868 If set to 1, more information is given.
870 Returns
871 -------
872 An object info dict with known fields from `info_fields` (see `InfoDict`).
873 """
875 if info is None:
876 ismagic = False
877 isalias = False
878 ospace = ''
879 else:
880 ismagic = info.ismagic
881 isalias = info.isalias
882 ospace = info.namespace
884 # Get docstring, special-casing aliases:
885 att_name = oname.split(".")[-1]
886 parents_docs = None
887 prelude = ""
888 if info and info.parent is not None and hasattr(info.parent, HOOK_NAME):
889 parents_docs_dict = getattr(info.parent, HOOK_NAME)
890 parents_docs = parents_docs_dict.get(att_name, None)
891 out: InfoDict = cast(
892 InfoDict,
893 {
894 **{field: None for field in _info_fields},
895 **{
896 "name": oname,
897 "found": True,
898 "isalias": isalias,
899 "ismagic": ismagic,
900 "subclasses": None,
901 },
902 },
903 )
905 if parents_docs:
906 ds = parents_docs
907 elif isalias:
908 if not callable(obj):
909 try:
910 ds = "Alias to the system command:\n %s" % obj[1]
911 except:
912 ds = "Alias: " + str(obj)
913 else:
914 ds = "Alias to " + str(obj)
915 if obj.__doc__:
916 ds += "\nDocstring:\n" + obj.__doc__
917 else:
918 ds_or_None = getdoc(obj)
919 if ds_or_None is None:
920 ds = '<no docstring>'
921 else:
922 ds = ds_or_None
924 ds = prelude + ds
926 # store output in a dict, we initialize it here and fill it as we go
928 string_max = 200 # max size of strings to show (snipped if longer)
929 shalf = int((string_max - 5) / 2)
931 if ismagic:
932 out['type_name'] = 'Magic function'
933 elif isalias:
934 out['type_name'] = 'System alias'
935 else:
936 out['type_name'] = type(obj).__name__
938 try:
939 bclass = obj.__class__
940 out['base_class'] = str(bclass)
941 except:
942 pass
944 # String form, but snip if too long in ? form (full in ??)
945 if detail_level >= self.str_detail_level:
946 try:
947 ostr = str(obj)
948 if not detail_level and len(ostr) > string_max:
949 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
950 # TODO: `'string_form'.expandtabs()` seems wrong, but
951 # it was (nearly) like this since the first commit ever.
952 ostr = ("\n" + " " * len("string_form".expandtabs())).join(
953 q.strip() for q in ostr.split("\n")
954 )
955 out["string_form"] = ostr
956 except:
957 pass
959 if ospace:
960 out['namespace'] = ospace
962 # Length (for strings and lists)
963 try:
964 out['length'] = str(len(obj))
965 except Exception:
966 pass
968 # Filename where object was defined
969 binary_file = False
970 fname = find_file(obj)
971 if fname is None:
972 # if anything goes wrong, we don't want to show source, so it's as
973 # if the file was binary
974 binary_file = True
975 else:
976 if fname.endswith(('.so', '.dll', '.pyd')):
977 binary_file = True
978 elif fname.endswith('<string>'):
979 fname = 'Dynamically generated function. No source code available.'
980 out['file'] = compress_user(fname)
982 # Original source code for a callable, class or property.
983 if detail_level:
984 # Flush the source cache because inspect can return out-of-date
985 # source
986 linecache.checkcache()
987 try:
988 if isinstance(obj, property) or not binary_file:
989 src = getsource(obj, oname)
990 if src is not None:
991 src = src.rstrip()
992 out['source'] = src
994 except Exception:
995 pass
997 # Add docstring only if no source is to be shown (avoid repetitions).
998 if ds and not self._source_contains_docstring(out.get('source'), ds):
999 out['docstring'] = ds
1001 # Constructor docstring for classes
1002 if inspect.isclass(obj):
1003 out['isclass'] = True
1005 # get the init signature:
1006 try:
1007 init_def = self._getdef(obj, oname)
1008 except AttributeError:
1009 init_def = None
1011 # get the __init__ docstring
1012 try:
1013 obj_init = obj.__init__
1014 except AttributeError:
1015 init_ds = None
1016 else:
1017 if init_def is None:
1018 # Get signature from init if top-level sig failed.
1019 # Can happen for built-in types (list, etc.).
1020 try:
1021 init_def = self._getdef(obj_init, oname)
1022 except AttributeError:
1023 pass
1024 init_ds = getdoc(obj_init)
1025 # Skip Python's auto-generated docstrings
1026 if init_ds == _object_init_docstring:
1027 init_ds = None
1029 if init_def:
1030 out['init_definition'] = init_def
1032 if init_ds:
1033 out['init_docstring'] = init_ds
1035 names = [sub.__name__ for sub in type.__subclasses__(obj)]
1036 if len(names) < 10:
1037 all_names = ', '.join(names)
1038 else:
1039 all_names = ', '.join(names[:10]+['...'])
1040 out['subclasses'] = all_names
1041 # and class docstring for instances:
1042 else:
1043 # reconstruct the function definition and print it:
1044 defln = self._getdef(obj, oname)
1045 if defln:
1046 out['definition'] = defln
1048 # First, check whether the instance docstring is identical to the
1049 # class one, and print it separately if they don't coincide. In
1050 # most cases they will, but it's nice to print all the info for
1051 # objects which use instance-customized docstrings.
1052 if ds:
1053 try:
1054 cls = getattr(obj,'__class__')
1055 except:
1056 class_ds = None
1057 else:
1058 class_ds = getdoc(cls)
1059 # Skip Python's auto-generated docstrings
1060 if class_ds in _builtin_type_docstrings:
1061 class_ds = None
1062 if class_ds and ds != class_ds:
1063 out['class_docstring'] = class_ds
1065 # Next, try to show constructor docstrings
1066 try:
1067 init_ds = getdoc(obj.__init__)
1068 # Skip Python's auto-generated docstrings
1069 if init_ds == _object_init_docstring:
1070 init_ds = None
1071 except AttributeError:
1072 init_ds = None
1073 if init_ds:
1074 out['init_docstring'] = init_ds
1076 # Call form docstring for callable instances
1077 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
1078 call_def = self._getdef(obj.__call__, oname)
1079 if call_def and (call_def != out.get('definition')):
1080 # it may never be the case that call def and definition differ,
1081 # but don't include the same signature twice
1082 out['call_def'] = call_def
1083 call_ds = getdoc(obj.__call__)
1084 # Skip Python's auto-generated docstrings
1085 if call_ds == _func_call_docstring:
1086 call_ds = None
1087 if call_ds:
1088 out['call_docstring'] = call_ds
1090 return out
1092 @staticmethod
1093 def _source_contains_docstring(src, doc):
1094 """
1095 Check whether the source *src* contains the docstring *doc*.
1097 This is is helper function to skip displaying the docstring if the
1098 source already contains it, avoiding repetition of information.
1099 """
1100 try:
1101 (def_node,) = ast.parse(dedent(src)).body
1102 return ast.get_docstring(def_node) == doc # type: ignore[arg-type]
1103 except Exception:
1104 # The source can become invalid or even non-existent (because it
1105 # is re-fetched from the source file) so the above code fail in
1106 # arbitrary ways.
1107 return False
1109 def psearch(self,pattern,ns_table,ns_search=[],
1110 ignore_case=False,show_all=False, *, list_types=False):
1111 """Search namespaces with wildcards for objects.
1113 Arguments:
1115 - pattern: string containing shell-like wildcards to use in namespace
1116 searches and optionally a type specification to narrow the search to
1117 objects of that type.
1119 - ns_table: dict of name->namespaces for search.
1121 Optional arguments:
1123 - ns_search: list of namespace names to include in search.
1125 - ignore_case(False): make the search case-insensitive.
1127 - show_all(False): show all names, including those starting with
1128 underscores.
1130 - list_types(False): list all available object types for object matching.
1131 """
1132 # print('ps pattern:<%r>' % pattern) # dbg
1134 # defaults
1135 type_pattern = 'all'
1136 filter = ''
1138 # list all object types
1139 if list_types:
1140 page.page('\n'.join(sorted(typestr2type)))
1141 return
1143 cmds = pattern.split()
1144 len_cmds = len(cmds)
1145 if len_cmds == 1:
1146 # Only filter pattern given
1147 filter = cmds[0]
1148 elif len_cmds == 2:
1149 # Both filter and type specified
1150 filter,type_pattern = cmds
1151 else:
1152 raise ValueError('invalid argument string for psearch: <%s>' %
1153 pattern)
1155 # filter search namespaces
1156 for name in ns_search:
1157 if name not in ns_table:
1158 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1159 (name,ns_table.keys()))
1161 # print('type_pattern:',type_pattern) # dbg
1162 search_result, namespaces_seen = set(), set()
1163 for ns_name in ns_search:
1164 ns = ns_table[ns_name]
1165 # Normally, locals and globals are the same, so we just check one.
1166 if id(ns) in namespaces_seen:
1167 continue
1168 namespaces_seen.add(id(ns))
1169 tmp_res = list_namespace(ns, type_pattern, filter,
1170 ignore_case=ignore_case, show_all=show_all)
1171 search_result.update(tmp_res)
1173 page.page('\n'.join(sorted(search_result)))
1176def _render_signature(obj_signature, obj_name) -> str:
1177 """
1178 This was mostly taken from inspect.Signature.__str__.
1179 Look there for the comments.
1180 The only change is to add linebreaks when this gets too long.
1181 """
1182 result = []
1183 pos_only = False
1184 kw_only = True
1185 for param in obj_signature.parameters.values():
1186 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1187 pos_only = True
1188 elif pos_only:
1189 result.append('/')
1190 pos_only = False
1192 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1193 kw_only = False
1194 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1195 result.append('*')
1196 kw_only = False
1198 result.append(str(param))
1200 if pos_only:
1201 result.append('/')
1203 # add up name, parameters, braces (2), and commas
1204 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1205 # This doesn’t fit behind “Signature: ” in an inspect window.
1206 rendered = '{}(\n{})'.format(obj_name, ''.join(
1207 ' {},\n'.format(r) for r in result)
1208 )
1209 else:
1210 rendered = '{}({})'.format(obj_name, ', '.join(result))
1212 if obj_signature.return_annotation is not inspect._empty:
1213 anno = inspect.formatannotation(obj_signature.return_annotation)
1214 rendered += ' -> {}'.format(anno)
1216 return rendered