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