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 

389 mime_hooks = traitlets.Dict( 

390 config=True, 

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

392 ).tag(config=True) 

393 

394 _theme_name: str 

395 

396 def __init__( 

397 self, 

398 *, 

399 theme_name: str, 

400 str_detail_level=0, 

401 parent=None, 

402 config=None, 

403 ): 

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

405 warnings.warn( 

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

407 DeprecationWarning, 

408 stacklevel=2, 

409 ) 

410 theme_name = theme_name.lower() 

411 self._theme_name = theme_name 

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

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

414 self.str_detail_level = str_detail_level 

415 self.set_theme_name(theme_name) 

416 

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

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

419 

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

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

422 

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

424 exception is suppressed.""" 

425 if not callable(obj): 

426 return None 

427 try: 

428 return _render_signature(signature(obj), oname) 

429 except: 

430 return None 

431 

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

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

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

435 

436 def set_theme_name(self, name: str): 

437 assert name == name.lower() 

438 assert name in PyColorize.theme_table.keys() 

439 self._theme_name = name 

440 self.parser.theme_name = name 

441 

442 def set_active_scheme(self, scheme: str): 

443 warnings.warn( 

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

445 DeprecationWarning, 

446 stacklevel=2, 

447 ) 

448 assert scheme == scheme.lower() 

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

450 self._theme_name = scheme 

451 self.parser.theme_name = scheme 

452 

453 def noinfo(self, msg, oname): 

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

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

456 if oname: 

457 print('for %s' % oname) 

458 else: 

459 print() 

460 

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

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

463 

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

465 

466 if not callable(obj): 

467 print('Object is not callable.') 

468 return 

469 

470 header = '' 

471 

472 if inspect.isclass(obj): 

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

474 

475 

476 output = self._getdef(obj,oname) 

477 if output is None: 

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

479 else: 

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

481 

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

483 @skip_doctest 

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

485 """Print the docstring for any object. 

486 

487 Optional: 

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

489 formatted docstrings. 

490 

491 Examples 

492 -------- 

493 In [1]: class NoInit: 

494 ...: pass 

495 

496 In [2]: class NoDoc: 

497 ...: def __init__(self): 

498 ...: pass 

499 

500 In [3]: %pdoc NoDoc 

501 No documentation found for NoDoc 

502 

503 In [4]: %pdoc NoInit 

504 No documentation found for NoInit 

505 

506 In [5]: obj = NoInit() 

507 

508 In [6]: %pdoc obj 

509 No documentation found for obj 

510 

511 In [5]: obj2 = NoDoc() 

512 

513 In [6]: %pdoc obj2 

514 No documentation found for obj2 

515 """ 

516 

517 lines = [] 

518 ds = getdoc(obj) 

519 if formatter: 

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

521 if ds: 

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

523 lines.append(indent(ds)) 

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

525 init_ds = getdoc(obj.__init__) 

526 if init_ds is not None: 

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

528 lines.append(indent(init_ds)) 

529 elif hasattr(obj,'__call__'): 

530 call_ds = getdoc(obj.__call__) 

531 if call_ds: 

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

533 lines.append(indent(call_ds)) 

534 

535 if not lines: 

536 self.noinfo('documentation',oname) 

537 else: 

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

539 

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

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

542 

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

544 linecache.checkcache() 

545 try: 

546 src = getsource(obj, oname=oname) 

547 except Exception: 

548 src = None 

549 

550 if src is None: 

551 self.noinfo('source', oname) 

552 else: 

553 page.page(self.format(src)) 

554 

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

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

557 

558 lineno = find_source_lines(obj) 

559 if lineno is None: 

560 self.noinfo('file', oname) 

561 return 

562 

563 ofile = find_file(obj) 

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

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

566 # filesystem. 

567 if ofile is None: 

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

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

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

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

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

573 else: 

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

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

576 # 0-offset, so we must adjust. 

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

578 

579 

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

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

582 

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

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

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

586 

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

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

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

590 

591 Note: 

592 

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

594 

595 """ 

596 defaults = { 

597 "text/plain": text, 

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

599 } 

600 

601 if formatter is None: 

602 return defaults 

603 else: 

604 formatted = formatter(text) 

605 

606 if not isinstance(formatted, dict): 

607 # Handle the deprecated behavior of a formatter returning 

608 # a string instead of a mime bundle. 

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

610 

611 else: 

612 return dict(defaults, **formatted) 

613 

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

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

616 # Format text/plain mimetype 

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

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

619 assert isinstance(item, tuple) 

620 

621 new_b: Bundle = {} 

622 lines = [] 

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

624 

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

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

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

628 lines.append( 

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

630 ) 

631 

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

633 

634 if "text/html" in bundle: 

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

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

637 assert isinstance(item, tuple) 

638 # Format the text/html mimetype 

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

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

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

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

643 ) 

644 

645 for k in bundle.keys(): 

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

647 continue 

648 else: 

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

650 return new_b 

651 

652 def _append_info_field( 

653 self, 

654 bundle: UnformattedBundle, 

655 title: str, 

656 key: str, 

657 info, 

658 omit_sections: List[str], 

659 formatter, 

660 ): 

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

662 if title in omit_sections or key in omit_sections: 

663 return 

664 field = info[key] 

665 if field is not None: 

666 formatted_field = self._mime_format(field, formatter) 

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

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

669 

670 def _make_info_unformatted( 

671 self, obj, info, formatter, detail_level, omit_sections 

672 ) -> UnformattedBundle: 

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

674 bundle: UnformattedBundle = { 

675 "text/plain": [], 

676 "text/html": [], 

677 } 

678 

679 # A convenience function to simplify calls below 

680 def append_field( 

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

682 ): 

683 self._append_info_field( 

684 bundle, 

685 title=title, 

686 key=key, 

687 info=info, 

688 omit_sections=omit_sections, 

689 formatter=formatter, 

690 ) 

691 

692 def code_formatter(text) -> Bundle: 

693 return { 

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

695 'text/html': pylight(text) 

696 } 

697 

698 if info["isalias"]: 

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

700 

701 elif info['ismagic']: 

702 if detail_level > 0: 

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

704 else: 

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

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

707 

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

709 # Functions, methods, classes 

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

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

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

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

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

715 else: 

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

717 

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

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

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

721 

722 else: 

723 # General Python objects 

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

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

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

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

728 

729 # Namespace 

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

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

732 

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

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

735 

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

737 # source found. 

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

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

740 else: 

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

742 

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

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

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

746 return bundle 

747 

748 

749 def _get_info( 

750 self, 

751 obj: Any, 

752 oname: str = "", 

753 formatter=None, 

754 info: Optional[OInfo] = None, 

755 detail_level: int = 0, 

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

757 ) -> Bundle: 

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

759 

760 Parameters 

761 ---------- 

762 obj : any 

763 Object to inspect and return info from 

764 oname : str (default: ''): 

765 Name of the variable pointing to `obj`. 

766 formatter : callable 

767 info 

768 already computed information 

769 detail_level : integer 

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

771 omit_sections : list[str] 

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

773 """ 

774 

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

776 omit_sections = list(omit_sections) 

777 

778 bundle = self._make_info_unformatted( 

779 obj, 

780 info_dict, 

781 formatter, 

782 detail_level=detail_level, 

783 omit_sections=omit_sections, 

784 ) 

785 if self.mime_hooks: 

786 hook_data = InspectorHookData( 

787 obj=obj, 

788 info=info, 

789 info_dict=info_dict, 

790 detail_level=detail_level, 

791 omit_sections=omit_sections, 

792 ) 

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

794 required_parameters = [ 

795 parameter 

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

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

798 ] 

799 if len(required_parameters) == 1: 

800 res = hook(hook_data) 

801 else: 

802 warnings.warn( 

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

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

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

806 DeprecationWarning, 

807 stacklevel=2, 

808 ) 

809 res = hook(obj, info) 

810 if res is not None: 

811 bundle[key] = res 

812 return self.format_mime(bundle) 

813 

814 def pinfo( 

815 self, 

816 obj, 

817 oname="", 

818 formatter=None, 

819 info: Optional[OInfo] = None, 

820 detail_level=0, 

821 enable_html_pager=True, 

822 omit_sections=(), 

823 ): 

824 """Show detailed information about an object. 

825 

826 Optional arguments: 

827 

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

829 

830 - formatter: callable (optional) 

831 A special formatter for docstrings. 

832 

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

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

835 in the form of a dictionary. 

836 

837 Although the support of custom formatter returning a string 

838 instead of a mime type bundle is deprecated. 

839 

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

841 precomputed already. 

842 

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

844 

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

846 """ 

847 assert info is not None 

848 info_b: Bundle = self._get_info( 

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

850 ) 

851 if not enable_html_pager: 

852 del info_b["text/html"] 

853 page.page(info_b) 

854 

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

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

857 

858 Parameters 

859 ---------- 

860 obj : any 

861 An object to find information about 

862 oname : str (default: '') 

863 Name of the variable pointing to `obj`. 

864 info : (default: None) 

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

866 which may have been precomputed already. 

867 detail_level : int (default:0) 

868 If set to 1, more information is given. 

869 

870 Returns 

871 ------- 

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

873 """ 

874 

875 if info is None: 

876 ismagic = False 

877 isalias = False 

878 ospace = '' 

879 else: 

880 ismagic = info.ismagic 

881 isalias = info.isalias 

882 ospace = info.namespace 

883 

884 # Get docstring, special-casing aliases: 

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

886 parents_docs = None 

887 prelude = "" 

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

889 parents_docs_dict = getattr(info.parent, HOOK_NAME) 

890 parents_docs = parents_docs_dict.get(att_name, None) 

891 out: InfoDict = cast( 

892 InfoDict, 

893 { 

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

895 **{ 

896 "name": oname, 

897 "found": True, 

898 "isalias": isalias, 

899 "ismagic": ismagic, 

900 "subclasses": None, 

901 }, 

902 }, 

903 ) 

904 

905 if parents_docs: 

906 ds = parents_docs 

907 elif isalias: 

908 if not callable(obj): 

909 try: 

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

911 except: 

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

913 else: 

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

915 if obj.__doc__: 

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

917 else: 

918 ds_or_None = getdoc(obj) 

919 if ds_or_None is None: 

920 ds = '<no docstring>' 

921 else: 

922 ds = ds_or_None 

923 

924 ds = prelude + ds 

925 

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

927 

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

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

930 

931 if ismagic: 

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

933 elif isalias: 

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

935 else: 

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

937 

938 try: 

939 bclass = obj.__class__ 

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

941 except: 

942 pass 

943 

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

945 if detail_level >= self.str_detail_level: 

946 try: 

947 ostr = str(obj) 

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

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

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

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

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

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

954 ) 

955 out["string_form"] = ostr 

956 except: 

957 pass 

958 

959 if ospace: 

960 out['namespace'] = ospace 

961 

962 # Length (for strings and lists) 

963 try: 

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

965 except Exception: 

966 pass 

967 

968 # Filename where object was defined 

969 binary_file = False 

970 fname = find_file(obj) 

971 if fname is None: 

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

973 # if the file was binary 

974 binary_file = True 

975 else: 

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

977 binary_file = True 

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

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

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

981 

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

983 if detail_level: 

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

985 # source 

986 linecache.checkcache() 

987 try: 

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

989 src = getsource(obj, oname) 

990 if src is not None: 

991 src = src.rstrip() 

992 out['source'] = src 

993 

994 except Exception: 

995 pass 

996 

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

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

999 out['docstring'] = ds 

1000 

1001 # Constructor docstring for classes 

1002 if inspect.isclass(obj): 

1003 out['isclass'] = True 

1004 

1005 # get the init signature: 

1006 try: 

1007 init_def = self._getdef(obj, oname) 

1008 except AttributeError: 

1009 init_def = None 

1010 

1011 # get the __init__ docstring 

1012 try: 

1013 obj_init = obj.__init__ 

1014 except AttributeError: 

1015 init_ds = None 

1016 else: 

1017 if init_def is None: 

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

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

1020 try: 

1021 init_def = self._getdef(obj_init, oname) 

1022 except AttributeError: 

1023 pass 

1024 init_ds = getdoc(obj_init) 

1025 # Skip Python's auto-generated docstrings 

1026 if init_ds == _object_init_docstring: 

1027 init_ds = None 

1028 

1029 if init_def: 

1030 out['init_definition'] = init_def 

1031 

1032 if init_ds: 

1033 out['init_docstring'] = init_ds 

1034 

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

1036 if len(names) < 10: 

1037 all_names = ', '.join(names) 

1038 else: 

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

1040 out['subclasses'] = all_names 

1041 # and class docstring for instances: 

1042 else: 

1043 # reconstruct the function definition and print it: 

1044 defln = self._getdef(obj, oname) 

1045 if defln: 

1046 out['definition'] = defln 

1047 

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

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

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

1051 # objects which use instance-customized docstrings. 

1052 if ds: 

1053 try: 

1054 cls = getattr(obj,'__class__') 

1055 except: 

1056 class_ds = None 

1057 else: 

1058 class_ds = getdoc(cls) 

1059 # Skip Python's auto-generated docstrings 

1060 if class_ds in _builtin_type_docstrings: 

1061 class_ds = None 

1062 if class_ds and ds != class_ds: 

1063 out['class_docstring'] = class_ds 

1064 

1065 # Next, try to show constructor docstrings 

1066 try: 

1067 init_ds = getdoc(obj.__init__) 

1068 # Skip Python's auto-generated docstrings 

1069 if init_ds == _object_init_docstring: 

1070 init_ds = None 

1071 except AttributeError: 

1072 init_ds = None 

1073 if init_ds: 

1074 out['init_docstring'] = init_ds 

1075 

1076 # Call form docstring for callable instances 

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

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

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

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

1081 # but don't include the same signature twice 

1082 out['call_def'] = call_def 

1083 call_ds = getdoc(obj.__call__) 

1084 # Skip Python's auto-generated docstrings 

1085 if call_ds == _func_call_docstring: 

1086 call_ds = None 

1087 if call_ds: 

1088 out['call_docstring'] = call_ds 

1089 

1090 return out 

1091 

1092 @staticmethod 

1093 def _source_contains_docstring(src, doc): 

1094 """ 

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

1096 

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

1098 source already contains it, avoiding repetition of information. 

1099 """ 

1100 try: 

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

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

1103 except Exception: 

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

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

1106 # arbitrary ways. 

1107 return False 

1108 

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

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

1111 """Search namespaces with wildcards for objects. 

1112 

1113 Arguments: 

1114 

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

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

1117 objects of that type. 

1118 

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

1120 

1121 Optional arguments: 

1122 

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

1124 

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

1126 

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

1128 underscores. 

1129 

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

1131 """ 

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

1133 

1134 # defaults 

1135 type_pattern = 'all' 

1136 filter = '' 

1137 

1138 # list all object types 

1139 if list_types: 

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

1141 return 

1142 

1143 cmds = pattern.split() 

1144 len_cmds = len(cmds) 

1145 if len_cmds == 1: 

1146 # Only filter pattern given 

1147 filter = cmds[0] 

1148 elif len_cmds == 2: 

1149 # Both filter and type specified 

1150 filter,type_pattern = cmds 

1151 else: 

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

1153 pattern) 

1154 

1155 # filter search namespaces 

1156 for name in ns_search: 

1157 if name not in ns_table: 

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

1159 (name,ns_table.keys())) 

1160 

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

1162 search_result, namespaces_seen = set(), set() 

1163 for ns_name in ns_search: 

1164 ns = ns_table[ns_name] 

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

1166 if id(ns) in namespaces_seen: 

1167 continue 

1168 namespaces_seen.add(id(ns)) 

1169 tmp_res = list_namespace(ns, type_pattern, filter, 

1170 ignore_case=ignore_case, show_all=show_all) 

1171 search_result.update(tmp_res) 

1172 

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

1174 

1175 

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

1177 """ 

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

1179 Look there for the comments. 

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

1181 """ 

1182 result = [] 

1183 pos_only = False 

1184 kw_only = True 

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

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

1187 pos_only = True 

1188 elif pos_only: 

1189 result.append('/') 

1190 pos_only = False 

1191 

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

1193 kw_only = False 

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

1195 result.append('*') 

1196 kw_only = False 

1197 

1198 result.append(str(param)) 

1199 

1200 if pos_only: 

1201 result.append('/') 

1202 

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

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

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

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

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

1208 ) 

1209 else: 

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

1211 

1212 if obj_signature.return_annotation is not inspect._empty: 

1213 anno = inspect.formatannotation(obj_signature.return_annotation) 

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

1215 

1216 return rendered