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