Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/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

585 statements  

1"""Tools for inspecting Python objects. 

2 

3Uses syntax highlighting for presenting the various information elements. 

4 

5Similar in spirit to the inspect module, but all calls take a name argument to 

6reference the name under which an object is being read. 

7""" 

8 

9# Copyright (c) IPython Development Team. 

10# Distributed under the terms of the Modified BSD License. 

11 

12__all__ = ['Inspector','InspectColors'] 

13 

14# stdlib modules 

15from dataclasses import dataclass 

16from inspect import signature 

17from textwrap import dedent 

18import ast 

19import html 

20import inspect 

21import io as stdlib_io 

22import linecache 

23import os 

24import types 

25import warnings 

26 

27 

28from typing import ( 

29 cast, 

30 Any, 

31 Optional, 

32 Dict, 

33 Union, 

34 List, 

35 TypedDict, 

36 TypeAlias, 

37 Tuple, 

38) 

39 

40import traitlets 

41 

42# IPython's own 

43from IPython.core import page 

44from IPython.lib.pretty import pretty 

45from IPython.testing.skipdoctest import skip_doctest 

46from IPython.utils import PyColorize, openpy 

47from IPython.utils.dir2 import safe_hasattr 

48from IPython.utils.path import compress_user 

49from IPython.utils.text import indent 

50from IPython.utils.wildcard import list_namespace, typestr2type 

51from IPython.utils.coloransi import TermColors 

52from IPython.utils.colorable import Colorable 

53from IPython.utils.decorators import undoc 

54 

55from pygments import highlight 

56from pygments.lexers import PythonLexer 

57from pygments.formatters import HtmlFormatter 

58 

59HOOK_NAME = "__custom_documentations__" 

60 

61 

62UnformattedBundle: TypeAlias = Dict[str, List[Tuple[str, str]]] # List of (title, body) 

63Bundle: TypeAlias = Dict[str, str] 

64 

65 

66@dataclass 

67class OInfo: 

68 ismagic: bool 

69 isalias: bool 

70 found: bool 

71 namespace: Optional[str] 

72 parent: Any 

73 obj: Any 

74 

75 def get(self, field): 

76 """Get a field from the object for backward compatibility with before 8.12 

77 

78 see https://github.com/h5py/h5py/issues/2253 

79 """ 

80 # We need to deprecate this at some point, but the warning will show in completion. 

81 # Let's comment this for now and uncomment end of 2023 ish 

82 # warnings.warn( 

83 # f"OInfo dataclass with fields access since IPython 8.12 please use OInfo.{field} instead." 

84 # "OInfo used to be a dict but a dataclass provide static fields verification with mypy." 

85 # "This warning and backward compatibility `get()` method were added in 8.13.", 

86 # DeprecationWarning, 

87 # stacklevel=2, 

88 # ) 

89 return getattr(self, field) 

90 

91 

92def pylight(code): 

93 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True)) 

94 

95# builtin docstrings to ignore 

96_func_call_docstring = types.FunctionType.__call__.__doc__ 

97_object_init_docstring = object.__init__.__doc__ 

98_builtin_type_docstrings = { 

99 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType, 

100 types.FunctionType, property) 

101} 

102 

103_builtin_func_type = type(all) 

104_builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions 

105#**************************************************************************** 

106# Builtin color schemes 

107 

108Colors = TermColors # just a shorthand 

109 

110InspectColors = PyColorize.ANSICodeColors 

111 

112#**************************************************************************** 

113# Auxiliary functions and objects 

114 

115 

116class InfoDict(TypedDict): 

117 type_name: Optional[str] 

118 base_class: Optional[str] 

119 string_form: Optional[str] 

120 namespace: Optional[str] 

121 length: Optional[str] 

122 file: Optional[str] 

123 definition: Optional[str] 

124 docstring: Optional[str] 

125 source: Optional[str] 

126 init_definition: Optional[str] 

127 class_docstring: Optional[str] 

128 init_docstring: Optional[str] 

129 call_def: Optional[str] 

130 call_docstring: Optional[str] 

131 subclasses: Optional[str] 

132 # These won't be printed but will be used to determine how to 

133 # format the object 

134 ismagic: bool 

135 isalias: bool 

136 isclass: bool 

137 found: bool 

138 name: str 

139 

140 

141_info_fields = list(InfoDict.__annotations__.keys()) 

142 

143 

144def __getattr__(name): 

145 if name == "info_fields": 

146 warnings.warn( 

147 "IPython.core.oinspect's `info_fields` is considered for deprecation and may be removed in the Future. ", 

148 DeprecationWarning, 

149 stacklevel=2, 

150 ) 

151 return _info_fields 

152 

153 raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 

154 

155 

156@dataclass 

157class InspectorHookData: 

158 """Data passed to the mime hook""" 

159 

160 obj: Any 

161 info: Optional[OInfo] 

162 info_dict: InfoDict 

163 detail_level: int 

164 omit_sections: list[str] 

165 

166 

167@undoc 

168def object_info( 

169 *, 

170 name: str, 

171 found: bool, 

172 isclass: bool = False, 

173 isalias: bool = False, 

174 ismagic: bool = False, 

175 **kw, 

176) -> InfoDict: 

177 """Make an object info dict with all fields present.""" 

178 infodict = kw 

179 infodict = {k: None for k in _info_fields if k not in infodict} 

180 infodict["name"] = name # type: ignore 

181 infodict["found"] = found # type: ignore 

182 infodict["isclass"] = isclass # type: ignore 

183 infodict["isalias"] = isalias # type: ignore 

184 infodict["ismagic"] = ismagic # type: ignore 

185 

186 return InfoDict(**infodict) # type:ignore 

187 

188 

189def get_encoding(obj): 

190 """Get encoding for python source file defining obj 

191 

192 Returns None if obj is not defined in a sourcefile. 

193 """ 

194 ofile = find_file(obj) 

195 # run contents of file through pager starting at line where the object 

196 # is defined, as long as the file isn't binary and is actually on the 

197 # filesystem. 

198 if ofile is None: 

199 return None 

200 elif ofile.endswith(('.so', '.dll', '.pyd')): 

201 return None 

202 elif not os.path.isfile(ofile): 

203 return None 

204 else: 

205 # Print only text files, not extension binaries. Note that 

206 # getsourcelines returns lineno with 1-offset and page() uses 

207 # 0-offset, so we must adjust. 

208 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2 

209 encoding, _lines = openpy.detect_encoding(buffer.readline) 

210 return encoding 

211 

212 

213def getdoc(obj) -> Union[str, None]: 

214 """Stable wrapper around inspect.getdoc. 

215 

216 This can't crash because of attribute problems. 

217 

218 It also attempts to call a getdoc() method on the given object. This 

219 allows objects which provide their docstrings via non-standard mechanisms 

220 (like Pyro proxies) to still be inspected by ipython's ? system. 

221 """ 

222 # Allow objects to offer customized documentation via a getdoc method: 

223 try: 

224 ds = obj.getdoc() 

225 except Exception: 

226 pass 

227 else: 

228 if isinstance(ds, str): 

229 return inspect.cleandoc(ds) 

230 docstr = inspect.getdoc(obj) 

231 return docstr 

232 

233 

234def getsource(obj, oname='') -> Union[str,None]: 

235 """Wrapper around inspect.getsource. 

236 

237 This can be modified by other projects to provide customized source 

238 extraction. 

239 

240 Parameters 

241 ---------- 

242 obj : object 

243 an object whose source code we will attempt to extract 

244 oname : str 

245 (optional) a name under which the object is known 

246 

247 Returns 

248 ------- 

249 src : unicode or None 

250 

251 """ 

252 

253 if isinstance(obj, property): 

254 sources = [] 

255 for attrname in ['fget', 'fset', 'fdel']: 

256 fn = getattr(obj, attrname) 

257 if fn is not None: 

258 encoding = get_encoding(fn) 

259 oname_prefix = ('%s.' % oname) if oname else '' 

260 sources.append(''.join(('# ', oname_prefix, attrname))) 

261 if inspect.isfunction(fn): 

262 _src = getsource(fn) 

263 if _src: 

264 # assert _src is not None, "please mypy" 

265 sources.append(dedent(_src)) 

266 else: 

267 # Default str/repr only prints function name, 

268 # pretty.pretty prints module name too. 

269 sources.append( 

270 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn)) 

271 ) 

272 if sources: 

273 return '\n'.join(sources) 

274 else: 

275 return None 

276 

277 else: 

278 # Get source for non-property objects. 

279 

280 obj = _get_wrapped(obj) 

281 

282 try: 

283 src = inspect.getsource(obj) 

284 except TypeError: 

285 # The object itself provided no meaningful source, try looking for 

286 # its class definition instead. 

287 try: 

288 src = inspect.getsource(obj.__class__) 

289 except (OSError, TypeError): 

290 return None 

291 except OSError: 

292 return None 

293 

294 return src 

295 

296 

297def is_simple_callable(obj): 

298 """True if obj is a function ()""" 

299 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \ 

300 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type)) 

301 

302@undoc 

303def getargspec(obj): 

304 """Wrapper around :func:`inspect.getfullargspec` 

305 

306 In addition to functions and methods, this can also handle objects with a 

307 ``__call__`` attribute. 

308 

309 DEPRECATED: Deprecated since 7.10. Do not use, will be removed. 

310 """ 

311 

312 warnings.warn('`getargspec` function is deprecated as of IPython 7.10' 

313 'and will be removed in future versions.', DeprecationWarning, stacklevel=2) 

314 

315 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj): 

316 obj = obj.__call__ 

317 

318 return inspect.getfullargspec(obj) 

319 

320@undoc 

321def format_argspec(argspec): 

322 """Format argspect, convenience wrapper around inspect's. 

323 

324 This takes a dict instead of ordered arguments and calls 

325 inspect.format_argspec with the arguments in the necessary order. 

326 

327 DEPRECATED (since 7.10): Do not use; will be removed in future versions. 

328 """ 

329 

330 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10' 

331 'and will be removed in future versions.', DeprecationWarning, stacklevel=2) 

332 

333 

334 return inspect.formatargspec(argspec['args'], argspec['varargs'], 

335 argspec['varkw'], argspec['defaults']) 

336 

337@undoc 

338def call_tip(oinfo, format_call=True): 

339 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict.""" 

340 warnings.warn( 

341 "`call_tip` function is deprecated as of IPython 6.0" 

342 "and will be removed in future versions.", 

343 DeprecationWarning, 

344 stacklevel=2, 

345 ) 

346 # Get call definition 

347 argspec = oinfo.get('argspec') 

348 if argspec is None: 

349 call_line = None 

350 else: 

351 # Callable objects will have 'self' as their first argument, prune 

352 # it out if it's there for clarity (since users do *not* pass an 

353 # extra first argument explicitly). 

354 try: 

355 has_self = argspec['args'][0] == 'self' 

356 except (KeyError, IndexError): 

357 pass 

358 else: 

359 if has_self: 

360 argspec['args'] = argspec['args'][1:] 

361 

362 call_line = oinfo['name']+format_argspec(argspec) 

363 

364 # Now get docstring. 

365 # The priority is: call docstring, constructor docstring, main one. 

366 doc = oinfo.get('call_docstring') 

367 if doc is None: 

368 doc = oinfo.get('init_docstring') 

369 if doc is None: 

370 doc = oinfo.get('docstring','') 

371 

372 return call_line, doc 

373 

374 

375def _get_wrapped(obj): 

376 """Get the original object if wrapped in one or more @decorators 

377 

378 Some objects automatically construct similar objects on any unrecognised 

379 attribute access (e.g. unittest.mock.call). To protect against infinite loops, 

380 this will arbitrarily cut off after 100 levels of obj.__wrapped__ 

381 attribute access. --TK, Jan 2016 

382 """ 

383 orig_obj = obj 

384 i = 0 

385 while safe_hasattr(obj, '__wrapped__'): 

386 obj = obj.__wrapped__ 

387 i += 1 

388 if i > 100: 

389 # __wrapped__ is probably a lie, so return the thing we started with 

390 return orig_obj 

391 return obj 

392 

393def find_file(obj) -> Optional[str]: 

394 """Find the absolute path to the file where an object was defined. 

395 

396 This is essentially a robust wrapper around `inspect.getabsfile`. 

397 

398 Returns None if no file can be found. 

399 

400 Parameters 

401 ---------- 

402 obj : any Python object 

403 

404 Returns 

405 ------- 

406 fname : str 

407 The absolute path to the file where the object was defined. 

408 """ 

409 obj = _get_wrapped(obj) 

410 

411 fname: Optional[str] = None 

412 try: 

413 fname = inspect.getabsfile(obj) 

414 except TypeError: 

415 # For an instance, the file that matters is where its class was 

416 # declared. 

417 try: 

418 fname = inspect.getabsfile(obj.__class__) 

419 except (OSError, TypeError): 

420 # Can happen for builtins 

421 pass 

422 except OSError: 

423 pass 

424 

425 return fname 

426 

427 

428def find_source_lines(obj): 

429 """Find the line number in a file where an object was defined. 

430 

431 This is essentially a robust wrapper around `inspect.getsourcelines`. 

432 

433 Returns None if no file can be found. 

434 

435 Parameters 

436 ---------- 

437 obj : any Python object 

438 

439 Returns 

440 ------- 

441 lineno : int 

442 The line number where the object definition starts. 

443 """ 

444 obj = _get_wrapped(obj) 

445 

446 try: 

447 lineno = inspect.getsourcelines(obj)[1] 

448 except TypeError: 

449 # For instances, try the class object like getsource() does 

450 try: 

451 lineno = inspect.getsourcelines(obj.__class__)[1] 

452 except (OSError, TypeError): 

453 return None 

454 except OSError: 

455 return None 

456 

457 return lineno 

458 

459class Inspector(Colorable): 

460 

461 mime_hooks = traitlets.Dict( 

462 config=True, 

463 help="dictionary of mime to callable to add information into help mimebundle dict", 

464 ).tag(config=True) 

465 

466 def __init__( 

467 self, 

468 color_table=InspectColors, 

469 code_color_table=PyColorize.ANSICodeColors, 

470 scheme=None, 

471 str_detail_level=0, 

472 parent=None, 

473 config=None, 

474 ): 

475 super(Inspector, self).__init__(parent=parent, config=config) 

476 self.color_table = color_table 

477 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme) 

478 self.format = self.parser.format 

479 self.str_detail_level = str_detail_level 

480 self.set_active_scheme(scheme) 

481 

482 def _getdef(self,obj,oname='') -> Union[str,None]: 

483 """Return the call signature for any callable object. 

484 

485 If any exception is generated, None is returned instead and the 

486 exception is suppressed.""" 

487 if not callable(obj): 

488 return None 

489 try: 

490 return _render_signature(signature(obj), oname) 

491 except: 

492 return None 

493 

494 def __head(self,h) -> str: 

495 """Return a header string with proper colors.""" 

496 return '%s%s%s' % (self.color_table.active_colors.header,h, 

497 self.color_table.active_colors.normal) 

498 

499 def set_active_scheme(self, scheme): 

500 if scheme is not None: 

501 self.color_table.set_active_scheme(scheme) 

502 self.parser.color_table.set_active_scheme(scheme) 

503 

504 def noinfo(self, msg, oname): 

505 """Generic message when no information is found.""" 

506 print('No %s found' % msg, end=' ') 

507 if oname: 

508 print('for %s' % oname) 

509 else: 

510 print() 

511 

512 def pdef(self, obj, oname=''): 

513 """Print the call signature for any callable object. 

514 

515 If the object is a class, print the constructor information.""" 

516 

517 if not callable(obj): 

518 print('Object is not callable.') 

519 return 

520 

521 header = '' 

522 

523 if inspect.isclass(obj): 

524 header = self.__head('Class constructor information:\n') 

525 

526 

527 output = self._getdef(obj,oname) 

528 if output is None: 

529 self.noinfo('definition header',oname) 

530 else: 

531 print(header,self.format(output), end=' ') 

532 

533 # In Python 3, all classes are new-style, so they all have __init__. 

534 @skip_doctest 

535 def pdoc(self, obj, oname='', formatter=None): 

536 """Print the docstring for any object. 

537 

538 Optional: 

539 -formatter: a function to run the docstring through for specially 

540 formatted docstrings. 

541 

542 Examples 

543 -------- 

544 In [1]: class NoInit: 

545 ...: pass 

546 

547 In [2]: class NoDoc: 

548 ...: def __init__(self): 

549 ...: pass 

550 

551 In [3]: %pdoc NoDoc 

552 No documentation found for NoDoc 

553 

554 In [4]: %pdoc NoInit 

555 No documentation found for NoInit 

556 

557 In [5]: obj = NoInit() 

558 

559 In [6]: %pdoc obj 

560 No documentation found for obj 

561 

562 In [5]: obj2 = NoDoc() 

563 

564 In [6]: %pdoc obj2 

565 No documentation found for obj2 

566 """ 

567 

568 head = self.__head # For convenience 

569 lines = [] 

570 ds = getdoc(obj) 

571 if formatter: 

572 ds = formatter(ds).get('plain/text', ds) 

573 if ds: 

574 lines.append(head("Class docstring:")) 

575 lines.append(indent(ds)) 

576 if inspect.isclass(obj) and hasattr(obj, '__init__'): 

577 init_ds = getdoc(obj.__init__) 

578 if init_ds is not None: 

579 lines.append(head("Init docstring:")) 

580 lines.append(indent(init_ds)) 

581 elif hasattr(obj,'__call__'): 

582 call_ds = getdoc(obj.__call__) 

583 if call_ds: 

584 lines.append(head("Call docstring:")) 

585 lines.append(indent(call_ds)) 

586 

587 if not lines: 

588 self.noinfo('documentation',oname) 

589 else: 

590 page.page('\n'.join(lines)) 

591 

592 def psource(self, obj, oname=''): 

593 """Print the source code for an object.""" 

594 

595 # Flush the source cache because inspect can return out-of-date source 

596 linecache.checkcache() 

597 try: 

598 src = getsource(obj, oname=oname) 

599 except Exception: 

600 src = None 

601 

602 if src is None: 

603 self.noinfo('source', oname) 

604 else: 

605 page.page(self.format(src)) 

606 

607 def pfile(self, obj, oname=''): 

608 """Show the whole file where an object was defined.""" 

609 

610 lineno = find_source_lines(obj) 

611 if lineno is None: 

612 self.noinfo('file', oname) 

613 return 

614 

615 ofile = find_file(obj) 

616 # run contents of file through pager starting at line where the object 

617 # is defined, as long as the file isn't binary and is actually on the 

618 # filesystem. 

619 if ofile is None: 

620 print("Could not find file for object") 

621 elif ofile.endswith((".so", ".dll", ".pyd")): 

622 print("File %r is binary, not printing." % ofile) 

623 elif not os.path.isfile(ofile): 

624 print('File %r does not exist, not printing.' % ofile) 

625 else: 

626 # Print only text files, not extension binaries. Note that 

627 # getsourcelines returns lineno with 1-offset and page() uses 

628 # 0-offset, so we must adjust. 

629 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1) 

630 

631 

632 def _mime_format(self, text:str, formatter=None) -> dict: 

633 """Return a mime bundle representation of the input text. 

634 

635 - if `formatter` is None, the returned mime bundle has 

636 a ``text/plain`` field, with the input text. 

637 a ``text/html`` field with a ``<pre>`` tag containing the input text. 

638 

639 - if ``formatter`` is not None, it must be a callable transforming the 

640 input text into a mime bundle. Default values for ``text/plain`` and 

641 ``text/html`` representations are the ones described above. 

642 

643 Note: 

644 

645 Formatters returning strings are supported but this behavior is deprecated. 

646 

647 """ 

648 defaults = { 

649 "text/plain": text, 

650 "text/html": f"<pre>{html.escape(text)}</pre>", 

651 } 

652 

653 if formatter is None: 

654 return defaults 

655 else: 

656 formatted = formatter(text) 

657 

658 if not isinstance(formatted, dict): 

659 # Handle the deprecated behavior of a formatter returning 

660 # a string instead of a mime bundle. 

661 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"} 

662 

663 else: 

664 return dict(defaults, **formatted) 

665 

666 def format_mime(self, bundle: UnformattedBundle) -> Bundle: 

667 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle""" 

668 # Format text/plain mimetype 

669 assert isinstance(bundle["text/plain"], list) 

670 for item in bundle["text/plain"]: 

671 assert isinstance(item, tuple) 

672 

673 new_b: Bundle = {} 

674 lines = [] 

675 _len = max(len(h) for h, _ in bundle["text/plain"]) 

676 

677 for head, body in bundle["text/plain"]: 

678 body = body.strip("\n") 

679 delim = "\n" if "\n" in body else " " 

680 lines.append( 

681 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}" 

682 ) 

683 

684 new_b["text/plain"] = "\n".join(lines) 

685 

686 if "text/html" in bundle: 

687 assert isinstance(bundle["text/html"], list) 

688 for item in bundle["text/html"]: 

689 assert isinstance(item, tuple) 

690 # Format the text/html mimetype 

691 if isinstance(bundle["text/html"], (list, tuple)): 

692 # bundle['text/html'] is a list of (head, formatted body) pairs 

693 new_b["text/html"] = "\n".join( 

694 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"]) 

695 ) 

696 

697 for k in bundle.keys(): 

698 if k in ("text/html", "text/plain"): 

699 continue 

700 else: 

701 new_b[k] = bundle[k] # type:ignore 

702 return new_b 

703 

704 def _append_info_field( 

705 self, 

706 bundle: UnformattedBundle, 

707 title: str, 

708 key: str, 

709 info, 

710 omit_sections: List[str], 

711 formatter, 

712 ): 

713 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted""" 

714 if title in omit_sections or key in omit_sections: 

715 return 

716 field = info[key] 

717 if field is not None: 

718 formatted_field = self._mime_format(field, formatter) 

719 bundle["text/plain"].append((title, formatted_field["text/plain"])) 

720 bundle["text/html"].append((title, formatted_field["text/html"])) 

721 

722 def _make_info_unformatted( 

723 self, obj, info, formatter, detail_level, omit_sections 

724 ) -> UnformattedBundle: 

725 """Assemble the mimebundle as unformatted lists of information""" 

726 bundle: UnformattedBundle = { 

727 "text/plain": [], 

728 "text/html": [], 

729 } 

730 

731 # A convenience function to simplify calls below 

732 def append_field( 

733 bundle: UnformattedBundle, title: str, key: str, formatter=None 

734 ): 

735 self._append_info_field( 

736 bundle, 

737 title=title, 

738 key=key, 

739 info=info, 

740 omit_sections=omit_sections, 

741 formatter=formatter, 

742 ) 

743 

744 def code_formatter(text) -> Bundle: 

745 return { 

746 'text/plain': self.format(text), 

747 'text/html': pylight(text) 

748 } 

749 

750 if info["isalias"]: 

751 append_field(bundle, "Repr", "string_form") 

752 

753 elif info['ismagic']: 

754 if detail_level > 0: 

755 append_field(bundle, "Source", "source", code_formatter) 

756 else: 

757 append_field(bundle, "Docstring", "docstring", formatter) 

758 append_field(bundle, "File", "file") 

759 

760 elif info['isclass'] or is_simple_callable(obj): 

761 # Functions, methods, classes 

762 append_field(bundle, "Signature", "definition", code_formatter) 

763 append_field(bundle, "Init signature", "init_definition", code_formatter) 

764 append_field(bundle, "Docstring", "docstring", formatter) 

765 if detail_level > 0 and info["source"]: 

766 append_field(bundle, "Source", "source", code_formatter) 

767 else: 

768 append_field(bundle, "Init docstring", "init_docstring", formatter) 

769 

770 append_field(bundle, "File", "file") 

771 append_field(bundle, "Type", "type_name") 

772 append_field(bundle, "Subclasses", "subclasses") 

773 

774 else: 

775 # General Python objects 

776 append_field(bundle, "Signature", "definition", code_formatter) 

777 append_field(bundle, "Call signature", "call_def", code_formatter) 

778 append_field(bundle, "Type", "type_name") 

779 append_field(bundle, "String form", "string_form") 

780 

781 # Namespace 

782 if info["namespace"] != "Interactive": 

783 append_field(bundle, "Namespace", "namespace") 

784 

785 append_field(bundle, "Length", "length") 

786 append_field(bundle, "File", "file") 

787 

788 # Source or docstring, depending on detail level and whether 

789 # source found. 

790 if detail_level > 0 and info["source"]: 

791 append_field(bundle, "Source", "source", code_formatter) 

792 else: 

793 append_field(bundle, "Docstring", "docstring", formatter) 

794 

795 append_field(bundle, "Class docstring", "class_docstring", formatter) 

796 append_field(bundle, "Init docstring", "init_docstring", formatter) 

797 append_field(bundle, "Call docstring", "call_docstring", formatter) 

798 return bundle 

799 

800 

801 def _get_info( 

802 self, 

803 obj: Any, 

804 oname: str = "", 

805 formatter=None, 

806 info: Optional[OInfo] = None, 

807 detail_level: int = 0, 

808 omit_sections: Union[List[str], Tuple[()]] = (), 

809 ) -> Bundle: 

810 """Retrieve an info dict and format it. 

811 

812 Parameters 

813 ---------- 

814 obj : any 

815 Object to inspect and return info from 

816 oname : str (default: ''): 

817 Name of the variable pointing to `obj`. 

818 formatter : callable 

819 info 

820 already computed information 

821 detail_level : integer 

822 Granularity of detail level, if set to 1, give more information. 

823 omit_sections : list[str] 

824 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`) 

825 """ 

826 

827 info_dict = self.info(obj, oname=oname, info=info, detail_level=detail_level) 

828 omit_sections = list(omit_sections) 

829 

830 bundle = self._make_info_unformatted( 

831 obj, 

832 info_dict, 

833 formatter, 

834 detail_level=detail_level, 

835 omit_sections=omit_sections, 

836 ) 

837 if self.mime_hooks: 

838 hook_data = InspectorHookData( 

839 obj=obj, 

840 info=info, 

841 info_dict=info_dict, 

842 detail_level=detail_level, 

843 omit_sections=omit_sections, 

844 ) 

845 for key, hook in self.mime_hooks.items(): # type:ignore 

846 required_parameters = [ 

847 parameter 

848 for parameter in inspect.signature(hook).parameters.values() 

849 if parameter.default != inspect.Parameter.default 

850 ] 

851 if len(required_parameters) == 1: 

852 res = hook(hook_data) 

853 else: 

854 warnings.warn( 

855 "MIME hook format changed in IPython 8.22; hooks should now accept" 

856 " a single parameter (InspectorHookData); support for hooks requiring" 

857 " two-parameters (obj and info) will be removed in a future version", 

858 DeprecationWarning, 

859 stacklevel=2, 

860 ) 

861 res = hook(obj, info) 

862 if res is not None: 

863 bundle[key] = res 

864 return self.format_mime(bundle) 

865 

866 def pinfo( 

867 self, 

868 obj, 

869 oname="", 

870 formatter=None, 

871 info: Optional[OInfo] = None, 

872 detail_level=0, 

873 enable_html_pager=True, 

874 omit_sections=(), 

875 ): 

876 """Show detailed information about an object. 

877 

878 Optional arguments: 

879 

880 - oname: name of the variable pointing to the object. 

881 

882 - formatter: callable (optional) 

883 A special formatter for docstrings. 

884 

885 The formatter is a callable that takes a string as an input 

886 and returns either a formatted string or a mime type bundle 

887 in the form of a dictionary. 

888 

889 Although the support of custom formatter returning a string 

890 instead of a mime type bundle is deprecated. 

891 

892 - info: a structure with some information fields which may have been 

893 precomputed already. 

894 

895 - detail_level: if set to 1, more information is given. 

896 

897 - omit_sections: set of section keys and titles to omit 

898 """ 

899 assert info is not None 

900 info_b: Bundle = self._get_info( 

901 obj, oname, formatter, info, detail_level, omit_sections=omit_sections 

902 ) 

903 if not enable_html_pager: 

904 del info_b["text/html"] 

905 page.page(info_b) 

906 

907 def _info(self, obj, oname="", info=None, detail_level=0): 

908 """ 

909 Inspector.info() was likely improperly marked as deprecated 

910 while only a parameter was deprecated. We "un-deprecate" it. 

911 """ 

912 

913 warnings.warn( 

914 "The `Inspector.info()` method has been un-deprecated as of 8.0 " 

915 "and the `formatter=` keyword removed. `Inspector._info` is now " 

916 "an alias, and you can just call `.info()` directly.", 

917 DeprecationWarning, 

918 stacklevel=2, 

919 ) 

920 return self.info(obj, oname=oname, info=info, detail_level=detail_level) 

921 

922 def info(self, obj, oname="", info=None, detail_level=0) -> InfoDict: 

923 """Compute a dict with detailed information about an object. 

924 

925 Parameters 

926 ---------- 

927 obj : any 

928 An object to find information about 

929 oname : str (default: '') 

930 Name of the variable pointing to `obj`. 

931 info : (default: None) 

932 A struct (dict like with attr access) with some information fields 

933 which may have been precomputed already. 

934 detail_level : int (default:0) 

935 If set to 1, more information is given. 

936 

937 Returns 

938 ------- 

939 An object info dict with known fields from `info_fields` (see `InfoDict`). 

940 """ 

941 

942 if info is None: 

943 ismagic = False 

944 isalias = False 

945 ospace = '' 

946 else: 

947 ismagic = info.ismagic 

948 isalias = info.isalias 

949 ospace = info.namespace 

950 

951 # Get docstring, special-casing aliases: 

952 att_name = oname.split(".")[-1] 

953 parents_docs = None 

954 prelude = "" 

955 if info and info.parent is not None and hasattr(info.parent, HOOK_NAME): 

956 parents_docs_dict = getattr(info.parent, HOOK_NAME) 

957 parents_docs = parents_docs_dict.get(att_name, None) 

958 out: InfoDict = cast( 

959 InfoDict, 

960 { 

961 **{field: None for field in _info_fields}, 

962 **{ 

963 "name": oname, 

964 "found": True, 

965 "isalias": isalias, 

966 "ismagic": ismagic, 

967 "subclasses": None, 

968 }, 

969 }, 

970 ) 

971 

972 if parents_docs: 

973 ds = parents_docs 

974 elif isalias: 

975 if not callable(obj): 

976 try: 

977 ds = "Alias to the system command:\n %s" % obj[1] 

978 except: 

979 ds = "Alias: " + str(obj) 

980 else: 

981 ds = "Alias to " + str(obj) 

982 if obj.__doc__: 

983 ds += "\nDocstring:\n" + obj.__doc__ 

984 else: 

985 ds_or_None = getdoc(obj) 

986 if ds_or_None is None: 

987 ds = '<no docstring>' 

988 else: 

989 ds = ds_or_None 

990 

991 ds = prelude + ds 

992 

993 # store output in a dict, we initialize it here and fill it as we go 

994 

995 string_max = 200 # max size of strings to show (snipped if longer) 

996 shalf = int((string_max - 5) / 2) 

997 

998 if ismagic: 

999 out['type_name'] = 'Magic function' 

1000 elif isalias: 

1001 out['type_name'] = 'System alias' 

1002 else: 

1003 out['type_name'] = type(obj).__name__ 

1004 

1005 try: 

1006 bclass = obj.__class__ 

1007 out['base_class'] = str(bclass) 

1008 except: 

1009 pass 

1010 

1011 # String form, but snip if too long in ? form (full in ??) 

1012 if detail_level >= self.str_detail_level: 

1013 try: 

1014 ostr = str(obj) 

1015 if not detail_level and len(ostr) > string_max: 

1016 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:] 

1017 # TODO: `'string_form'.expandtabs()` seems wrong, but 

1018 # it was (nearly) like this since the first commit ever. 

1019 ostr = ("\n" + " " * len("string_form".expandtabs())).join( 

1020 q.strip() for q in ostr.split("\n") 

1021 ) 

1022 out["string_form"] = ostr 

1023 except: 

1024 pass 

1025 

1026 if ospace: 

1027 out['namespace'] = ospace 

1028 

1029 # Length (for strings and lists) 

1030 try: 

1031 out['length'] = str(len(obj)) 

1032 except Exception: 

1033 pass 

1034 

1035 # Filename where object was defined 

1036 binary_file = False 

1037 fname = find_file(obj) 

1038 if fname is None: 

1039 # if anything goes wrong, we don't want to show source, so it's as 

1040 # if the file was binary 

1041 binary_file = True 

1042 else: 

1043 if fname.endswith(('.so', '.dll', '.pyd')): 

1044 binary_file = True 

1045 elif fname.endswith('<string>'): 

1046 fname = 'Dynamically generated function. No source code available.' 

1047 out['file'] = compress_user(fname) 

1048 

1049 # Original source code for a callable, class or property. 

1050 if detail_level: 

1051 # Flush the source cache because inspect can return out-of-date 

1052 # source 

1053 linecache.checkcache() 

1054 try: 

1055 if isinstance(obj, property) or not binary_file: 

1056 src = getsource(obj, oname) 

1057 if src is not None: 

1058 src = src.rstrip() 

1059 out['source'] = src 

1060 

1061 except Exception: 

1062 pass 

1063 

1064 # Add docstring only if no source is to be shown (avoid repetitions). 

1065 if ds and not self._source_contains_docstring(out.get('source'), ds): 

1066 out['docstring'] = ds 

1067 

1068 # Constructor docstring for classes 

1069 if inspect.isclass(obj): 

1070 out['isclass'] = True 

1071 

1072 # get the init signature: 

1073 try: 

1074 init_def = self._getdef(obj, oname) 

1075 except AttributeError: 

1076 init_def = None 

1077 

1078 # get the __init__ docstring 

1079 try: 

1080 obj_init = obj.__init__ 

1081 except AttributeError: 

1082 init_ds = None 

1083 else: 

1084 if init_def is None: 

1085 # Get signature from init if top-level sig failed. 

1086 # Can happen for built-in types (list, etc.). 

1087 try: 

1088 init_def = self._getdef(obj_init, oname) 

1089 except AttributeError: 

1090 pass 

1091 init_ds = getdoc(obj_init) 

1092 # Skip Python's auto-generated docstrings 

1093 if init_ds == _object_init_docstring: 

1094 init_ds = None 

1095 

1096 if init_def: 

1097 out['init_definition'] = init_def 

1098 

1099 if init_ds: 

1100 out['init_docstring'] = init_ds 

1101 

1102 names = [sub.__name__ for sub in type.__subclasses__(obj)] 

1103 if len(names) < 10: 

1104 all_names = ', '.join(names) 

1105 else: 

1106 all_names = ', '.join(names[:10]+['...']) 

1107 out['subclasses'] = all_names 

1108 # and class docstring for instances: 

1109 else: 

1110 # reconstruct the function definition and print it: 

1111 defln = self._getdef(obj, oname) 

1112 if defln: 

1113 out['definition'] = defln 

1114 

1115 # First, check whether the instance docstring is identical to the 

1116 # class one, and print it separately if they don't coincide. In 

1117 # most cases they will, but it's nice to print all the info for 

1118 # objects which use instance-customized docstrings. 

1119 if ds: 

1120 try: 

1121 cls = getattr(obj,'__class__') 

1122 except: 

1123 class_ds = None 

1124 else: 

1125 class_ds = getdoc(cls) 

1126 # Skip Python's auto-generated docstrings 

1127 if class_ds in _builtin_type_docstrings: 

1128 class_ds = None 

1129 if class_ds and ds != class_ds: 

1130 out['class_docstring'] = class_ds 

1131 

1132 # Next, try to show constructor docstrings 

1133 try: 

1134 init_ds = getdoc(obj.__init__) 

1135 # Skip Python's auto-generated docstrings 

1136 if init_ds == _object_init_docstring: 

1137 init_ds = None 

1138 except AttributeError: 

1139 init_ds = None 

1140 if init_ds: 

1141 out['init_docstring'] = init_ds 

1142 

1143 # Call form docstring for callable instances 

1144 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj): 

1145 call_def = self._getdef(obj.__call__, oname) 

1146 if call_def and (call_def != out.get('definition')): 

1147 # it may never be the case that call def and definition differ, 

1148 # but don't include the same signature twice 

1149 out['call_def'] = call_def 

1150 call_ds = getdoc(obj.__call__) 

1151 # Skip Python's auto-generated docstrings 

1152 if call_ds == _func_call_docstring: 

1153 call_ds = None 

1154 if call_ds: 

1155 out['call_docstring'] = call_ds 

1156 

1157 return out 

1158 

1159 @staticmethod 

1160 def _source_contains_docstring(src, doc): 

1161 """ 

1162 Check whether the source *src* contains the docstring *doc*. 

1163 

1164 This is is helper function to skip displaying the docstring if the 

1165 source already contains it, avoiding repetition of information. 

1166 """ 

1167 try: 

1168 (def_node,) = ast.parse(dedent(src)).body 

1169 return ast.get_docstring(def_node) == doc # type: ignore[arg-type] 

1170 except Exception: 

1171 # The source can become invalid or even non-existent (because it 

1172 # is re-fetched from the source file) so the above code fail in 

1173 # arbitrary ways. 

1174 return False 

1175 

1176 def psearch(self,pattern,ns_table,ns_search=[], 

1177 ignore_case=False,show_all=False, *, list_types=False): 

1178 """Search namespaces with wildcards for objects. 

1179 

1180 Arguments: 

1181 

1182 - pattern: string containing shell-like wildcards to use in namespace 

1183 searches and optionally a type specification to narrow the search to 

1184 objects of that type. 

1185 

1186 - ns_table: dict of name->namespaces for search. 

1187 

1188 Optional arguments: 

1189 

1190 - ns_search: list of namespace names to include in search. 

1191 

1192 - ignore_case(False): make the search case-insensitive. 

1193 

1194 - show_all(False): show all names, including those starting with 

1195 underscores. 

1196 

1197 - list_types(False): list all available object types for object matching. 

1198 """ 

1199 # print('ps pattern:<%r>' % pattern) # dbg 

1200 

1201 # defaults 

1202 type_pattern = 'all' 

1203 filter = '' 

1204 

1205 # list all object types 

1206 if list_types: 

1207 page.page('\n'.join(sorted(typestr2type))) 

1208 return 

1209 

1210 cmds = pattern.split() 

1211 len_cmds = len(cmds) 

1212 if len_cmds == 1: 

1213 # Only filter pattern given 

1214 filter = cmds[0] 

1215 elif len_cmds == 2: 

1216 # Both filter and type specified 

1217 filter,type_pattern = cmds 

1218 else: 

1219 raise ValueError('invalid argument string for psearch: <%s>' % 

1220 pattern) 

1221 

1222 # filter search namespaces 

1223 for name in ns_search: 

1224 if name not in ns_table: 

1225 raise ValueError('invalid namespace <%s>. Valid names: %s' % 

1226 (name,ns_table.keys())) 

1227 

1228 # print('type_pattern:',type_pattern) # dbg 

1229 search_result, namespaces_seen = set(), set() 

1230 for ns_name in ns_search: 

1231 ns = ns_table[ns_name] 

1232 # Normally, locals and globals are the same, so we just check one. 

1233 if id(ns) in namespaces_seen: 

1234 continue 

1235 namespaces_seen.add(id(ns)) 

1236 tmp_res = list_namespace(ns, type_pattern, filter, 

1237 ignore_case=ignore_case, show_all=show_all) 

1238 search_result.update(tmp_res) 

1239 

1240 page.page('\n'.join(sorted(search_result))) 

1241 

1242 

1243def _render_signature(obj_signature, obj_name) -> str: 

1244 """ 

1245 This was mostly taken from inspect.Signature.__str__. 

1246 Look there for the comments. 

1247 The only change is to add linebreaks when this gets too long. 

1248 """ 

1249 result = [] 

1250 pos_only = False 

1251 kw_only = True 

1252 for param in obj_signature.parameters.values(): 

1253 if param.kind == inspect.Parameter.POSITIONAL_ONLY: 

1254 pos_only = True 

1255 elif pos_only: 

1256 result.append('/') 

1257 pos_only = False 

1258 

1259 if param.kind == inspect.Parameter.VAR_POSITIONAL: 

1260 kw_only = False 

1261 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only: 

1262 result.append('*') 

1263 kw_only = False 

1264 

1265 result.append(str(param)) 

1266 

1267 if pos_only: 

1268 result.append('/') 

1269 

1270 # add up name, parameters, braces (2), and commas 

1271 if len(obj_name) + sum(len(r) + 2 for r in result) > 75: 

1272 # This doesn’t fit behind “Signature: ” in an inspect window. 

1273 rendered = '{}(\n{})'.format(obj_name, ''.join( 

1274 ' {},\n'.format(r) for r in result) 

1275 ) 

1276 else: 

1277 rendered = '{}({})'.format(obj_name, ', '.join(result)) 

1278 

1279 if obj_signature.return_annotation is not inspect._empty: 

1280 anno = inspect.formatannotation(obj_signature.return_annotation) 

1281 rendered += ' -> {}'.format(anno) 

1282 

1283 return rendered