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