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

538 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +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 

72 def get(self, field): 

73 """Get a field from the object for backward compatibility with before 8.12 

74 

75 see https://github.com/h5py/h5py/issues/2253 

76 """ 

77 # We need to deprecate this at some point, but the warning will show in completion. 

78 # Let's comment this for now and uncomment end of 2023 ish 

79 # warnings.warn( 

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

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

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

83 # DeprecationWarning, 

84 # stacklevel=2, 

85 # ) 

86 return getattr(self, field) 

87 

88 

89def pylight(code): 

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

91 

92# builtin docstrings to ignore 

93_func_call_docstring = types.FunctionType.__call__.__doc__ 

94_object_init_docstring = object.__init__.__doc__ 

95_builtin_type_docstrings = { 

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

97 types.FunctionType, property) 

98} 

99 

100_builtin_func_type = type(all) 

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

102#**************************************************************************** 

103# Builtin color schemes 

104 

105Colors = TermColors # just a shorthand 

106 

107InspectColors = PyColorize.ANSICodeColors 

108 

109#**************************************************************************** 

110# Auxiliary functions and objects 

111 

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

113# effectively defines the order of display 

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

115 'length', 'file', 'definition', 'docstring', 'source', 

116 'init_definition', 'class_docstring', 'init_docstring', 

117 'call_def', 'call_docstring', 

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

119 # format the object 

120 'ismagic', 'isalias', 'isclass', 'found', 'name' 

121 ] 

122 

123 

124def object_info(**kw): 

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

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

127 infodict.update(kw) 

128 return infodict 

129 

130 

131def get_encoding(obj): 

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

133 

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

135 """ 

136 ofile = find_file(obj) 

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

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

139 # filesystem. 

140 if ofile is None: 

141 return None 

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

143 return None 

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

145 return None 

146 else: 

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

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

149 # 0-offset, so we must adjust. 

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

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

152 return encoding 

153 

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

155 """Stable wrapper around inspect.getdoc. 

156 

157 This can't crash because of attribute problems. 

158 

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

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

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

162 """ 

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

164 try: 

165 ds = obj.getdoc() 

166 except Exception: 

167 pass 

168 else: 

169 if isinstance(ds, str): 

170 return inspect.cleandoc(ds) 

171 docstr = inspect.getdoc(obj) 

172 return docstr 

173 

174 

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

176 """Wrapper around inspect.getsource. 

177 

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

179 extraction. 

180 

181 Parameters 

182 ---------- 

183 obj : object 

184 an object whose source code we will attempt to extract 

185 oname : str 

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

187 

188 Returns 

189 ------- 

190 src : unicode or None 

191 

192 """ 

193 

194 if isinstance(obj, property): 

195 sources = [] 

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

197 fn = getattr(obj, attrname) 

198 if fn is not None: 

199 encoding = get_encoding(fn) 

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

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

202 if inspect.isfunction(fn): 

203 _src = getsource(fn) 

204 if _src: 

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

206 sources.append(dedent(_src)) 

207 else: 

208 # Default str/repr only prints function name, 

209 # pretty.pretty prints module name too. 

210 sources.append( 

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

212 ) 

213 if sources: 

214 return '\n'.join(sources) 

215 else: 

216 return None 

217 

218 else: 

219 # Get source for non-property objects. 

220 

221 obj = _get_wrapped(obj) 

222 

223 try: 

224 src = inspect.getsource(obj) 

225 except TypeError: 

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

227 # its class definition instead. 

228 try: 

229 src = inspect.getsource(obj.__class__) 

230 except (OSError, TypeError): 

231 return None 

232 except OSError: 

233 return None 

234 

235 return src 

236 

237 

238def is_simple_callable(obj): 

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

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

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

242 

243@undoc 

244def getargspec(obj): 

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

246 

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

248 ``__call__`` attribute. 

249 

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

251 """ 

252 

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

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

255 

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

257 obj = obj.__call__ 

258 

259 return inspect.getfullargspec(obj) 

260 

261@undoc 

262def format_argspec(argspec): 

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

264 

265 This takes a dict instead of ordered arguments and calls 

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

267 

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

269 """ 

270 

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

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

273 

274 

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

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

277 

278@undoc 

279def call_tip(oinfo, format_call=True): 

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

281 warnings.warn( 

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

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

284 DeprecationWarning, 

285 stacklevel=2, 

286 ) 

287 # Get call definition 

288 argspec = oinfo.get('argspec') 

289 if argspec is None: 

290 call_line = None 

291 else: 

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

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

294 # extra first argument explicitly). 

295 try: 

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

297 except (KeyError, IndexError): 

298 pass 

299 else: 

300 if has_self: 

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

302 

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

304 

305 # Now get docstring. 

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

307 doc = oinfo.get('call_docstring') 

308 if doc is None: 

309 doc = oinfo.get('init_docstring') 

310 if doc is None: 

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

312 

313 return call_line, doc 

314 

315 

316def _get_wrapped(obj): 

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

318 

319 Some objects automatically construct similar objects on any unrecognised 

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

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

322 attribute access. --TK, Jan 2016 

323 """ 

324 orig_obj = obj 

325 i = 0 

326 while safe_hasattr(obj, '__wrapped__'): 

327 obj = obj.__wrapped__ 

328 i += 1 

329 if i > 100: 

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

331 return orig_obj 

332 return obj 

333 

334def find_file(obj) -> str: 

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

336 

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

338 

339 Returns None if no file can be found. 

340 

341 Parameters 

342 ---------- 

343 obj : any Python object 

344 

345 Returns 

346 ------- 

347 fname : str 

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

349 """ 

350 obj = _get_wrapped(obj) 

351 

352 fname = None 

353 try: 

354 fname = inspect.getabsfile(obj) 

355 except TypeError: 

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

357 # declared. 

358 try: 

359 fname = inspect.getabsfile(obj.__class__) 

360 except (OSError, TypeError): 

361 # Can happen for builtins 

362 pass 

363 except OSError: 

364 pass 

365 

366 return cast_unicode(fname) 

367 

368 

369def find_source_lines(obj): 

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

371 

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

373 

374 Returns None if no file can be found. 

375 

376 Parameters 

377 ---------- 

378 obj : any Python object 

379 

380 Returns 

381 ------- 

382 lineno : int 

383 The line number where the object definition starts. 

384 """ 

385 obj = _get_wrapped(obj) 

386 

387 try: 

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

389 except TypeError: 

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

391 try: 

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

393 except (OSError, TypeError): 

394 return None 

395 except OSError: 

396 return None 

397 

398 return lineno 

399 

400class Inspector(Colorable): 

401 

402 def __init__(self, color_table=InspectColors, 

403 code_color_table=PyColorize.ANSICodeColors, 

404 scheme=None, 

405 str_detail_level=0, 

406 parent=None, config=None): 

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

408 self.color_table = color_table 

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

410 self.format = self.parser.format 

411 self.str_detail_level = str_detail_level 

412 self.set_active_scheme(scheme) 

413 

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

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

416 

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

418 exception is suppressed.""" 

419 try: 

420 return _render_signature(signature(obj), oname) 

421 except: 

422 return None 

423 

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

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

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

427 self.color_table.active_colors.normal) 

428 

429 def set_active_scheme(self, scheme): 

430 if scheme is not None: 

431 self.color_table.set_active_scheme(scheme) 

432 self.parser.color_table.set_active_scheme(scheme) 

433 

434 def noinfo(self, msg, oname): 

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

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

437 if oname: 

438 print('for %s' % oname) 

439 else: 

440 print() 

441 

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

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

444 

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

446 

447 if not callable(obj): 

448 print('Object is not callable.') 

449 return 

450 

451 header = '' 

452 

453 if inspect.isclass(obj): 

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

455 

456 

457 output = self._getdef(obj,oname) 

458 if output is None: 

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

460 else: 

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

462 

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

464 @skip_doctest 

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

466 """Print the docstring for any object. 

467 

468 Optional: 

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

470 formatted docstrings. 

471 

472 Examples 

473 -------- 

474 In [1]: class NoInit: 

475 ...: pass 

476 

477 In [2]: class NoDoc: 

478 ...: def __init__(self): 

479 ...: pass 

480 

481 In [3]: %pdoc NoDoc 

482 No documentation found for NoDoc 

483 

484 In [4]: %pdoc NoInit 

485 No documentation found for NoInit 

486 

487 In [5]: obj = NoInit() 

488 

489 In [6]: %pdoc obj 

490 No documentation found for obj 

491 

492 In [5]: obj2 = NoDoc() 

493 

494 In [6]: %pdoc obj2 

495 No documentation found for obj2 

496 """ 

497 

498 head = self.__head # For convenience 

499 lines = [] 

500 ds = getdoc(obj) 

501 if formatter: 

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

503 if ds: 

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

505 lines.append(indent(ds)) 

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

507 init_ds = getdoc(obj.__init__) 

508 if init_ds is not None: 

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

510 lines.append(indent(init_ds)) 

511 elif hasattr(obj,'__call__'): 

512 call_ds = getdoc(obj.__call__) 

513 if call_ds: 

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

515 lines.append(indent(call_ds)) 

516 

517 if not lines: 

518 self.noinfo('documentation',oname) 

519 else: 

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

521 

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

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

524 

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

526 linecache.checkcache() 

527 try: 

528 src = getsource(obj, oname=oname) 

529 except Exception: 

530 src = None 

531 

532 if src is None: 

533 self.noinfo('source', oname) 

534 else: 

535 page.page(self.format(src)) 

536 

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

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

539 

540 lineno = find_source_lines(obj) 

541 if lineno is None: 

542 self.noinfo('file', oname) 

543 return 

544 

545 ofile = find_file(obj) 

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

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

548 # filesystem. 

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

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

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

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

553 else: 

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

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

556 # 0-offset, so we must adjust. 

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

558 

559 

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

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

562 

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

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

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

566 

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

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

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

570 

571 Note: 

572 

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

574 

575 """ 

576 defaults = { 

577 "text/plain": text, 

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

579 } 

580 

581 if formatter is None: 

582 return defaults 

583 else: 

584 formatted = formatter(text) 

585 

586 if not isinstance(formatted, dict): 

587 # Handle the deprecated behavior of a formatter returning 

588 # a string instead of a mime bundle. 

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

590 

591 else: 

592 return dict(defaults, **formatted) 

593 

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

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

596 # Format text/plain mimetype 

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

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

599 assert isinstance(item, tuple) 

600 

601 new_b: Bundle = {} 

602 lines = [] 

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

604 

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

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

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

608 lines.append( 

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

610 ) 

611 

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

613 

614 if "text/html" in bundle: 

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

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

617 assert isinstance(item, tuple) 

618 # Format the text/html mimetype 

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

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

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

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

623 ) 

624 

625 for k in bundle.keys(): 

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

627 continue 

628 else: 

629 new_b = bundle[k] # type:ignore 

630 return new_b 

631 

632 def _append_info_field( 

633 self, 

634 bundle: UnformattedBundle, 

635 title: str, 

636 key: str, 

637 info, 

638 omit_sections, 

639 formatter, 

640 ): 

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

642 if title in omit_sections or key in omit_sections: 

643 return 

644 field = info[key] 

645 if field is not None: 

646 formatted_field = self._mime_format(field, formatter) 

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

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

649 

650 def _make_info_unformatted( 

651 self, obj, info, formatter, detail_level, omit_sections 

652 ) -> UnformattedBundle: 

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

654 bundle: UnformattedBundle = { 

655 "text/plain": [], 

656 "text/html": [], 

657 } 

658 

659 # A convenience function to simplify calls below 

660 def append_field( 

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

662 ): 

663 self._append_info_field( 

664 bundle, 

665 title=title, 

666 key=key, 

667 info=info, 

668 omit_sections=omit_sections, 

669 formatter=formatter, 

670 ) 

671 

672 def code_formatter(text) -> Bundle: 

673 return { 

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

675 'text/html': pylight(text) 

676 } 

677 

678 if info["isalias"]: 

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

680 

681 elif info['ismagic']: 

682 if detail_level > 0: 

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

684 else: 

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

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

687 

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

689 # Functions, methods, classes 

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

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

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

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

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

695 else: 

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

697 

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

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

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

701 

702 else: 

703 # General Python objects 

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

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

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

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

708 

709 # Namespace 

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

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

712 

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

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

715 

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

717 # source found. 

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

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

720 else: 

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

722 

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

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

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

726 return bundle 

727 

728 

729 def _get_info( 

730 self, 

731 obj: Any, 

732 oname: str = "", 

733 formatter=None, 

734 info: Optional[OInfo] = None, 

735 detail_level=0, 

736 omit_sections=(), 

737 ) -> Bundle: 

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

739 

740 Parameters 

741 ---------- 

742 obj : any 

743 Object to inspect and return info from 

744 oname : str (default: ''): 

745 Name of the variable pointing to `obj`. 

746 formatter : callable 

747 info 

748 already computed information 

749 detail_level : integer 

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

751 omit_sections : container[str] 

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

753 """ 

754 

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

756 bundle = self._make_info_unformatted( 

757 obj, 

758 info_dict, 

759 formatter, 

760 detail_level=detail_level, 

761 omit_sections=omit_sections, 

762 ) 

763 return self.format_mime(bundle) 

764 

765 def pinfo( 

766 self, 

767 obj, 

768 oname="", 

769 formatter=None, 

770 info: Optional[OInfo] = None, 

771 detail_level=0, 

772 enable_html_pager=True, 

773 omit_sections=(), 

774 ): 

775 """Show detailed information about an object. 

776 

777 Optional arguments: 

778 

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

780 

781 - formatter: callable (optional) 

782 A special formatter for docstrings. 

783 

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

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

786 in the form of a dictionary. 

787 

788 Although the support of custom formatter returning a string 

789 instead of a mime type bundle is deprecated. 

790 

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

792 precomputed already. 

793 

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

795 

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

797 """ 

798 assert info is not None 

799 info_b: Bundle = self._get_info( 

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

801 ) 

802 if not enable_html_pager: 

803 del info_b["text/html"] 

804 page.page(info_b) 

805 

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

807 """ 

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

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

810 """ 

811 

812 warnings.warn( 

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

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

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

816 DeprecationWarning, 

817 stacklevel=2, 

818 ) 

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

820 

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

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

823 

824 Parameters 

825 ---------- 

826 obj : any 

827 An object to find information about 

828 oname : str (default: '') 

829 Name of the variable pointing to `obj`. 

830 info : (default: None) 

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

832 which may have been precomputed already. 

833 detail_level : int (default:0) 

834 If set to 1, more information is given. 

835 

836 Returns 

837 ------- 

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

839 strings, values are string or None. 

840 """ 

841 

842 if info is None: 

843 ismagic = False 

844 isalias = False 

845 ospace = '' 

846 else: 

847 ismagic = info.ismagic 

848 isalias = info.isalias 

849 ospace = info.namespace 

850 

851 # Get docstring, special-casing aliases: 

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

853 parents_docs = None 

854 prelude = "" 

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

856 parents_docs_dict = getattr(info.parent, HOOK_NAME) 

857 parents_docs = parents_docs_dict.get(att_name, None) 

858 out = dict( 

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

860 ) 

861 

862 if parents_docs: 

863 ds = parents_docs 

864 elif isalias: 

865 if not callable(obj): 

866 try: 

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

868 except: 

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

870 else: 

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

872 if obj.__doc__: 

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

874 else: 

875 ds_or_None = getdoc(obj) 

876 if ds_or_None is None: 

877 ds = '<no docstring>' 

878 else: 

879 ds = ds_or_None 

880 

881 ds = prelude + ds 

882 

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

884 

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

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

887 

888 if ismagic: 

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

890 elif isalias: 

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

892 else: 

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

894 

895 try: 

896 bclass = obj.__class__ 

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

898 except: 

899 pass 

900 

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

902 if detail_level >= self.str_detail_level: 

903 try: 

904 ostr = str(obj) 

905 str_head = 'string_form' 

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

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

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

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

910 out[str_head] = ostr 

911 except: 

912 pass 

913 

914 if ospace: 

915 out['namespace'] = ospace 

916 

917 # Length (for strings and lists) 

918 try: 

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

920 except Exception: 

921 pass 

922 

923 # Filename where object was defined 

924 binary_file = False 

925 fname = find_file(obj) 

926 if fname is None: 

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

928 # if the file was binary 

929 binary_file = True 

930 else: 

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

932 binary_file = True 

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

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

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

936 

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

938 if detail_level: 

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

940 # source 

941 linecache.checkcache() 

942 try: 

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

944 src = getsource(obj, oname) 

945 if src is not None: 

946 src = src.rstrip() 

947 out['source'] = src 

948 

949 except Exception: 

950 pass 

951 

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

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

954 out['docstring'] = ds 

955 

956 # Constructor docstring for classes 

957 if inspect.isclass(obj): 

958 out['isclass'] = True 

959 

960 # get the init signature: 

961 try: 

962 init_def = self._getdef(obj, oname) 

963 except AttributeError: 

964 init_def = None 

965 

966 # get the __init__ docstring 

967 try: 

968 obj_init = obj.__init__ 

969 except AttributeError: 

970 init_ds = None 

971 else: 

972 if init_def is None: 

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

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

975 try: 

976 init_def = self._getdef(obj_init, oname) 

977 except AttributeError: 

978 pass 

979 init_ds = getdoc(obj_init) 

980 # Skip Python's auto-generated docstrings 

981 if init_ds == _object_init_docstring: 

982 init_ds = None 

983 

984 if init_def: 

985 out['init_definition'] = init_def 

986 

987 if init_ds: 

988 out['init_docstring'] = init_ds 

989 

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

991 if len(names) < 10: 

992 all_names = ', '.join(names) 

993 else: 

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

995 out['subclasses'] = all_names 

996 # and class docstring for instances: 

997 else: 

998 # reconstruct the function definition and print it: 

999 defln = self._getdef(obj, oname) 

1000 if defln: 

1001 out['definition'] = defln 

1002 

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

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

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

1006 # objects which use instance-customized docstrings. 

1007 if ds: 

1008 try: 

1009 cls = getattr(obj,'__class__') 

1010 except: 

1011 class_ds = None 

1012 else: 

1013 class_ds = getdoc(cls) 

1014 # Skip Python's auto-generated docstrings 

1015 if class_ds in _builtin_type_docstrings: 

1016 class_ds = None 

1017 if class_ds and ds != class_ds: 

1018 out['class_docstring'] = class_ds 

1019 

1020 # Next, try to show constructor docstrings 

1021 try: 

1022 init_ds = getdoc(obj.__init__) 

1023 # Skip Python's auto-generated docstrings 

1024 if init_ds == _object_init_docstring: 

1025 init_ds = None 

1026 except AttributeError: 

1027 init_ds = None 

1028 if init_ds: 

1029 out['init_docstring'] = init_ds 

1030 

1031 # Call form docstring for callable instances 

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

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

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

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

1036 # but don't include the same signature twice 

1037 out['call_def'] = call_def 

1038 call_ds = getdoc(obj.__call__) 

1039 # Skip Python's auto-generated docstrings 

1040 if call_ds == _func_call_docstring: 

1041 call_ds = None 

1042 if call_ds: 

1043 out['call_docstring'] = call_ds 

1044 

1045 return object_info(**out) 

1046 

1047 @staticmethod 

1048 def _source_contains_docstring(src, doc): 

1049 """ 

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

1051 

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

1053 source already contains it, avoiding repetition of information. 

1054 """ 

1055 try: 

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

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

1058 except Exception: 

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

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

1061 # arbitrary ways. 

1062 return False 

1063 

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

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

1066 """Search namespaces with wildcards for objects. 

1067 

1068 Arguments: 

1069 

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

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

1072 objects of that type. 

1073 

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

1075 

1076 Optional arguments: 

1077 

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

1079 

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

1081 

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

1083 underscores. 

1084 

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

1086 """ 

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

1088 

1089 # defaults 

1090 type_pattern = 'all' 

1091 filter = '' 

1092 

1093 # list all object types 

1094 if list_types: 

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

1096 return 

1097 

1098 cmds = pattern.split() 

1099 len_cmds = len(cmds) 

1100 if len_cmds == 1: 

1101 # Only filter pattern given 

1102 filter = cmds[0] 

1103 elif len_cmds == 2: 

1104 # Both filter and type specified 

1105 filter,type_pattern = cmds 

1106 else: 

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

1108 pattern) 

1109 

1110 # filter search namespaces 

1111 for name in ns_search: 

1112 if name not in ns_table: 

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

1114 (name,ns_table.keys())) 

1115 

1116 #print 'type_pattern:',type_pattern # dbg 

1117 search_result, namespaces_seen = set(), set() 

1118 for ns_name in ns_search: 

1119 ns = ns_table[ns_name] 

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

1121 if id(ns) in namespaces_seen: 

1122 continue 

1123 namespaces_seen.add(id(ns)) 

1124 tmp_res = list_namespace(ns, type_pattern, filter, 

1125 ignore_case=ignore_case, show_all=show_all) 

1126 search_result.update(tmp_res) 

1127 

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

1129 

1130 

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

1132 """ 

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

1134 Look there for the comments. 

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

1136 """ 

1137 result = [] 

1138 pos_only = False 

1139 kw_only = True 

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

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

1142 pos_only = True 

1143 elif pos_only: 

1144 result.append('/') 

1145 pos_only = False 

1146 

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

1148 kw_only = False 

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

1150 result.append('*') 

1151 kw_only = False 

1152 

1153 result.append(str(param)) 

1154 

1155 if pos_only: 

1156 result.append('/') 

1157 

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

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

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

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

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

1163 ) 

1164 else: 

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

1166 

1167 if obj_signature.return_annotation is not inspect._empty: 

1168 anno = inspect.formatannotation(obj_signature.return_annotation) 

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

1170 

1171 return rendered