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