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