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