##// END OF EJS Templates
Fix bug where extra mimekey would drop the full bundle. (#14301)
M Bussonnier -
r28594:112e5968 merge
parent child Browse files
Show More
@@ -1,1183 +1,1183
1 1 """Tools for inspecting Python objects.
2 2
3 3 Uses syntax highlighting for presenting the various information elements.
4 4
5 5 Similar in spirit to the inspect module, but all calls take a name argument to
6 6 reference the name under which an object is being read.
7 7 """
8 8
9 9 # Copyright (c) IPython Development Team.
10 10 # Distributed under the terms of the Modified BSD License.
11 11
12 12 __all__ = ['Inspector','InspectColors']
13 13
14 14 # stdlib modules
15 15 from dataclasses import dataclass
16 16 from inspect import signature
17 17 from textwrap import dedent
18 18 import ast
19 19 import html
20 20 import inspect
21 21 import io as stdlib_io
22 22 import linecache
23 23 import os
24 24 import types
25 25 import warnings
26 26
27 27 from typing import Any, Optional, Dict, Union, List, Tuple
28 28
29 29 from typing import TypeAlias
30 30
31 31 import traitlets
32 32
33 33 # IPython's own
34 34 from IPython.core import page
35 35 from IPython.lib.pretty import pretty
36 36 from IPython.testing.skipdoctest import skip_doctest
37 37 from IPython.utils import PyColorize
38 38 from IPython.utils import openpy
39 39 from IPython.utils.dir2 import safe_hasattr
40 40 from IPython.utils.path import compress_user
41 41 from IPython.utils.text import indent
42 42 from IPython.utils.wildcard import list_namespace
43 43 from IPython.utils.wildcard import typestr2type
44 44 from IPython.utils.coloransi import TermColors
45 45 from IPython.utils.py3compat import cast_unicode
46 46 from IPython.utils.colorable import Colorable
47 47 from IPython.utils.decorators import undoc
48 48
49 49 from pygments import highlight
50 50 from pygments.lexers import PythonLexer
51 51 from pygments.formatters import HtmlFormatter
52 52
53 53 HOOK_NAME = "__custom_documentations__"
54 54
55 55
56 56 UnformattedBundle: TypeAlias = Dict[str, List[Tuple[str, str]]] # List of (title, body)
57 57 Bundle: TypeAlias = Dict[str, str]
58 58
59 59
60 60 @dataclass
61 61 class OInfo:
62 62 ismagic: bool
63 63 isalias: bool
64 64 found: bool
65 65 namespace: Optional[str]
66 66 parent: Any
67 67 obj: Any
68 68
69 69 def get(self, field):
70 70 """Get a field from the object for backward compatibility with before 8.12
71 71
72 72 see https://github.com/h5py/h5py/issues/2253
73 73 """
74 74 # We need to deprecate this at some point, but the warning will show in completion.
75 75 # Let's comment this for now and uncomment end of 2023 ish
76 76 # warnings.warn(
77 77 # f"OInfo dataclass with fields access since IPython 8.12 please use OInfo.{field} instead."
78 78 # "OInfo used to be a dict but a dataclass provide static fields verification with mypy."
79 79 # "This warning and backward compatibility `get()` method were added in 8.13.",
80 80 # DeprecationWarning,
81 81 # stacklevel=2,
82 82 # )
83 83 return getattr(self, field)
84 84
85 85
86 86 def pylight(code):
87 87 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
88 88
89 89 # builtin docstrings to ignore
90 90 _func_call_docstring = types.FunctionType.__call__.__doc__
91 91 _object_init_docstring = object.__init__.__doc__
92 92 _builtin_type_docstrings = {
93 93 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
94 94 types.FunctionType, property)
95 95 }
96 96
97 97 _builtin_func_type = type(all)
98 98 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
99 99 #****************************************************************************
100 100 # Builtin color schemes
101 101
102 102 Colors = TermColors # just a shorthand
103 103
104 104 InspectColors = PyColorize.ANSICodeColors
105 105
106 106 #****************************************************************************
107 107 # Auxiliary functions and objects
108 108
109 109 # See the messaging spec for the definition of all these fields. This list
110 110 # effectively defines the order of display
111 111 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
112 112 'length', 'file', 'definition', 'docstring', 'source',
113 113 'init_definition', 'class_docstring', 'init_docstring',
114 114 'call_def', 'call_docstring',
115 115 # These won't be printed but will be used to determine how to
116 116 # format the object
117 117 'ismagic', 'isalias', 'isclass', 'found', 'name'
118 118 ]
119 119
120 120
121 121 def object_info(**kw):
122 122 """Make an object info dict with all fields present."""
123 123 infodict = {k:None for k in info_fields}
124 124 infodict.update(kw)
125 125 return infodict
126 126
127 127
128 128 def get_encoding(obj):
129 129 """Get encoding for python source file defining obj
130 130
131 131 Returns None if obj is not defined in a sourcefile.
132 132 """
133 133 ofile = find_file(obj)
134 134 # run contents of file through pager starting at line where the object
135 135 # is defined, as long as the file isn't binary and is actually on the
136 136 # filesystem.
137 137 if ofile is None:
138 138 return None
139 139 elif ofile.endswith(('.so', '.dll', '.pyd')):
140 140 return None
141 141 elif not os.path.isfile(ofile):
142 142 return None
143 143 else:
144 144 # Print only text files, not extension binaries. Note that
145 145 # getsourcelines returns lineno with 1-offset and page() uses
146 146 # 0-offset, so we must adjust.
147 147 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
148 148 encoding, _lines = openpy.detect_encoding(buffer.readline)
149 149 return encoding
150 150
151 151 def getdoc(obj) -> Union[str,None]:
152 152 """Stable wrapper around inspect.getdoc.
153 153
154 154 This can't crash because of attribute problems.
155 155
156 156 It also attempts to call a getdoc() method on the given object. This
157 157 allows objects which provide their docstrings via non-standard mechanisms
158 158 (like Pyro proxies) to still be inspected by ipython's ? system.
159 159 """
160 160 # Allow objects to offer customized documentation via a getdoc method:
161 161 try:
162 162 ds = obj.getdoc()
163 163 except Exception:
164 164 pass
165 165 else:
166 166 if isinstance(ds, str):
167 167 return inspect.cleandoc(ds)
168 168 docstr = inspect.getdoc(obj)
169 169 return docstr
170 170
171 171
172 172 def getsource(obj, oname='') -> Union[str,None]:
173 173 """Wrapper around inspect.getsource.
174 174
175 175 This can be modified by other projects to provide customized source
176 176 extraction.
177 177
178 178 Parameters
179 179 ----------
180 180 obj : object
181 181 an object whose source code we will attempt to extract
182 182 oname : str
183 183 (optional) a name under which the object is known
184 184
185 185 Returns
186 186 -------
187 187 src : unicode or None
188 188
189 189 """
190 190
191 191 if isinstance(obj, property):
192 192 sources = []
193 193 for attrname in ['fget', 'fset', 'fdel']:
194 194 fn = getattr(obj, attrname)
195 195 if fn is not None:
196 196 encoding = get_encoding(fn)
197 197 oname_prefix = ('%s.' % oname) if oname else ''
198 198 sources.append(''.join(('# ', oname_prefix, attrname)))
199 199 if inspect.isfunction(fn):
200 200 _src = getsource(fn)
201 201 if _src:
202 202 # assert _src is not None, "please mypy"
203 203 sources.append(dedent(_src))
204 204 else:
205 205 # Default str/repr only prints function name,
206 206 # pretty.pretty prints module name too.
207 207 sources.append(
208 208 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
209 209 )
210 210 if sources:
211 211 return '\n'.join(sources)
212 212 else:
213 213 return None
214 214
215 215 else:
216 216 # Get source for non-property objects.
217 217
218 218 obj = _get_wrapped(obj)
219 219
220 220 try:
221 221 src = inspect.getsource(obj)
222 222 except TypeError:
223 223 # The object itself provided no meaningful source, try looking for
224 224 # its class definition instead.
225 225 try:
226 226 src = inspect.getsource(obj.__class__)
227 227 except (OSError, TypeError):
228 228 return None
229 229 except OSError:
230 230 return None
231 231
232 232 return src
233 233
234 234
235 235 def is_simple_callable(obj):
236 236 """True if obj is a function ()"""
237 237 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
238 238 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
239 239
240 240 @undoc
241 241 def getargspec(obj):
242 242 """Wrapper around :func:`inspect.getfullargspec`
243 243
244 244 In addition to functions and methods, this can also handle objects with a
245 245 ``__call__`` attribute.
246 246
247 247 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
248 248 """
249 249
250 250 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
251 251 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
252 252
253 253 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
254 254 obj = obj.__call__
255 255
256 256 return inspect.getfullargspec(obj)
257 257
258 258 @undoc
259 259 def format_argspec(argspec):
260 260 """Format argspect, convenience wrapper around inspect's.
261 261
262 262 This takes a dict instead of ordered arguments and calls
263 263 inspect.format_argspec with the arguments in the necessary order.
264 264
265 265 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
266 266 """
267 267
268 268 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
269 269 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
270 270
271 271
272 272 return inspect.formatargspec(argspec['args'], argspec['varargs'],
273 273 argspec['varkw'], argspec['defaults'])
274 274
275 275 @undoc
276 276 def call_tip(oinfo, format_call=True):
277 277 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
278 278 warnings.warn(
279 279 "`call_tip` function is deprecated as of IPython 6.0"
280 280 "and will be removed in future versions.",
281 281 DeprecationWarning,
282 282 stacklevel=2,
283 283 )
284 284 # Get call definition
285 285 argspec = oinfo.get('argspec')
286 286 if argspec is None:
287 287 call_line = None
288 288 else:
289 289 # Callable objects will have 'self' as their first argument, prune
290 290 # it out if it's there for clarity (since users do *not* pass an
291 291 # extra first argument explicitly).
292 292 try:
293 293 has_self = argspec['args'][0] == 'self'
294 294 except (KeyError, IndexError):
295 295 pass
296 296 else:
297 297 if has_self:
298 298 argspec['args'] = argspec['args'][1:]
299 299
300 300 call_line = oinfo['name']+format_argspec(argspec)
301 301
302 302 # Now get docstring.
303 303 # The priority is: call docstring, constructor docstring, main one.
304 304 doc = oinfo.get('call_docstring')
305 305 if doc is None:
306 306 doc = oinfo.get('init_docstring')
307 307 if doc is None:
308 308 doc = oinfo.get('docstring','')
309 309
310 310 return call_line, doc
311 311
312 312
313 313 def _get_wrapped(obj):
314 314 """Get the original object if wrapped in one or more @decorators
315 315
316 316 Some objects automatically construct similar objects on any unrecognised
317 317 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
318 318 this will arbitrarily cut off after 100 levels of obj.__wrapped__
319 319 attribute access. --TK, Jan 2016
320 320 """
321 321 orig_obj = obj
322 322 i = 0
323 323 while safe_hasattr(obj, '__wrapped__'):
324 324 obj = obj.__wrapped__
325 325 i += 1
326 326 if i > 100:
327 327 # __wrapped__ is probably a lie, so return the thing we started with
328 328 return orig_obj
329 329 return obj
330 330
331 331 def find_file(obj) -> Optional[str]:
332 332 """Find the absolute path to the file where an object was defined.
333 333
334 334 This is essentially a robust wrapper around `inspect.getabsfile`.
335 335
336 336 Returns None if no file can be found.
337 337
338 338 Parameters
339 339 ----------
340 340 obj : any Python object
341 341
342 342 Returns
343 343 -------
344 344 fname : str
345 345 The absolute path to the file where the object was defined.
346 346 """
347 347 obj = _get_wrapped(obj)
348 348
349 349 fname: Optional[str] = None
350 350 try:
351 351 fname = inspect.getabsfile(obj)
352 352 except TypeError:
353 353 # For an instance, the file that matters is where its class was
354 354 # declared.
355 355 try:
356 356 fname = inspect.getabsfile(obj.__class__)
357 357 except (OSError, TypeError):
358 358 # Can happen for builtins
359 359 pass
360 360 except OSError:
361 361 pass
362 362
363 363 return fname
364 364
365 365
366 366 def find_source_lines(obj):
367 367 """Find the line number in a file where an object was defined.
368 368
369 369 This is essentially a robust wrapper around `inspect.getsourcelines`.
370 370
371 371 Returns None if no file can be found.
372 372
373 373 Parameters
374 374 ----------
375 375 obj : any Python object
376 376
377 377 Returns
378 378 -------
379 379 lineno : int
380 380 The line number where the object definition starts.
381 381 """
382 382 obj = _get_wrapped(obj)
383 383
384 384 try:
385 385 lineno = inspect.getsourcelines(obj)[1]
386 386 except TypeError:
387 387 # For instances, try the class object like getsource() does
388 388 try:
389 389 lineno = inspect.getsourcelines(obj.__class__)[1]
390 390 except (OSError, TypeError):
391 391 return None
392 392 except OSError:
393 393 return None
394 394
395 395 return lineno
396 396
397 397 class Inspector(Colorable):
398 398
399 399 mime_hooks = traitlets.Dict(
400 400 config=True,
401 401 help="dictionary of mime to callable to add informations into help mimebundle dict",
402 402 ).tag(config=True)
403 403
404 404 def __init__(
405 405 self,
406 406 color_table=InspectColors,
407 407 code_color_table=PyColorize.ANSICodeColors,
408 408 scheme=None,
409 409 str_detail_level=0,
410 410 parent=None,
411 411 config=None,
412 412 ):
413 413 super(Inspector, self).__init__(parent=parent, config=config)
414 414 self.color_table = color_table
415 415 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
416 416 self.format = self.parser.format
417 417 self.str_detail_level = str_detail_level
418 418 self.set_active_scheme(scheme)
419 419
420 420 def _getdef(self,obj,oname='') -> Union[str,None]:
421 421 """Return the call signature for any callable object.
422 422
423 423 If any exception is generated, None is returned instead and the
424 424 exception is suppressed."""
425 425 if not callable(obj):
426 426 return None
427 427 try:
428 428 return _render_signature(signature(obj), oname)
429 429 except:
430 430 return None
431 431
432 432 def __head(self,h) -> str:
433 433 """Return a header string with proper colors."""
434 434 return '%s%s%s' % (self.color_table.active_colors.header,h,
435 435 self.color_table.active_colors.normal)
436 436
437 437 def set_active_scheme(self, scheme):
438 438 if scheme is not None:
439 439 self.color_table.set_active_scheme(scheme)
440 440 self.parser.color_table.set_active_scheme(scheme)
441 441
442 442 def noinfo(self, msg, oname):
443 443 """Generic message when no information is found."""
444 444 print('No %s found' % msg, end=' ')
445 445 if oname:
446 446 print('for %s' % oname)
447 447 else:
448 448 print()
449 449
450 450 def pdef(self, obj, oname=''):
451 451 """Print the call signature for any callable object.
452 452
453 453 If the object is a class, print the constructor information."""
454 454
455 455 if not callable(obj):
456 456 print('Object is not callable.')
457 457 return
458 458
459 459 header = ''
460 460
461 461 if inspect.isclass(obj):
462 462 header = self.__head('Class constructor information:\n')
463 463
464 464
465 465 output = self._getdef(obj,oname)
466 466 if output is None:
467 467 self.noinfo('definition header',oname)
468 468 else:
469 469 print(header,self.format(output), end=' ')
470 470
471 471 # In Python 3, all classes are new-style, so they all have __init__.
472 472 @skip_doctest
473 473 def pdoc(self, obj, oname='', formatter=None):
474 474 """Print the docstring for any object.
475 475
476 476 Optional:
477 477 -formatter: a function to run the docstring through for specially
478 478 formatted docstrings.
479 479
480 480 Examples
481 481 --------
482 482 In [1]: class NoInit:
483 483 ...: pass
484 484
485 485 In [2]: class NoDoc:
486 486 ...: def __init__(self):
487 487 ...: pass
488 488
489 489 In [3]: %pdoc NoDoc
490 490 No documentation found for NoDoc
491 491
492 492 In [4]: %pdoc NoInit
493 493 No documentation found for NoInit
494 494
495 495 In [5]: obj = NoInit()
496 496
497 497 In [6]: %pdoc obj
498 498 No documentation found for obj
499 499
500 500 In [5]: obj2 = NoDoc()
501 501
502 502 In [6]: %pdoc obj2
503 503 No documentation found for obj2
504 504 """
505 505
506 506 head = self.__head # For convenience
507 507 lines = []
508 508 ds = getdoc(obj)
509 509 if formatter:
510 510 ds = formatter(ds).get('plain/text', ds)
511 511 if ds:
512 512 lines.append(head("Class docstring:"))
513 513 lines.append(indent(ds))
514 514 if inspect.isclass(obj) and hasattr(obj, '__init__'):
515 515 init_ds = getdoc(obj.__init__)
516 516 if init_ds is not None:
517 517 lines.append(head("Init docstring:"))
518 518 lines.append(indent(init_ds))
519 519 elif hasattr(obj,'__call__'):
520 520 call_ds = getdoc(obj.__call__)
521 521 if call_ds:
522 522 lines.append(head("Call docstring:"))
523 523 lines.append(indent(call_ds))
524 524
525 525 if not lines:
526 526 self.noinfo('documentation',oname)
527 527 else:
528 528 page.page('\n'.join(lines))
529 529
530 530 def psource(self, obj, oname=''):
531 531 """Print the source code for an object."""
532 532
533 533 # Flush the source cache because inspect can return out-of-date source
534 534 linecache.checkcache()
535 535 try:
536 536 src = getsource(obj, oname=oname)
537 537 except Exception:
538 538 src = None
539 539
540 540 if src is None:
541 541 self.noinfo('source', oname)
542 542 else:
543 543 page.page(self.format(src))
544 544
545 545 def pfile(self, obj, oname=''):
546 546 """Show the whole file where an object was defined."""
547 547
548 548 lineno = find_source_lines(obj)
549 549 if lineno is None:
550 550 self.noinfo('file', oname)
551 551 return
552 552
553 553 ofile = find_file(obj)
554 554 # run contents of file through pager starting at line where the object
555 555 # is defined, as long as the file isn't binary and is actually on the
556 556 # filesystem.
557 557 if ofile.endswith(('.so', '.dll', '.pyd')):
558 558 print('File %r is binary, not printing.' % ofile)
559 559 elif not os.path.isfile(ofile):
560 560 print('File %r does not exist, not printing.' % ofile)
561 561 else:
562 562 # Print only text files, not extension binaries. Note that
563 563 # getsourcelines returns lineno with 1-offset and page() uses
564 564 # 0-offset, so we must adjust.
565 565 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
566 566
567 567
568 568 def _mime_format(self, text:str, formatter=None) -> dict:
569 569 """Return a mime bundle representation of the input text.
570 570
571 571 - if `formatter` is None, the returned mime bundle has
572 572 a ``text/plain`` field, with the input text.
573 573 a ``text/html`` field with a ``<pre>`` tag containing the input text.
574 574
575 575 - if ``formatter`` is not None, it must be a callable transforming the
576 576 input text into a mime bundle. Default values for ``text/plain`` and
577 577 ``text/html`` representations are the ones described above.
578 578
579 579 Note:
580 580
581 581 Formatters returning strings are supported but this behavior is deprecated.
582 582
583 583 """
584 584 defaults = {
585 585 "text/plain": text,
586 586 "text/html": f"<pre>{html.escape(text)}</pre>",
587 587 }
588 588
589 589 if formatter is None:
590 590 return defaults
591 591 else:
592 592 formatted = formatter(text)
593 593
594 594 if not isinstance(formatted, dict):
595 595 # Handle the deprecated behavior of a formatter returning
596 596 # a string instead of a mime bundle.
597 597 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
598 598
599 599 else:
600 600 return dict(defaults, **formatted)
601 601
602 602 def format_mime(self, bundle: UnformattedBundle) -> Bundle:
603 603 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
604 604 # Format text/plain mimetype
605 605 assert isinstance(bundle["text/plain"], list)
606 606 for item in bundle["text/plain"]:
607 607 assert isinstance(item, tuple)
608 608
609 609 new_b: Bundle = {}
610 610 lines = []
611 611 _len = max(len(h) for h, _ in bundle["text/plain"])
612 612
613 613 for head, body in bundle["text/plain"]:
614 614 body = body.strip("\n")
615 615 delim = "\n" if "\n" in body else " "
616 616 lines.append(
617 617 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
618 618 )
619 619
620 620 new_b["text/plain"] = "\n".join(lines)
621 621
622 622 if "text/html" in bundle:
623 623 assert isinstance(bundle["text/html"], list)
624 624 for item in bundle["text/html"]:
625 625 assert isinstance(item, tuple)
626 626 # Format the text/html mimetype
627 627 if isinstance(bundle["text/html"], (list, tuple)):
628 628 # bundle['text/html'] is a list of (head, formatted body) pairs
629 629 new_b["text/html"] = "\n".join(
630 630 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
631 631 )
632 632
633 633 for k in bundle.keys():
634 634 if k in ("text/html", "text/plain"):
635 635 continue
636 636 else:
637 new_b = bundle[k] # type:ignore
637 new_b[k] = bundle[k] # type:ignore
638 638 return new_b
639 639
640 640 def _append_info_field(
641 641 self,
642 642 bundle: UnformattedBundle,
643 643 title: str,
644 644 key: str,
645 645 info,
646 646 omit_sections,
647 647 formatter,
648 648 ):
649 649 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
650 650 if title in omit_sections or key in omit_sections:
651 651 return
652 652 field = info[key]
653 653 if field is not None:
654 654 formatted_field = self._mime_format(field, formatter)
655 655 bundle["text/plain"].append((title, formatted_field["text/plain"]))
656 656 bundle["text/html"].append((title, formatted_field["text/html"]))
657 657
658 658 def _make_info_unformatted(
659 659 self, obj, info, formatter, detail_level, omit_sections
660 660 ) -> UnformattedBundle:
661 661 """Assemble the mimebundle as unformatted lists of information"""
662 662 bundle: UnformattedBundle = {
663 663 "text/plain": [],
664 664 "text/html": [],
665 665 }
666 666
667 667 # A convenience function to simplify calls below
668 668 def append_field(
669 669 bundle: UnformattedBundle, title: str, key: str, formatter=None
670 670 ):
671 671 self._append_info_field(
672 672 bundle,
673 673 title=title,
674 674 key=key,
675 675 info=info,
676 676 omit_sections=omit_sections,
677 677 formatter=formatter,
678 678 )
679 679
680 680 def code_formatter(text) -> Bundle:
681 681 return {
682 682 'text/plain': self.format(text),
683 683 'text/html': pylight(text)
684 684 }
685 685
686 686 if info["isalias"]:
687 687 append_field(bundle, "Repr", "string_form")
688 688
689 689 elif info['ismagic']:
690 690 if detail_level > 0:
691 691 append_field(bundle, "Source", "source", code_formatter)
692 692 else:
693 693 append_field(bundle, "Docstring", "docstring", formatter)
694 694 append_field(bundle, "File", "file")
695 695
696 696 elif info['isclass'] or is_simple_callable(obj):
697 697 # Functions, methods, classes
698 698 append_field(bundle, "Signature", "definition", code_formatter)
699 699 append_field(bundle, "Init signature", "init_definition", code_formatter)
700 700 append_field(bundle, "Docstring", "docstring", formatter)
701 701 if detail_level > 0 and info["source"]:
702 702 append_field(bundle, "Source", "source", code_formatter)
703 703 else:
704 704 append_field(bundle, "Init docstring", "init_docstring", formatter)
705 705
706 706 append_field(bundle, "File", "file")
707 707 append_field(bundle, "Type", "type_name")
708 708 append_field(bundle, "Subclasses", "subclasses")
709 709
710 710 else:
711 711 # General Python objects
712 712 append_field(bundle, "Signature", "definition", code_formatter)
713 713 append_field(bundle, "Call signature", "call_def", code_formatter)
714 714 append_field(bundle, "Type", "type_name")
715 715 append_field(bundle, "String form", "string_form")
716 716
717 717 # Namespace
718 718 if info["namespace"] != "Interactive":
719 719 append_field(bundle, "Namespace", "namespace")
720 720
721 721 append_field(bundle, "Length", "length")
722 722 append_field(bundle, "File", "file")
723 723
724 724 # Source or docstring, depending on detail level and whether
725 725 # source found.
726 726 if detail_level > 0 and info["source"]:
727 727 append_field(bundle, "Source", "source", code_formatter)
728 728 else:
729 729 append_field(bundle, "Docstring", "docstring", formatter)
730 730
731 731 append_field(bundle, "Class docstring", "class_docstring", formatter)
732 732 append_field(bundle, "Init docstring", "init_docstring", formatter)
733 733 append_field(bundle, "Call docstring", "call_docstring", formatter)
734 734 return bundle
735 735
736 736
737 737 def _get_info(
738 738 self,
739 739 obj: Any,
740 740 oname: str = "",
741 741 formatter=None,
742 742 info: Optional[OInfo] = None,
743 743 detail_level=0,
744 744 omit_sections=(),
745 745 ) -> Bundle:
746 746 """Retrieve an info dict and format it.
747 747
748 748 Parameters
749 749 ----------
750 750 obj : any
751 751 Object to inspect and return info from
752 752 oname : str (default: ''):
753 753 Name of the variable pointing to `obj`.
754 754 formatter : callable
755 755 info
756 756 already computed information
757 757 detail_level : integer
758 758 Granularity of detail level, if set to 1, give more information.
759 759 omit_sections : container[str]
760 760 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
761 761 """
762 762
763 763 info_dict = self.info(obj, oname=oname, info=info, detail_level=detail_level)
764 764 bundle = self._make_info_unformatted(
765 765 obj,
766 766 info_dict,
767 767 formatter,
768 768 detail_level=detail_level,
769 769 omit_sections=omit_sections,
770 770 )
771 771 for key, hook in self.mime_hooks.items():
772 772 res = hook(obj, info)
773 773 if res is not None:
774 774 bundle[key] = res
775 775 return self.format_mime(bundle)
776 776
777 777 def pinfo(
778 778 self,
779 779 obj,
780 780 oname="",
781 781 formatter=None,
782 782 info: Optional[OInfo] = None,
783 783 detail_level=0,
784 784 enable_html_pager=True,
785 785 omit_sections=(),
786 786 ):
787 787 """Show detailed information about an object.
788 788
789 789 Optional arguments:
790 790
791 791 - oname: name of the variable pointing to the object.
792 792
793 793 - formatter: callable (optional)
794 794 A special formatter for docstrings.
795 795
796 796 The formatter is a callable that takes a string as an input
797 797 and returns either a formatted string or a mime type bundle
798 798 in the form of a dictionary.
799 799
800 800 Although the support of custom formatter returning a string
801 801 instead of a mime type bundle is deprecated.
802 802
803 803 - info: a structure with some information fields which may have been
804 804 precomputed already.
805 805
806 806 - detail_level: if set to 1, more information is given.
807 807
808 808 - omit_sections: set of section keys and titles to omit
809 809 """
810 810 assert info is not None
811 811 info_b: Bundle = self._get_info(
812 812 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
813 813 )
814 814 if not enable_html_pager:
815 815 del info_b["text/html"]
816 816 page.page(info_b)
817 817
818 818 def _info(self, obj, oname="", info=None, detail_level=0):
819 819 """
820 820 Inspector.info() was likely improperly marked as deprecated
821 821 while only a parameter was deprecated. We "un-deprecate" it.
822 822 """
823 823
824 824 warnings.warn(
825 825 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
826 826 "and the `formatter=` keyword removed. `Inspector._info` is now "
827 827 "an alias, and you can just call `.info()` directly.",
828 828 DeprecationWarning,
829 829 stacklevel=2,
830 830 )
831 831 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
832 832
833 833 def info(self, obj, oname="", info=None, detail_level=0) -> Dict[str, Any]:
834 834 """Compute a dict with detailed information about an object.
835 835
836 836 Parameters
837 837 ----------
838 838 obj : any
839 839 An object to find information about
840 840 oname : str (default: '')
841 841 Name of the variable pointing to `obj`.
842 842 info : (default: None)
843 843 A struct (dict like with attr access) with some information fields
844 844 which may have been precomputed already.
845 845 detail_level : int (default:0)
846 846 If set to 1, more information is given.
847 847
848 848 Returns
849 849 -------
850 850 An object info dict with known fields from `info_fields`. Keys are
851 851 strings, values are string or None.
852 852 """
853 853
854 854 if info is None:
855 855 ismagic = False
856 856 isalias = False
857 857 ospace = ''
858 858 else:
859 859 ismagic = info.ismagic
860 860 isalias = info.isalias
861 861 ospace = info.namespace
862 862
863 863 # Get docstring, special-casing aliases:
864 864 att_name = oname.split(".")[-1]
865 865 parents_docs = None
866 866 prelude = ""
867 867 if info and info.parent is not None and hasattr(info.parent, HOOK_NAME):
868 868 parents_docs_dict = getattr(info.parent, HOOK_NAME)
869 869 parents_docs = parents_docs_dict.get(att_name, None)
870 870 out = dict(
871 871 name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None
872 872 )
873 873
874 874 if parents_docs:
875 875 ds = parents_docs
876 876 elif isalias:
877 877 if not callable(obj):
878 878 try:
879 879 ds = "Alias to the system command:\n %s" % obj[1]
880 880 except:
881 881 ds = "Alias: " + str(obj)
882 882 else:
883 883 ds = "Alias to " + str(obj)
884 884 if obj.__doc__:
885 885 ds += "\nDocstring:\n" + obj.__doc__
886 886 else:
887 887 ds_or_None = getdoc(obj)
888 888 if ds_or_None is None:
889 889 ds = '<no docstring>'
890 890 else:
891 891 ds = ds_or_None
892 892
893 893 ds = prelude + ds
894 894
895 895 # store output in a dict, we initialize it here and fill it as we go
896 896
897 897 string_max = 200 # max size of strings to show (snipped if longer)
898 898 shalf = int((string_max - 5) / 2)
899 899
900 900 if ismagic:
901 901 out['type_name'] = 'Magic function'
902 902 elif isalias:
903 903 out['type_name'] = 'System alias'
904 904 else:
905 905 out['type_name'] = type(obj).__name__
906 906
907 907 try:
908 908 bclass = obj.__class__
909 909 out['base_class'] = str(bclass)
910 910 except:
911 911 pass
912 912
913 913 # String form, but snip if too long in ? form (full in ??)
914 914 if detail_level >= self.str_detail_level:
915 915 try:
916 916 ostr = str(obj)
917 917 str_head = 'string_form'
918 918 if not detail_level and len(ostr)>string_max:
919 919 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
920 920 ostr = ("\n" + " " * len(str_head.expandtabs())).\
921 921 join(q.strip() for q in ostr.split("\n"))
922 922 out[str_head] = ostr
923 923 except:
924 924 pass
925 925
926 926 if ospace:
927 927 out['namespace'] = ospace
928 928
929 929 # Length (for strings and lists)
930 930 try:
931 931 out['length'] = str(len(obj))
932 932 except Exception:
933 933 pass
934 934
935 935 # Filename where object was defined
936 936 binary_file = False
937 937 fname = find_file(obj)
938 938 if fname is None:
939 939 # if anything goes wrong, we don't want to show source, so it's as
940 940 # if the file was binary
941 941 binary_file = True
942 942 else:
943 943 if fname.endswith(('.so', '.dll', '.pyd')):
944 944 binary_file = True
945 945 elif fname.endswith('<string>'):
946 946 fname = 'Dynamically generated function. No source code available.'
947 947 out['file'] = compress_user(fname)
948 948
949 949 # Original source code for a callable, class or property.
950 950 if detail_level:
951 951 # Flush the source cache because inspect can return out-of-date
952 952 # source
953 953 linecache.checkcache()
954 954 try:
955 955 if isinstance(obj, property) or not binary_file:
956 956 src = getsource(obj, oname)
957 957 if src is not None:
958 958 src = src.rstrip()
959 959 out['source'] = src
960 960
961 961 except Exception:
962 962 pass
963 963
964 964 # Add docstring only if no source is to be shown (avoid repetitions).
965 965 if ds and not self._source_contains_docstring(out.get('source'), ds):
966 966 out['docstring'] = ds
967 967
968 968 # Constructor docstring for classes
969 969 if inspect.isclass(obj):
970 970 out['isclass'] = True
971 971
972 972 # get the init signature:
973 973 try:
974 974 init_def = self._getdef(obj, oname)
975 975 except AttributeError:
976 976 init_def = None
977 977
978 978 # get the __init__ docstring
979 979 try:
980 980 obj_init = obj.__init__
981 981 except AttributeError:
982 982 init_ds = None
983 983 else:
984 984 if init_def is None:
985 985 # Get signature from init if top-level sig failed.
986 986 # Can happen for built-in types (list, etc.).
987 987 try:
988 988 init_def = self._getdef(obj_init, oname)
989 989 except AttributeError:
990 990 pass
991 991 init_ds = getdoc(obj_init)
992 992 # Skip Python's auto-generated docstrings
993 993 if init_ds == _object_init_docstring:
994 994 init_ds = None
995 995
996 996 if init_def:
997 997 out['init_definition'] = init_def
998 998
999 999 if init_ds:
1000 1000 out['init_docstring'] = init_ds
1001 1001
1002 1002 names = [sub.__name__ for sub in type.__subclasses__(obj)]
1003 1003 if len(names) < 10:
1004 1004 all_names = ', '.join(names)
1005 1005 else:
1006 1006 all_names = ', '.join(names[:10]+['...'])
1007 1007 out['subclasses'] = all_names
1008 1008 # and class docstring for instances:
1009 1009 else:
1010 1010 # reconstruct the function definition and print it:
1011 1011 defln = self._getdef(obj, oname)
1012 1012 if defln:
1013 1013 out['definition'] = defln
1014 1014
1015 1015 # First, check whether the instance docstring is identical to the
1016 1016 # class one, and print it separately if they don't coincide. In
1017 1017 # most cases they will, but it's nice to print all the info for
1018 1018 # objects which use instance-customized docstrings.
1019 1019 if ds:
1020 1020 try:
1021 1021 cls = getattr(obj,'__class__')
1022 1022 except:
1023 1023 class_ds = None
1024 1024 else:
1025 1025 class_ds = getdoc(cls)
1026 1026 # Skip Python's auto-generated docstrings
1027 1027 if class_ds in _builtin_type_docstrings:
1028 1028 class_ds = None
1029 1029 if class_ds and ds != class_ds:
1030 1030 out['class_docstring'] = class_ds
1031 1031
1032 1032 # Next, try to show constructor docstrings
1033 1033 try:
1034 1034 init_ds = getdoc(obj.__init__)
1035 1035 # Skip Python's auto-generated docstrings
1036 1036 if init_ds == _object_init_docstring:
1037 1037 init_ds = None
1038 1038 except AttributeError:
1039 1039 init_ds = None
1040 1040 if init_ds:
1041 1041 out['init_docstring'] = init_ds
1042 1042
1043 1043 # Call form docstring for callable instances
1044 1044 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
1045 1045 call_def = self._getdef(obj.__call__, oname)
1046 1046 if call_def and (call_def != out.get('definition')):
1047 1047 # it may never be the case that call def and definition differ,
1048 1048 # but don't include the same signature twice
1049 1049 out['call_def'] = call_def
1050 1050 call_ds = getdoc(obj.__call__)
1051 1051 # Skip Python's auto-generated docstrings
1052 1052 if call_ds == _func_call_docstring:
1053 1053 call_ds = None
1054 1054 if call_ds:
1055 1055 out['call_docstring'] = call_ds
1056 1056
1057 1057 return object_info(**out)
1058 1058
1059 1059 @staticmethod
1060 1060 def _source_contains_docstring(src, doc):
1061 1061 """
1062 1062 Check whether the source *src* contains the docstring *doc*.
1063 1063
1064 1064 This is is helper function to skip displaying the docstring if the
1065 1065 source already contains it, avoiding repetition of information.
1066 1066 """
1067 1067 try:
1068 1068 (def_node,) = ast.parse(dedent(src)).body
1069 1069 return ast.get_docstring(def_node) == doc # type: ignore[arg-type]
1070 1070 except Exception:
1071 1071 # The source can become invalid or even non-existent (because it
1072 1072 # is re-fetched from the source file) so the above code fail in
1073 1073 # arbitrary ways.
1074 1074 return False
1075 1075
1076 1076 def psearch(self,pattern,ns_table,ns_search=[],
1077 1077 ignore_case=False,show_all=False, *, list_types=False):
1078 1078 """Search namespaces with wildcards for objects.
1079 1079
1080 1080 Arguments:
1081 1081
1082 1082 - pattern: string containing shell-like wildcards to use in namespace
1083 1083 searches and optionally a type specification to narrow the search to
1084 1084 objects of that type.
1085 1085
1086 1086 - ns_table: dict of name->namespaces for search.
1087 1087
1088 1088 Optional arguments:
1089 1089
1090 1090 - ns_search: list of namespace names to include in search.
1091 1091
1092 1092 - ignore_case(False): make the search case-insensitive.
1093 1093
1094 1094 - show_all(False): show all names, including those starting with
1095 1095 underscores.
1096 1096
1097 1097 - list_types(False): list all available object types for object matching.
1098 1098 """
1099 1099 #print 'ps pattern:<%r>' % pattern # dbg
1100 1100
1101 1101 # defaults
1102 1102 type_pattern = 'all'
1103 1103 filter = ''
1104 1104
1105 1105 # list all object types
1106 1106 if list_types:
1107 1107 page.page('\n'.join(sorted(typestr2type)))
1108 1108 return
1109 1109
1110 1110 cmds = pattern.split()
1111 1111 len_cmds = len(cmds)
1112 1112 if len_cmds == 1:
1113 1113 # Only filter pattern given
1114 1114 filter = cmds[0]
1115 1115 elif len_cmds == 2:
1116 1116 # Both filter and type specified
1117 1117 filter,type_pattern = cmds
1118 1118 else:
1119 1119 raise ValueError('invalid argument string for psearch: <%s>' %
1120 1120 pattern)
1121 1121
1122 1122 # filter search namespaces
1123 1123 for name in ns_search:
1124 1124 if name not in ns_table:
1125 1125 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1126 1126 (name,ns_table.keys()))
1127 1127
1128 1128 #print 'type_pattern:',type_pattern # dbg
1129 1129 search_result, namespaces_seen = set(), set()
1130 1130 for ns_name in ns_search:
1131 1131 ns = ns_table[ns_name]
1132 1132 # Normally, locals and globals are the same, so we just check one.
1133 1133 if id(ns) in namespaces_seen:
1134 1134 continue
1135 1135 namespaces_seen.add(id(ns))
1136 1136 tmp_res = list_namespace(ns, type_pattern, filter,
1137 1137 ignore_case=ignore_case, show_all=show_all)
1138 1138 search_result.update(tmp_res)
1139 1139
1140 1140 page.page('\n'.join(sorted(search_result)))
1141 1141
1142 1142
1143 1143 def _render_signature(obj_signature, obj_name) -> str:
1144 1144 """
1145 1145 This was mostly taken from inspect.Signature.__str__.
1146 1146 Look there for the comments.
1147 1147 The only change is to add linebreaks when this gets too long.
1148 1148 """
1149 1149 result = []
1150 1150 pos_only = False
1151 1151 kw_only = True
1152 1152 for param in obj_signature.parameters.values():
1153 1153 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1154 1154 pos_only = True
1155 1155 elif pos_only:
1156 1156 result.append('/')
1157 1157 pos_only = False
1158 1158
1159 1159 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1160 1160 kw_only = False
1161 1161 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1162 1162 result.append('*')
1163 1163 kw_only = False
1164 1164
1165 1165 result.append(str(param))
1166 1166
1167 1167 if pos_only:
1168 1168 result.append('/')
1169 1169
1170 1170 # add up name, parameters, braces (2), and commas
1171 1171 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1172 1172 # This doesn’t fit behind “Signature: ” in an inspect window.
1173 1173 rendered = '{}(\n{})'.format(obj_name, ''.join(
1174 1174 ' {},\n'.format(r) for r in result)
1175 1175 )
1176 1176 else:
1177 1177 rendered = '{}({})'.format(obj_name, ', '.join(result))
1178 1178
1179 1179 if obj_signature.return_annotation is not inspect._empty:
1180 1180 anno = inspect.formatannotation(obj_signature.return_annotation)
1181 1181 rendered += ' -> {}'.format(anno)
1182 1182
1183 1183 return rendered
General Comments 0
You need to be logged in to leave comments. Login now