oinspect.py 39 KB

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