Show More
@@ -0,0 +1,89 b'' | |||
|
1 | """Tests for the object inspection functionality. | |
|
2 | """ | |
|
3 | #----------------------------------------------------------------------------- | |
|
4 | # Copyright (C) 2010 The IPython Development Team. | |
|
5 | # | |
|
6 | # Distributed under the terms of the BSD License. | |
|
7 | # | |
|
8 | # The full license is in the file COPYING.txt, distributed with this software. | |
|
9 | #----------------------------------------------------------------------------- | |
|
10 | ||
|
11 | #----------------------------------------------------------------------------- | |
|
12 | # Imports | |
|
13 | #----------------------------------------------------------------------------- | |
|
14 | from __future__ import print_function | |
|
15 | ||
|
16 | # Stdlib imports | |
|
17 | ||
|
18 | # Third-party imports | |
|
19 | import nose.tools as nt | |
|
20 | ||
|
21 | # Our own imports | |
|
22 | from .. import oinspect | |
|
23 | ||
|
24 | #----------------------------------------------------------------------------- | |
|
25 | # Globals and constants | |
|
26 | #----------------------------------------------------------------------------- | |
|
27 | ||
|
28 | inspector = oinspect.Inspector() | |
|
29 | ||
|
30 | #----------------------------------------------------------------------------- | |
|
31 | # Local utilities | |
|
32 | #----------------------------------------------------------------------------- | |
|
33 | ||
|
34 | # A few generic objects we can then inspect in the tests below | |
|
35 | ||
|
36 | class Call(object): | |
|
37 | """This is the class docstring.""" | |
|
38 | ||
|
39 | def __init__(self, x, y=1): | |
|
40 | """This is the constructor docstring.""" | |
|
41 | ||
|
42 | def __call__(self, *a, **kw): | |
|
43 | """This is the call docstring.""" | |
|
44 | ||
|
45 | def method(self, x, z=2): | |
|
46 | """Some method's docstring""" | |
|
47 | ||
|
48 | def f(x, y=2, *a, **kw): | |
|
49 | """A simple function.""" | |
|
50 | ||
|
51 | def g(y, z=3, *a, **kw): | |
|
52 | pass # no docstring | |
|
53 | ||
|
54 | ||
|
55 | def check_calltip(obj, name, call, docstring): | |
|
56 | """Generic check pattern all calltip tests will use""" | |
|
57 | info = inspector.info(obj, name) | |
|
58 | call_line, ds = oinspect.call_tip(info) | |
|
59 | nt.assert_equal(call_line, call) | |
|
60 | nt.assert_equal(ds, docstring) | |
|
61 | ||
|
62 | #----------------------------------------------------------------------------- | |
|
63 | # Tests | |
|
64 | #----------------------------------------------------------------------------- | |
|
65 | ||
|
66 | def test_calltip_class(): | |
|
67 | check_calltip(Call, 'Call', 'Call(x, y=1)', Call.__init__.__doc__) | |
|
68 | ||
|
69 | ||
|
70 | def test_calltip_instance(): | |
|
71 | c = Call(1) | |
|
72 | check_calltip(c, 'c', 'c(*a, **kw)', c.__call__.__doc__) | |
|
73 | ||
|
74 | ||
|
75 | def test_calltip_method(): | |
|
76 | c = Call(1) | |
|
77 | check_calltip(c.method, 'c.method', 'c.method(x, z=2)', c.method.__doc__) | |
|
78 | ||
|
79 | ||
|
80 | def test_calltip_function(): | |
|
81 | check_calltip(f, 'f', 'f(x, y=2, *a, **kw)', f.__doc__) | |
|
82 | ||
|
83 | ||
|
84 | def test_calltip_function2(): | |
|
85 | check_calltip(g, 'g', 'g(y, z=3, *a, **kw)', '<no docstring>') | |
|
86 | ||
|
87 | ||
|
88 | def test_calltip_builtin(): | |
|
89 | check_calltip(sum, 'sum', None, sum.__doc__) |
@@ -1196,9 +1196,10 b' class InteractiveShell(Configurable, Magic):' | |||
|
1196 | 1196 | def object_inspect(self, oname): |
|
1197 | 1197 | info = self._object_find(oname) |
|
1198 | 1198 | if info.found: |
|
1199 | return self.inspector.info(info.obj, info=info) | |
|
1199 | return self.inspector.info(info.obj, oname, info=info) | |
|
1200 | 1200 | else: |
|
1201 |
return oinspect.mk_object_info({' |
|
|
1201 | return oinspect.mk_object_info({'name' : oname, | |
|
1202 | 'found' : False}) | |
|
1202 | 1203 | |
|
1203 | 1204 | #------------------------------------------------------------------------- |
|
1204 | 1205 | # Things related to history management |
@@ -76,18 +76,15 b" info_fields = ['type_name', 'base_class', 'string_form', 'namespace'," | |||
|
76 | 76 | 'call_def', 'call_docstring', |
|
77 | 77 | # These won't be printed but will be used to determine how to |
|
78 | 78 | # format the object |
|
79 | 'ismagic', 'isalias', 'argspec', 'found', | |
|
79 | 'ismagic', 'isalias', 'argspec', 'found', 'name', | |
|
80 | 80 | ] |
|
81 | 81 | |
|
82 | 82 | |
|
83 | ObjectInfo = namedtuple('ObjectInfo', info_fields) | |
|
84 | ||
|
85 | ||
|
86 | def mk_object_info(kw): | |
|
87 | """Make a f""" | |
|
83 | def object_info(**kw): | |
|
84 | """Make an object info dict with all fields present.""" | |
|
88 | 85 | infodict = dict(izip_longest(info_fields, [None])) |
|
89 | 86 | infodict.update(kw) |
|
90 |
return |
|
|
87 | return infodict | |
|
91 | 88 | |
|
92 | 89 | |
|
93 | 90 | def getdoc(obj): |
@@ -161,11 +158,76 b' def getargspec(obj):' | |||
|
161 | 158 | func_obj = obj |
|
162 | 159 | elif inspect.ismethod(obj): |
|
163 | 160 | func_obj = obj.im_func |
|
161 | elif hasattr(obj, '__call__'): | |
|
162 | func_obj = obj.__call__ | |
|
164 | 163 | else: |
|
165 | 164 | raise TypeError('arg is not a Python function') |
|
166 | 165 | args, varargs, varkw = inspect.getargs(func_obj.func_code) |
|
167 | 166 | return args, varargs, varkw, func_obj.func_defaults |
|
168 | 167 | |
|
168 | ||
|
169 | def format_argspec(argspec): | |
|
170 | """Format argspect, convenience wrapper around inspect's. | |
|
171 | ||
|
172 | This takes a dict instead of ordered arguments and calls | |
|
173 | inspect.format_argspec with the arguments in the necessary order. | |
|
174 | """ | |
|
175 | return inspect.formatargspec(argspec['args'], argspec['varargs'], | |
|
176 | argspec['varkw'], argspec['defaults']) | |
|
177 | ||
|
178 | ||
|
179 | def call_tip(oinfo, format_call=True): | |
|
180 | """Extract call tip data from an oinfo dict. | |
|
181 | ||
|
182 | Parameters | |
|
183 | ---------- | |
|
184 | oinfo : dict | |
|
185 | ||
|
186 | format_call : bool, optional | |
|
187 | If True, the call line is formatted and returned as a string. If not, a | |
|
188 | tuple of (name, argspec) is returned. | |
|
189 | ||
|
190 | Returns | |
|
191 | ------- | |
|
192 | call_info : None, str or (str, dict) tuple. | |
|
193 | When format_call is True, the whole call information is formattted as a | |
|
194 | single string. Otherwise, the object's name and its argspec dict are | |
|
195 | returned. If no call information is available, None is returned. | |
|
196 | ||
|
197 | docstring : str or None | |
|
198 | The most relevant docstring for calling purposes is returned, if | |
|
199 | available. The priority is: call docstring for callable instances, then | |
|
200 | constructor docstring for classes, then main object's docstring otherwise | |
|
201 | (regular functions). | |
|
202 | """ | |
|
203 | # Get call definition | |
|
204 | argspec = oinfo['argspec'] | |
|
205 | if argspec is None: | |
|
206 | call_line = None | |
|
207 | else: | |
|
208 | # Callable objects will have 'self' as their first argument, prune | |
|
209 | # it out if it's there for clarity (since users do *not* pass an | |
|
210 | # extra first argument explicitly). | |
|
211 | try: | |
|
212 | has_self = argspec['args'][0] == 'self' | |
|
213 | except (KeyError, IndexError): | |
|
214 | pass | |
|
215 | else: | |
|
216 | if has_self: | |
|
217 | argspec['args'] = argspec['args'][1:] | |
|
218 | ||
|
219 | call_line = oinfo['name']+format_argspec(argspec) | |
|
220 | ||
|
221 | # Now get docstring. | |
|
222 | # The priority is: call docstring, constructor docstring, main one. | |
|
223 | doc = oinfo['call_docstring'] | |
|
224 | if doc is None: | |
|
225 | doc = oinfo['init_docstring'] | |
|
226 | if doc is None: | |
|
227 | doc = oinfo['docstring'] | |
|
228 | ||
|
229 | return call_line, doc | |
|
230 | ||
|
169 | 231 | #**************************************************************************** |
|
170 | 232 | # Class definitions |
|
171 | 233 | |
@@ -178,7 +240,9 b' class myStringIO(StringIO.StringIO):' | |||
|
178 | 240 | |
|
179 | 241 | |
|
180 | 242 | class Inspector: |
|
181 |
def __init__(self,color_table |
|
|
243 | def __init__(self, color_table=InspectColors, | |
|
244 | code_color_table=PyColorize.ANSICodeColors, | |
|
245 | scheme='NoColor', | |
|
182 | 246 | str_detail_level=0): |
|
183 | 247 | self.color_table = color_table |
|
184 | 248 | self.parser = PyColorize.Parser(code_color_table,out='str') |
@@ -565,6 +629,7 b' class Inspector:' | |||
|
565 | 629 | ismagic = info.ismagic |
|
566 | 630 | isalias = info.isalias |
|
567 | 631 | ospace = info.namespace |
|
632 | ||
|
568 | 633 | # Get docstring, special-casing aliases: |
|
569 | 634 | if isalias: |
|
570 | 635 | if not callable(obj): |
@@ -583,9 +648,8 b' class Inspector:' | |||
|
583 | 648 | if formatter is not None: |
|
584 | 649 | ds = formatter(ds) |
|
585 | 650 | |
|
586 |
# store output in a dict, we |
|
|
587 | # initialize it here and fill it as we go | |
|
588 | out = dict(found=True, isalias=isalias, ismagic=ismagic) | |
|
651 | # store output in a dict, we initialize it here and fill it as we go | |
|
652 | out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic) | |
|
589 | 653 | |
|
590 | 654 | string_max = 200 # max size of strings to show (snipped if longer) |
|
591 | 655 | shalf = int((string_max -5)/2) |
@@ -650,17 +714,14 b' class Inspector:' | |||
|
650 | 714 | binary_file = True |
|
651 | 715 | |
|
652 | 716 | # reconstruct the function definition and print it: |
|
653 | defln = self._getdef(obj,oname) | |
|
717 | defln = self._getdef(obj, oname) | |
|
654 | 718 | if defln: |
|
655 | 719 | out['definition'] = self.format(defln) |
|
656 | args, varargs, varkw, func_defaults = getargspec(obj) | |
|
657 | out['argspec'] = dict(args=args, varargs=varargs, | |
|
658 | varkw=varkw, func_defaults=func_defaults) | |
|
659 | ||
|
720 | ||
|
660 | 721 | # Docstrings only in detail 0 mode, since source contains them (we |
|
661 | 722 | # avoid repetitions). If source fails, we add them back, see below. |
|
662 | 723 | if ds and detail_level == 0: |
|
663 |
out['docstring'] = |
|
|
724 | out['docstring'] = ds | |
|
664 | 725 | |
|
665 | 726 | # Original source code for any callable |
|
666 | 727 | if detail_level: |
@@ -700,11 +761,11 b' class Inspector:' | |||
|
700 | 761 | if init_def: |
|
701 | 762 | out['init_definition'] = self.format(init_def) |
|
702 | 763 | if init_ds: |
|
703 |
out['init_docstring'] = |
|
|
764 | out['init_docstring'] = init_ds | |
|
765 | ||
|
704 | 766 | # and class docstring for instances: |
|
705 | 767 | elif obj_type is types.InstanceType or \ |
|
706 | isinstance(obj,object): | |
|
707 | ||
|
768 | isinstance(obj, object): | |
|
708 | 769 | # First, check whether the instance docstring is identical to the |
|
709 | 770 | # class one, and print it separately if they don't coincide. In |
|
710 | 771 | # most cases they will, but it's nice to print all the info for |
@@ -723,7 +784,7 b' class Inspector:' | |||
|
723 | 784 | class_ds.startswith('module(name[,') ): |
|
724 | 785 | class_ds = None |
|
725 | 786 | if class_ds and ds != class_ds: |
|
726 |
out['class_docstring'] = |
|
|
787 | out['class_docstring'] = class_ds | |
|
727 | 788 | |
|
728 | 789 | # Next, try to show constructor docstrings |
|
729 | 790 | try: |
@@ -735,11 +796,11 b' class Inspector:' | |||
|
735 | 796 | except AttributeError: |
|
736 | 797 | init_ds = None |
|
737 | 798 | if init_ds: |
|
738 |
out['init_docstring'] = |
|
|
799 | out['init_docstring'] = init_ds | |
|
739 | 800 | |
|
740 | 801 | # Call form docstring for callable instances |
|
741 | if hasattr(obj,'__call__'): | |
|
742 | call_def = self._getdef(obj.__call__,oname) | |
|
802 | if hasattr(obj, '__call__'): | |
|
803 | call_def = self._getdef(obj.__call__, oname) | |
|
743 | 804 | if call_def is not None: |
|
744 | 805 | out['call_def'] = self.format(call_def) |
|
745 | 806 | call_ds = getdoc(obj.__call__) |
@@ -747,9 +808,30 b' class Inspector:' | |||
|
747 | 808 | if call_ds and call_ds.startswith('x.__call__(...) <==> x(...)'): |
|
748 | 809 | call_ds = None |
|
749 | 810 | if call_ds: |
|
750 |
out['call_docstring'] = |
|
|
811 | out['call_docstring'] = call_ds | |
|
812 | ||
|
813 | # Compute the object's argspec as a callable. The key is to decide | |
|
814 | # whether to pull it from the object itself, from its __init__ or | |
|
815 | # from its __call__ method. | |
|
816 | ||
|
817 | if inspect.isclass(obj): | |
|
818 | callable_obj = obj.__init__ | |
|
819 | elif callable(obj): | |
|
820 | callable_obj = obj | |
|
821 | else: | |
|
822 | callable_obj = None | |
|
823 | ||
|
824 | if callable_obj: | |
|
825 | try: | |
|
826 | args, varargs, varkw, defaults = getargspec(callable_obj) | |
|
827 | except (TypeError, AttributeError): | |
|
828 | # For extensions/builtins we can't retrieve the argspec | |
|
829 | pass | |
|
830 | else: | |
|
831 | out['argspec'] = dict(args=args, varargs=varargs, | |
|
832 | varkw=varkw, defaults=defaults) | |
|
751 | 833 | |
|
752 |
return |
|
|
834 | return object_info(**out) | |
|
753 | 835 | |
|
754 | 836 | |
|
755 | 837 | def psearch(self,pattern,ns_table,ns_search=[], |
@@ -122,15 +122,20 b' class CallTipWidget(QtGui.QLabel):' | |||
|
122 | 122 | # 'CallTipWidget' interface |
|
123 | 123 | #-------------------------------------------------------------------------- |
|
124 | 124 | |
|
125 |
def show_ |
|
|
126 |
""" Attempts to show the specified docstring at the |
|
|
127 |
location. The docstring is |
|
|
125 | def show_call_info(self, call_line=None, doc=None, maxlines=20): | |
|
126 | """ Attempts to show the specified call line and docstring at the | |
|
127 | current cursor location. The docstring is possibly truncated for | |
|
128 | 128 | length. |
|
129 | 129 | """ |
|
130 | doc = dedent(doc.rstrip()).lstrip() | |
|
131 | match = re.match("(?:[^\n]*\n){%i}" % maxlines, doc) | |
|
132 | if match: | |
|
133 | doc = doc[:match.end()] + '\n[Documentation continues...]' | |
|
130 | if doc: | |
|
131 | match = re.match("(?:[^\n]*\n){%i}" % maxlines, doc) | |
|
132 | if match: | |
|
133 | doc = doc[:match.end()] + '\n[Documentation continues...]' | |
|
134 | else: | |
|
135 | doc = '' | |
|
136 | ||
|
137 | if call_line: | |
|
138 | doc = '\n\n'.join([call_line, doc]) | |
|
134 | 139 | return self.show_tip(doc) |
|
135 | 140 | |
|
136 | 141 | def show_tip(self, tip): |
@@ -10,6 +10,7 b' from PyQt4 import QtCore, QtGui' | |||
|
10 | 10 | |
|
11 | 11 | # Local imports |
|
12 | 12 | from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt |
|
13 | from IPython.core.oinspect import call_tip | |
|
13 | 14 | from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin |
|
14 | 15 | from IPython.utils.traitlets import Bool |
|
15 | 16 | from bracket_matcher import BracketMatcher |
@@ -334,9 +335,13 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||
|
334 | 335 | info = self._request_info.get('call_tip') |
|
335 | 336 | if info and info.id == rep['parent_header']['msg_id'] and \ |
|
336 | 337 | info.pos == cursor.position(): |
|
337 | doc = rep['content']['docstring'] | |
|
338 | if doc: | |
|
339 | self._call_tip_widget.show_docstring(doc) | |
|
338 | # Get the information for a call tip. For now we format the call | |
|
339 | # line as string, later we can pass False to format_call and | |
|
340 | # syntax-highlight it ourselves for nicer formatting in the | |
|
341 | # calltip. | |
|
342 | call_info, doc = call_tip(rep['content'], format_call=True) | |
|
343 | if call_info or doc: | |
|
344 | self._call_tip_widget.show_call_info(call_info, doc) | |
|
340 | 345 | |
|
341 | 346 | def _handle_pyout(self, msg): |
|
342 | 347 | """ Handle display hook output. |
@@ -303,9 +303,8 b' class Kernel(Configurable):' | |||
|
303 | 303 | |
|
304 | 304 | def object_info_request(self, ident, parent): |
|
305 | 305 | object_info = self.shell.object_inspect(parent['content']['oname']) |
|
306 |
# Before we send this object over, we |
|
|
307 | # it for JSON usage | |
|
308 | oinfo = json_clean(object_info._asdict()) | |
|
306 | # Before we send this object over, we scrub it for JSON usage | |
|
307 | oinfo = json_clean(object_info) | |
|
309 | 308 | msg = self.session.send(self.reply_socket, 'object_info_reply', |
|
310 | 309 | oinfo, parent, ident) |
|
311 | 310 | io.raw_print(msg) |
@@ -462,6 +462,9 b' field names that IPython prints at the terminal.' | |||
|
462 | 462 | Message type: ``object_info_reply``:: |
|
463 | 463 | |
|
464 | 464 | content = { |
|
465 | # The name the object was requested under | |
|
466 | 'name' : str, | |
|
467 | ||
|
465 | 468 | # Boolean flag indicating whether the named object was found or not. If |
|
466 | 469 | # it's false, all other fields will be empty. |
|
467 | 470 | 'found' : bool, |
@@ -511,7 +514,7 b' Message type: ``object_info_reply``::' | |||
|
511 | 514 | # that these must be matched *in reverse* with the 'args' |
|
512 | 515 | # list above, since the first positional args have no default |
|
513 | 516 | # value at all. |
|
514 |
|
|
|
517 | defaults : list, | |
|
515 | 518 | }, |
|
516 | 519 | |
|
517 | 520 | # For instances, provide the constructor signature (the definition of |
General Comments 0
You need to be logged in to leave comments.
Login now