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