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

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 html 

21import inspect 

22import io as stdlib_io 

23import linecache 

24import os 

25import types 

26import warnings 

27from pygments.token import Token 

28 

29 

30from typing import ( 

31 cast, 

32 Any, 

33 TypedDict, 

34 TypeAlias, 

35) 

36 

37import traitlets 

38from traitlets.config import Configurable 

39 

40# IPython's own 

41from IPython.core import page 

42from IPython.lib.pretty import pretty 

43from IPython.testing.skipdoctest import skip_doctest 

44from IPython.utils import PyColorize, openpy 

45from IPython.utils.dir2 import safe_hasattr 

46from IPython.utils.path import compress_user 

47from IPython.utils.text import indent 

48from IPython.utils.wildcard import list_namespace, typestr2type 

49from IPython.utils.decorators import undoc 

50 

51from pygments import highlight 

52from pygments.lexers import PythonLexer 

53from pygments.formatters import HtmlFormatter 

54 

55HOOK_NAME = "__custom_documentations__" 

56 

57 

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

59Bundle: TypeAlias = dict[str, str] 

60 

61 

62@dataclass 

63class OInfo: 

64 ismagic: bool 

65 isalias: bool 

66 found: bool 

67 namespace: str | None 

68 parent: Any 

69 obj: Any 

70 

71def pylight(code): 

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

73 

74# builtin docstrings to ignore 

75_func_call_docstring = types.FunctionType.__call__.__doc__ 

76_object_init_docstring = object.__init__.__doc__ 

77_builtin_type_docstrings = { 

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

79 types.FunctionType, property) 

80} 

81 

82_builtin_func_type = type(all) 

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

84#**************************************************************************** 

85# Builtin color schemes 

86 

87 

88#**************************************************************************** 

89# Auxiliary functions and objects 

90 

91 

92class InfoDict(TypedDict): 

93 type_name: str | None 

94 base_class: str | None 

95 string_form: str | None 

96 namespace: str | None 

97 length: str | None 

98 file: str | None 

99 definition: str | None 

100 docstring: str | None 

101 source: str | None 

102 init_definition: str | None 

103 class_docstring: str | None 

104 init_docstring: str | None 

105 call_def: str | None 

106 call_docstring: str | None 

107 subclasses: str | None 

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

109 # format the object 

110 ismagic: bool 

111 isalias: bool 

112 isclass: bool 

113 found: bool 

114 name: str 

115 

116 

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

118 

119 

120def __getattr__(name): 

121 if name == "info_fields": 

122 warnings.warn( 

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

124 DeprecationWarning, 

125 stacklevel=2, 

126 ) 

127 return _info_fields 

128 

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

130 

131 

132@dataclass 

133class InspectorHookData: 

134 """Data passed to the mime hook""" 

135 

136 obj: Any 

137 info: OInfo | None 

138 info_dict: InfoDict 

139 detail_level: int 

140 omit_sections: list[str] 

141 

142 

143@undoc 

144def object_info( 

145 *, 

146 name: str, 

147 found: bool, 

148 isclass: bool = False, 

149 isalias: bool = False, 

150 ismagic: bool = False, 

151 **kw, 

152) -> InfoDict: 

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

154 infodict = dict(kw) 

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

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

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

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

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

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

161 

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

163 

164 

165def get_encoding(obj): 

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

167 

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

169 """ 

170 ofile = find_file(obj) 

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

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

173 # filesystem. 

174 if ofile is None: 

175 return None 

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

177 return None 

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

179 return None 

180 else: 

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

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

183 # 0-offset, so we must adjust. 

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

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

186 return encoding 

187 

188 

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

190 """Stable wrapper around inspect.getdoc. 

191 

192 This can't crash because of attribute problems. 

193 

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

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

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

197 """ 

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

199 try: 

200 ds = obj.getdoc() 

201 except Exception: 

202 pass 

203 else: 

204 if isinstance(ds, str): 

205 return inspect.cleandoc(ds) 

206 docstr = inspect.getdoc(obj) 

207 return docstr 

208 

209 

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

211 """Wrapper around inspect.getsource. 

212 

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

214 extraction. 

215 

216 Parameters 

217 ---------- 

218 obj : object 

219 an object whose source code we will attempt to extract 

220 oname : str 

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

222 

223 Returns 

224 ------- 

225 src : unicode or None 

226 

227 """ 

228 

229 if isinstance(obj, property): 

230 sources = [] 

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

232 fn = getattr(obj, attrname) 

233 if fn is not None: 

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

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

236 if inspect.isfunction(fn): 

237 _src = getsource(fn) 

238 if _src: 

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

240 sources.append(dedent(_src)) 

241 else: 

242 # Default str/repr only prints function name, 

243 # pretty.pretty prints module name too. 

244 sources.append( 

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

246 ) 

247 if sources: 

248 return '\n'.join(sources) 

249 else: 

250 return None 

251 

252 else: 

253 # Get source for non-property objects. 

254 

255 obj = _get_wrapped(obj) 

256 

257 try: 

258 src = inspect.getsource(obj) 

259 except TypeError: 

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

261 # its class definition instead. 

262 try: 

263 src = inspect.getsource(obj.__class__) 

264 except (OSError, TypeError): 

265 return None 

266 except OSError: 

267 return None 

268 

269 return src 

270 

271 

272def is_simple_callable(obj): 

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

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

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

276 

277def _get_wrapped(obj): 

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

279 

280 Some objects automatically construct similar objects on any unrecognised 

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

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

283 attribute access. --TK, Jan 2016 

284 """ 

285 orig_obj = obj 

286 i = 0 

287 while safe_hasattr(obj, '__wrapped__'): 

288 obj = obj.__wrapped__ 

289 i += 1 

290 if i > 100: 

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

292 return orig_obj 

293 return obj 

294 

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

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

297 

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

299 

300 Returns None if no file can be found. 

301 

302 Parameters 

303 ---------- 

304 obj : any Python object 

305 

306 Returns 

307 ------- 

308 fname : str 

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

310 """ 

311 obj = _get_wrapped(obj) 

312 

313 fname: str | None = None 

314 try: 

315 fname = inspect.getabsfile(obj) 

316 except TypeError: 

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

318 # declared. 

319 try: 

320 fname = inspect.getabsfile(obj.__class__) 

321 except (OSError, TypeError): 

322 # Can happen for builtins 

323 pass 

324 except OSError: 

325 pass 

326 

327 return fname 

328 

329 

330def find_source_lines(obj): 

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

332 

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

334 

335 Returns None if no file can be found. 

336 

337 Parameters 

338 ---------- 

339 obj : any Python object 

340 

341 Returns 

342 ------- 

343 lineno : int 

344 The line number where the object definition starts. 

345 """ 

346 obj = _get_wrapped(obj) 

347 

348 try: 

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

350 except TypeError: 

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

352 try: 

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

354 except (OSError, TypeError): 

355 return None 

356 except OSError: 

357 return None 

358 

359 return lineno 

360 

361 

362_sentinel = object() 

363 

364 

365class Inspector(Configurable): 

366 mime_hooks = traitlets.Dict( 

367 config=True, 

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

369 ).tag(config=True) 

370 

371 _theme_name: str 

372 

373 def __init__( 

374 self, 

375 *, 

376 theme_name: str, 

377 str_detail_level=0, 

378 parent=None, 

379 config=None, 

380 ): 

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

382 warnings.warn( 

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

384 DeprecationWarning, 

385 stacklevel=2, 

386 ) 

387 theme_name = theme_name.lower() 

388 self._theme_name = theme_name 

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

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

391 self.str_detail_level = str_detail_level 

392 self.set_theme_name(theme_name) 

393 

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

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

396 

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

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

399 

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

401 exception is suppressed.""" 

402 if not callable(obj): 

403 return None 

404 try: 

405 return _render_signature(signature(obj), oname) 

406 except Exception: 

407 return None 

408 

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

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

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

412 

413 def set_theme_name(self, name: str): 

414 assert name == name.lower() 

415 assert name in PyColorize.theme_table.keys() 

416 self._theme_name = name 

417 self.parser.theme_name = name 

418 

419 def set_active_scheme(self, scheme: str): 

420 warnings.warn( 

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

422 DeprecationWarning, 

423 stacklevel=2, 

424 ) 

425 assert scheme == scheme.lower() 

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

427 self._theme_name = scheme 

428 self.parser.theme_name = scheme 

429 

430 def noinfo(self, msg, oname): 

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

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

433 if oname: 

434 print('for %s' % oname) 

435 else: 

436 print() 

437 

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

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

440 

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

442 

443 if not callable(obj): 

444 print('Object is not callable.') 

445 return 

446 

447 header = '' 

448 

449 if inspect.isclass(obj): 

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

451 

452 

453 output = self._getdef(obj,oname) 

454 if output is None: 

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

456 else: 

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

458 

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

460 @skip_doctest 

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

462 """Print the docstring for any object. 

463 

464 Optional: 

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

466 formatted docstrings. 

467 

468 Examples 

469 -------- 

470 In [1]: class NoInit: 

471 ...: pass 

472 

473 In [2]: class NoDoc: 

474 ...: def __init__(self): 

475 ...: pass 

476 

477 In [3]: %pdoc NoDoc 

478 No documentation found for NoDoc 

479 

480 In [4]: %pdoc NoInit 

481 No documentation found for NoInit 

482 

483 In [5]: obj = NoInit() 

484 

485 In [6]: %pdoc obj 

486 No documentation found for obj 

487 

488 In [5]: obj2 = NoDoc() 

489 

490 In [6]: %pdoc obj2 

491 No documentation found for obj2 

492 """ 

493 

494 lines = [] 

495 ds = getdoc(obj) 

496 if formatter: 

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

498 if ds: 

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

500 lines.append(indent(ds)) 

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

502 init_ds = getdoc(obj.__init__) 

503 if init_ds is not None: 

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

505 lines.append(indent(init_ds)) 

506 elif hasattr(obj,'__call__'): 

507 call_ds = getdoc(obj.__call__) 

508 if call_ds: 

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

510 lines.append(indent(call_ds)) 

511 

512 if not lines: 

513 self.noinfo('documentation',oname) 

514 else: 

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

516 

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

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

519 

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

521 linecache.checkcache() 

522 try: 

523 src = getsource(obj, oname=oname) 

524 except Exception: 

525 src = None 

526 

527 if src is None: 

528 self.noinfo('source', oname) 

529 else: 

530 page.page(self.format(src)) 

531 

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

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

534 

535 lineno = find_source_lines(obj) 

536 if lineno is None: 

537 self.noinfo('file', oname) 

538 return 

539 

540 ofile = find_file(obj) 

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

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

543 # filesystem. 

544 if ofile is None: 

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

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

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

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

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

550 else: 

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

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

553 # 0-offset, so we must adjust. 

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

555 

556 

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

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

559 

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

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

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

563 

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

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

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

567 

568 Note: 

569 

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

571 

572 """ 

573 defaults = { 

574 "text/plain": text, 

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

576 } 

577 

578 if formatter is None: 

579 return defaults 

580 else: 

581 formatted = formatter(text) 

582 

583 if not isinstance(formatted, dict): 

584 # Handle the deprecated behavior of a formatter returning 

585 # a string instead of a mime bundle. 

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

587 

588 else: 

589 return dict(defaults, **formatted) 

590 

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

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

593 # Format text/plain mimetype 

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

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

596 assert isinstance(item, tuple) 

597 

598 new_b: Bundle = {} 

599 lines = [] 

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

601 

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

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

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

605 lines.append( 

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

607 ) 

608 

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

610 

611 if "text/html" in bundle: 

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

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

614 assert isinstance(item, tuple) 

615 # Format the text/html mimetype 

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

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

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

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

620 ) 

621 

622 for k in bundle.keys(): 

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

624 continue 

625 else: 

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

627 return new_b 

628 

629 def _append_info_field( 

630 self, 

631 bundle: UnformattedBundle, 

632 title: str, 

633 key: str, 

634 info, 

635 omit_sections: list[str], 

636 formatter, 

637 ): 

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

639 if title in omit_sections or key in omit_sections: 

640 return 

641 field = info[key] 

642 if field is not None: 

643 formatted_field = self._mime_format(field, formatter) 

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

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

646 

647 def _make_info_unformatted( 

648 self, obj, info, formatter, detail_level, omit_sections 

649 ) -> UnformattedBundle: 

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

651 bundle: UnformattedBundle = { 

652 "text/plain": [], 

653 "text/html": [], 

654 } 

655 

656 # A convenience function to simplify calls below 

657 def append_field( 

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

659 ): 

660 self._append_info_field( 

661 bundle, 

662 title=title, 

663 key=key, 

664 info=info, 

665 omit_sections=omit_sections, 

666 formatter=formatter, 

667 ) 

668 

669 def code_formatter(text) -> Bundle: 

670 return { 

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

672 'text/html': pylight(text) 

673 } 

674 

675 if info["isalias"]: 

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

677 

678 elif info['ismagic']: 

679 if detail_level > 0: 

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

681 else: 

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

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

684 

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

686 # Functions, methods, classes 

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

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

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

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

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

692 else: 

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

694 

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

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

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

698 

699 else: 

700 # General Python objects 

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

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

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

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

705 

706 # Namespace 

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

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

709 

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

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

712 

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

714 # source found. 

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

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

717 else: 

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

719 

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

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

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

723 return bundle 

724 

725 

726 def _get_info( 

727 self, 

728 obj: Any, 

729 oname: str = "", 

730 formatter=None, 

731 info: OInfo | None = None, 

732 detail_level: int = 0, 

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

734 ) -> Bundle: 

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

736 

737 Parameters 

738 ---------- 

739 obj : any 

740 Object to inspect and return info from 

741 oname : str (default: ''): 

742 Name of the variable pointing to `obj`. 

743 formatter : callable 

744 info 

745 already computed information 

746 detail_level : integer 

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

748 omit_sections : list[str] 

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

750 """ 

751 

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

753 omit_sections = list(omit_sections) 

754 

755 bundle = self._make_info_unformatted( 

756 obj, 

757 info_dict, 

758 formatter, 

759 detail_level=detail_level, 

760 omit_sections=omit_sections, 

761 ) 

762 if self.mime_hooks: 

763 hook_data = InspectorHookData( 

764 obj=obj, 

765 info=info, 

766 info_dict=info_dict, 

767 detail_level=detail_level, 

768 omit_sections=omit_sections, 

769 ) 

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

771 required_parameters = [ 

772 parameter 

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

774 if parameter.default is inspect.Parameter.empty 

775 ] 

776 if len(required_parameters) == 1: 

777 res = hook(hook_data) 

778 else: 

779 warnings.warn( 

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

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

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

783 DeprecationWarning, 

784 stacklevel=2, 

785 ) 

786 res = hook(obj, info) 

787 if res is not None: 

788 bundle[key] = res 

789 return self.format_mime(bundle) 

790 

791 def pinfo( 

792 self, 

793 obj, 

794 oname="", 

795 formatter=None, 

796 info: OInfo | None = None, 

797 detail_level=0, 

798 enable_html_pager=True, 

799 omit_sections=(), 

800 ): 

801 """Show detailed information about an object. 

802 

803 Optional arguments: 

804 

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

806 

807 - formatter: callable (optional) 

808 A special formatter for docstrings. 

809 

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

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

812 in the form of a dictionary. 

813 

814 Although the support of custom formatter returning a string 

815 instead of a mime type bundle is deprecated. 

816 

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

818 precomputed already. 

819 

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

821 

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

823 """ 

824 assert info is not None 

825 info_b: Bundle = self._get_info( 

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

827 ) 

828 if not enable_html_pager: 

829 del info_b["text/html"] 

830 page.page(info_b) 

831 

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

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

834 

835 Parameters 

836 ---------- 

837 obj : any 

838 An object to find information about 

839 oname : str (default: '') 

840 Name of the variable pointing to `obj`. 

841 info : (default: None) 

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

843 which may have been precomputed already. 

844 detail_level : int (default:0) 

845 If set to 1, more information is given. 

846 

847 Returns 

848 ------- 

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

850 """ 

851 

852 if info is None: 

853 ismagic = False 

854 isalias = False 

855 ospace = '' 

856 else: 

857 ismagic = info.ismagic 

858 isalias = info.isalias 

859 ospace = info.namespace 

860 

861 # Get docstring, special-casing aliases: 

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

863 parents_docs = None 

864 prelude = "" 

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

866 parents_docs_dict = getattr(info.parent, HOOK_NAME) 

867 if isinstance(parents_docs_dict, dict): 

868 parents_docs = parents_docs_dict.get(att_name, None) 

869 out: InfoDict = cast( 

870 InfoDict, 

871 { 

872 **dict.fromkeys(_info_fields), 

873 **{ 

874 "name": oname, 

875 "found": True, 

876 "isalias": isalias, 

877 "ismagic": ismagic, 

878 "subclasses": None, 

879 }, 

880 }, 

881 ) 

882 

883 if parents_docs: 

884 ds = parents_docs 

885 elif isalias: 

886 if not callable(obj): 

887 try: 

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

889 except (TypeError, IndexError): 

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

891 else: 

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

893 if obj.__doc__: 

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

895 else: 

896 ds_or_None = getdoc(obj) 

897 if ds_or_None is None: 

898 ds = '<no docstring>' 

899 else: 

900 ds = ds_or_None 

901 

902 ds = prelude + ds 

903 

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

905 

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

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

908 

909 if ismagic: 

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

911 elif isalias: 

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

913 else: 

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

915 

916 try: 

917 bclass = obj.__class__ 

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

919 except AttributeError: 

920 pass 

921 

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

923 if detail_level >= self.str_detail_level: 

924 try: 

925 ostr = str(obj) 

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

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

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

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

930 ) 

931 out["string_form"] = ostr 

932 except Exception: 

933 pass 

934 

935 if ospace: 

936 out['namespace'] = ospace 

937 

938 # Length (for strings and lists) 

939 try: 

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

941 except Exception: 

942 pass 

943 

944 # Filename where object was defined 

945 binary_file = False 

946 fname = find_file(obj) 

947 if fname is None: 

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

949 # if the file was binary 

950 binary_file = True 

951 else: 

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

953 binary_file = True 

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

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

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

957 

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

959 if detail_level: 

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

961 # source 

962 linecache.checkcache() 

963 try: 

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

965 src = getsource(obj, oname) 

966 if src is not None: 

967 src = src.rstrip() 

968 out['source'] = src 

969 

970 except Exception: 

971 pass 

972 

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

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

975 out['docstring'] = ds 

976 

977 # Constructor docstring for classes 

978 if inspect.isclass(obj): 

979 out['isclass'] = True 

980 

981 # get the init signature: 

982 try: 

983 init_def = self._getdef(obj, oname) 

984 except AttributeError: 

985 init_def = None 

986 

987 # get the __init__ docstring 

988 try: 

989 obj_init = obj.__init__ 

990 except AttributeError: 

991 init_ds = None 

992 else: 

993 if init_def is None: 

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

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

996 try: 

997 init_def = self._getdef(obj_init, oname) 

998 except AttributeError: 

999 pass 

1000 init_ds = getdoc(obj_init) 

1001 # Skip Python's auto-generated docstrings 

1002 if init_ds == _object_init_docstring: 

1003 init_ds = None 

1004 

1005 if init_def: 

1006 out['init_definition'] = init_def 

1007 

1008 if init_ds: 

1009 out['init_docstring'] = init_ds 

1010 

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

1012 if len(names) < 10: 

1013 all_names = ', '.join(names) 

1014 else: 

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

1016 out['subclasses'] = all_names 

1017 # and class docstring for instances: 

1018 else: 

1019 # reconstruct the function definition and print it: 

1020 defln = self._getdef(obj, oname) 

1021 if defln: 

1022 out['definition'] = defln 

1023 

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

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

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

1027 # objects which use instance-customized docstrings. 

1028 if ds: 

1029 try: 

1030 cls = getattr(obj,'__class__') 

1031 except AttributeError: 

1032 class_ds = None 

1033 else: 

1034 class_ds = getdoc(cls) 

1035 # Skip Python's auto-generated docstrings 

1036 if class_ds in _builtin_type_docstrings: 

1037 class_ds = None 

1038 if class_ds and ds != class_ds: 

1039 out['class_docstring'] = class_ds 

1040 

1041 # Next, try to show constructor docstrings 

1042 try: 

1043 init_ds = getdoc(obj.__init__) 

1044 # Skip Python's auto-generated docstrings 

1045 if init_ds == _object_init_docstring: 

1046 init_ds = None 

1047 except AttributeError: 

1048 init_ds = None 

1049 if init_ds: 

1050 out['init_docstring'] = init_ds 

1051 

1052 # Call form docstring for callable instances 

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

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

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

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

1057 # but don't include the same signature twice 

1058 out['call_def'] = call_def 

1059 call_ds = getdoc(obj.__call__) 

1060 # Skip Python's auto-generated docstrings 

1061 if call_ds == _func_call_docstring: 

1062 call_ds = None 

1063 if call_ds: 

1064 out['call_docstring'] = call_ds 

1065 

1066 return out 

1067 

1068 @staticmethod 

1069 def _source_contains_docstring(src, doc): 

1070 """ 

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

1072 

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

1074 source already contains it, avoiding repetition of information. 

1075 """ 

1076 try: 

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

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

1079 except Exception: 

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

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

1082 # arbitrary ways. 

1083 return False 

1084 

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

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

1087 """Search namespaces with wildcards for objects. 

1088 

1089 Arguments: 

1090 

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

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

1093 objects of that type. 

1094 

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

1096 

1097 Optional arguments: 

1098 

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

1100 

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

1102 

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

1104 underscores. 

1105 

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

1107 """ 

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

1109 

1110 # defaults 

1111 type_pattern = 'all' 

1112 filter = '' 

1113 

1114 # list all object types 

1115 if list_types: 

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

1117 return 

1118 

1119 cmds = pattern.split() 

1120 len_cmds = len(cmds) 

1121 if len_cmds == 1: 

1122 # Only filter pattern given 

1123 filter = cmds[0] 

1124 elif len_cmds == 2: 

1125 # Both filter and type specified 

1126 filter,type_pattern = cmds 

1127 else: 

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

1129 pattern) 

1130 

1131 # filter search namespaces 

1132 for name in ns_search: 

1133 if name not in ns_table: 

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

1135 (name,ns_table.keys())) 

1136 

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

1138 search_result, namespaces_seen = set(), set() 

1139 for ns_name in ns_search: 

1140 ns = ns_table[ns_name] 

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

1142 if id(ns) in namespaces_seen: 

1143 continue 

1144 namespaces_seen.add(id(ns)) 

1145 tmp_res = list_namespace(ns, type_pattern, filter, 

1146 ignore_case=ignore_case, show_all=show_all) 

1147 search_result.update(tmp_res) 

1148 

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

1150 

1151 

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

1153 """ 

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

1155 Look there for the comments. 

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

1157 """ 

1158 result = [] 

1159 pos_only = False 

1160 kw_only = True 

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

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

1163 pos_only = True 

1164 elif pos_only: 

1165 result.append('/') 

1166 pos_only = False 

1167 

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

1169 kw_only = False 

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

1171 result.append('*') 

1172 kw_only = False 

1173 

1174 result.append(str(param)) 

1175 

1176 if pos_only: 

1177 result.append('/') 

1178 

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

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

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

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

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

1184 ) 

1185 else: 

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

1187 

1188 if obj_signature.return_annotation is not inspect._empty: 

1189 anno = inspect.formatannotation(obj_signature.return_annotation) 

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

1191 

1192 return rendered