Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/oinspect.py: 3%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

563 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""" 

8from __future__ import annotations 

9 

10# Copyright (c) IPython Development Team. 

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

12 

13__all__ = ["Inspector"] 

14 

15# stdlib modules 

16from dataclasses import dataclass 

17from inspect import signature 

18from textwrap import dedent 

19import ast 

20import inspect 

21import io as stdlib_io 

22import linecache 

23import os 

24import types 

25import warnings 

26from pygments.token import Token 

27 

28 

29from typing import ( 

30 cast, 

31 Any, 

32 TypedDict, 

33 TypeAlias, 

34) 

35 

36import traitlets 

37from traitlets.config import Configurable 

38 

39# IPython's own 

40from IPython.core import page 

41from IPython.lib.pretty import pretty 

42from IPython.testing.skipdoctest import skip_doctest 

43from IPython.utils import PyColorize, openpy 

44from IPython.utils.dir2 import safe_hasattr 

45from IPython.utils.path import compress_user 

46from IPython.utils.text import indent 

47from IPython.utils.wildcard import list_namespace, typestr2type 

48from IPython.utils.decorators import undoc 

49 

50 

51HOOK_NAME = "__custom_documentations__" 

52 

53 

54UnformattedBundle: TypeAlias = dict[str, list[tuple[str, str]]] # List of (title, body) 

55Bundle: TypeAlias = dict[str, str] 

56 

57 

58@dataclass 

59class OInfo: 

60 ismagic: bool 

61 isalias: bool 

62 found: bool 

63 namespace: str | None 

64 parent: Any 

65 obj: Any 

66 

67def pylight(code): 

68 # `pygments.lexers` and `pygments.formatters` pull in the pygments plugin 

69 # machinery (importlib.metadata, zipfile); only HTML-formatted docstrings 

70 # need them, so keep them out of `import IPython.core.oinspect` 

71 from pygments import highlight 

72 from pygments.formatters import HtmlFormatter 

73 from pygments.lexers import PythonLexer 

74 

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

76 

77# builtin docstrings to ignore 

78_func_call_docstring = types.FunctionType.__call__.__doc__ 

79_object_init_docstring = object.__init__.__doc__ 

80_builtin_type_docstrings = { 

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

82 types.FunctionType, property) 

83} 

84 

85_builtin_func_type = type(all) 

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

87#**************************************************************************** 

88# Builtin color schemes 

89 

90 

91#**************************************************************************** 

92# Auxiliary functions and objects 

93 

94 

95class InfoDict(TypedDict): 

96 type_name: str | None 

97 base_class: str | None 

98 string_form: str | None 

99 namespace: str | None 

100 length: str | None 

101 file: str | None 

102 definition: str | None 

103 docstring: str | None 

104 source: str | None 

105 init_definition: str | None 

106 class_docstring: str | None 

107 init_docstring: str | None 

108 call_def: str | None 

109 call_docstring: str | None 

110 subclasses: str | None 

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

112 # format the object 

113 ismagic: bool 

114 isalias: bool 

115 isclass: bool 

116 found: bool 

117 name: str 

118 

119 

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

121 

122 

123def __getattr__(name): 

124 if name == "info_fields": 

125 warnings.warn( 

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

127 DeprecationWarning, 

128 stacklevel=2, 

129 ) 

130 return _info_fields 

131 

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

133 

134 

135@dataclass 

136class InspectorHookData: 

137 """Data passed to the mime hook""" 

138 

139 obj: Any 

140 info: OInfo | None 

141 info_dict: InfoDict 

142 detail_level: int 

143 omit_sections: list[str] 

144 

145 

146@undoc 

147def object_info( 

148 *, 

149 name: str, 

150 found: bool, 

151 isclass: bool = False, 

152 isalias: bool = False, 

153 ismagic: bool = False, 

154 **kw, 

155) -> InfoDict: 

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

157 infodict = dict(kw) 

158 infodict.update({k: None for k in _info_fields if k not in infodict}) 

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

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

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

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

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

164 

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

166 

167 

168def get_encoding(obj): 

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

170 

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

172 """ 

173 ofile = find_file(obj) 

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

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

176 # filesystem. 

177 if ofile is None: 

178 return None 

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

180 return None 

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

182 return None 

183 else: 

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

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

186 # 0-offset, so we must adjust. 

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

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

189 return encoding 

190 

191 

192def getdoc(obj) -> str | None: 

193 """Stable wrapper around inspect.getdoc. 

194 

195 This can't crash because of attribute problems. 

196 

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

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

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

200 """ 

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

202 try: 

203 ds = obj.getdoc() 

204 except Exception: 

205 pass 

206 else: 

207 if isinstance(ds, str): 

208 return inspect.cleandoc(ds) 

209 docstr = inspect.getdoc(obj) 

210 return docstr 

211 

212 

213def getsource(obj, oname='') -> str | None: 

214 """Wrapper around inspect.getsource. 

215 

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

217 extraction. 

218 

219 Parameters 

220 ---------- 

221 obj : object 

222 an object whose source code we will attempt to extract 

223 oname : str 

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

225 

226 Returns 

227 ------- 

228 src : unicode or None 

229 

230 """ 

231 

232 if isinstance(obj, property): 

233 sources = [] 

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

235 fn = getattr(obj, attrname) 

236 if fn is not None: 

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

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

239 if inspect.isfunction(fn): 

240 _src = getsource(fn) 

241 if _src: 

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

243 sources.append(dedent(_src)) 

244 else: 

245 # Default str/repr only prints function name, 

246 # pretty.pretty prints module name too. 

247 sources.append( 

248 '{}{} = {}\n'.format(oname_prefix, attrname, pretty(fn)) 

249 ) 

250 if sources: 

251 return '\n'.join(sources) 

252 else: 

253 return None 

254 

255 else: 

256 # Get source for non-property objects. 

257 

258 obj = _get_wrapped(obj) 

259 

260 try: 

261 src = inspect.getsource(obj) 

262 except TypeError: 

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

264 # its class definition instead. 

265 try: 

266 src = inspect.getsource(obj.__class__) 

267 except (OSError, TypeError): 

268 return None 

269 except OSError: 

270 return None 

271 

272 return src 

273 

274 

275def is_simple_callable(obj): 

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

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

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

279 

280def _get_wrapped(obj): 

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

282 

283 Some objects automatically construct similar objects on any unrecognised 

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

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

286 attribute access. --TK, Jan 2016 

287 """ 

288 orig_obj = obj 

289 i = 0 

290 while safe_hasattr(obj, '__wrapped__'): 

291 obj = obj.__wrapped__ 

292 i += 1 

293 if i > 100: 

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

295 return orig_obj 

296 return obj 

297 

298def find_file(obj) -> str | None: 

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

300 

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

302 

303 Returns None if no file can be found. 

304 

305 Parameters 

306 ---------- 

307 obj : any Python object 

308 

309 Returns 

310 ------- 

311 fname : str 

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

313 """ 

314 obj = _get_wrapped(obj) 

315 

316 fname: str | None = None 

317 try: 

318 fname = inspect.getabsfile(obj) 

319 except TypeError: 

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

321 # declared. 

322 try: 

323 fname = inspect.getabsfile(obj.__class__) 

324 except (OSError, TypeError): 

325 # Can happen for builtins 

326 pass 

327 except OSError: 

328 pass 

329 

330 return fname 

331 

332 

333def find_source_lines(obj): 

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

335 

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

337 

338 Returns None if no file can be found. 

339 

340 Parameters 

341 ---------- 

342 obj : any Python object 

343 

344 Returns 

345 ------- 

346 lineno : int 

347 The line number where the object definition starts. 

348 """ 

349 obj = _get_wrapped(obj) 

350 

351 try: 

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

353 except TypeError: 

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

355 try: 

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

357 except (OSError, TypeError): 

358 return None 

359 except OSError: 

360 return None 

361 

362 return lineno 

363 

364 

365_sentinel = object() 

366 

367 

368class Inspector(Configurable): 

369 mime_hooks = traitlets.Dict( 

370 config=True, 

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

372 ).tag(config=True) 

373 

374 _theme_name: str 

375 

376 def __init__( 

377 self, 

378 *, 

379 theme_name: str, 

380 str_detail_level=0, 

381 parent=None, 

382 config=None, 

383 ): 

384 if theme_name in ["Linux", "LightBG", "Neutral", "NoColor"]: 

385 warnings.warn( 

386 f"Theme names and color schemes are lowercase in IPython 9.0 use {theme_name.lower()} instead", 

387 DeprecationWarning, 

388 stacklevel=2, 

389 ) 

390 theme_name = theme_name.lower() 

391 self._theme_name = theme_name 

392 super().__init__(parent=parent, config=config) 

393 self.parser = PyColorize.Parser(out="str", theme_name=theme_name) 

394 self.str_detail_level = str_detail_level 

395 self.set_theme_name(theme_name) 

396 

397 def format(self, *args, **kwargs): 

398 return self.parser.format(*args, **kwargs) 

399 

400 def _getdef(self,obj,oname='') -> str | None: 

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

402 

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

404 exception is suppressed.""" 

405 if not callable(obj): 

406 return None 

407 try: 

408 return _render_signature(signature(obj), oname) 

409 except Exception: 

410 return None 

411 

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

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

414 return PyColorize.theme_table[self._theme_name].format([(Token.Header, h)]) 

415 

416 def set_theme_name(self, name: str): 

417 assert name == name.lower() 

418 assert name in PyColorize.theme_table.keys() 

419 self._theme_name = name 

420 self.parser.theme_name = name 

421 

422 def set_active_scheme(self, scheme: str): 

423 warnings.warn( 

424 "set_active_scheme is deprecated and replaced by set_theme_name as of IPython 9.0", 

425 DeprecationWarning, 

426 stacklevel=2, 

427 ) 

428 assert scheme == scheme.lower() 

429 if scheme is not None and self._theme_name != scheme: 

430 self._theme_name = scheme 

431 self.parser.theme_name = scheme 

432 

433 def noinfo(self, msg, oname): 

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

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

436 if oname: 

437 print('for %s' % oname) 

438 else: 

439 print() 

440 

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

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

443 

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

445 

446 if not callable(obj): 

447 print('Object is not callable.') 

448 return 

449 

450 header = '' 

451 

452 if inspect.isclass(obj): 

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

454 

455 

456 output = self._getdef(obj,oname) 

457 if output is None: 

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

459 else: 

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

461 

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

463 @skip_doctest 

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

465 """Print the docstring for any object. 

466 

467 Optional: 

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

469 formatted docstrings. 

470 

471 Examples 

472 -------- 

473 In [1]: class NoInit: 

474 ...: pass 

475 

476 In [2]: class NoDoc: 

477 ...: def __init__(self): 

478 ...: pass 

479 

480 In [3]: %pdoc NoDoc 

481 No documentation found for NoDoc 

482 

483 In [4]: %pdoc NoInit 

484 No documentation found for NoInit 

485 

486 In [5]: obj = NoInit() 

487 

488 In [6]: %pdoc obj 

489 No documentation found for obj 

490 

491 In [5]: obj2 = NoDoc() 

492 

493 In [6]: %pdoc obj2 

494 No documentation found for obj2 

495 """ 

496 

497 lines = [] 

498 ds = getdoc(obj) 

499 if formatter: 

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

501 if ds: 

502 lines.append(self.__head("Class docstring:")) 

503 lines.append(indent(ds)) 

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

505 init_ds = getdoc(obj.__init__) 

506 if init_ds is not None: 

507 lines.append(self.__head("Init docstring:")) 

508 lines.append(indent(init_ds)) 

509 elif hasattr(obj,'__call__'): 

510 call_ds = getdoc(obj.__call__) 

511 if call_ds: 

512 lines.append(self.__head("Call docstring:")) 

513 lines.append(indent(call_ds)) 

514 

515 if not lines: 

516 self.noinfo('documentation',oname) 

517 else: 

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

519 

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

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

522 

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

524 linecache.checkcache() 

525 try: 

526 src = getsource(obj, oname=oname) 

527 except Exception: 

528 src = None 

529 

530 if src is None: 

531 self.noinfo('source', oname) 

532 else: 

533 page.page(self.format(src)) 

534 

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

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

537 

538 lineno = find_source_lines(obj) 

539 if lineno is None: 

540 self.noinfo('file', oname) 

541 return 

542 

543 ofile = find_file(obj) 

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

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

546 # filesystem. 

547 if ofile is None: 

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

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

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

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

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

553 else: 

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

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

556 # 0-offset, so we must adjust. 

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

558 

559 

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

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

562 

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

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

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

566 

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

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

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

570 

571 Note: 

572 

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

574 

575 """ 

576 import html 

577 

578 defaults = { 

579 "text/plain": text, 

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

581 } 

582 

583 if formatter is None: 

584 return defaults 

585 else: 

586 formatted = formatter(text) 

587 

588 if not isinstance(formatted, dict): 

589 # Handle the deprecated behavior of a formatter returning 

590 # a string instead of a mime bundle. 

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

592 

593 else: 

594 return dict(defaults, **formatted) 

595 

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

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

598 # Format text/plain mimetype 

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

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

601 assert isinstance(item, tuple) 

602 

603 new_b: Bundle = {} 

604 lines = [] 

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

606 

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

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

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

610 lines.append( 

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

612 ) 

613 

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

615 

616 if "text/html" in bundle: 

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

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

619 assert isinstance(item, tuple) 

620 # Format the text/html mimetype 

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

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

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

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

625 ) 

626 

627 for k in bundle.keys(): 

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

629 continue 

630 else: 

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

632 return new_b 

633 

634 def _append_info_field( 

635 self, 

636 bundle: UnformattedBundle, 

637 title: str, 

638 key: str, 

639 info, 

640 omit_sections: list[str], 

641 formatter, 

642 ): 

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

644 if title in omit_sections or key in omit_sections: 

645 return 

646 field = info[key] 

647 if field is not None: 

648 formatted_field = self._mime_format(field, formatter) 

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

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

651 

652 def _make_info_unformatted( 

653 self, obj, info, formatter, detail_level, omit_sections 

654 ) -> UnformattedBundle: 

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

656 bundle: UnformattedBundle = { 

657 "text/plain": [], 

658 "text/html": [], 

659 } 

660 

661 # A convenience function to simplify calls below 

662 def append_field( 

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

664 ): 

665 self._append_info_field( 

666 bundle, 

667 title=title, 

668 key=key, 

669 info=info, 

670 omit_sections=omit_sections, 

671 formatter=formatter, 

672 ) 

673 

674 def code_formatter(text) -> Bundle: 

675 return { 

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

677 'text/html': pylight(text) 

678 } 

679 

680 if info["isalias"]: 

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

682 

683 elif info['ismagic']: 

684 if detail_level > 0: 

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

686 else: 

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

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

689 

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

691 # Functions, methods, classes 

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

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

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

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

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

697 else: 

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

699 

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

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

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

703 

704 else: 

705 # General Python objects 

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

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

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

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

710 

711 # Namespace 

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

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

714 

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

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

717 

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

719 # source found. 

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

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

722 else: 

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

724 

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

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

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

728 return bundle 

729 

730 

731 def _get_info( 

732 self, 

733 obj: Any, 

734 oname: str = "", 

735 formatter=None, 

736 info: OInfo | None = None, 

737 detail_level: int = 0, 

738 omit_sections: list[str] | tuple[()] = (), 

739 ) -> Bundle: 

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

741 

742 Parameters 

743 ---------- 

744 obj : any 

745 Object to inspect and return info from 

746 oname : str (default: ''): 

747 Name of the variable pointing to `obj`. 

748 formatter : callable 

749 info 

750 already computed information 

751 detail_level : integer 

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

753 omit_sections : list[str] 

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

755 """ 

756 

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

758 omit_sections = list(omit_sections) 

759 

760 bundle = self._make_info_unformatted( 

761 obj, 

762 info_dict, 

763 formatter, 

764 detail_level=detail_level, 

765 omit_sections=omit_sections, 

766 ) 

767 if self.mime_hooks: 

768 hook_data = InspectorHookData( 

769 obj=obj, 

770 info=info, 

771 info_dict=info_dict, 

772 detail_level=detail_level, 

773 omit_sections=omit_sections, 

774 ) 

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

776 required_parameters = [ 

777 parameter 

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

779 if parameter.default is inspect.Parameter.empty 

780 ] 

781 if len(required_parameters) == 1: 

782 res = hook(hook_data) 

783 else: 

784 warnings.warn( 

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

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

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

788 DeprecationWarning, 

789 stacklevel=2, 

790 ) 

791 res = hook(obj, info) 

792 if res is not None: 

793 bundle[key] = res 

794 return self.format_mime(bundle) 

795 

796 def pinfo( 

797 self, 

798 obj, 

799 oname="", 

800 formatter=None, 

801 info: OInfo | None = None, 

802 detail_level=0, 

803 enable_html_pager=True, 

804 omit_sections=(), 

805 ): 

806 """Show detailed information about an object. 

807 

808 Optional arguments: 

809 

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

811 

812 - formatter: callable (optional) 

813 A special formatter for docstrings. 

814 

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

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

817 in the form of a dictionary. 

818 

819 Although the support of custom formatter returning a string 

820 instead of a mime type bundle is deprecated. 

821 

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

823 precomputed already. 

824 

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

826 

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

828 """ 

829 assert info is not None 

830 info_b: Bundle = self._get_info( 

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

832 ) 

833 if not enable_html_pager: 

834 del info_b["text/html"] 

835 page.page(info_b) 

836 

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

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

839 

840 Parameters 

841 ---------- 

842 obj : any 

843 An object to find information about 

844 oname : str (default: '') 

845 Name of the variable pointing to `obj`. 

846 info : (default: None) 

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

848 which may have been precomputed already. 

849 detail_level : int (default:0) 

850 If set to 1, more information is given. 

851 

852 Returns 

853 ------- 

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

855 """ 

856 

857 if info is None: 

858 ismagic = False 

859 isalias = False 

860 ospace = '' 

861 else: 

862 ismagic = info.ismagic 

863 isalias = info.isalias 

864 ospace = info.namespace 

865 

866 # Get docstring, special-casing aliases: 

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

868 parents_docs = None 

869 prelude = "" 

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

871 parents_docs_dict = getattr(info.parent, HOOK_NAME) 

872 if isinstance(parents_docs_dict, dict): 

873 parents_docs = parents_docs_dict.get(att_name, None) 

874 out: InfoDict = cast( 

875 InfoDict, 

876 { 

877 **dict.fromkeys(_info_fields), 

878 **{ 

879 "name": oname, 

880 "found": True, 

881 "isalias": isalias, 

882 "ismagic": ismagic, 

883 "subclasses": None, 

884 }, 

885 }, 

886 ) 

887 

888 if parents_docs: 

889 ds = parents_docs 

890 elif isalias: 

891 if not callable(obj): 

892 try: 

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

894 except (TypeError, IndexError): 

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

896 else: 

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

898 if obj.__doc__: 

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

900 else: 

901 ds_or_None = getdoc(obj) 

902 if ds_or_None is None: 

903 ds = '<no docstring>' 

904 else: 

905 ds = ds_or_None 

906 

907 ds = prelude + ds 

908 

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

910 

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

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

913 

914 if ismagic: 

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

916 elif isalias: 

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

918 else: 

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

920 

921 try: 

922 bclass = obj.__class__ 

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

924 except AttributeError: 

925 pass 

926 

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

928 if detail_level >= self.str_detail_level: 

929 try: 

930 ostr = str(obj) 

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

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

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

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

935 ) 

936 out["string_form"] = ostr 

937 except Exception: 

938 pass 

939 

940 if ospace: 

941 out['namespace'] = ospace 

942 

943 # Length (for strings and lists) 

944 try: 

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

946 except Exception: 

947 pass 

948 

949 # Filename where object was defined 

950 binary_file = False 

951 fname = find_file(obj) 

952 if fname is None: 

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

954 # if the file was binary 

955 binary_file = True 

956 else: 

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

958 binary_file = True 

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

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

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

962 

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

964 if detail_level: 

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

966 # source 

967 linecache.checkcache() 

968 try: 

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

970 src = getsource(obj, oname) 

971 if src is not None: 

972 src = src.rstrip() 

973 out['source'] = src 

974 

975 except Exception: 

976 pass 

977 

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

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

980 out['docstring'] = ds 

981 

982 # Constructor docstring for classes 

983 if inspect.isclass(obj): 

984 out['isclass'] = True 

985 

986 # get the init signature: 

987 try: 

988 init_def = self._getdef(obj, oname) 

989 except AttributeError: 

990 init_def = None 

991 

992 # get the __init__ docstring 

993 try: 

994 obj_init = obj.__init__ 

995 except AttributeError: 

996 init_ds = None 

997 else: 

998 if init_def is None: 

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

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

1001 try: 

1002 init_def = self._getdef(obj_init, oname) 

1003 except AttributeError: 

1004 pass 

1005 init_ds = getdoc(obj_init) 

1006 # Skip Python's auto-generated docstrings 

1007 if init_ds == _object_init_docstring: 

1008 init_ds = None 

1009 

1010 if init_def: 

1011 out['init_definition'] = init_def 

1012 

1013 if init_ds: 

1014 out['init_docstring'] = init_ds 

1015 

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

1017 if len(names) < 10: 

1018 all_names = ', '.join(names) 

1019 else: 

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

1021 out['subclasses'] = all_names 

1022 # and class docstring for instances: 

1023 else: 

1024 # reconstruct the function definition and print it: 

1025 defln = self._getdef(obj, oname) 

1026 if defln: 

1027 out['definition'] = defln 

1028 

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

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

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

1032 # objects which use instance-customized docstrings. 

1033 if ds: 

1034 try: 

1035 cls = getattr(obj,'__class__') 

1036 except AttributeError: 

1037 class_ds = None 

1038 else: 

1039 class_ds = getdoc(cls) 

1040 # Skip Python's auto-generated docstrings 

1041 if class_ds in _builtin_type_docstrings: 

1042 class_ds = None 

1043 if class_ds and ds != class_ds: 

1044 out['class_docstring'] = class_ds 

1045 

1046 # Next, try to show constructor docstrings 

1047 try: 

1048 init_ds = getdoc(obj.__init__) 

1049 # Skip Python's auto-generated docstrings 

1050 if init_ds == _object_init_docstring: 

1051 init_ds = None 

1052 except AttributeError: 

1053 init_ds = None 

1054 if init_ds: 

1055 out['init_docstring'] = init_ds 

1056 

1057 # Call form docstring for callable instances 

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

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

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

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

1062 # but don't include the same signature twice 

1063 out['call_def'] = call_def 

1064 call_ds = getdoc(obj.__call__) 

1065 # Skip Python's auto-generated docstrings 

1066 if call_ds == _func_call_docstring: 

1067 call_ds = None 

1068 if call_ds: 

1069 out['call_docstring'] = call_ds 

1070 

1071 return out 

1072 

1073 @staticmethod 

1074 def _source_contains_docstring(src, doc): 

1075 """ 

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

1077 

1078 This is a helper function to skip displaying the docstring if the 

1079 source already contains it, avoiding repetition of information. 

1080 """ 

1081 try: 

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

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

1084 except Exception: 

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

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

1087 # arbitrary ways. 

1088 return False 

1089 

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

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

1092 """Search namespaces with wildcards for objects. 

1093 

1094 Arguments: 

1095 

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

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

1098 objects of that type. 

1099 

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

1101 

1102 Optional arguments: 

1103 

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

1105 

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

1107 

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

1109 underscores. 

1110 

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

1112 """ 

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

1114 

1115 # defaults 

1116 type_pattern = 'all' 

1117 filter = '' 

1118 

1119 # list all object types 

1120 if list_types: 

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

1122 return 

1123 

1124 cmds = pattern.split() 

1125 len_cmds = len(cmds) 

1126 if len_cmds == 1: 

1127 # Only filter pattern given 

1128 filter = cmds[0] 

1129 elif len_cmds == 2: 

1130 # Both filter and type specified 

1131 filter,type_pattern = cmds 

1132 else: 

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

1134 pattern) 

1135 

1136 # filter search namespaces 

1137 for name in ns_search: 

1138 if name not in ns_table: 

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

1140 (name,ns_table.keys())) 

1141 

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

1143 search_result, namespaces_seen = set(), set() 

1144 for ns_name in ns_search: 

1145 ns = ns_table[ns_name] 

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

1147 if id(ns) in namespaces_seen: 

1148 continue 

1149 namespaces_seen.add(id(ns)) 

1150 tmp_res = list_namespace(ns, type_pattern, filter, 

1151 ignore_case=ignore_case, show_all=show_all) 

1152 search_result.update(tmp_res) 

1153 

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

1155 

1156 

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

1158 """ 

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

1160 Look there for the comments. 

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

1162 """ 

1163 result = [] 

1164 pos_only = False 

1165 kw_only = True 

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

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

1168 pos_only = True 

1169 elif pos_only: 

1170 result.append('/') 

1171 pos_only = False 

1172 

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

1174 kw_only = False 

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

1176 result.append('*') 

1177 kw_only = False 

1178 

1179 result.append(str(param)) 

1180 

1181 if pos_only: 

1182 result.append('/') 

1183 

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

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

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

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

1188 f' {r},\n' for r in result) 

1189 ) 

1190 else: 

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

1192 

1193 if obj_signature.return_annotation is not inspect._empty: 

1194 anno = inspect.formatannotation(obj_signature.return_annotation) 

1195 rendered += f' -> {anno}' 

1196 

1197 return rendered