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

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

564 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"] 

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 

26from pygments.token import Token 

27 

28 

29from typing import ( 

30 cast, 

31 Any, 

32 Optional, 

33 Dict, 

34 Union, 

35 List, 

36 TypedDict, 

37 TypeAlias, 

38 Tuple, 

39) 

40 

41import traitlets 

42from traitlets.config import Configurable 

43 

44# IPython's own 

45from IPython.core import page 

46from IPython.lib.pretty import pretty 

47from IPython.testing.skipdoctest import skip_doctest 

48from IPython.utils import PyColorize, openpy 

49from IPython.utils.dir2 import safe_hasattr 

50from IPython.utils.path import compress_user 

51from IPython.utils.text import indent 

52from IPython.utils.wildcard import list_namespace, typestr2type 

53from IPython.utils.decorators import undoc 

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 # Jan 2025: decomenting for IPython 9.0 

83 warnings.warn( 

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

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

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

87 DeprecationWarning, 

88 stacklevel=2, 

89 ) 

90 return getattr(self, field) 

91 

92 

93def pylight(code): 

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

95 

96# builtin docstrings to ignore 

97_func_call_docstring = types.FunctionType.__call__.__doc__ 

98_object_init_docstring = object.__init__.__doc__ 

99_builtin_type_docstrings = { 

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

101 types.FunctionType, property) 

102} 

103 

104_builtin_func_type = type(all) 

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

106#**************************************************************************** 

107# Builtin color schemes 

108 

109 

110#**************************************************************************** 

111# Auxiliary functions and objects 

112 

113 

114class InfoDict(TypedDict): 

115 type_name: Optional[str] 

116 base_class: Optional[str] 

117 string_form: Optional[str] 

118 namespace: Optional[str] 

119 length: Optional[str] 

120 file: Optional[str] 

121 definition: Optional[str] 

122 docstring: Optional[str] 

123 source: Optional[str] 

124 init_definition: Optional[str] 

125 class_docstring: Optional[str] 

126 init_docstring: Optional[str] 

127 call_def: Optional[str] 

128 call_docstring: Optional[str] 

129 subclasses: Optional[str] 

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

131 # format the object 

132 ismagic: bool 

133 isalias: bool 

134 isclass: bool 

135 found: bool 

136 name: str 

137 

138 

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

140 

141 

142def __getattr__(name): 

143 if name == "info_fields": 

144 warnings.warn( 

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

146 DeprecationWarning, 

147 stacklevel=2, 

148 ) 

149 return _info_fields 

150 

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

152 

153 

154@dataclass 

155class InspectorHookData: 

156 """Data passed to the mime hook""" 

157 

158 obj: Any 

159 info: Optional[OInfo] 

160 info_dict: InfoDict 

161 detail_level: int 

162 omit_sections: list[str] 

163 

164 

165@undoc 

166def object_info( 

167 *, 

168 name: str, 

169 found: bool, 

170 isclass: bool = False, 

171 isalias: bool = False, 

172 ismagic: bool = False, 

173 **kw, 

174) -> InfoDict: 

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

176 infodict = dict(kw) 

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

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

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

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

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

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

183 

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

185 

186 

187def get_encoding(obj): 

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

189 

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

191 """ 

192 ofile = find_file(obj) 

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

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

195 # filesystem. 

196 if ofile is None: 

197 return None 

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

199 return None 

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

201 return None 

202 else: 

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

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

205 # 0-offset, so we must adjust. 

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

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

208 return encoding 

209 

210 

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

212 """Stable wrapper around inspect.getdoc. 

213 

214 This can't crash because of attribute problems. 

215 

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

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

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

219 """ 

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

221 try: 

222 ds = obj.getdoc() 

223 except Exception: 

224 pass 

225 else: 

226 if isinstance(ds, str): 

227 return inspect.cleandoc(ds) 

228 docstr = inspect.getdoc(obj) 

229 return docstr 

230 

231 

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

233 """Wrapper around inspect.getsource. 

234 

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

236 extraction. 

237 

238 Parameters 

239 ---------- 

240 obj : object 

241 an object whose source code we will attempt to extract 

242 oname : str 

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

244 

245 Returns 

246 ------- 

247 src : unicode or None 

248 

249 """ 

250 

251 if isinstance(obj, property): 

252 sources = [] 

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

254 fn = getattr(obj, attrname) 

255 if fn is not None: 

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

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

258 if inspect.isfunction(fn): 

259 _src = getsource(fn) 

260 if _src: 

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

262 sources.append(dedent(_src)) 

263 else: 

264 # Default str/repr only prints function name, 

265 # pretty.pretty prints module name too. 

266 sources.append( 

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

268 ) 

269 if sources: 

270 return '\n'.join(sources) 

271 else: 

272 return None 

273 

274 else: 

275 # Get source for non-property objects. 

276 

277 obj = _get_wrapped(obj) 

278 

279 try: 

280 src = inspect.getsource(obj) 

281 except TypeError: 

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

283 # its class definition instead. 

284 try: 

285 src = inspect.getsource(obj.__class__) 

286 except (OSError, TypeError): 

287 return None 

288 except OSError: 

289 return None 

290 

291 return src 

292 

293 

294def is_simple_callable(obj): 

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

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

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

298 

299def _get_wrapped(obj): 

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

301 

302 Some objects automatically construct similar objects on any unrecognised 

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

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

305 attribute access. --TK, Jan 2016 

306 """ 

307 orig_obj = obj 

308 i = 0 

309 while safe_hasattr(obj, '__wrapped__'): 

310 obj = obj.__wrapped__ 

311 i += 1 

312 if i > 100: 

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

314 return orig_obj 

315 return obj 

316 

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

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

319 

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

321 

322 Returns None if no file can be found. 

323 

324 Parameters 

325 ---------- 

326 obj : any Python object 

327 

328 Returns 

329 ------- 

330 fname : str 

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

332 """ 

333 obj = _get_wrapped(obj) 

334 

335 fname: Optional[str] = None 

336 try: 

337 fname = inspect.getabsfile(obj) 

338 except TypeError: 

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

340 # declared. 

341 try: 

342 fname = inspect.getabsfile(obj.__class__) 

343 except (OSError, TypeError): 

344 # Can happen for builtins 

345 pass 

346 except OSError: 

347 pass 

348 

349 return fname 

350 

351 

352def find_source_lines(obj): 

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

354 

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

356 

357 Returns None if no file can be found. 

358 

359 Parameters 

360 ---------- 

361 obj : any Python object 

362 

363 Returns 

364 ------- 

365 lineno : int 

366 The line number where the object definition starts. 

367 """ 

368 obj = _get_wrapped(obj) 

369 

370 try: 

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

372 except TypeError: 

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

374 try: 

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

376 except (OSError, TypeError): 

377 return None 

378 except OSError: 

379 return None 

380 

381 return lineno 

382 

383 

384_sentinel = object() 

385 

386 

387class Inspector(Configurable): 

388 mime_hooks = traitlets.Dict( 

389 config=True, 

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

391 ).tag(config=True) 

392 

393 _theme_name: str 

394 

395 def __init__( 

396 self, 

397 *, 

398 theme_name: str, 

399 str_detail_level=0, 

400 parent=None, 

401 config=None, 

402 ): 

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

404 warnings.warn( 

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

406 DeprecationWarning, 

407 stacklevel=2, 

408 ) 

409 theme_name = theme_name.lower() 

410 self._theme_name = theme_name 

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

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

413 self.str_detail_level = str_detail_level 

414 self.set_theme_name(theme_name) 

415 

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

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

418 

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

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

421 

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

423 exception is suppressed.""" 

424 if not callable(obj): 

425 return None 

426 try: 

427 return _render_signature(signature(obj), oname) 

428 except: 

429 return None 

430 

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

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

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

434 

435 def set_theme_name(self, name: str): 

436 assert name == name.lower() 

437 assert name in PyColorize.theme_table.keys() 

438 self._theme_name = name 

439 self.parser.theme_name = name 

440 

441 def set_active_scheme(self, scheme: str): 

442 warnings.warn( 

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

444 DeprecationWarning, 

445 stacklevel=2, 

446 ) 

447 assert scheme == scheme.lower() 

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

449 self._theme_name = scheme 

450 self.parser.theme_name = scheme 

451 

452 def noinfo(self, msg, oname): 

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

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

455 if oname: 

456 print('for %s' % oname) 

457 else: 

458 print() 

459 

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

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

462 

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

464 

465 if not callable(obj): 

466 print('Object is not callable.') 

467 return 

468 

469 header = '' 

470 

471 if inspect.isclass(obj): 

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

473 

474 

475 output = self._getdef(obj,oname) 

476 if output is None: 

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

478 else: 

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

480 

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

482 @skip_doctest 

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

484 """Print the docstring for any object. 

485 

486 Optional: 

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

488 formatted docstrings. 

489 

490 Examples 

491 -------- 

492 In [1]: class NoInit: 

493 ...: pass 

494 

495 In [2]: class NoDoc: 

496 ...: def __init__(self): 

497 ...: pass 

498 

499 In [3]: %pdoc NoDoc 

500 No documentation found for NoDoc 

501 

502 In [4]: %pdoc NoInit 

503 No documentation found for NoInit 

504 

505 In [5]: obj = NoInit() 

506 

507 In [6]: %pdoc obj 

508 No documentation found for obj 

509 

510 In [5]: obj2 = NoDoc() 

511 

512 In [6]: %pdoc obj2 

513 No documentation found for obj2 

514 """ 

515 

516 lines = [] 

517 ds = getdoc(obj) 

518 if formatter: 

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

520 if ds: 

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

522 lines.append(indent(ds)) 

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

524 init_ds = getdoc(obj.__init__) 

525 if init_ds is not None: 

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

527 lines.append(indent(init_ds)) 

528 elif hasattr(obj,'__call__'): 

529 call_ds = getdoc(obj.__call__) 

530 if call_ds: 

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

532 lines.append(indent(call_ds)) 

533 

534 if not lines: 

535 self.noinfo('documentation',oname) 

536 else: 

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

538 

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

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

541 

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

543 linecache.checkcache() 

544 try: 

545 src = getsource(obj, oname=oname) 

546 except Exception: 

547 src = None 

548 

549 if src is None: 

550 self.noinfo('source', oname) 

551 else: 

552 page.page(self.format(src)) 

553 

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

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

556 

557 lineno = find_source_lines(obj) 

558 if lineno is None: 

559 self.noinfo('file', oname) 

560 return 

561 

562 ofile = find_file(obj) 

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

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

565 # filesystem. 

566 if ofile is None: 

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

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

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

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

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

572 else: 

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

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

575 # 0-offset, so we must adjust. 

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

577 

578 

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

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

581 

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

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

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

585 

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

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

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

589 

590 Note: 

591 

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

593 

594 """ 

595 defaults = { 

596 "text/plain": text, 

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

598 } 

599 

600 if formatter is None: 

601 return defaults 

602 else: 

603 formatted = formatter(text) 

604 

605 if not isinstance(formatted, dict): 

606 # Handle the deprecated behavior of a formatter returning 

607 # a string instead of a mime bundle. 

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

609 

610 else: 

611 return dict(defaults, **formatted) 

612 

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

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

615 # Format text/plain mimetype 

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

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

618 assert isinstance(item, tuple) 

619 

620 new_b: Bundle = {} 

621 lines = [] 

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

623 

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

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

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

627 lines.append( 

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

629 ) 

630 

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

632 

633 if "text/html" in bundle: 

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

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

636 assert isinstance(item, tuple) 

637 # Format the text/html mimetype 

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

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

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

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

642 ) 

643 

644 for k in bundle.keys(): 

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

646 continue 

647 else: 

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

649 return new_b 

650 

651 def _append_info_field( 

652 self, 

653 bundle: UnformattedBundle, 

654 title: str, 

655 key: str, 

656 info, 

657 omit_sections: List[str], 

658 formatter, 

659 ): 

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

661 if title in omit_sections or key in omit_sections: 

662 return 

663 field = info[key] 

664 if field is not None: 

665 formatted_field = self._mime_format(field, formatter) 

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

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

668 

669 def _make_info_unformatted( 

670 self, obj, info, formatter, detail_level, omit_sections 

671 ) -> UnformattedBundle: 

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

673 bundle: UnformattedBundle = { 

674 "text/plain": [], 

675 "text/html": [], 

676 } 

677 

678 # A convenience function to simplify calls below 

679 def append_field( 

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

681 ): 

682 self._append_info_field( 

683 bundle, 

684 title=title, 

685 key=key, 

686 info=info, 

687 omit_sections=omit_sections, 

688 formatter=formatter, 

689 ) 

690 

691 def code_formatter(text) -> Bundle: 

692 return { 

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

694 'text/html': pylight(text) 

695 } 

696 

697 if info["isalias"]: 

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

699 

700 elif info['ismagic']: 

701 if detail_level > 0: 

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

703 else: 

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

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

706 

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

708 # Functions, methods, classes 

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

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

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

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

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

714 else: 

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

716 

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

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

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

720 

721 else: 

722 # General Python objects 

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

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

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

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

727 

728 # Namespace 

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

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

731 

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

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

734 

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

736 # source found. 

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

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

739 else: 

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

741 

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

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

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

745 return bundle 

746 

747 

748 def _get_info( 

749 self, 

750 obj: Any, 

751 oname: str = "", 

752 formatter=None, 

753 info: Optional[OInfo] = None, 

754 detail_level: int = 0, 

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

756 ) -> Bundle: 

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

758 

759 Parameters 

760 ---------- 

761 obj : any 

762 Object to inspect and return info from 

763 oname : str (default: ''): 

764 Name of the variable pointing to `obj`. 

765 formatter : callable 

766 info 

767 already computed information 

768 detail_level : integer 

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

770 omit_sections : list[str] 

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

772 """ 

773 

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

775 omit_sections = list(omit_sections) 

776 

777 bundle = self._make_info_unformatted( 

778 obj, 

779 info_dict, 

780 formatter, 

781 detail_level=detail_level, 

782 omit_sections=omit_sections, 

783 ) 

784 if self.mime_hooks: 

785 hook_data = InspectorHookData( 

786 obj=obj, 

787 info=info, 

788 info_dict=info_dict, 

789 detail_level=detail_level, 

790 omit_sections=omit_sections, 

791 ) 

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

793 required_parameters = [ 

794 parameter 

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

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

797 ] 

798 if len(required_parameters) == 1: 

799 res = hook(hook_data) 

800 else: 

801 warnings.warn( 

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

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

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

805 DeprecationWarning, 

806 stacklevel=2, 

807 ) 

808 res = hook(obj, info) 

809 if res is not None: 

810 bundle[key] = res 

811 return self.format_mime(bundle) 

812 

813 def pinfo( 

814 self, 

815 obj, 

816 oname="", 

817 formatter=None, 

818 info: Optional[OInfo] = None, 

819 detail_level=0, 

820 enable_html_pager=True, 

821 omit_sections=(), 

822 ): 

823 """Show detailed information about an object. 

824 

825 Optional arguments: 

826 

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

828 

829 - formatter: callable (optional) 

830 A special formatter for docstrings. 

831 

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

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

834 in the form of a dictionary. 

835 

836 Although the support of custom formatter returning a string 

837 instead of a mime type bundle is deprecated. 

838 

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

840 precomputed already. 

841 

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

843 

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

845 """ 

846 assert info is not None 

847 info_b: Bundle = self._get_info( 

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

849 ) 

850 if not enable_html_pager: 

851 del info_b["text/html"] 

852 page.page(info_b) 

853 

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

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

856 

857 Parameters 

858 ---------- 

859 obj : any 

860 An object to find information about 

861 oname : str (default: '') 

862 Name of the variable pointing to `obj`. 

863 info : (default: None) 

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

865 which may have been precomputed already. 

866 detail_level : int (default:0) 

867 If set to 1, more information is given. 

868 

869 Returns 

870 ------- 

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

872 """ 

873 

874 if info is None: 

875 ismagic = False 

876 isalias = False 

877 ospace = '' 

878 else: 

879 ismagic = info.ismagic 

880 isalias = info.isalias 

881 ospace = info.namespace 

882 

883 # Get docstring, special-casing aliases: 

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

885 parents_docs = None 

886 prelude = "" 

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

888 parents_docs_dict = getattr(info.parent, HOOK_NAME) 

889 parents_docs = parents_docs_dict.get(att_name, None) 

890 out: InfoDict = cast( 

891 InfoDict, 

892 { 

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

894 **{ 

895 "name": oname, 

896 "found": True, 

897 "isalias": isalias, 

898 "ismagic": ismagic, 

899 "subclasses": None, 

900 }, 

901 }, 

902 ) 

903 

904 if parents_docs: 

905 ds = parents_docs 

906 elif isalias: 

907 if not callable(obj): 

908 try: 

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

910 except: 

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

912 else: 

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

914 if obj.__doc__: 

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

916 else: 

917 ds_or_None = getdoc(obj) 

918 if ds_or_None is None: 

919 ds = '<no docstring>' 

920 else: 

921 ds = ds_or_None 

922 

923 ds = prelude + ds 

924 

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

926 

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

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

929 

930 if ismagic: 

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

932 elif isalias: 

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

934 else: 

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

936 

937 try: 

938 bclass = obj.__class__ 

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

940 except: 

941 pass 

942 

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

944 if detail_level >= self.str_detail_level: 

945 try: 

946 ostr = str(obj) 

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

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

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

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

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

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

953 ) 

954 out["string_form"] = ostr 

955 except: 

956 pass 

957 

958 if ospace: 

959 out['namespace'] = ospace 

960 

961 # Length (for strings and lists) 

962 try: 

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

964 except Exception: 

965 pass 

966 

967 # Filename where object was defined 

968 binary_file = False 

969 fname = find_file(obj) 

970 if fname is None: 

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

972 # if the file was binary 

973 binary_file = True 

974 else: 

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

976 binary_file = True 

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

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

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

980 

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

982 if detail_level: 

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

984 # source 

985 linecache.checkcache() 

986 try: 

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

988 src = getsource(obj, oname) 

989 if src is not None: 

990 src = src.rstrip() 

991 out['source'] = src 

992 

993 except Exception: 

994 pass 

995 

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

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

998 out['docstring'] = ds 

999 

1000 # Constructor docstring for classes 

1001 if inspect.isclass(obj): 

1002 out['isclass'] = True 

1003 

1004 # get the init signature: 

1005 try: 

1006 init_def = self._getdef(obj, oname) 

1007 except AttributeError: 

1008 init_def = None 

1009 

1010 # get the __init__ docstring 

1011 try: 

1012 obj_init = obj.__init__ 

1013 except AttributeError: 

1014 init_ds = None 

1015 else: 

1016 if init_def is None: 

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

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

1019 try: 

1020 init_def = self._getdef(obj_init, oname) 

1021 except AttributeError: 

1022 pass 

1023 init_ds = getdoc(obj_init) 

1024 # Skip Python's auto-generated docstrings 

1025 if init_ds == _object_init_docstring: 

1026 init_ds = None 

1027 

1028 if init_def: 

1029 out['init_definition'] = init_def 

1030 

1031 if init_ds: 

1032 out['init_docstring'] = init_ds 

1033 

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

1035 if len(names) < 10: 

1036 all_names = ', '.join(names) 

1037 else: 

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

1039 out['subclasses'] = all_names 

1040 # and class docstring for instances: 

1041 else: 

1042 # reconstruct the function definition and print it: 

1043 defln = self._getdef(obj, oname) 

1044 if defln: 

1045 out['definition'] = defln 

1046 

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

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

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

1050 # objects which use instance-customized docstrings. 

1051 if ds: 

1052 try: 

1053 cls = getattr(obj,'__class__') 

1054 except: 

1055 class_ds = None 

1056 else: 

1057 class_ds = getdoc(cls) 

1058 # Skip Python's auto-generated docstrings 

1059 if class_ds in _builtin_type_docstrings: 

1060 class_ds = None 

1061 if class_ds and ds != class_ds: 

1062 out['class_docstring'] = class_ds 

1063 

1064 # Next, try to show constructor docstrings 

1065 try: 

1066 init_ds = getdoc(obj.__init__) 

1067 # Skip Python's auto-generated docstrings 

1068 if init_ds == _object_init_docstring: 

1069 init_ds = None 

1070 except AttributeError: 

1071 init_ds = None 

1072 if init_ds: 

1073 out['init_docstring'] = init_ds 

1074 

1075 # Call form docstring for callable instances 

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

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

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

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

1080 # but don't include the same signature twice 

1081 out['call_def'] = call_def 

1082 call_ds = getdoc(obj.__call__) 

1083 # Skip Python's auto-generated docstrings 

1084 if call_ds == _func_call_docstring: 

1085 call_ds = None 

1086 if call_ds: 

1087 out['call_docstring'] = call_ds 

1088 

1089 return out 

1090 

1091 @staticmethod 

1092 def _source_contains_docstring(src, doc): 

1093 """ 

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

1095 

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

1097 source already contains it, avoiding repetition of information. 

1098 """ 

1099 try: 

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

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

1102 except Exception: 

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

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

1105 # arbitrary ways. 

1106 return False 

1107 

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

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

1110 """Search namespaces with wildcards for objects. 

1111 

1112 Arguments: 

1113 

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

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

1116 objects of that type. 

1117 

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

1119 

1120 Optional arguments: 

1121 

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

1123 

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

1125 

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

1127 underscores. 

1128 

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

1130 """ 

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

1132 

1133 # defaults 

1134 type_pattern = 'all' 

1135 filter = '' 

1136 

1137 # list all object types 

1138 if list_types: 

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

1140 return 

1141 

1142 cmds = pattern.split() 

1143 len_cmds = len(cmds) 

1144 if len_cmds == 1: 

1145 # Only filter pattern given 

1146 filter = cmds[0] 

1147 elif len_cmds == 2: 

1148 # Both filter and type specified 

1149 filter,type_pattern = cmds 

1150 else: 

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

1152 pattern) 

1153 

1154 # filter search namespaces 

1155 for name in ns_search: 

1156 if name not in ns_table: 

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

1158 (name,ns_table.keys())) 

1159 

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

1161 search_result, namespaces_seen = set(), set() 

1162 for ns_name in ns_search: 

1163 ns = ns_table[ns_name] 

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

1165 if id(ns) in namespaces_seen: 

1166 continue 

1167 namespaces_seen.add(id(ns)) 

1168 tmp_res = list_namespace(ns, type_pattern, filter, 

1169 ignore_case=ignore_case, show_all=show_all) 

1170 search_result.update(tmp_res) 

1171 

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

1173 

1174 

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

1176 """ 

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

1178 Look there for the comments. 

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

1180 """ 

1181 result = [] 

1182 pos_only = False 

1183 kw_only = True 

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

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

1186 pos_only = True 

1187 elif pos_only: 

1188 result.append('/') 

1189 pos_only = False 

1190 

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

1192 kw_only = False 

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

1194 result.append('*') 

1195 kw_only = False 

1196 

1197 result.append(str(param)) 

1198 

1199 if pos_only: 

1200 result.append('/') 

1201 

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

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

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

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

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

1207 ) 

1208 else: 

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

1210 

1211 if obj_signature.return_annotation is not inspect._empty: 

1212 anno = inspect.formatannotation(obj_signature.return_annotation) 

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

1214 

1215 return rendered