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 | def object_inspect(self, oname): |
|
1196 | def object_inspect(self, oname): | |
1197 | info = self._object_find(oname) |
|
1197 | info = self._object_find(oname) | |
1198 | if info.found: |
|
1198 | if info.found: | |
1199 | return self.inspector.info(info.obj, info=info) |
|
1199 | return self.inspector.info(info.obj, oname, info=info) | |
1200 | else: |
|
1200 | else: | |
1201 |
return oinspect.mk_object_info({' |
|
1201 | return oinspect.mk_object_info({'name' : oname, | |
|
1202 | 'found' : False}) | |||
1202 |
|
1203 | |||
1203 | #------------------------------------------------------------------------- |
|
1204 | #------------------------------------------------------------------------- | |
1204 | # Things related to history management |
|
1205 | # Things related to history management |
@@ -76,18 +76,15 b" info_fields = ['type_name', 'base_class', 'string_form', 'namespace'," | |||||
76 | 'call_def', 'call_docstring', |
|
76 | 'call_def', 'call_docstring', | |
77 | # These won't be printed but will be used to determine how to |
|
77 | # These won't be printed but will be used to determine how to | |
78 | # format the object |
|
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) |
|
83 | def object_info(**kw): | |
84 |
|
84 | """Make an object info dict with all fields present.""" | ||
85 |
|
||||
86 | def mk_object_info(kw): |
|
|||
87 | """Make a f""" |
|
|||
88 | infodict = dict(izip_longest(info_fields, [None])) |
|
85 | infodict = dict(izip_longest(info_fields, [None])) | |
89 | infodict.update(kw) |
|
86 | infodict.update(kw) | |
90 |
return |
|
87 | return infodict | |
91 |
|
88 | |||
92 |
|
89 | |||
93 | def getdoc(obj): |
|
90 | def getdoc(obj): | |
@@ -161,11 +158,76 b' def getargspec(obj):' | |||||
161 | func_obj = obj |
|
158 | func_obj = obj | |
162 | elif inspect.ismethod(obj): |
|
159 | elif inspect.ismethod(obj): | |
163 | func_obj = obj.im_func |
|
160 | func_obj = obj.im_func | |
|
161 | elif hasattr(obj, '__call__'): | |||
|
162 | func_obj = obj.__call__ | |||
164 | else: |
|
163 | else: | |
165 | raise TypeError('arg is not a Python function') |
|
164 | raise TypeError('arg is not a Python function') | |
166 | args, varargs, varkw = inspect.getargs(func_obj.func_code) |
|
165 | args, varargs, varkw = inspect.getargs(func_obj.func_code) | |
167 | return args, varargs, varkw, func_obj.func_defaults |
|
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 | # Class definitions |
|
232 | # Class definitions | |
171 |
|
233 | |||
@@ -178,7 +240,9 b' class myStringIO(StringIO.StringIO):' | |||||
178 |
|
240 | |||
179 |
|
241 | |||
180 | class Inspector: |
|
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 | str_detail_level=0): |
|
246 | str_detail_level=0): | |
183 | self.color_table = color_table |
|
247 | self.color_table = color_table | |
184 | self.parser = PyColorize.Parser(code_color_table,out='str') |
|
248 | self.parser = PyColorize.Parser(code_color_table,out='str') | |
@@ -565,6 +629,7 b' class Inspector:' | |||||
565 | ismagic = info.ismagic |
|
629 | ismagic = info.ismagic | |
566 | isalias = info.isalias |
|
630 | isalias = info.isalias | |
567 | ospace = info.namespace |
|
631 | ospace = info.namespace | |
|
632 | ||||
568 | # Get docstring, special-casing aliases: |
|
633 | # Get docstring, special-casing aliases: | |
569 | if isalias: |
|
634 | if isalias: | |
570 | if not callable(obj): |
|
635 | if not callable(obj): | |
@@ -583,9 +648,8 b' class Inspector:' | |||||
583 | if formatter is not None: |
|
648 | if formatter is not None: | |
584 | ds = formatter(ds) |
|
649 | ds = formatter(ds) | |
585 |
|
650 | |||
586 |
# store output in a dict, we |
|
651 | # store output in a dict, we initialize it here and fill it as we go | |
587 | # initialize it here and fill it as we go |
|
652 | out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic) | |
588 | out = dict(found=True, isalias=isalias, ismagic=ismagic) |
|
|||
589 |
|
653 | |||
590 | string_max = 200 # max size of strings to show (snipped if longer) |
|
654 | string_max = 200 # max size of strings to show (snipped if longer) | |
591 | shalf = int((string_max -5)/2) |
|
655 | shalf = int((string_max -5)/2) | |
@@ -650,17 +714,14 b' class Inspector:' | |||||
650 | binary_file = True |
|
714 | binary_file = True | |
651 |
|
715 | |||
652 | # reconstruct the function definition and print it: |
|
716 | # reconstruct the function definition and print it: | |
653 | defln = self._getdef(obj,oname) |
|
717 | defln = self._getdef(obj, oname) | |
654 | if defln: |
|
718 | if defln: | |
655 | out['definition'] = self.format(defln) |
|
719 | out['definition'] = self.format(defln) | |
656 | args, varargs, varkw, func_defaults = getargspec(obj) |
|
720 | ||
657 | out['argspec'] = dict(args=args, varargs=varargs, |
|
|||
658 | varkw=varkw, func_defaults=func_defaults) |
|
|||
659 |
|
||||
660 | # Docstrings only in detail 0 mode, since source contains them (we |
|
721 | # Docstrings only in detail 0 mode, since source contains them (we | |
661 | # avoid repetitions). If source fails, we add them back, see below. |
|
722 | # avoid repetitions). If source fails, we add them back, see below. | |
662 | if ds and detail_level == 0: |
|
723 | if ds and detail_level == 0: | |
663 |
out['docstring'] = |
|
724 | out['docstring'] = ds | |
664 |
|
725 | |||
665 | # Original source code for any callable |
|
726 | # Original source code for any callable | |
666 | if detail_level: |
|
727 | if detail_level: | |
@@ -700,11 +761,11 b' class Inspector:' | |||||
700 | if init_def: |
|
761 | if init_def: | |
701 | out['init_definition'] = self.format(init_def) |
|
762 | out['init_definition'] = self.format(init_def) | |
702 | if init_ds: |
|
763 | if init_ds: | |
703 |
out['init_docstring'] = |
|
764 | out['init_docstring'] = init_ds | |
|
765 | ||||
704 | # and class docstring for instances: |
|
766 | # and class docstring for instances: | |
705 | elif obj_type is types.InstanceType or \ |
|
767 | elif obj_type is types.InstanceType or \ | |
706 | isinstance(obj,object): |
|
768 | isinstance(obj, object): | |
707 |
|
||||
708 | # First, check whether the instance docstring is identical to the |
|
769 | # First, check whether the instance docstring is identical to the | |
709 | # class one, and print it separately if they don't coincide. In |
|
770 | # class one, and print it separately if they don't coincide. In | |
710 | # most cases they will, but it's nice to print all the info for |
|
771 | # most cases they will, but it's nice to print all the info for | |
@@ -723,7 +784,7 b' class Inspector:' | |||||
723 | class_ds.startswith('module(name[,') ): |
|
784 | class_ds.startswith('module(name[,') ): | |
724 | class_ds = None |
|
785 | class_ds = None | |
725 | if class_ds and ds != class_ds: |
|
786 | if class_ds and ds != class_ds: | |
726 |
out['class_docstring'] = |
|
787 | out['class_docstring'] = class_ds | |
727 |
|
788 | |||
728 | # Next, try to show constructor docstrings |
|
789 | # Next, try to show constructor docstrings | |
729 | try: |
|
790 | try: | |
@@ -735,11 +796,11 b' class Inspector:' | |||||
735 | except AttributeError: |
|
796 | except AttributeError: | |
736 | init_ds = None |
|
797 | init_ds = None | |
737 | if init_ds: |
|
798 | if init_ds: | |
738 |
out['init_docstring'] = |
|
799 | out['init_docstring'] = init_ds | |
739 |
|
800 | |||
740 | # Call form docstring for callable instances |
|
801 | # Call form docstring for callable instances | |
741 | if hasattr(obj,'__call__'): |
|
802 | if hasattr(obj, '__call__'): | |
742 | call_def = self._getdef(obj.__call__,oname) |
|
803 | call_def = self._getdef(obj.__call__, oname) | |
743 | if call_def is not None: |
|
804 | if call_def is not None: | |
744 | out['call_def'] = self.format(call_def) |
|
805 | out['call_def'] = self.format(call_def) | |
745 | call_ds = getdoc(obj.__call__) |
|
806 | call_ds = getdoc(obj.__call__) | |
@@ -747,9 +808,30 b' class Inspector:' | |||||
747 | if call_ds and call_ds.startswith('x.__call__(...) <==> x(...)'): |
|
808 | if call_ds and call_ds.startswith('x.__call__(...) <==> x(...)'): | |
748 | call_ds = None |
|
809 | call_ds = None | |
749 | if call_ds: |
|
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 | def psearch(self,pattern,ns_table,ns_search=[], |
|
837 | def psearch(self,pattern,ns_table,ns_search=[], |
@@ -122,15 +122,20 b' class CallTipWidget(QtGui.QLabel):' | |||||
122 | # 'CallTipWidget' interface |
|
122 | # 'CallTipWidget' interface | |
123 | #-------------------------------------------------------------------------- |
|
123 | #-------------------------------------------------------------------------- | |
124 |
|
124 | |||
125 |
def show_ |
|
125 | def show_call_info(self, call_line=None, doc=None, maxlines=20): | |
126 |
""" Attempts to show the specified docstring at the |
|
126 | """ Attempts to show the specified call line and docstring at the | |
127 |
location. The docstring is |
|
127 | current cursor location. The docstring is possibly truncated for | |
128 | length. |
|
128 | length. | |
129 | """ |
|
129 | """ | |
130 | doc = dedent(doc.rstrip()).lstrip() |
|
130 | if doc: | |
131 | match = re.match("(?:[^\n]*\n){%i}" % maxlines, doc) |
|
131 | match = re.match("(?:[^\n]*\n){%i}" % maxlines, doc) | |
132 | if match: |
|
132 | if match: | |
133 | doc = doc[:match.end()] + '\n[Documentation continues...]' |
|
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 | return self.show_tip(doc) |
|
139 | return self.show_tip(doc) | |
135 |
|
140 | |||
136 | def show_tip(self, tip): |
|
141 | def show_tip(self, tip): |
@@ -10,6 +10,7 b' from PyQt4 import QtCore, QtGui' | |||||
10 |
|
10 | |||
11 | # Local imports |
|
11 | # Local imports | |
12 | from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt |
|
12 | from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt | |
|
13 | from IPython.core.oinspect import call_tip | |||
13 | from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin |
|
14 | from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin | |
14 | from IPython.utils.traitlets import Bool |
|
15 | from IPython.utils.traitlets import Bool | |
15 | from bracket_matcher import BracketMatcher |
|
16 | from bracket_matcher import BracketMatcher | |
@@ -334,9 +335,13 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||||
334 | info = self._request_info.get('call_tip') |
|
335 | info = self._request_info.get('call_tip') | |
335 | if info and info.id == rep['parent_header']['msg_id'] and \ |
|
336 | if info and info.id == rep['parent_header']['msg_id'] and \ | |
336 | info.pos == cursor.position(): |
|
337 | info.pos == cursor.position(): | |
337 | doc = rep['content']['docstring'] |
|
338 | # Get the information for a call tip. For now we format the call | |
338 | if doc: |
|
339 | # line as string, later we can pass False to format_call and | |
339 | self._call_tip_widget.show_docstring(doc) |
|
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 | def _handle_pyout(self, msg): |
|
346 | def _handle_pyout(self, msg): | |
342 | """ Handle display hook output. |
|
347 | """ Handle display hook output. |
@@ -303,9 +303,8 b' class Kernel(Configurable):' | |||||
303 |
|
303 | |||
304 | def object_info_request(self, ident, parent): |
|
304 | def object_info_request(self, ident, parent): | |
305 | object_info = self.shell.object_inspect(parent['content']['oname']) |
|
305 | object_info = self.shell.object_inspect(parent['content']['oname']) | |
306 |
# Before we send this object over, we |
|
306 | # Before we send this object over, we scrub it for JSON usage | |
307 | # it for JSON usage |
|
307 | oinfo = json_clean(object_info) | |
308 | oinfo = json_clean(object_info._asdict()) |
|
|||
309 | msg = self.session.send(self.reply_socket, 'object_info_reply', |
|
308 | msg = self.session.send(self.reply_socket, 'object_info_reply', | |
310 | oinfo, parent, ident) |
|
309 | oinfo, parent, ident) | |
311 | io.raw_print(msg) |
|
310 | io.raw_print(msg) |
@@ -462,6 +462,9 b' field names that IPython prints at the terminal.' | |||||
462 | Message type: ``object_info_reply``:: |
|
462 | Message type: ``object_info_reply``:: | |
463 |
|
463 | |||
464 | content = { |
|
464 | content = { | |
|
465 | # The name the object was requested under | |||
|
466 | 'name' : str, | |||
|
467 | ||||
465 | # Boolean flag indicating whether the named object was found or not. If |
|
468 | # Boolean flag indicating whether the named object was found or not. If | |
466 | # it's false, all other fields will be empty. |
|
469 | # it's false, all other fields will be empty. | |
467 | 'found' : bool, |
|
470 | 'found' : bool, | |
@@ -511,7 +514,7 b' Message type: ``object_info_reply``::' | |||||
511 | # that these must be matched *in reverse* with the 'args' |
|
514 | # that these must be matched *in reverse* with the 'args' | |
512 | # list above, since the first positional args have no default |
|
515 | # list above, since the first positional args have no default | |
513 | # value at all. |
|
516 | # value at all. | |
514 |
|
|
517 | defaults : list, | |
515 | }, |
|
518 | }, | |
516 |
|
519 | |||
517 | # For instances, provide the constructor signature (the definition of |
|
520 | # For instances, provide the constructor signature (the definition of |
General Comments 0
You need to be logged in to leave comments.
Login now