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