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

536 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-04-10 06:20 +0000

1# -*- coding: utf-8 -*- 

2"""Tools for inspecting Python objects. 

3 

4Uses syntax highlighting for presenting the various information elements. 

5 

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

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

8""" 

9 

10# Copyright (c) IPython Development Team. 

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

12 

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

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 sys 

26import types 

27import warnings 

28 

29from typing import Any, Optional, Dict, Union, List, Tuple 

30 

31if sys.version_info <= (3, 10): 

32 from typing_extensions import TypeAlias 

33else: 

34 from typing import TypeAlias 

35 

36# IPython's own 

37from IPython.core import page 

38from IPython.lib.pretty import pretty 

39from IPython.testing.skipdoctest import skip_doctest 

40from IPython.utils import PyColorize 

41from IPython.utils import openpy 

42from IPython.utils.dir2 import safe_hasattr 

43from IPython.utils.path import compress_user 

44from IPython.utils.text import indent 

45from IPython.utils.wildcard import list_namespace 

46from IPython.utils.wildcard import typestr2type 

47from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable 

48from IPython.utils.py3compat import cast_unicode 

49from IPython.utils.colorable import Colorable 

50from IPython.utils.decorators import undoc 

51 

52from pygments import highlight 

53from pygments.lexers import PythonLexer 

54from pygments.formatters import HtmlFormatter 

55 

56HOOK_NAME = "__custom_documentations__" 

57 

58 

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

60Bundle: TypeAlias = Dict[str, str] 

61 

62 

63@dataclass 

64class OInfo: 

65 ismagic: bool 

66 isalias: bool 

67 found: bool 

68 namespace: Optional[str] 

69 parent: Any 

70 obj: Any 

71 

72def pylight(code): 

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

74 

75# builtin docstrings to ignore 

76_func_call_docstring = types.FunctionType.__call__.__doc__ 

77_object_init_docstring = object.__init__.__doc__ 

78_builtin_type_docstrings = { 

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

80 types.FunctionType, property) 

81} 

82 

83_builtin_func_type = type(all) 

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

85#**************************************************************************** 

86# Builtin color schemes 

87 

88Colors = TermColors # just a shorthand 

89 

90InspectColors = PyColorize.ANSICodeColors 

91 

92#**************************************************************************** 

93# Auxiliary functions and objects 

94 

95# See the messaging spec for the definition of all these fields. This list 

96# effectively defines the order of display 

97info_fields = ['type_name', 'base_class', 'string_form', 'namespace', 

98 'length', 'file', 'definition', 'docstring', 'source', 

99 'init_definition', 'class_docstring', 'init_docstring', 

100 'call_def', 'call_docstring', 

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

102 # format the object 

103 'ismagic', 'isalias', 'isclass', 'found', 'name' 

104 ] 

105 

106 

107def object_info(**kw): 

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

109 infodict = {k:None for k in info_fields} 

110 infodict.update(kw) 

111 return infodict 

112 

113 

114def get_encoding(obj): 

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

116 

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

118 """ 

119 ofile = find_file(obj) 

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

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

122 # filesystem. 

123 if ofile is None: 

124 return None 

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

126 return None 

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

128 return None 

129 else: 

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

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

132 # 0-offset, so we must adjust. 

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

134 encoding, lines = openpy.detect_encoding(buffer.readline) 

135 return encoding 

136 

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

138 """Stable wrapper around inspect.getdoc. 

139 

140 This can't crash because of attribute problems. 

141 

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

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

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

145 """ 

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

147 try: 

148 ds = obj.getdoc() 

149 except Exception: 

150 pass 

151 else: 

152 if isinstance(ds, str): 

153 return inspect.cleandoc(ds) 

154 docstr = inspect.getdoc(obj) 

155 return docstr 

156 

157 

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

159 """Wrapper around inspect.getsource. 

160 

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

162 extraction. 

163 

164 Parameters 

165 ---------- 

166 obj : object 

167 an object whose source code we will attempt to extract 

168 oname : str 

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

170 

171 Returns 

172 ------- 

173 src : unicode or None 

174 

175 """ 

176 

177 if isinstance(obj, property): 

178 sources = [] 

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

180 fn = getattr(obj, attrname) 

181 if fn is not None: 

182 encoding = get_encoding(fn) 

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

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

185 if inspect.isfunction(fn): 

186 _src = getsource(fn) 

187 if _src: 

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

189 sources.append(dedent(_src)) 

190 else: 

191 # Default str/repr only prints function name, 

192 # pretty.pretty prints module name too. 

193 sources.append( 

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

195 ) 

196 if sources: 

197 return '\n'.join(sources) 

198 else: 

199 return None 

200 

201 else: 

202 # Get source for non-property objects. 

203 

204 obj = _get_wrapped(obj) 

205 

206 try: 

207 src = inspect.getsource(obj) 

208 except TypeError: 

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

210 # its class definition instead. 

211 try: 

212 src = inspect.getsource(obj.__class__) 

213 except (OSError, TypeError): 

214 return None 

215 except OSError: 

216 return None 

217 

218 return src 

219 

220 

221def is_simple_callable(obj): 

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

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

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

225 

226@undoc 

227def getargspec(obj): 

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

229 

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

231 ``__call__`` attribute. 

232 

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

234 """ 

235 

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

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

238 

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

240 obj = obj.__call__ 

241 

242 return inspect.getfullargspec(obj) 

243 

244@undoc 

245def format_argspec(argspec): 

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

247 

248 This takes a dict instead of ordered arguments and calls 

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

250 

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

252 """ 

253 

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

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

256 

257 

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

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

260 

261@undoc 

262def call_tip(oinfo, format_call=True): 

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

264 warnings.warn( 

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

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

267 DeprecationWarning, 

268 stacklevel=2, 

269 ) 

270 # Get call definition 

271 argspec = oinfo.get('argspec') 

272 if argspec is None: 

273 call_line = None 

274 else: 

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

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

277 # extra first argument explicitly). 

278 try: 

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

280 except (KeyError, IndexError): 

281 pass 

282 else: 

283 if has_self: 

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

285 

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

287 

288 # Now get docstring. 

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

290 doc = oinfo.get('call_docstring') 

291 if doc is None: 

292 doc = oinfo.get('init_docstring') 

293 if doc is None: 

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

295 

296 return call_line, doc 

297 

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) -> 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 = 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 cast_unicode(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 

383class Inspector(Colorable): 

384 

385 def __init__(self, color_table=InspectColors, 

386 code_color_table=PyColorize.ANSICodeColors, 

387 scheme=None, 

388 str_detail_level=0, 

389 parent=None, config=None): 

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

391 self.color_table = color_table 

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

393 self.format = self.parser.format 

394 self.str_detail_level = str_detail_level 

395 self.set_active_scheme(scheme) 

396 

397 def _getdef(self,obj,oname='') -> Union[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 try: 

403 return _render_signature(signature(obj), oname) 

404 except: 

405 return None 

406 

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

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

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

410 self.color_table.active_colors.normal) 

411 

412 def set_active_scheme(self, scheme): 

413 if scheme is not None: 

414 self.color_table.set_active_scheme(scheme) 

415 self.parser.color_table.set_active_scheme(scheme) 

416 

417 def noinfo(self, msg, oname): 

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

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

420 if oname: 

421 print('for %s' % oname) 

422 else: 

423 print() 

424 

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

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

427 

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

429 

430 if not callable(obj): 

431 print('Object is not callable.') 

432 return 

433 

434 header = '' 

435 

436 if inspect.isclass(obj): 

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

438 

439 

440 output = self._getdef(obj,oname) 

441 if output is None: 

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

443 else: 

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

445 

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

447 @skip_doctest 

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

449 """Print the docstring for any object. 

450 

451 Optional: 

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

453 formatted docstrings. 

454 

455 Examples 

456 -------- 

457 In [1]: class NoInit: 

458 ...: pass 

459 

460 In [2]: class NoDoc: 

461 ...: def __init__(self): 

462 ...: pass 

463 

464 In [3]: %pdoc NoDoc 

465 No documentation found for NoDoc 

466 

467 In [4]: %pdoc NoInit 

468 No documentation found for NoInit 

469 

470 In [5]: obj = NoInit() 

471 

472 In [6]: %pdoc obj 

473 No documentation found for obj 

474 

475 In [5]: obj2 = NoDoc() 

476 

477 In [6]: %pdoc obj2 

478 No documentation found for obj2 

479 """ 

480 

481 head = self.__head # For convenience 

482 lines = [] 

483 ds = getdoc(obj) 

484 if formatter: 

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

486 if ds: 

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

488 lines.append(indent(ds)) 

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

490 init_ds = getdoc(obj.__init__) 

491 if init_ds is not None: 

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

493 lines.append(indent(init_ds)) 

494 elif hasattr(obj,'__call__'): 

495 call_ds = getdoc(obj.__call__) 

496 if call_ds: 

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

498 lines.append(indent(call_ds)) 

499 

500 if not lines: 

501 self.noinfo('documentation',oname) 

502 else: 

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

504 

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

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

507 

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

509 linecache.checkcache() 

510 try: 

511 src = getsource(obj, oname=oname) 

512 except Exception: 

513 src = None 

514 

515 if src is None: 

516 self.noinfo('source', oname) 

517 else: 

518 page.page(self.format(src)) 

519 

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

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

522 

523 lineno = find_source_lines(obj) 

524 if lineno is None: 

525 self.noinfo('file', oname) 

526 return 

527 

528 ofile = find_file(obj) 

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

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

531 # filesystem. 

532 if ofile.endswith(('.so', '.dll', '.pyd')): 

533 print('File %r is binary, not printing.' % ofile) 

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

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

536 else: 

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

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

539 # 0-offset, so we must adjust. 

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

541 

542 

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

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

545 

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

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

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

549 

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

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

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

553 

554 Note: 

555 

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

557 

558 """ 

559 defaults = { 

560 "text/plain": text, 

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

562 } 

563 

564 if formatter is None: 

565 return defaults 

566 else: 

567 formatted = formatter(text) 

568 

569 if not isinstance(formatted, dict): 

570 # Handle the deprecated behavior of a formatter returning 

571 # a string instead of a mime bundle. 

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

573 

574 else: 

575 return dict(defaults, **formatted) 

576 

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

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

579 # Format text/plain mimetype 

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

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

582 assert isinstance(item, tuple) 

583 

584 new_b: Bundle = {} 

585 lines = [] 

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

587 

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

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

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

591 lines.append( 

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

593 ) 

594 

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

596 

597 if "text/html" in bundle: 

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

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

600 assert isinstance(item, tuple) 

601 # Format the text/html mimetype 

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

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

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

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

606 ) 

607 

608 for k in bundle.keys(): 

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

610 continue 

611 else: 

612 new_b = bundle[k] # type:ignore 

613 return new_b 

614 

615 def _append_info_field( 

616 self, 

617 bundle: UnformattedBundle, 

618 title: str, 

619 key: str, 

620 info, 

621 omit_sections, 

622 formatter, 

623 ): 

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

625 if title in omit_sections or key in omit_sections: 

626 return 

627 field = info[key] 

628 if field is not None: 

629 formatted_field = self._mime_format(field, formatter) 

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

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

632 

633 def _make_info_unformatted( 

634 self, obj, info, formatter, detail_level, omit_sections 

635 ) -> UnformattedBundle: 

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

637 bundle: UnformattedBundle = { 

638 "text/plain": [], 

639 "text/html": [], 

640 } 

641 

642 # A convenience function to simplify calls below 

643 def append_field( 

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

645 ): 

646 self._append_info_field( 

647 bundle, 

648 title=title, 

649 key=key, 

650 info=info, 

651 omit_sections=omit_sections, 

652 formatter=formatter, 

653 ) 

654 

655 def code_formatter(text) -> Bundle: 

656 return { 

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

658 'text/html': pylight(text) 

659 } 

660 

661 if info["isalias"]: 

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

663 

664 elif info['ismagic']: 

665 if detail_level > 0: 

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

667 else: 

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

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

670 

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

672 # Functions, methods, classes 

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

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

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

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

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

678 else: 

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

680 

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

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

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

684 

685 else: 

686 # General Python objects 

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

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

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

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

691 

692 # Namespace 

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

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

695 

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

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

698 

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

700 # source found. 

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

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

703 else: 

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

705 

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

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

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

709 return bundle 

710 

711 

712 def _get_info( 

713 self, 

714 obj: Any, 

715 oname: str = "", 

716 formatter=None, 

717 info: Optional[OInfo] = None, 

718 detail_level=0, 

719 omit_sections=(), 

720 ) -> Bundle: 

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

722 

723 Parameters 

724 ---------- 

725 obj : any 

726 Object to inspect and return info from 

727 oname : str (default: ''): 

728 Name of the variable pointing to `obj`. 

729 formatter : callable 

730 info 

731 already computed information 

732 detail_level : integer 

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

734 omit_sections : container[str] 

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

736 """ 

737 

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

739 bundle = self._make_info_unformatted( 

740 obj, 

741 info_dict, 

742 formatter, 

743 detail_level=detail_level, 

744 omit_sections=omit_sections, 

745 ) 

746 return self.format_mime(bundle) 

747 

748 def pinfo( 

749 self, 

750 obj, 

751 oname="", 

752 formatter=None, 

753 info: Optional[OInfo] = None, 

754 detail_level=0, 

755 enable_html_pager=True, 

756 omit_sections=(), 

757 ): 

758 """Show detailed information about an object. 

759 

760 Optional arguments: 

761 

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

763 

764 - formatter: callable (optional) 

765 A special formatter for docstrings. 

766 

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

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

769 in the form of a dictionary. 

770 

771 Although the support of custom formatter returning a string 

772 instead of a mime type bundle is deprecated. 

773 

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

775 precomputed already. 

776 

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

778 

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

780 """ 

781 assert info is not None 

782 info_b: Bundle = self._get_info( 

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

784 ) 

785 if not enable_html_pager: 

786 del info_b["text/html"] 

787 page.page(info_b) 

788 

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

790 """ 

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

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

793 """ 

794 

795 warnings.warn( 

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

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

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

799 DeprecationWarning, 

800 stacklevel=2, 

801 ) 

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

803 

804 def info(self, obj, oname="", info=None, detail_level=0) -> Dict[str, Any]: 

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

806 

807 Parameters 

808 ---------- 

809 obj : any 

810 An object to find information about 

811 oname : str (default: '') 

812 Name of the variable pointing to `obj`. 

813 info : (default: None) 

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

815 which may have been precomputed already. 

816 detail_level : int (default:0) 

817 If set to 1, more information is given. 

818 

819 Returns 

820 ------- 

821 An object info dict with known fields from `info_fields`. Keys are 

822 strings, values are string or None. 

823 """ 

824 

825 if info is None: 

826 ismagic = False 

827 isalias = False 

828 ospace = '' 

829 else: 

830 ismagic = info.ismagic 

831 isalias = info.isalias 

832 ospace = info.namespace 

833 

834 # Get docstring, special-casing aliases: 

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

836 parents_docs = None 

837 prelude = "" 

838 if info and info.parent and hasattr(info.parent, HOOK_NAME): 

839 parents_docs_dict = getattr(info.parent, HOOK_NAME) 

840 parents_docs = parents_docs_dict.get(att_name, None) 

841 out = dict( 

842 name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None 

843 ) 

844 

845 if parents_docs: 

846 ds = parents_docs 

847 elif isalias: 

848 if not callable(obj): 

849 try: 

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

851 except: 

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

853 else: 

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

855 if obj.__doc__: 

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

857 else: 

858 ds_or_None = getdoc(obj) 

859 if ds_or_None is None: 

860 ds = '<no docstring>' 

861 else: 

862 ds = ds_or_None 

863 

864 ds = prelude + ds 

865 

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

867 

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

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

870 

871 if ismagic: 

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

873 elif isalias: 

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

875 else: 

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

877 

878 try: 

879 bclass = obj.__class__ 

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

881 except: 

882 pass 

883 

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

885 if detail_level >= self.str_detail_level: 

886 try: 

887 ostr = str(obj) 

888 str_head = 'string_form' 

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

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

891 ostr = ("\n" + " " * len(str_head.expandtabs())).\ 

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

893 out[str_head] = ostr 

894 except: 

895 pass 

896 

897 if ospace: 

898 out['namespace'] = ospace 

899 

900 # Length (for strings and lists) 

901 try: 

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

903 except Exception: 

904 pass 

905 

906 # Filename where object was defined 

907 binary_file = False 

908 fname = find_file(obj) 

909 if fname is None: 

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

911 # if the file was binary 

912 binary_file = True 

913 else: 

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

915 binary_file = True 

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

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

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

919 

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

921 if detail_level: 

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

923 # source 

924 linecache.checkcache() 

925 try: 

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

927 src = getsource(obj, oname) 

928 if src is not None: 

929 src = src.rstrip() 

930 out['source'] = src 

931 

932 except Exception: 

933 pass 

934 

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

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

937 out['docstring'] = ds 

938 

939 # Constructor docstring for classes 

940 if inspect.isclass(obj): 

941 out['isclass'] = True 

942 

943 # get the init signature: 

944 try: 

945 init_def = self._getdef(obj, oname) 

946 except AttributeError: 

947 init_def = None 

948 

949 # get the __init__ docstring 

950 try: 

951 obj_init = obj.__init__ 

952 except AttributeError: 

953 init_ds = None 

954 else: 

955 if init_def is None: 

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

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

958 try: 

959 init_def = self._getdef(obj_init, oname) 

960 except AttributeError: 

961 pass 

962 init_ds = getdoc(obj_init) 

963 # Skip Python's auto-generated docstrings 

964 if init_ds == _object_init_docstring: 

965 init_ds = None 

966 

967 if init_def: 

968 out['init_definition'] = init_def 

969 

970 if init_ds: 

971 out['init_docstring'] = init_ds 

972 

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

974 if len(names) < 10: 

975 all_names = ', '.join(names) 

976 else: 

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

978 out['subclasses'] = all_names 

979 # and class docstring for instances: 

980 else: 

981 # reconstruct the function definition and print it: 

982 defln = self._getdef(obj, oname) 

983 if defln: 

984 out['definition'] = defln 

985 

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

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

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

989 # objects which use instance-customized docstrings. 

990 if ds: 

991 try: 

992 cls = getattr(obj,'__class__') 

993 except: 

994 class_ds = None 

995 else: 

996 class_ds = getdoc(cls) 

997 # Skip Python's auto-generated docstrings 

998 if class_ds in _builtin_type_docstrings: 

999 class_ds = None 

1000 if class_ds and ds != class_ds: 

1001 out['class_docstring'] = class_ds 

1002 

1003 # Next, try to show constructor docstrings 

1004 try: 

1005 init_ds = getdoc(obj.__init__) 

1006 # Skip Python's auto-generated docstrings 

1007 if init_ds == _object_init_docstring: 

1008 init_ds = None 

1009 except AttributeError: 

1010 init_ds = None 

1011 if init_ds: 

1012 out['init_docstring'] = init_ds 

1013 

1014 # Call form docstring for callable instances 

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

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

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

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

1019 # but don't include the same signature twice 

1020 out['call_def'] = call_def 

1021 call_ds = getdoc(obj.__call__) 

1022 # Skip Python's auto-generated docstrings 

1023 if call_ds == _func_call_docstring: 

1024 call_ds = None 

1025 if call_ds: 

1026 out['call_docstring'] = call_ds 

1027 

1028 return object_info(**out) 

1029 

1030 @staticmethod 

1031 def _source_contains_docstring(src, doc): 

1032 """ 

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

1034 

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

1036 source already contains it, avoiding repetition of information. 

1037 """ 

1038 try: 

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

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

1041 except Exception: 

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

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

1044 # arbitrary ways. 

1045 return False 

1046 

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

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

1049 """Search namespaces with wildcards for objects. 

1050 

1051 Arguments: 

1052 

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

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

1055 objects of that type. 

1056 

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

1058 

1059 Optional arguments: 

1060 

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

1062 

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

1064 

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

1066 underscores. 

1067 

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

1069 """ 

1070 #print 'ps pattern:<%r>' % pattern # dbg 

1071 

1072 # defaults 

1073 type_pattern = 'all' 

1074 filter = '' 

1075 

1076 # list all object types 

1077 if list_types: 

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

1079 return 

1080 

1081 cmds = pattern.split() 

1082 len_cmds = len(cmds) 

1083 if len_cmds == 1: 

1084 # Only filter pattern given 

1085 filter = cmds[0] 

1086 elif len_cmds == 2: 

1087 # Both filter and type specified 

1088 filter,type_pattern = cmds 

1089 else: 

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

1091 pattern) 

1092 

1093 # filter search namespaces 

1094 for name in ns_search: 

1095 if name not in ns_table: 

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

1097 (name,ns_table.keys())) 

1098 

1099 #print 'type_pattern:',type_pattern # dbg 

1100 search_result, namespaces_seen = set(), set() 

1101 for ns_name in ns_search: 

1102 ns = ns_table[ns_name] 

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

1104 if id(ns) in namespaces_seen: 

1105 continue 

1106 namespaces_seen.add(id(ns)) 

1107 tmp_res = list_namespace(ns, type_pattern, filter, 

1108 ignore_case=ignore_case, show_all=show_all) 

1109 search_result.update(tmp_res) 

1110 

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

1112 

1113 

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

1115 """ 

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

1117 Look there for the comments. 

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

1119 """ 

1120 result = [] 

1121 pos_only = False 

1122 kw_only = True 

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

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

1125 pos_only = True 

1126 elif pos_only: 

1127 result.append('/') 

1128 pos_only = False 

1129 

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

1131 kw_only = False 

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

1133 result.append('*') 

1134 kw_only = False 

1135 

1136 result.append(str(param)) 

1137 

1138 if pos_only: 

1139 result.append('/') 

1140 

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

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

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

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

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

1146 ) 

1147 else: 

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

1149 

1150 if obj_signature.return_annotation is not inspect._empty: 

1151 anno = inspect.formatannotation(obj_signature.return_annotation) 

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

1153 

1154 return rendered