##// END OF EJS Templates
Merge pull request #1845 from fperez/magic_inspect...
Fernando Perez -
r7433:44b81218 merge
parent child Browse files
Show More
@@ -79,7 +79,7 b' class NamespaceMagics(Magics):'
79 In [3]: %pdef urllib.urlopen
79 In [3]: %pdef urllib.urlopen
80 urllib.urlopen(url, data=None, proxies=None)
80 urllib.urlopen(url, data=None, proxies=None)
81 """
81 """
82 self._inspect('pdef',parameter_s, namespaces)
82 self.shell._inspect('pdef',parameter_s, namespaces)
83
83
84 @line_magic
84 @line_magic
85 def pdoc(self, parameter_s='', namespaces=None):
85 def pdoc(self, parameter_s='', namespaces=None):
@@ -87,12 +87,12 b' class NamespaceMagics(Magics):'
87
87
88 If the given object is a class, it will print both the class and the
88 If the given object is a class, it will print both the class and the
89 constructor docstrings."""
89 constructor docstrings."""
90 self._inspect('pdoc',parameter_s, namespaces)
90 self.shell._inspect('pdoc',parameter_s, namespaces)
91
91
92 @line_magic
92 @line_magic
93 def psource(self, parameter_s='', namespaces=None):
93 def psource(self, parameter_s='', namespaces=None):
94 """Print (or run through pager) the source code for an object."""
94 """Print (or run through pager) the source code for an object."""
95 self._inspect('psource',parameter_s, namespaces)
95 self.shell._inspect('psource',parameter_s, namespaces)
96
96
97 @line_magic
97 @line_magic
98 def pfile(self, parameter_s=''):
98 def pfile(self, parameter_s=''):
@@ -108,7 +108,7 b' class NamespaceMagics(Magics):'
108 viewer."""
108 viewer."""
109
109
110 # first interpret argument as an object name
110 # first interpret argument as an object name
111 out = self._inspect('pfile',parameter_s)
111 out = self.shell._inspect('pfile',parameter_s)
112 # if not, try the input as a filename
112 # if not, try the input as a filename
113 if out == 'not found':
113 if out == 'not found':
114 try:
114 try:
@@ -230,6 +230,75 b' def call_tip(oinfo, format_call=True):'
230 return call_line, doc
230 return call_line, doc
231
231
232
232
233 def find_file(obj):
234 """Find the absolute path to the file where an object was defined.
235
236 This is essentially a robust wrapper around `inspect.getabsfile`.
237
238 Returns None if no file can be found.
239
240 Parameters
241 ----------
242 obj : any Python object
243
244 Returns
245 -------
246 fname : str
247 The absolute path to the file where the object was defined.
248 """
249 # get source if obj was decorated with @decorator
250 if hasattr(obj, '__wrapped__'):
251 obj = obj.__wrapped__
252
253 try:
254 fname = inspect.getabsfile(obj)
255 except TypeError:
256 # For an instance, the file that matters is where its class was
257 # declared.
258 if hasattr(obj, '__class__'):
259 try:
260 fname = inspect.getabsfile(obj.__class__)
261 except TypeError:
262 # Can happen for builtins
263 fname = None
264 except:
265 fname = None
266 return fname
267
268
269 def find_source_lines(obj):
270 """Find the line number in a file where an object was defined.
271
272 This is essentially a robust wrapper around `inspect.getsourcelines`.
273
274 Returns None if no file can be found.
275
276 Parameters
277 ----------
278 obj : any Python object
279
280 Returns
281 -------
282 lineno : int
283 The line number where the object definition starts.
284 """
285 # get source if obj was decorated with @decorator
286 if hasattr(obj, '__wrapped__'):
287 obj = obj.__wrapped__
288
289 try:
290 try:
291 lineno = inspect.getsourcelines(obj)[1]
292 except TypeError:
293 # For instances, try the class object like getsource() does
294 if hasattr(obj, '__class__'):
295 lineno = inspect.getsourcelines(obj.__class__)[1]
296 except:
297 return None
298
299 return lineno
300
301
233 class Inspector:
302 class Inspector:
234 def __init__(self, color_table=InspectColors,
303 def __init__(self, color_table=InspectColors,
235 code_color_table=PyColorize.ANSICodeColors,
304 code_color_table=PyColorize.ANSICodeColors,
@@ -259,11 +328,11 b' class Inspector:'
259 return '%s%s%s' % (self.color_table.active_colors.header,h,
328 return '%s%s%s' % (self.color_table.active_colors.header,h,
260 self.color_table.active_colors.normal)
329 self.color_table.active_colors.normal)
261
330
262 def set_active_scheme(self,scheme):
331 def set_active_scheme(self, scheme):
263 self.color_table.set_active_scheme(scheme)
332 self.color_table.set_active_scheme(scheme)
264 self.parser.color_table.set_active_scheme(scheme)
333 self.parser.color_table.set_active_scheme(scheme)
265
334
266 def noinfo(self,msg,oname):
335 def noinfo(self, msg, oname):
267 """Generic message when no information is found."""
336 """Generic message when no information is found."""
268 print 'No %s found' % msg,
337 print 'No %s found' % msg,
269 if oname:
338 if oname:
@@ -271,7 +340,7 b' class Inspector:'
271 else:
340 else:
272 print
341 print
273
342
274 def pdef(self,obj,oname=''):
343 def pdef(self, obj, oname=''):
275 """Print the definition header for any callable object.
344 """Print the definition header for any callable object.
276
345
277 If the object is a class, print the constructor information."""
346 If the object is a class, print the constructor information."""
@@ -366,28 +435,18 b' class Inspector:'
366 else:
435 else:
367 page.page(self.format(py3compat.unicode_to_str(src)))
436 page.page(self.format(py3compat.unicode_to_str(src)))
368
437
369 def pfile(self,obj,oname=''):
438 def pfile(self, obj, oname=''):
370 """Show the whole file where an object was defined."""
439 """Show the whole file where an object was defined."""
371
440
372 try:
441 lineno = find_source_lines(obj)
373 try:
442 if lineno is None:
374 lineno = inspect.getsourcelines(obj)[1]
443 self.noinfo('file', oname)
375 except TypeError:
376 # For instances, try the class object like getsource() does
377 if hasattr(obj,'__class__'):
378 lineno = inspect.getsourcelines(obj.__class__)[1]
379 # Adjust the inspected object so getabsfile() below works
380 obj = obj.__class__
381 except:
382 self.noinfo('file',oname)
383 return
444 return
384
445
385 # We only reach this point if object was successfully queried
446 ofile = find_file(obj)
386
447 # run contents of file through pager starting at line where the object
387 # run contents of file through pager starting at line
448 # is defined, as long as the file isn't binary and is actually on the
388 # where the object is defined
449 # filesystem.
389 ofile = inspect.getabsfile(obj)
390
391 if ofile.endswith(('.so', '.dll', '.pyd')):
450 if ofile.endswith(('.so', '.dll', '.pyd')):
392 print 'File %r is binary, not printing.' % ofile
451 print 'File %r is binary, not printing.' % ofile
393 elif not os.path.isfile(ofile):
452 elif not os.path.isfile(ofile):
@@ -396,7 +455,7 b' class Inspector:'
396 # Print only text files, not extension binaries. Note that
455 # Print only text files, not extension binaries. Note that
397 # getsourcelines returns lineno with 1-offset and page() uses
456 # getsourcelines returns lineno with 1-offset and page() uses
398 # 0-offset, so we must adjust.
457 # 0-offset, so we must adjust.
399 page.page(self.format(open(ofile).read()),lineno-1)
458 page.page(self.format(open(ofile).read()), lineno-1)
400
459
401 def _format_fields(self, fields, title_width=12):
460 def _format_fields(self, fields, title_width=12):
402 """Formats a list of fields for display.
461 """Formats a list of fields for display.
@@ -570,24 +629,18 b' class Inspector:'
570
629
571 # Filename where object was defined
630 # Filename where object was defined
572 binary_file = False
631 binary_file = False
573 try:
632 fname = find_file(obj)
574 try:
633 if fname is None:
575 fname = inspect.getabsfile(obj)
576 except TypeError:
577 # For an instance, the file that matters is where its class was
578 # declared.
579 if hasattr(obj,'__class__'):
580 fname = inspect.getabsfile(obj.__class__)
581 if fname.endswith('<string>'):
582 fname = 'Dynamically generated function. No source code available.'
583 if fname.endswith(('.so', '.dll', '.pyd')):
584 binary_file = True
585 out['file'] = fname
586 except:
587 # if anything goes wrong, we don't want to show source, so it's as
634 # if anything goes wrong, we don't want to show source, so it's as
588 # if the file was binary
635 # if the file was binary
589 binary_file = True
636 binary_file = True
590
637 else:
638 if fname.endswith(('.so', '.dll', '.pyd')):
639 binary_file = True
640 elif fname.endswith('<string>'):
641 fname = 'Dynamically generated function. No source code available.'
642 out['file'] = fname
643
591 # reconstruct the function definition and print it:
644 # reconstruct the function definition and print it:
592 defln = self._getdef(obj, oname)
645 defln = self._getdef(obj, oname)
593 if defln:
646 if defln:
@@ -606,10 +659,10 b' class Inspector:'
606 source = None
659 source = None
607 try:
660 try:
608 try:
661 try:
609 source = getsource(obj,binary_file)
662 source = getsource(obj, binary_file)
610 except TypeError:
663 except TypeError:
611 if hasattr(obj,'__class__'):
664 if hasattr(obj, '__class__'):
612 source = getsource(obj.__class__,binary_file)
665 source = getsource(obj.__class__, binary_file)
613 if source is not None:
666 if source is not None:
614 out['source'] = source.rstrip()
667 out['source'] = source.rstrip()
615 except Exception:
668 except Exception:
@@ -14,6 +14,7 b''
14 from __future__ import print_function
14 from __future__ import print_function
15
15
16 # Stdlib imports
16 # Stdlib imports
17 import os
17
18
18 # Third-party imports
19 # Third-party imports
19 import nose.tools as nt
20 import nose.tools as nt
@@ -24,8 +25,10 b' from IPython.core.magic import (Magics, magics_class, line_magic,'
24 cell_magic, line_cell_magic,
25 cell_magic, line_cell_magic,
25 register_line_magic, register_cell_magic,
26 register_line_magic, register_cell_magic,
26 register_line_cell_magic)
27 register_line_cell_magic)
28 from IPython.external.decorator import decorator
27 from IPython.utils import py3compat
29 from IPython.utils import py3compat
28
30
31
29 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
30 # Globals and constants
33 # Globals and constants
31 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
@@ -37,6 +40,58 b' ip = get_ipython()'
37 # Local utilities
40 # Local utilities
38 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
39
42
43 # WARNING: since this test checks the line number where a function is
44 # defined, if any code is inserted above, the following line will need to be
45 # updated. Do NOT insert any whitespace between the next line and the function
46 # definition below.
47 THIS_LINE_NUMBER = 47 # Put here the actual number of this line
48 def test_find_source_lines():
49 nt.assert_equal(oinspect.find_source_lines(test_find_source_lines),
50 THIS_LINE_NUMBER+1)
51
52
53 def test_find_file():
54 nt.assert_equal(oinspect.find_file(test_find_file),
55 os.path.abspath(__file__))
56
57
58 def test_find_file_decorated1():
59
60 @decorator
61 def noop1(f):
62 def wrapper():
63 return f(*a, **kw)
64 return wrapper
65
66 @noop1
67 def f(x):
68 "My docstring"
69
70 nt.assert_equal(oinspect.find_file(f),
71 os.path.abspath(__file__))
72 nt.assert_equal(f.__doc__, "My docstring")
73
74
75 def test_find_file_decorated2():
76
77 @decorator
78 def noop2(f, *a, **kw):
79 return f(*a, **kw)
80
81 @noop2
82 def f(x):
83 "My docstring 2"
84
85 nt.assert_equal(oinspect.find_file(f),
86 os.path.abspath(__file__))
87 nt.assert_equal(f.__doc__, "My docstring 2")
88
89
90 def test_find_file_magic():
91 run = ip.find_line_magic('run')
92 nt.assert_not_equal(oinspect.find_file(run), None)
93
94
40 # A few generic objects we can then inspect in the tests below
95 # A few generic objects we can then inspect in the tests below
41
96
42 class Call(object):
97 class Call(object):
General Comments 0
You need to be logged in to leave comments. Login now