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