##// END OF EJS Templates
updates per review...
MinRK -
Show More
@@ -1,357 +1,360 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Paging capabilities for IPython.core
3 Paging capabilities for IPython.core
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 * Fernando Perez
8 * Fernando Perez
9
9
10 Notes
10 Notes
11 -----
11 -----
12
12
13 For now this uses ipapi, so it can't be in IPython.utils. If we can get
13 For now this uses ipapi, so it can't be in IPython.utils. If we can get
14 rid of that dependency, we could move it there.
14 rid of that dependency, we could move it there.
15 -----
15 -----
16 """
16 """
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Copyright (C) 2008-2011 The IPython Development Team
19 # Copyright (C) 2008-2011 The IPython Development Team
20 #
20 #
21 # Distributed under the terms of the BSD License. The full license is in
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
22 # the file COPYING, distributed as part of this software.
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Imports
26 # Imports
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 from __future__ import print_function
28 from __future__ import print_function
29
29
30 import os
30 import os
31 import re
31 import re
32 import sys
32 import sys
33 import tempfile
33 import tempfile
34
34
35 from io import UnsupportedOperation
35 from io import UnsupportedOperation
36
36
37 from IPython import get_ipython
37 from IPython import get_ipython
38 from IPython.core.error import TryNext
38 from IPython.core.error import TryNext
39 from IPython.utils.data import chop
39 from IPython.utils.data import chop
40 from IPython.utils import io
40 from IPython.utils import io
41 from IPython.utils.process import system
41 from IPython.utils.process import system
42 from IPython.utils.terminal import get_terminal_size
42 from IPython.utils.terminal import get_terminal_size
43 from IPython.utils import py3compat
43 from IPython.utils import py3compat
44
44
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Classes and functions
47 # Classes and functions
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50 esc_re = re.compile(r"(\x1b[^m]+m)")
50 esc_re = re.compile(r"(\x1b[^m]+m)")
51
51
52 def page_dumb(strng, start=0, screen_lines=25):
52 def page_dumb(strng, start=0, screen_lines=25):
53 """Very dumb 'pager' in Python, for when nothing else works.
53 """Very dumb 'pager' in Python, for when nothing else works.
54
54
55 Only moves forward, same interface as page(), except for pager_cmd and
55 Only moves forward, same interface as page(), except for pager_cmd and
56 mode."""
56 mode."""
57
57
58 out_ln = strng.splitlines()[start:]
58 out_ln = strng.splitlines()[start:]
59 screens = chop(out_ln,screen_lines-1)
59 screens = chop(out_ln,screen_lines-1)
60 if len(screens) == 1:
60 if len(screens) == 1:
61 print(os.linesep.join(screens[0]), file=io.stdout)
61 print(os.linesep.join(screens[0]), file=io.stdout)
62 else:
62 else:
63 last_escape = ""
63 last_escape = ""
64 for scr in screens[0:-1]:
64 for scr in screens[0:-1]:
65 hunk = os.linesep.join(scr)
65 hunk = os.linesep.join(scr)
66 print(last_escape + hunk, file=io.stdout)
66 print(last_escape + hunk, file=io.stdout)
67 if not page_more():
67 if not page_more():
68 return
68 return
69 esc_list = esc_re.findall(hunk)
69 esc_list = esc_re.findall(hunk)
70 if len(esc_list) > 0:
70 if len(esc_list) > 0:
71 last_escape = esc_list[-1]
71 last_escape = esc_list[-1]
72 print(last_escape + os.linesep.join(screens[-1]), file=io.stdout)
72 print(last_escape + os.linesep.join(screens[-1]), file=io.stdout)
73
73
74 def _detect_screen_size(screen_lines_def):
74 def _detect_screen_size(screen_lines_def):
75 """Attempt to work out the number of lines on the screen.
75 """Attempt to work out the number of lines on the screen.
76
76
77 This is called by page(). It can raise an error (e.g. when run in the
77 This is called by page(). It can raise an error (e.g. when run in the
78 test suite), so it's separated out so it can easily be called in a try block.
78 test suite), so it's separated out so it can easily be called in a try block.
79 """
79 """
80 TERM = os.environ.get('TERM',None)
80 TERM = os.environ.get('TERM',None)
81 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
81 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
82 # curses causes problems on many terminals other than xterm, and
82 # curses causes problems on many terminals other than xterm, and
83 # some termios calls lock up on Sun OS5.
83 # some termios calls lock up on Sun OS5.
84 return screen_lines_def
84 return screen_lines_def
85
85
86 try:
86 try:
87 import termios
87 import termios
88 import curses
88 import curses
89 except ImportError:
89 except ImportError:
90 return screen_lines_def
90 return screen_lines_def
91
91
92 # There is a bug in curses, where *sometimes* it fails to properly
92 # There is a bug in curses, where *sometimes* it fails to properly
93 # initialize, and then after the endwin() call is made, the
93 # initialize, and then after the endwin() call is made, the
94 # terminal is left in an unusable state. Rather than trying to
94 # terminal is left in an unusable state. Rather than trying to
95 # check everytime for this (by requesting and comparing termios
95 # check everytime for this (by requesting and comparing termios
96 # flags each time), we just save the initial terminal state and
96 # flags each time), we just save the initial terminal state and
97 # unconditionally reset it every time. It's cheaper than making
97 # unconditionally reset it every time. It's cheaper than making
98 # the checks.
98 # the checks.
99 term_flags = termios.tcgetattr(sys.stdout)
99 term_flags = termios.tcgetattr(sys.stdout)
100
100
101 # Curses modifies the stdout buffer size by default, which messes
101 # Curses modifies the stdout buffer size by default, which messes
102 # up Python's normal stdout buffering. This would manifest itself
102 # up Python's normal stdout buffering. This would manifest itself
103 # to IPython users as delayed printing on stdout after having used
103 # to IPython users as delayed printing on stdout after having used
104 # the pager.
104 # the pager.
105 #
105 #
106 # We can prevent this by manually setting the NCURSES_NO_SETBUF
106 # We can prevent this by manually setting the NCURSES_NO_SETBUF
107 # environment variable. For more details, see:
107 # environment variable. For more details, see:
108 # http://bugs.python.org/issue10144
108 # http://bugs.python.org/issue10144
109 NCURSES_NO_SETBUF = os.environ.get('NCURSES_NO_SETBUF', None)
109 NCURSES_NO_SETBUF = os.environ.get('NCURSES_NO_SETBUF', None)
110 os.environ['NCURSES_NO_SETBUF'] = ''
110 os.environ['NCURSES_NO_SETBUF'] = ''
111
111
112 # Proceed with curses initialization
112 # Proceed with curses initialization
113 try:
113 try:
114 scr = curses.initscr()
114 scr = curses.initscr()
115 except AttributeError:
115 except AttributeError:
116 # Curses on Solaris may not be complete, so we can't use it there
116 # Curses on Solaris may not be complete, so we can't use it there
117 return screen_lines_def
117 return screen_lines_def
118
118
119 screen_lines_real,screen_cols = scr.getmaxyx()
119 screen_lines_real,screen_cols = scr.getmaxyx()
120 curses.endwin()
120 curses.endwin()
121
121
122 # Restore environment
122 # Restore environment
123 if NCURSES_NO_SETBUF is None:
123 if NCURSES_NO_SETBUF is None:
124 del os.environ['NCURSES_NO_SETBUF']
124 del os.environ['NCURSES_NO_SETBUF']
125 else:
125 else:
126 os.environ['NCURSES_NO_SETBUF'] = NCURSES_NO_SETBUF
126 os.environ['NCURSES_NO_SETBUF'] = NCURSES_NO_SETBUF
127
127
128 # Restore terminal state in case endwin() didn't.
128 # Restore terminal state in case endwin() didn't.
129 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
129 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
130 # Now we have what we needed: the screen size in rows/columns
130 # Now we have what we needed: the screen size in rows/columns
131 return screen_lines_real
131 return screen_lines_real
132 #print '***Screen size:',screen_lines_real,'lines x',\
132 #print '***Screen size:',screen_lines_real,'lines x',\
133 #screen_cols,'columns.' # dbg
133 #screen_cols,'columns.' # dbg
134
134
135 def page(strng, start=0, screen_lines=0, pager_cmd=None):
135 def page(strng, start=0, screen_lines=0, pager_cmd=None):
136 """Print a string, piping through a pager after a certain length.
136 """Display a string, piping through a pager after a certain length.
137
138 strng can be a mime-bundle dict, supplying multiple representations,
139 keyed by mime-type.
137
140
138 The screen_lines parameter specifies the number of *usable* lines of your
141 The screen_lines parameter specifies the number of *usable* lines of your
139 terminal screen (total lines minus lines you need to reserve to show other
142 terminal screen (total lines minus lines you need to reserve to show other
140 information).
143 information).
141
144
142 If you set screen_lines to a number <=0, page() will try to auto-determine
145 If you set screen_lines to a number <=0, page() will try to auto-determine
143 your screen size and will only use up to (screen_size+screen_lines) for
146 your screen size and will only use up to (screen_size+screen_lines) for
144 printing, paging after that. That is, if you want auto-detection but need
147 printing, paging after that. That is, if you want auto-detection but need
145 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
148 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
146 auto-detection without any lines reserved simply use screen_lines = 0.
149 auto-detection without any lines reserved simply use screen_lines = 0.
147
150
148 If a string won't fit in the allowed lines, it is sent through the
151 If a string won't fit in the allowed lines, it is sent through the
149 specified pager command. If none given, look for PAGER in the environment,
152 specified pager command. If none given, look for PAGER in the environment,
150 and ultimately default to less.
153 and ultimately default to less.
151
154
152 If no system pager works, the string is sent through a 'dumb pager'
155 If no system pager works, the string is sent through a 'dumb pager'
153 written in python, very simplistic.
156 written in python, very simplistic.
154 """
157 """
155
158
156 # for compatibility with mime-bundle form:
159 # for compatibility with mime-bundle form:
157 if isinstance(strng, dict):
160 if isinstance(strng, dict):
158 strng = strng['text/plain']
161 strng = strng['text/plain']
159
162
160 # Some routines may auto-compute start offsets incorrectly and pass a
163 # Some routines may auto-compute start offsets incorrectly and pass a
161 # negative value. Offset to 0 for robustness.
164 # negative value. Offset to 0 for robustness.
162 start = max(0, start)
165 start = max(0, start)
163
166
164 # first, try the hook
167 # first, try the hook
165 ip = get_ipython()
168 ip = get_ipython()
166 if ip:
169 if ip:
167 try:
170 try:
168 ip.hooks.show_in_pager(strng)
171 ip.hooks.show_in_pager(strng)
169 return
172 return
170 except TryNext:
173 except TryNext:
171 pass
174 pass
172
175
173 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
176 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
174 TERM = os.environ.get('TERM','dumb')
177 TERM = os.environ.get('TERM','dumb')
175 if TERM in ['dumb','emacs'] and os.name != 'nt':
178 if TERM in ['dumb','emacs'] and os.name != 'nt':
176 print(strng)
179 print(strng)
177 return
180 return
178 # chop off the topmost part of the string we don't want to see
181 # chop off the topmost part of the string we don't want to see
179 str_lines = strng.splitlines()[start:]
182 str_lines = strng.splitlines()[start:]
180 str_toprint = os.linesep.join(str_lines)
183 str_toprint = os.linesep.join(str_lines)
181 num_newlines = len(str_lines)
184 num_newlines = len(str_lines)
182 len_str = len(str_toprint)
185 len_str = len(str_toprint)
183
186
184 # Dumb heuristics to guesstimate number of on-screen lines the string
187 # Dumb heuristics to guesstimate number of on-screen lines the string
185 # takes. Very basic, but good enough for docstrings in reasonable
188 # takes. Very basic, but good enough for docstrings in reasonable
186 # terminals. If someone later feels like refining it, it's not hard.
189 # terminals. If someone later feels like refining it, it's not hard.
187 numlines = max(num_newlines,int(len_str/80)+1)
190 numlines = max(num_newlines,int(len_str/80)+1)
188
191
189 screen_lines_def = get_terminal_size()[1]
192 screen_lines_def = get_terminal_size()[1]
190
193
191 # auto-determine screen size
194 # auto-determine screen size
192 if screen_lines <= 0:
195 if screen_lines <= 0:
193 try:
196 try:
194 screen_lines += _detect_screen_size(screen_lines_def)
197 screen_lines += _detect_screen_size(screen_lines_def)
195 except (TypeError, UnsupportedOperation):
198 except (TypeError, UnsupportedOperation):
196 print(str_toprint, file=io.stdout)
199 print(str_toprint, file=io.stdout)
197 return
200 return
198
201
199 #print 'numlines',numlines,'screenlines',screen_lines # dbg
202 #print 'numlines',numlines,'screenlines',screen_lines # dbg
200 if numlines <= screen_lines :
203 if numlines <= screen_lines :
201 #print '*** normal print' # dbg
204 #print '*** normal print' # dbg
202 print(str_toprint, file=io.stdout)
205 print(str_toprint, file=io.stdout)
203 else:
206 else:
204 # Try to open pager and default to internal one if that fails.
207 # Try to open pager and default to internal one if that fails.
205 # All failure modes are tagged as 'retval=1', to match the return
208 # All failure modes are tagged as 'retval=1', to match the return
206 # value of a failed system command. If any intermediate attempt
209 # value of a failed system command. If any intermediate attempt
207 # sets retval to 1, at the end we resort to our own page_dumb() pager.
210 # sets retval to 1, at the end we resort to our own page_dumb() pager.
208 pager_cmd = get_pager_cmd(pager_cmd)
211 pager_cmd = get_pager_cmd(pager_cmd)
209 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
212 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
210 if os.name == 'nt':
213 if os.name == 'nt':
211 if pager_cmd.startswith('type'):
214 if pager_cmd.startswith('type'):
212 # The default WinXP 'type' command is failing on complex strings.
215 # The default WinXP 'type' command is failing on complex strings.
213 retval = 1
216 retval = 1
214 else:
217 else:
215 fd, tmpname = tempfile.mkstemp('.txt')
218 fd, tmpname = tempfile.mkstemp('.txt')
216 try:
219 try:
217 os.close(fd)
220 os.close(fd)
218 with open(tmpname, 'wt') as tmpfile:
221 with open(tmpname, 'wt') as tmpfile:
219 tmpfile.write(strng)
222 tmpfile.write(strng)
220 cmd = "%s < %s" % (pager_cmd, tmpname)
223 cmd = "%s < %s" % (pager_cmd, tmpname)
221 # tmpfile needs to be closed for windows
224 # tmpfile needs to be closed for windows
222 if os.system(cmd):
225 if os.system(cmd):
223 retval = 1
226 retval = 1
224 else:
227 else:
225 retval = None
228 retval = None
226 finally:
229 finally:
227 os.remove(tmpname)
230 os.remove(tmpname)
228 else:
231 else:
229 try:
232 try:
230 retval = None
233 retval = None
231 # if I use popen4, things hang. No idea why.
234 # if I use popen4, things hang. No idea why.
232 #pager,shell_out = os.popen4(pager_cmd)
235 #pager,shell_out = os.popen4(pager_cmd)
233 pager = os.popen(pager_cmd, 'w')
236 pager = os.popen(pager_cmd, 'w')
234 try:
237 try:
235 pager_encoding = pager.encoding or sys.stdout.encoding
238 pager_encoding = pager.encoding or sys.stdout.encoding
236 pager.write(py3compat.cast_bytes_py2(
239 pager.write(py3compat.cast_bytes_py2(
237 strng, encoding=pager_encoding))
240 strng, encoding=pager_encoding))
238 finally:
241 finally:
239 retval = pager.close()
242 retval = pager.close()
240 except IOError as msg: # broken pipe when user quits
243 except IOError as msg: # broken pipe when user quits
241 if msg.args == (32, 'Broken pipe'):
244 if msg.args == (32, 'Broken pipe'):
242 retval = None
245 retval = None
243 else:
246 else:
244 retval = 1
247 retval = 1
245 except OSError:
248 except OSError:
246 # Other strange problems, sometimes seen in Win2k/cygwin
249 # Other strange problems, sometimes seen in Win2k/cygwin
247 retval = 1
250 retval = 1
248 if retval is not None:
251 if retval is not None:
249 page_dumb(strng,screen_lines=screen_lines)
252 page_dumb(strng,screen_lines=screen_lines)
250
253
251
254
252 def page_file(fname, start=0, pager_cmd=None):
255 def page_file(fname, start=0, pager_cmd=None):
253 """Page a file, using an optional pager command and starting line.
256 """Page a file, using an optional pager command and starting line.
254 """
257 """
255
258
256 pager_cmd = get_pager_cmd(pager_cmd)
259 pager_cmd = get_pager_cmd(pager_cmd)
257 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
260 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
258
261
259 try:
262 try:
260 if os.environ['TERM'] in ['emacs','dumb']:
263 if os.environ['TERM'] in ['emacs','dumb']:
261 raise EnvironmentError
264 raise EnvironmentError
262 system(pager_cmd + ' ' + fname)
265 system(pager_cmd + ' ' + fname)
263 except:
266 except:
264 try:
267 try:
265 if start > 0:
268 if start > 0:
266 start -= 1
269 start -= 1
267 page(open(fname).read(),start)
270 page(open(fname).read(),start)
268 except:
271 except:
269 print('Unable to show file',repr(fname))
272 print('Unable to show file',repr(fname))
270
273
271
274
272 def get_pager_cmd(pager_cmd=None):
275 def get_pager_cmd(pager_cmd=None):
273 """Return a pager command.
276 """Return a pager command.
274
277
275 Makes some attempts at finding an OS-correct one.
278 Makes some attempts at finding an OS-correct one.
276 """
279 """
277 if os.name == 'posix':
280 if os.name == 'posix':
278 default_pager_cmd = 'less -r' # -r for color control sequences
281 default_pager_cmd = 'less -r' # -r for color control sequences
279 elif os.name in ['nt','dos']:
282 elif os.name in ['nt','dos']:
280 default_pager_cmd = 'type'
283 default_pager_cmd = 'type'
281
284
282 if pager_cmd is None:
285 if pager_cmd is None:
283 try:
286 try:
284 pager_cmd = os.environ['PAGER']
287 pager_cmd = os.environ['PAGER']
285 except:
288 except:
286 pager_cmd = default_pager_cmd
289 pager_cmd = default_pager_cmd
287 return pager_cmd
290 return pager_cmd
288
291
289
292
290 def get_pager_start(pager, start):
293 def get_pager_start(pager, start):
291 """Return the string for paging files with an offset.
294 """Return the string for paging files with an offset.
292
295
293 This is the '+N' argument which less and more (under Unix) accept.
296 This is the '+N' argument which less and more (under Unix) accept.
294 """
297 """
295
298
296 if pager in ['less','more']:
299 if pager in ['less','more']:
297 if start:
300 if start:
298 start_string = '+' + str(start)
301 start_string = '+' + str(start)
299 else:
302 else:
300 start_string = ''
303 start_string = ''
301 else:
304 else:
302 start_string = ''
305 start_string = ''
303 return start_string
306 return start_string
304
307
305
308
306 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
309 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
307 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
310 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
308 import msvcrt
311 import msvcrt
309 def page_more():
312 def page_more():
310 """ Smart pausing between pages
313 """ Smart pausing between pages
311
314
312 @return: True if need print more lines, False if quit
315 @return: True if need print more lines, False if quit
313 """
316 """
314 io.stdout.write('---Return to continue, q to quit--- ')
317 io.stdout.write('---Return to continue, q to quit--- ')
315 ans = msvcrt.getwch()
318 ans = msvcrt.getwch()
316 if ans in ("q", "Q"):
319 if ans in ("q", "Q"):
317 result = False
320 result = False
318 else:
321 else:
319 result = True
322 result = True
320 io.stdout.write("\b"*37 + " "*37 + "\b"*37)
323 io.stdout.write("\b"*37 + " "*37 + "\b"*37)
321 return result
324 return result
322 else:
325 else:
323 def page_more():
326 def page_more():
324 ans = py3compat.input('---Return to continue, q to quit--- ')
327 ans = py3compat.input('---Return to continue, q to quit--- ')
325 if ans.lower().startswith('q'):
328 if ans.lower().startswith('q'):
326 return False
329 return False
327 else:
330 else:
328 return True
331 return True
329
332
330
333
331 def snip_print(str,width = 75,print_full = 0,header = ''):
334 def snip_print(str,width = 75,print_full = 0,header = ''):
332 """Print a string snipping the midsection to fit in width.
335 """Print a string snipping the midsection to fit in width.
333
336
334 print_full: mode control:
337 print_full: mode control:
335
338
336 - 0: only snip long strings
339 - 0: only snip long strings
337 - 1: send to page() directly.
340 - 1: send to page() directly.
338 - 2: snip long strings and ask for full length viewing with page()
341 - 2: snip long strings and ask for full length viewing with page()
339
342
340 Return 1 if snipping was necessary, 0 otherwise."""
343 Return 1 if snipping was necessary, 0 otherwise."""
341
344
342 if print_full == 1:
345 if print_full == 1:
343 page(header+str)
346 page(header+str)
344 return 0
347 return 0
345
348
346 print(header, end=' ')
349 print(header, end=' ')
347 if len(str) < width:
350 if len(str) < width:
348 print(str)
351 print(str)
349 snip = 0
352 snip = 0
350 else:
353 else:
351 whalf = int((width -5)/2)
354 whalf = int((width -5)/2)
352 print(str[:whalf] + ' <...> ' + str[-whalf:])
355 print(str[:whalf] + ' <...> ' + str[-whalf:])
353 snip = 1
356 snip = 1
354 if snip and print_full == 2:
357 if snip and print_full == 2:
355 if py3compat.input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
358 if py3compat.input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
356 page(str)
359 page(str)
357 return snip
360 return snip
@@ -1,150 +1,150 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Release data for the IPython project."""
2 """Release data for the IPython project."""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2008, IPython Development Team.
5 # Copyright (c) 2008, IPython Development Team.
6 # Copyright (c) 2001, Fernando Perez <fernando.perez@colorado.edu>
6 # Copyright (c) 2001, Fernando Perez <fernando.perez@colorado.edu>
7 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
7 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
8 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
8 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
9 #
9 #
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11 #
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 # Name of the package for release purposes. This is the name which labels
15 # Name of the package for release purposes. This is the name which labels
16 # the tarballs and RPMs made by distutils, so it's best to lowercase it.
16 # the tarballs and RPMs made by distutils, so it's best to lowercase it.
17 name = 'ipython'
17 name = 'ipython'
18
18
19 # IPython version information. An empty _version_extra corresponds to a full
19 # IPython version information. An empty _version_extra corresponds to a full
20 # release. 'dev' as a _version_extra string means this is a development
20 # release. 'dev' as a _version_extra string means this is a development
21 # version
21 # version
22 _version_major = 3
22 _version_major = 3
23 _version_minor = 0
23 _version_minor = 0
24 _version_patch = 0
24 _version_patch = 0
25 _version_extra = 'dev'
25 _version_extra = 'dev'
26 # _version_extra = 'rc1'
26 # _version_extra = 'rc1'
27 # _version_extra = '' # Uncomment this for full releases
27 # _version_extra = '' # Uncomment this for full releases
28
28
29 # release.codename is deprecated in 2.0, will be removed in 3.0
29 # release.codename is deprecated in 2.0, will be removed in 3.0
30 codename = ''
30 codename = ''
31
31
32 # Construct full version string from these.
32 # Construct full version string from these.
33 _ver = [_version_major, _version_minor, _version_patch]
33 _ver = [_version_major, _version_minor, _version_patch]
34
34
35 __version__ = '.'.join(map(str, _ver))
35 __version__ = '.'.join(map(str, _ver))
36 if _version_extra:
36 if _version_extra:
37 __version__ = __version__ + '-' + _version_extra
37 __version__ = __version__ + '-' + _version_extra
38
38
39 version = __version__ # backwards compatibility name
39 version = __version__ # backwards compatibility name
40 version_info = (_version_major, _version_minor, _version_patch, _version_extra)
40 version_info = (_version_major, _version_minor, _version_patch, _version_extra)
41
41
42 # Change this when incrementing the kernel protocol version
42 # Change this when incrementing the kernel protocol version
43 kernel_protocol_version_info = (5, 0, 0)
43 kernel_protocol_version_info = (5, 0)
44 kernel_protocol_version = "%i.%i.%i" % kernel_protocol_version_info
44 kernel_protocol_version = "%i.%i" % kernel_protocol_version_info
45
45
46 description = "IPython: Productive Interactive Computing"
46 description = "IPython: Productive Interactive Computing"
47
47
48 long_description = \
48 long_description = \
49 """
49 """
50 IPython provides a rich toolkit to help you make the most out of using Python
50 IPython provides a rich toolkit to help you make the most out of using Python
51 interactively. Its main components are:
51 interactively. Its main components are:
52
52
53 * Powerful interactive Python shells (terminal- and Qt-based).
53 * Powerful interactive Python shells (terminal- and Qt-based).
54 * A web-based interactive notebook environment with all shell features plus
54 * A web-based interactive notebook environment with all shell features plus
55 support for embedded figures, animations and rich media.
55 support for embedded figures, animations and rich media.
56 * Support for interactive data visualization and use of GUI toolkits.
56 * Support for interactive data visualization and use of GUI toolkits.
57 * Flexible, embeddable interpreters to load into your own projects.
57 * Flexible, embeddable interpreters to load into your own projects.
58 * A high-performance library for high level and interactive parallel computing
58 * A high-performance library for high level and interactive parallel computing
59 that works in multicore systems, clusters, supercomputing and cloud scenarios.
59 that works in multicore systems, clusters, supercomputing and cloud scenarios.
60
60
61 The enhanced interactive Python shells have the following main features:
61 The enhanced interactive Python shells have the following main features:
62
62
63 * Comprehensive object introspection.
63 * Comprehensive object introspection.
64
64
65 * Input history, persistent across sessions.
65 * Input history, persistent across sessions.
66
66
67 * Caching of output results during a session with automatically generated
67 * Caching of output results during a session with automatically generated
68 references.
68 references.
69
69
70 * Extensible tab completion, with support by default for completion of python
70 * Extensible tab completion, with support by default for completion of python
71 variables and keywords, filenames and function keywords.
71 variables and keywords, filenames and function keywords.
72
72
73 * Extensible system of 'magic' commands for controlling the environment and
73 * Extensible system of 'magic' commands for controlling the environment and
74 performing many tasks related either to IPython or the operating system.
74 performing many tasks related either to IPython or the operating system.
75
75
76 * A rich configuration system with easy switching between different setups
76 * A rich configuration system with easy switching between different setups
77 (simpler than changing $PYTHONSTARTUP environment variables every time).
77 (simpler than changing $PYTHONSTARTUP environment variables every time).
78
78
79 * Session logging and reloading.
79 * Session logging and reloading.
80
80
81 * Extensible syntax processing for special purpose situations.
81 * Extensible syntax processing for special purpose situations.
82
82
83 * Access to the system shell with user-extensible alias system.
83 * Access to the system shell with user-extensible alias system.
84
84
85 * Easily embeddable in other Python programs and GUIs.
85 * Easily embeddable in other Python programs and GUIs.
86
86
87 * Integrated access to the pdb debugger and the Python profiler.
87 * Integrated access to the pdb debugger and the Python profiler.
88
88
89 The parallel computing architecture has the following main features:
89 The parallel computing architecture has the following main features:
90
90
91 * Quickly parallelize Python code from an interactive Python/IPython session.
91 * Quickly parallelize Python code from an interactive Python/IPython session.
92
92
93 * A flexible and dynamic process model that be deployed on anything from
93 * A flexible and dynamic process model that be deployed on anything from
94 multicore workstations to supercomputers.
94 multicore workstations to supercomputers.
95
95
96 * An architecture that supports many different styles of parallelism, from
96 * An architecture that supports many different styles of parallelism, from
97 message passing to task farming.
97 message passing to task farming.
98
98
99 * Both blocking and fully asynchronous interfaces.
99 * Both blocking and fully asynchronous interfaces.
100
100
101 * High level APIs that enable many things to be parallelized in a few lines
101 * High level APIs that enable many things to be parallelized in a few lines
102 of code.
102 of code.
103
103
104 * Share live parallel jobs with other users securely.
104 * Share live parallel jobs with other users securely.
105
105
106 * Dynamically load balanced task farming system.
106 * Dynamically load balanced task farming system.
107
107
108 * Robust error handling in parallel code.
108 * Robust error handling in parallel code.
109
109
110 The latest development version is always available from IPython's `GitHub
110 The latest development version is always available from IPython's `GitHub
111 site <http://github.com/ipython>`_.
111 site <http://github.com/ipython>`_.
112 """
112 """
113
113
114 license = 'BSD'
114 license = 'BSD'
115
115
116 authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'),
116 authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'),
117 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
117 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
118 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
118 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
119 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
119 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
120 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
120 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
121 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com'),
121 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com'),
122 'Thomas' : ('Thomas A. Kluyver', 'takowl@gmail.com'),
122 'Thomas' : ('Thomas A. Kluyver', 'takowl@gmail.com'),
123 'Jorgen' : ('Jorgen Stenarson', 'jorgen.stenarson@bostream.nu'),
123 'Jorgen' : ('Jorgen Stenarson', 'jorgen.stenarson@bostream.nu'),
124 'Matthias' : ('Matthias Bussonnier', 'bussonniermatthias@gmail.com'),
124 'Matthias' : ('Matthias Bussonnier', 'bussonniermatthias@gmail.com'),
125 }
125 }
126
126
127 author = 'The IPython Development Team'
127 author = 'The IPython Development Team'
128
128
129 author_email = 'ipython-dev@scipy.org'
129 author_email = 'ipython-dev@scipy.org'
130
130
131 url = 'http://ipython.org'
131 url = 'http://ipython.org'
132
132
133 download_url = 'https://github.com/ipython/ipython/downloads'
133 download_url = 'https://github.com/ipython/ipython/downloads'
134
134
135 platforms = ['Linux','Mac OSX','Windows XP/Vista/7/8']
135 platforms = ['Linux','Mac OSX','Windows XP/Vista/7/8']
136
136
137 keywords = ['Interactive','Interpreter','Shell','Parallel','Distributed',
137 keywords = ['Interactive','Interpreter','Shell','Parallel','Distributed',
138 'Web-based computing', 'Qt console', 'Embedding']
138 'Web-based computing', 'Qt console', 'Embedding']
139
139
140 classifiers = [
140 classifiers = [
141 'Intended Audience :: Developers',
141 'Intended Audience :: Developers',
142 'Intended Audience :: Science/Research',
142 'Intended Audience :: Science/Research',
143 'License :: OSI Approved :: BSD License',
143 'License :: OSI Approved :: BSD License',
144 'Programming Language :: Python',
144 'Programming Language :: Python',
145 'Programming Language :: Python :: 2',
145 'Programming Language :: Python :: 2',
146 'Programming Language :: Python :: 2.7',
146 'Programming Language :: Python :: 2.7',
147 'Programming Language :: Python :: 3',
147 'Programming Language :: Python :: 3',
148 'Topic :: System :: Distributed Computing',
148 'Topic :: System :: Distributed Computing',
149 'Topic :: System :: Shells'
149 'Topic :: System :: Shells'
150 ]
150 ]
@@ -1,350 +1,345 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 //============================================================================
4 //============================================================================
5 // Tooltip
5 // Tooltip
6 //============================================================================
6 //============================================================================
7 //
7 //
8 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
8 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
9 //
9 //
10 // you can configure the differents action of pressing shift-tab several times in a row by
10 // you can configure the differents action of pressing shift-tab several times in a row by
11 // setting/appending different fonction in the array
11 // setting/appending different fonction in the array
12 // IPython.tooltip.tabs_functions
12 // IPython.tooltip.tabs_functions
13 //
13 //
14 // eg :
14 // eg :
15 // IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
15 // IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')}
16 //
16 //
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18 "use strict";
19
19
20 var utils = IPython.utils;
20 var utils = IPython.utils;
21
21
22 // tooltip constructor
22 // tooltip constructor
23 var Tooltip = function () {
23 var Tooltip = function () {
24 var that = this;
24 var that = this;
25 this.time_before_tooltip = 1200;
25 this.time_before_tooltip = 1200;
26
26
27 // handle to html
27 // handle to html
28 this.tooltip = $('#tooltip');
28 this.tooltip = $('#tooltip');
29 this._hidden = true;
29 this._hidden = true;
30
30
31 // variable for consecutive call
31 // variable for consecutive call
32 this._old_cell = null;
32 this._old_cell = null;
33 this._old_request = null;
33 this._old_request = null;
34 this._consecutive_counter = 0;
34 this._consecutive_counter = 0;
35
35
36 // 'sticky ?'
36 // 'sticky ?'
37 this._sticky = false;
37 this._sticky = false;
38
38
39 // display tooltip if the docstring is empty?
39 // display tooltip if the docstring is empty?
40 this._hide_if_no_docstring = false;
40 this._hide_if_no_docstring = false;
41
41
42 // contain the button in the upper right corner
42 // contain the button in the upper right corner
43 this.buttons = $('<div/>').addClass('tooltipbuttons');
43 this.buttons = $('<div/>').addClass('tooltipbuttons');
44
44
45 // will contain the docstring
45 // will contain the docstring
46 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
46 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
47
47
48 // build the buttons menu on the upper right
48 // build the buttons menu on the upper right
49 // expand the tooltip to see more
49 // expand the tooltip to see more
50 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
50 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
51 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press shift-tab twice)').click(function () {
51 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press shift-tab twice)').click(function () {
52 that.expand();
52 that.expand();
53 }).append(
53 }).append(
54 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
54 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
55
55
56 // open in pager
56 // open in pager
57 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press shift-tab 4 times)');
57 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press shift-tab 4 times)');
58 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
58 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
59 morelink.append(morespan);
59 morelink.append(morespan);
60 morelink.click(function () {
60 morelink.click(function () {
61 that.showInPager(that._old_cell);
61 that.showInPager(that._old_cell);
62 });
62 });
63
63
64 // close the tooltip
64 // close the tooltip
65 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
65 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
66 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
66 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
67 closelink.append(closespan);
67 closelink.append(closespan);
68 closelink.click(function () {
68 closelink.click(function () {
69 that.remove_and_cancel_tooltip(true);
69 that.remove_and_cancel_tooltip(true);
70 });
70 });
71
71
72 this._clocklink = $('<a/>').attr('href', "#");
72 this._clocklink = $('<a/>').attr('href', "#");
73 this._clocklink.attr('role', "button");
73 this._clocklink.attr('role', "button");
74 this._clocklink.addClass('ui-button');
74 this._clocklink.addClass('ui-button');
75 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
75 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
76 var clockspan = $('<span/>').text('Close');
76 var clockspan = $('<span/>').text('Close');
77 clockspan.addClass('ui-icon');
77 clockspan.addClass('ui-icon');
78 clockspan.addClass('ui-icon-clock');
78 clockspan.addClass('ui-icon-clock');
79 this._clocklink.append(clockspan);
79 this._clocklink.append(clockspan);
80 this._clocklink.click(function () {
80 this._clocklink.click(function () {
81 that.cancel_stick();
81 that.cancel_stick();
82 });
82 });
83
83
84
84
85
85
86
86
87 //construct the tooltip
87 //construct the tooltip
88 // add in the reverse order you want them to appear
88 // add in the reverse order you want them to appear
89 this.buttons.append(closelink);
89 this.buttons.append(closelink);
90 this.buttons.append(expandlink);
90 this.buttons.append(expandlink);
91 this.buttons.append(morelink);
91 this.buttons.append(morelink);
92 this.buttons.append(this._clocklink);
92 this.buttons.append(this._clocklink);
93 this._clocklink.hide();
93 this._clocklink.hide();
94
94
95
95
96 // we need a phony element to make the small arrow
96 // we need a phony element to make the small arrow
97 // of the tooltip in css
97 // of the tooltip in css
98 // we will move the arrow later
98 // we will move the arrow later
99 this.arrow = $('<div/>').addClass('pretooltiparrow');
99 this.arrow = $('<div/>').addClass('pretooltiparrow');
100 this.tooltip.append(this.buttons);
100 this.tooltip.append(this.buttons);
101 this.tooltip.append(this.arrow);
101 this.tooltip.append(this.arrow);
102 this.tooltip.append(this.text);
102 this.tooltip.append(this.text);
103
103
104 // function that will be called if you press tab 1, 2, 3... times in a row
104 // function that will be called if you press tab 1, 2, 3... times in a row
105 this.tabs_functions = [function (cell, text, cursor) {
105 this.tabs_functions = [function (cell, text, cursor) {
106 that._request_tooltip(cell, text, cursor);
106 that._request_tooltip(cell, text, cursor);
107 }, function () {
107 }, function () {
108 that.expand();
108 that.expand();
109 }, function () {
109 }, function () {
110 that.stick();
110 that.stick();
111 }, function (cell) {
111 }, function (cell) {
112 that.cancel_stick();
112 that.cancel_stick();
113 that.showInPager(cell);
113 that.showInPager(cell);
114 }];
114 }];
115 // call after all the tabs function above have bee call to clean their effects
115 // call after all the tabs function above have bee call to clean their effects
116 // if necessary
116 // if necessary
117 this.reset_tabs_function = function (cell, text) {
117 this.reset_tabs_function = function (cell, text) {
118 this._old_cell = (cell) ? cell : null;
118 this._old_cell = (cell) ? cell : null;
119 this._old_request = (text) ? text : null;
119 this._old_request = (text) ? text : null;
120 this._consecutive_counter = 0;
120 this._consecutive_counter = 0;
121 };
121 };
122 };
122 };
123
123
124 Tooltip.prototype.is_visible = function () {
124 Tooltip.prototype.is_visible = function () {
125 return !this._hidden;
125 return !this._hidden;
126 };
126 };
127
127
128 Tooltip.prototype.showInPager = function (cell) {
128 Tooltip.prototype.showInPager = function (cell) {
129 // reexecute last call in pager by appending ? to show back in pager
129 // reexecute last call in pager by appending ? to show back in pager
130 var that = this;
130 var that = this;
131 var payload = {};
131 var payload = {};
132 payload.text = that._reply.content.data['text/plain'];
132 payload.text = that._reply.content.data['text/plain'];
133
133
134 $([IPython.events]).trigger('open_with_text.Pager', payload);
134 $([IPython.events]).trigger('open_with_text.Pager', payload);
135 this.remove_and_cancel_tooltip();
135 this.remove_and_cancel_tooltip();
136 };
136 };
137
137
138 // grow the tooltip verticaly
138 // grow the tooltip verticaly
139 Tooltip.prototype.expand = function () {
139 Tooltip.prototype.expand = function () {
140 this.text.removeClass('smalltooltip');
140 this.text.removeClass('smalltooltip');
141 this.text.addClass('bigtooltip');
141 this.text.addClass('bigtooltip');
142 $('#expanbutton').hide('slow');
142 $('#expanbutton').hide('slow');
143 };
143 };
144
144
145 // deal with all the logic of hiding the tooltip
145 // deal with all the logic of hiding the tooltip
146 // and reset it's status
146 // and reset it's status
147 Tooltip.prototype._hide = function () {
147 Tooltip.prototype._hide = function () {
148 this._hidden = true;
148 this._hidden = true;
149 this.tooltip.fadeOut('fast');
149 this.tooltip.fadeOut('fast');
150 $('#expanbutton').show('slow');
150 $('#expanbutton').show('slow');
151 this.text.removeClass('bigtooltip');
151 this.text.removeClass('bigtooltip');
152 this.text.addClass('smalltooltip');
152 this.text.addClass('smalltooltip');
153 // keep scroll top to be sure to always see the first line
153 // keep scroll top to be sure to always see the first line
154 this.text.scrollTop(0);
154 this.text.scrollTop(0);
155 this.code_mirror = null;
155 this.code_mirror = null;
156 };
156 };
157
157
158 // return true on successfully removing a visible tooltip; otherwise return
158 // return true on successfully removing a visible tooltip; otherwise return
159 // false.
159 // false.
160 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
160 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
161 // note that we don't handle closing directly inside the calltip
161 // note that we don't handle closing directly inside the calltip
162 // as in the completer, because it is not focusable, so won't
162 // as in the completer, because it is not focusable, so won't
163 // get the event.
163 // get the event.
164 this.cancel_pending();
164 this.cancel_pending();
165 if (!this._hidden) {
165 if (!this._hidden) {
166 if (force || !this._sticky) {
166 if (force || !this._sticky) {
167 this.cancel_stick();
167 this.cancel_stick();
168 this._hide();
168 this._hide();
169 }
169 }
170 this.reset_tabs_function();
170 this.reset_tabs_function();
171 return true;
171 return true;
172 } else {
172 } else {
173 return false;
173 return false;
174 }
174 }
175 };
175 };
176
176
177 // cancel autocall done after '(' for example.
177 // cancel autocall done after '(' for example.
178 Tooltip.prototype.cancel_pending = function () {
178 Tooltip.prototype.cancel_pending = function () {
179 if (this._tooltip_timeout !== null) {
179 if (this._tooltip_timeout !== null) {
180 clearTimeout(this._tooltip_timeout);
180 clearTimeout(this._tooltip_timeout);
181 this._tooltip_timeout = null;
181 this._tooltip_timeout = null;
182 }
182 }
183 };
183 };
184
184
185 // will trigger tooltip after timeout
185 // will trigger tooltip after timeout
186 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
186 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
187 var that = this;
187 var that = this;
188 this._tooltip_timeout = setTimeout(function () {
188 this._tooltip_timeout = setTimeout(function () {
189 that.request(cell, hide_if_no_docstring);
189 that.request(cell, hide_if_no_docstring);
190 }, that.time_before_tooltip);
190 }, that.time_before_tooltip);
191 };
191 };
192
192
193 // easy access for julia monkey patching.
193 // easy access for julia monkey patching.
194 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
194 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
195
195
196 Tooltip.prototype.extract_oir_token = function(line){
196 Tooltip.prototype.extract_oir_token = function(line){
197 // use internally just to make the request to the kernel
197 // use internally just to make the request to the kernel
198 // Feel free to shorten this logic if you are better
198 // Feel free to shorten this logic if you are better
199 // than me in regEx
199 // than me in regEx
200 // basicaly you shoul be able to get xxx.xxx.xxx from
200 // basicaly you shoul be able to get xxx.xxx.xxx from
201 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
201 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
202 // remove everything between matchin bracket (need to iterate)
202 // remove everything between matchin bracket (need to iterate)
203 var matchBracket = /\([^\(\)]+\)/g;
203 var matchBracket = /\([^\(\)]+\)/g;
204 var endBracket = /\([^\(]*$/g;
204 var endBracket = /\([^\(]*$/g;
205 var oldline = line;
205 var oldline = line;
206
206
207 line = line.replace(matchBracket, "");
207 line = line.replace(matchBracket, "");
208 while (oldline != line) {
208 while (oldline != line) {
209 oldline = line;
209 oldline = line;
210 line = line.replace(matchBracket, "");
210 line = line.replace(matchBracket, "");
211 }
211 }
212 // remove everything after last open bracket
212 // remove everything after last open bracket
213 line = line.replace(endBracket, "");
213 line = line.replace(endBracket, "");
214 // reset the regex object
214 // reset the regex object
215 Tooltip.last_token_re.lastIndex = 0;
215 Tooltip.last_token_re.lastIndex = 0;
216 return Tooltip.last_token_re.exec(line);
216 return Tooltip.last_token_re.exec(line);
217 };
217 };
218
218
219 Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
219 Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
220 var callbacks = $.proxy(this._show, this);
220 var callbacks = $.proxy(this._show, this);
221 var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
221 var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
222 };
222 };
223
223
224 // make an imediate completion request
224 // make an imediate completion request
225 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
225 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
226 // request(codecell)
226 // request(codecell)
227 // Deal with extracting the text from the cell and counting
227 // Deal with extracting the text from the cell and counting
228 // call in a row
228 // call in a row
229 this.cancel_pending();
229 this.cancel_pending();
230 var editor = cell.code_mirror;
230 var editor = cell.code_mirror;
231 var cursor = editor.getCursor();
231 var cursor = editor.getCursor();
232 var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor);
232 var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor);
233 var text = cell.get_text();
233 var text = cell.get_text();
234
234
235 this._hide_if_no_docstring = hide_if_no_docstring;
235 this._hide_if_no_docstring = hide_if_no_docstring;
236
236
237 if(editor.somethingSelected()){
237 if(editor.somethingSelected()){
238 text = editor.getSelection();
238 text = editor.getSelection();
239 }
239 }
240
240
241 // need a permanent handel to code_mirror for future auto recall
241 // need a permanent handel to code_mirror for future auto recall
242 this.code_mirror = editor;
242 this.code_mirror = editor;
243
243
244 // now we treat the different number of keypress
244 // now we treat the different number of keypress
245 // first if same cell, same text, increment counter by 1
245 // first if same cell, same text, increment counter by 1
246 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
246 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
247 this._consecutive_counter++;
247 this._consecutive_counter++;
248 } else {
248 } else {
249 // else reset
249 // else reset
250 this.cancel_stick();
250 this.cancel_stick();
251 this.reset_tabs_function (cell, text);
251 this.reset_tabs_function (cell, text);
252 }
252 }
253
253
254 // don't do anything if line begins with '(' or is empty
255 // if (text === "" || text === "(") {
256 // return;
257 // }
258
259 this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
254 this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
260
255
261 // then if we are at the end of list function, reset
256 // then if we are at the end of list function, reset
262 if (this._consecutive_counter == this.tabs_functions.length) {
257 if (this._consecutive_counter == this.tabs_functions.length) {
263 this.reset_tabs_function (cell, text, cursor);
258 this.reset_tabs_function (cell, text, cursor);
264 }
259 }
265
260
266 return;
261 return;
267 };
262 };
268
263
269 // cancel the option of having the tooltip to stick
264 // cancel the option of having the tooltip to stick
270 Tooltip.prototype.cancel_stick = function () {
265 Tooltip.prototype.cancel_stick = function () {
271 clearTimeout(this._stick_timeout);
266 clearTimeout(this._stick_timeout);
272 this._stick_timeout = null;
267 this._stick_timeout = null;
273 this._clocklink.hide('slow');
268 this._clocklink.hide('slow');
274 this._sticky = false;
269 this._sticky = false;
275 };
270 };
276
271
277 // put the tooltip in a sicky state for 10 seconds
272 // put the tooltip in a sicky state for 10 seconds
278 // it won't be removed by remove_and_cancell() unless you called with
273 // it won't be removed by remove_and_cancell() unless you called with
279 // the first parameter set to true.
274 // the first parameter set to true.
280 // remove_and_cancell_tooltip(true)
275 // remove_and_cancell_tooltip(true)
281 Tooltip.prototype.stick = function (time) {
276 Tooltip.prototype.stick = function (time) {
282 time = (time !== undefined) ? time : 10;
277 time = (time !== undefined) ? time : 10;
283 var that = this;
278 var that = this;
284 this._sticky = true;
279 this._sticky = true;
285 this._clocklink.show('slow');
280 this._clocklink.show('slow');
286 this._stick_timeout = setTimeout(function () {
281 this._stick_timeout = setTimeout(function () {
287 that._sticky = false;
282 that._sticky = false;
288 that._clocklink.hide('slow');
283 that._clocklink.hide('slow');
289 }, time * 1000);
284 }, time * 1000);
290 };
285 };
291
286
292 // should be called with the kernel reply to actually show the tooltip
287 // should be called with the kernel reply to actually show the tooltip
293 Tooltip.prototype._show = function (reply) {
288 Tooltip.prototype._show = function (reply) {
294 // move the bubble if it is not hidden
289 // move the bubble if it is not hidden
295 // otherwise fade it
290 // otherwise fade it
296 this._reply = reply;
291 this._reply = reply;
297 var content = reply.content;
292 var content = reply.content;
298 if (!content.found) {
293 if (!content.found) {
299 // object not found, nothing to show
294 // object not found, nothing to show
300 return;
295 return;
301 }
296 }
302 this.name = content.name;
297 this.name = content.name;
303
298
304 // do some math to have the tooltip arrow on more or less on left or right
299 // do some math to have the tooltip arrow on more or less on left or right
305 // width of the editor
300 // width of the editor
306 var w = $(this.code_mirror.getScrollerElement()).width();
301 var w = $(this.code_mirror.getScrollerElement()).width();
307 // ofset of the editor
302 // ofset of the editor
308 var o = $(this.code_mirror.getScrollerElement()).offset();
303 var o = $(this.code_mirror.getScrollerElement()).offset();
309
304
310 // whatever anchor/head order but arrow at mid x selection
305 // whatever anchor/head order but arrow at mid x selection
311 var anchor = this.code_mirror.cursorCoords(false);
306 var anchor = this.code_mirror.cursorCoords(false);
312 var head = this.code_mirror.cursorCoords(true);
307 var head = this.code_mirror.cursorCoords(true);
313 var xinit = (head.left+anchor.left)/2;
308 var xinit = (head.left+anchor.left)/2;
314 var xinter = o.left + (xinit - o.left) / w * (w - 450);
309 var xinter = o.left + (xinit - o.left) / w * (w - 450);
315 var posarrowleft = xinit - xinter;
310 var posarrowleft = xinit - xinter;
316
311
317 if (this._hidden === false) {
312 if (this._hidden === false) {
318 this.tooltip.animate({
313 this.tooltip.animate({
319 'left': xinter - 30 + 'px',
314 'left': xinter - 30 + 'px',
320 'top': (head.bottom + 10) + 'px'
315 'top': (head.bottom + 10) + 'px'
321 });
316 });
322 } else {
317 } else {
323 this.tooltip.css({
318 this.tooltip.css({
324 'left': xinter - 30 + 'px'
319 'left': xinter - 30 + 'px'
325 });
320 });
326 this.tooltip.css({
321 this.tooltip.css({
327 'top': (head.bottom + 10) + 'px'
322 'top': (head.bottom + 10) + 'px'
328 });
323 });
329 }
324 }
330 this.arrow.animate({
325 this.arrow.animate({
331 'left': posarrowleft + 'px'
326 'left': posarrowleft + 'px'
332 });
327 });
333
328
334 this._hidden = false;
329 this._hidden = false;
335 this.tooltip.fadeIn('fast');
330 this.tooltip.fadeIn('fast');
336 this.text.children().remove();
331 this.text.children().remove();
337
332
338 // This should support rich data types, but only text/plain for now
333 // This should support rich data types, but only text/plain for now
339 // Any HTML within the docstring is escaped by the fixConsole() method.
334 // Any HTML within the docstring is escaped by the fixConsole() method.
340 var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
335 var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
341 this.text.append(pre);
336 this.text.append(pre);
342 // keep scroll top to be sure to always see the first line
337 // keep scroll top to be sure to always see the first line
343 this.text.scrollTop(0);
338 this.text.scrollTop(0);
344 };
339 };
345
340
346 IPython.Tooltip = Tooltip;
341 IPython.Tooltip = Tooltip;
347
342
348 return IPython;
343 return IPython;
349
344
350 }(IPython));
345 }(IPython));
@@ -1,620 +1,626 b''
1 """Base classes to manage a Client's interaction with a running kernel"""
1 """Base classes to manage a Client's interaction with a running kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 import atexit
8 import atexit
9 import errno
9 import errno
10 from threading import Thread
10 from threading import Thread
11 import time
11 import time
12
12
13 import zmq
13 import zmq
14 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
14 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
15 # during garbage collection of threads at exit:
15 # during garbage collection of threads at exit:
16 from zmq import ZMQError
16 from zmq import ZMQError
17 from zmq.eventloop import ioloop, zmqstream
17 from zmq.eventloop import ioloop, zmqstream
18
18
19 # Local imports
19 # Local imports
20 from .channelsabc import (
20 from .channelsabc import (
21 ShellChannelABC, IOPubChannelABC,
21 ShellChannelABC, IOPubChannelABC,
22 HBChannelABC, StdInChannelABC,
22 HBChannelABC, StdInChannelABC,
23 )
23 )
24 from IPython.utils.py3compat import string_types, iteritems
24 from IPython.utils.py3compat import string_types, iteritems
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Constants and exceptions
27 # Constants and exceptions
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 class InvalidPortNumber(Exception):
30 class InvalidPortNumber(Exception):
31 pass
31 pass
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Utility functions
34 # Utility functions
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 # some utilities to validate message structure, these might get moved elsewhere
37 # some utilities to validate message structure, these might get moved elsewhere
38 # if they prove to have more generic utility
38 # if they prove to have more generic utility
39
39
40 def validate_string_list(lst):
40 def validate_string_list(lst):
41 """Validate that the input is a list of strings.
41 """Validate that the input is a list of strings.
42
42
43 Raises ValueError if not."""
43 Raises ValueError if not."""
44 if not isinstance(lst, list):
44 if not isinstance(lst, list):
45 raise ValueError('input %r must be a list' % lst)
45 raise ValueError('input %r must be a list' % lst)
46 for x in lst:
46 for x in lst:
47 if not isinstance(x, string_types):
47 if not isinstance(x, string_types):
48 raise ValueError('element %r in list must be a string' % x)
48 raise ValueError('element %r in list must be a string' % x)
49
49
50
50
51 def validate_string_dict(dct):
51 def validate_string_dict(dct):
52 """Validate that the input is a dict with string keys and values.
52 """Validate that the input is a dict with string keys and values.
53
53
54 Raises ValueError if not."""
54 Raises ValueError if not."""
55 for k,v in iteritems(dct):
55 for k,v in iteritems(dct):
56 if not isinstance(k, string_types):
56 if not isinstance(k, string_types):
57 raise ValueError('key %r in dict must be a string' % k)
57 raise ValueError('key %r in dict must be a string' % k)
58 if not isinstance(v, string_types):
58 if not isinstance(v, string_types):
59 raise ValueError('value %r in dict must be a string' % v)
59 raise ValueError('value %r in dict must be a string' % v)
60
60
61
61
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63 # ZMQ Socket Channel classes
63 # ZMQ Socket Channel classes
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65
65
66 class ZMQSocketChannel(Thread):
66 class ZMQSocketChannel(Thread):
67 """The base class for the channels that use ZMQ sockets."""
67 """The base class for the channels that use ZMQ sockets."""
68 context = None
68 context = None
69 session = None
69 session = None
70 socket = None
70 socket = None
71 ioloop = None
71 ioloop = None
72 stream = None
72 stream = None
73 _address = None
73 _address = None
74 _exiting = False
74 _exiting = False
75 proxy_methods = []
75 proxy_methods = []
76
76
77 def __init__(self, context, session, address):
77 def __init__(self, context, session, address):
78 """Create a channel.
78 """Create a channel.
79
79
80 Parameters
80 Parameters
81 ----------
81 ----------
82 context : :class:`zmq.Context`
82 context : :class:`zmq.Context`
83 The ZMQ context to use.
83 The ZMQ context to use.
84 session : :class:`session.Session`
84 session : :class:`session.Session`
85 The session to use.
85 The session to use.
86 address : zmq url
86 address : zmq url
87 Standard (ip, port) tuple that the kernel is listening on.
87 Standard (ip, port) tuple that the kernel is listening on.
88 """
88 """
89 super(ZMQSocketChannel, self).__init__()
89 super(ZMQSocketChannel, self).__init__()
90 self.daemon = True
90 self.daemon = True
91
91
92 self.context = context
92 self.context = context
93 self.session = session
93 self.session = session
94 if isinstance(address, tuple):
94 if isinstance(address, tuple):
95 if address[1] == 0:
95 if address[1] == 0:
96 message = 'The port number for a channel cannot be 0.'
96 message = 'The port number for a channel cannot be 0.'
97 raise InvalidPortNumber(message)
97 raise InvalidPortNumber(message)
98 address = "tcp://%s:%i" % address
98 address = "tcp://%s:%i" % address
99 self._address = address
99 self._address = address
100 atexit.register(self._notice_exit)
100 atexit.register(self._notice_exit)
101
101
102 def _notice_exit(self):
102 def _notice_exit(self):
103 self._exiting = True
103 self._exiting = True
104
104
105 def _run_loop(self):
105 def _run_loop(self):
106 """Run my loop, ignoring EINTR events in the poller"""
106 """Run my loop, ignoring EINTR events in the poller"""
107 while True:
107 while True:
108 try:
108 try:
109 self.ioloop.start()
109 self.ioloop.start()
110 except ZMQError as e:
110 except ZMQError as e:
111 if e.errno == errno.EINTR:
111 if e.errno == errno.EINTR:
112 continue
112 continue
113 else:
113 else:
114 raise
114 raise
115 except Exception:
115 except Exception:
116 if self._exiting:
116 if self._exiting:
117 break
117 break
118 else:
118 else:
119 raise
119 raise
120 else:
120 else:
121 break
121 break
122
122
123 def stop(self):
123 def stop(self):
124 """Stop the channel's event loop and join its thread.
124 """Stop the channel's event loop and join its thread.
125
125
126 This calls :meth:`~threading.Thread.join` and returns when the thread
126 This calls :meth:`~threading.Thread.join` and returns when the thread
127 terminates. :class:`RuntimeError` will be raised if
127 terminates. :class:`RuntimeError` will be raised if
128 :meth:`~threading.Thread.start` is called again.
128 :meth:`~threading.Thread.start` is called again.
129 """
129 """
130 if self.ioloop is not None:
130 if self.ioloop is not None:
131 self.ioloop.stop()
131 self.ioloop.stop()
132 self.join()
132 self.join()
133 self.close()
133 self.close()
134
134
135 def close(self):
135 def close(self):
136 if self.ioloop is not None:
136 if self.ioloop is not None:
137 try:
137 try:
138 self.ioloop.close(all_fds=True)
138 self.ioloop.close(all_fds=True)
139 except Exception:
139 except Exception:
140 pass
140 pass
141 if self.socket is not None:
141 if self.socket is not None:
142 try:
142 try:
143 self.socket.close(linger=0)
143 self.socket.close(linger=0)
144 except Exception:
144 except Exception:
145 pass
145 pass
146 self.socket = None
146 self.socket = None
147
147
148 @property
148 @property
149 def address(self):
149 def address(self):
150 """Get the channel's address as a zmq url string.
150 """Get the channel's address as a zmq url string.
151
151
152 These URLS have the form: 'tcp://127.0.0.1:5555'.
152 These URLS have the form: 'tcp://127.0.0.1:5555'.
153 """
153 """
154 return self._address
154 return self._address
155
155
156 def _queue_send(self, msg):
156 def _queue_send(self, msg):
157 """Queue a message to be sent from the IOLoop's thread.
157 """Queue a message to be sent from the IOLoop's thread.
158
158
159 Parameters
159 Parameters
160 ----------
160 ----------
161 msg : message to send
161 msg : message to send
162
162
163 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
163 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
164 thread control of the action.
164 thread control of the action.
165 """
165 """
166 def thread_send():
166 def thread_send():
167 self.session.send(self.stream, msg)
167 self.session.send(self.stream, msg)
168 self.ioloop.add_callback(thread_send)
168 self.ioloop.add_callback(thread_send)
169
169
170 def _handle_recv(self, msg):
170 def _handle_recv(self, msg):
171 """Callback for stream.on_recv.
171 """Callback for stream.on_recv.
172
172
173 Unpacks message, and calls handlers with it.
173 Unpacks message, and calls handlers with it.
174 """
174 """
175 ident,smsg = self.session.feed_identities(msg)
175 ident,smsg = self.session.feed_identities(msg)
176 self.call_handlers(self.session.unserialize(smsg))
176 self.call_handlers(self.session.unserialize(smsg))
177
177
178
178
179
179
180 class ShellChannel(ZMQSocketChannel):
180 class ShellChannel(ZMQSocketChannel):
181 """The shell channel for issuing request/replies to the kernel."""
181 """The shell channel for issuing request/replies to the kernel."""
182
182
183 command_queue = None
183 command_queue = None
184 # flag for whether execute requests should be allowed to call raw_input:
184 # flag for whether execute requests should be allowed to call raw_input:
185 allow_stdin = True
185 allow_stdin = True
186 proxy_methods = [
186 proxy_methods = [
187 'execute',
187 'execute',
188 'complete',
188 'complete',
189 'inspect',
189 'inspect',
190 'history',
190 'history',
191 'kernel_info',
191 'kernel_info',
192 'shutdown',
192 'shutdown',
193 ]
193 ]
194
194
195 def __init__(self, context, session, address):
195 def __init__(self, context, session, address):
196 super(ShellChannel, self).__init__(context, session, address)
196 super(ShellChannel, self).__init__(context, session, address)
197 self.ioloop = ioloop.IOLoop()
197 self.ioloop = ioloop.IOLoop()
198
198
199 def run(self):
199 def run(self):
200 """The thread's main activity. Call start() instead."""
200 """The thread's main activity. Call start() instead."""
201 self.socket = self.context.socket(zmq.DEALER)
201 self.socket = self.context.socket(zmq.DEALER)
202 self.socket.linger = 1000
202 self.socket.linger = 1000
203 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
203 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
204 self.socket.connect(self.address)
204 self.socket.connect(self.address)
205 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
205 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
206 self.stream.on_recv(self._handle_recv)
206 self.stream.on_recv(self._handle_recv)
207 self._run_loop()
207 self._run_loop()
208
208
209 def call_handlers(self, msg):
209 def call_handlers(self, msg):
210 """This method is called in the ioloop thread when a message arrives.
210 """This method is called in the ioloop thread when a message arrives.
211
211
212 Subclasses should override this method to handle incoming messages.
212 Subclasses should override this method to handle incoming messages.
213 It is important to remember that this method is called in the thread
213 It is important to remember that this method is called in the thread
214 so that some logic must be done to ensure that the application level
214 so that some logic must be done to ensure that the application level
215 handlers are called in the application thread.
215 handlers are called in the application thread.
216 """
216 """
217 raise NotImplementedError('call_handlers must be defined in a subclass.')
217 raise NotImplementedError('call_handlers must be defined in a subclass.')
218
218
219 def execute(self, code, silent=False, store_history=True,
219 def execute(self, code, silent=False, store_history=True,
220 user_expressions=None, allow_stdin=None):
220 user_expressions=None, allow_stdin=None):
221 """Execute code in the kernel.
221 """Execute code in the kernel.
222
222
223 Parameters
223 Parameters
224 ----------
224 ----------
225 code : str
225 code : str
226 A string of Python code.
226 A string of Python code.
227
227
228 silent : bool, optional (default False)
228 silent : bool, optional (default False)
229 If set, the kernel will execute the code as quietly possible, and
229 If set, the kernel will execute the code as quietly possible, and
230 will force store_history to be False.
230 will force store_history to be False.
231
231
232 store_history : bool, optional (default True)
232 store_history : bool, optional (default True)
233 If set, the kernel will store command history. This is forced
233 If set, the kernel will store command history. This is forced
234 to be False if silent is True.
234 to be False if silent is True.
235
235
236 user_expressions : dict, optional
236 user_expressions : dict, optional
237 A dict mapping names to expressions to be evaluated in the user's
237 A dict mapping names to expressions to be evaluated in the user's
238 dict. The expression values are returned as strings formatted using
238 dict. The expression values are returned as strings formatted using
239 :func:`repr`.
239 :func:`repr`.
240
240
241 allow_stdin : bool, optional (default self.allow_stdin)
241 allow_stdin : bool, optional (default self.allow_stdin)
242 Flag for whether the kernel can send stdin requests to frontends.
242 Flag for whether the kernel can send stdin requests to frontends.
243
243
244 Some frontends (e.g. the Notebook) do not support stdin requests.
244 Some frontends (e.g. the Notebook) do not support stdin requests.
245 If raw_input is called from code executed from such a frontend, a
245 If raw_input is called from code executed from such a frontend, a
246 StdinNotImplementedError will be raised.
246 StdinNotImplementedError will be raised.
247
247
248 Returns
248 Returns
249 -------
249 -------
250 The msg_id of the message sent.
250 The msg_id of the message sent.
251 """
251 """
252 if user_expressions is None:
252 if user_expressions is None:
253 user_expressions = {}
253 user_expressions = {}
254 if allow_stdin is None:
254 if allow_stdin is None:
255 allow_stdin = self.allow_stdin
255 allow_stdin = self.allow_stdin
256
256
257
257
258 # Don't waste network traffic if inputs are invalid
258 # Don't waste network traffic if inputs are invalid
259 if not isinstance(code, string_types):
259 if not isinstance(code, string_types):
260 raise ValueError('code %r must be a string' % code)
260 raise ValueError('code %r must be a string' % code)
261 validate_string_dict(user_expressions)
261 validate_string_dict(user_expressions)
262
262
263 # Create class for content/msg creation. Related to, but possibly
263 # Create class for content/msg creation. Related to, but possibly
264 # not in Session.
264 # not in Session.
265 content = dict(code=code, silent=silent, store_history=store_history,
265 content = dict(code=code, silent=silent, store_history=store_history,
266 user_expressions=user_expressions,
266 user_expressions=user_expressions,
267 allow_stdin=allow_stdin,
267 allow_stdin=allow_stdin,
268 )
268 )
269 msg = self.session.msg('execute_request', content)
269 msg = self.session.msg('execute_request', content)
270 self._queue_send(msg)
270 self._queue_send(msg)
271 return msg['header']['msg_id']
271 return msg['header']['msg_id']
272
272
273 def complete(self, code, cursor_pos=0, block=None):
273 def complete(self, code, cursor_pos=None):
274 """Tab complete text in the kernel's namespace.
274 """Tab complete text in the kernel's namespace.
275
275
276 Parameters
276 Parameters
277 ----------
277 ----------
278 code : str
278 code : str
279 The context in which completion is requested.
279 The context in which completion is requested.
280 Can be anything between a variable name and an entire cell.
280 Can be anything between a variable name and an entire cell.
281 cursor_pos : int, optional
281 cursor_pos : int, optional
282 The position of the cursor in the block of code where the completion was requested.
282 The position of the cursor in the block of code where the completion was requested.
283 Default: ``len(code)``
283
284
284 Returns
285 Returns
285 -------
286 -------
286 The msg_id of the message sent.
287 The msg_id of the message sent.
287 """
288 """
289 if cursor_pos is None:
290 cursor_pos = len(code)
288 content = dict(code=code, cursor_pos=cursor_pos)
291 content = dict(code=code, cursor_pos=cursor_pos)
289 msg = self.session.msg('complete_request', content)
292 msg = self.session.msg('complete_request', content)
290 self._queue_send(msg)
293 self._queue_send(msg)
291 return msg['header']['msg_id']
294 return msg['header']['msg_id']
292
295
293 def inspect(self, code, cursor_pos=0, detail_level=0):
296 def inspect(self, code, cursor_pos=None, detail_level=0):
294 """Get metadata information about an object in the kernel's namespace.
297 """Get metadata information about an object in the kernel's namespace.
295
298
296 It is up to the kernel to determine the appropriate object to inspect.
299 It is up to the kernel to determine the appropriate object to inspect.
297
300
298 Parameters
301 Parameters
299 ----------
302 ----------
300 code : str
303 code : str
301 The context in which info is requested.
304 The context in which info is requested.
302 Can be anything between a variable name and an entire cell.
305 Can be anything between a variable name and an entire cell.
303 cursor_pos : int, optional
306 cursor_pos : int, optional
304 The position of the cursor in the block of code where the info was requested.
307 The position of the cursor in the block of code where the info was requested.
308 Default: ``len(code)``
305 detail_level : int, optional
309 detail_level : int, optional
306 The level of detail for the introspection (0-2)
310 The level of detail for the introspection (0-2)
307
311
308 Returns
312 Returns
309 -------
313 -------
310 The msg_id of the message sent.
314 The msg_id of the message sent.
311 """
315 """
316 if cursor_pos is None:
317 cursor_pos = len(code)
312 content = dict(code=code, cursor_pos=cursor_pos,
318 content = dict(code=code, cursor_pos=cursor_pos,
313 detail_level=detail_level,
319 detail_level=detail_level,
314 )
320 )
315 msg = self.session.msg('inspect_request', content)
321 msg = self.session.msg('inspect_request', content)
316 self._queue_send(msg)
322 self._queue_send(msg)
317 return msg['header']['msg_id']
323 return msg['header']['msg_id']
318
324
319 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
325 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
320 """Get entries from the kernel's history list.
326 """Get entries from the kernel's history list.
321
327
322 Parameters
328 Parameters
323 ----------
329 ----------
324 raw : bool
330 raw : bool
325 If True, return the raw input.
331 If True, return the raw input.
326 output : bool
332 output : bool
327 If True, then return the output as well.
333 If True, then return the output as well.
328 hist_access_type : str
334 hist_access_type : str
329 'range' (fill in session, start and stop params), 'tail' (fill in n)
335 'range' (fill in session, start and stop params), 'tail' (fill in n)
330 or 'search' (fill in pattern param).
336 or 'search' (fill in pattern param).
331
337
332 session : int
338 session : int
333 For a range request, the session from which to get lines. Session
339 For a range request, the session from which to get lines. Session
334 numbers are positive integers; negative ones count back from the
340 numbers are positive integers; negative ones count back from the
335 current session.
341 current session.
336 start : int
342 start : int
337 The first line number of a history range.
343 The first line number of a history range.
338 stop : int
344 stop : int
339 The final (excluded) line number of a history range.
345 The final (excluded) line number of a history range.
340
346
341 n : int
347 n : int
342 The number of lines of history to get for a tail request.
348 The number of lines of history to get for a tail request.
343
349
344 pattern : str
350 pattern : str
345 The glob-syntax pattern for a search request.
351 The glob-syntax pattern for a search request.
346
352
347 Returns
353 Returns
348 -------
354 -------
349 The msg_id of the message sent.
355 The msg_id of the message sent.
350 """
356 """
351 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
357 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
352 **kwargs)
358 **kwargs)
353 msg = self.session.msg('history_request', content)
359 msg = self.session.msg('history_request', content)
354 self._queue_send(msg)
360 self._queue_send(msg)
355 return msg['header']['msg_id']
361 return msg['header']['msg_id']
356
362
357 def kernel_info(self):
363 def kernel_info(self):
358 """Request kernel info."""
364 """Request kernel info."""
359 msg = self.session.msg('kernel_info_request')
365 msg = self.session.msg('kernel_info_request')
360 self._queue_send(msg)
366 self._queue_send(msg)
361 return msg['header']['msg_id']
367 return msg['header']['msg_id']
362
368
363 def shutdown(self, restart=False):
369 def shutdown(self, restart=False):
364 """Request an immediate kernel shutdown.
370 """Request an immediate kernel shutdown.
365
371
366 Upon receipt of the (empty) reply, client code can safely assume that
372 Upon receipt of the (empty) reply, client code can safely assume that
367 the kernel has shut down and it's safe to forcefully terminate it if
373 the kernel has shut down and it's safe to forcefully terminate it if
368 it's still alive.
374 it's still alive.
369
375
370 The kernel will send the reply via a function registered with Python's
376 The kernel will send the reply via a function registered with Python's
371 atexit module, ensuring it's truly done as the kernel is done with all
377 atexit module, ensuring it's truly done as the kernel is done with all
372 normal operation.
378 normal operation.
373 """
379 """
374 # Send quit message to kernel. Once we implement kernel-side setattr,
380 # Send quit message to kernel. Once we implement kernel-side setattr,
375 # this should probably be done that way, but for now this will do.
381 # this should probably be done that way, but for now this will do.
376 msg = self.session.msg('shutdown_request', {'restart':restart})
382 msg = self.session.msg('shutdown_request', {'restart':restart})
377 self._queue_send(msg)
383 self._queue_send(msg)
378 return msg['header']['msg_id']
384 return msg['header']['msg_id']
379
385
380
386
381
387
382 class IOPubChannel(ZMQSocketChannel):
388 class IOPubChannel(ZMQSocketChannel):
383 """The iopub channel which listens for messages that the kernel publishes.
389 """The iopub channel which listens for messages that the kernel publishes.
384
390
385 This channel is where all output is published to frontends.
391 This channel is where all output is published to frontends.
386 """
392 """
387
393
388 def __init__(self, context, session, address):
394 def __init__(self, context, session, address):
389 super(IOPubChannel, self).__init__(context, session, address)
395 super(IOPubChannel, self).__init__(context, session, address)
390 self.ioloop = ioloop.IOLoop()
396 self.ioloop = ioloop.IOLoop()
391
397
392 def run(self):
398 def run(self):
393 """The thread's main activity. Call start() instead."""
399 """The thread's main activity. Call start() instead."""
394 self.socket = self.context.socket(zmq.SUB)
400 self.socket = self.context.socket(zmq.SUB)
395 self.socket.linger = 1000
401 self.socket.linger = 1000
396 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
402 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
397 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
403 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
398 self.socket.connect(self.address)
404 self.socket.connect(self.address)
399 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
405 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
400 self.stream.on_recv(self._handle_recv)
406 self.stream.on_recv(self._handle_recv)
401 self._run_loop()
407 self._run_loop()
402
408
403 def call_handlers(self, msg):
409 def call_handlers(self, msg):
404 """This method is called in the ioloop thread when a message arrives.
410 """This method is called in the ioloop thread when a message arrives.
405
411
406 Subclasses should override this method to handle incoming messages.
412 Subclasses should override this method to handle incoming messages.
407 It is important to remember that this method is called in the thread
413 It is important to remember that this method is called in the thread
408 so that some logic must be done to ensure that the application leve
414 so that some logic must be done to ensure that the application leve
409 handlers are called in the application thread.
415 handlers are called in the application thread.
410 """
416 """
411 raise NotImplementedError('call_handlers must be defined in a subclass.')
417 raise NotImplementedError('call_handlers must be defined in a subclass.')
412
418
413 def flush(self, timeout=1.0):
419 def flush(self, timeout=1.0):
414 """Immediately processes all pending messages on the iopub channel.
420 """Immediately processes all pending messages on the iopub channel.
415
421
416 Callers should use this method to ensure that :meth:`call_handlers`
422 Callers should use this method to ensure that :meth:`call_handlers`
417 has been called for all messages that have been received on the
423 has been called for all messages that have been received on the
418 0MQ SUB socket of this channel.
424 0MQ SUB socket of this channel.
419
425
420 This method is thread safe.
426 This method is thread safe.
421
427
422 Parameters
428 Parameters
423 ----------
429 ----------
424 timeout : float, optional
430 timeout : float, optional
425 The maximum amount of time to spend flushing, in seconds. The
431 The maximum amount of time to spend flushing, in seconds. The
426 default is one second.
432 default is one second.
427 """
433 """
428 # We do the IOLoop callback process twice to ensure that the IOLoop
434 # We do the IOLoop callback process twice to ensure that the IOLoop
429 # gets to perform at least one full poll.
435 # gets to perform at least one full poll.
430 stop_time = time.time() + timeout
436 stop_time = time.time() + timeout
431 for i in range(2):
437 for i in range(2):
432 self._flushed = False
438 self._flushed = False
433 self.ioloop.add_callback(self._flush)
439 self.ioloop.add_callback(self._flush)
434 while not self._flushed and time.time() < stop_time:
440 while not self._flushed and time.time() < stop_time:
435 time.sleep(0.01)
441 time.sleep(0.01)
436
442
437 def _flush(self):
443 def _flush(self):
438 """Callback for :method:`self.flush`."""
444 """Callback for :method:`self.flush`."""
439 self.stream.flush()
445 self.stream.flush()
440 self._flushed = True
446 self._flushed = True
441
447
442
448
443 class StdInChannel(ZMQSocketChannel):
449 class StdInChannel(ZMQSocketChannel):
444 """The stdin channel to handle raw_input requests that the kernel makes."""
450 """The stdin channel to handle raw_input requests that the kernel makes."""
445
451
446 msg_queue = None
452 msg_queue = None
447 proxy_methods = ['input']
453 proxy_methods = ['input']
448
454
449 def __init__(self, context, session, address):
455 def __init__(self, context, session, address):
450 super(StdInChannel, self).__init__(context, session, address)
456 super(StdInChannel, self).__init__(context, session, address)
451 self.ioloop = ioloop.IOLoop()
457 self.ioloop = ioloop.IOLoop()
452
458
453 def run(self):
459 def run(self):
454 """The thread's main activity. Call start() instead."""
460 """The thread's main activity. Call start() instead."""
455 self.socket = self.context.socket(zmq.DEALER)
461 self.socket = self.context.socket(zmq.DEALER)
456 self.socket.linger = 1000
462 self.socket.linger = 1000
457 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
463 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
458 self.socket.connect(self.address)
464 self.socket.connect(self.address)
459 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
465 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
460 self.stream.on_recv(self._handle_recv)
466 self.stream.on_recv(self._handle_recv)
461 self._run_loop()
467 self._run_loop()
462
468
463 def call_handlers(self, msg):
469 def call_handlers(self, msg):
464 """This method is called in the ioloop thread when a message arrives.
470 """This method is called in the ioloop thread when a message arrives.
465
471
466 Subclasses should override this method to handle incoming messages.
472 Subclasses should override this method to handle incoming messages.
467 It is important to remember that this method is called in the thread
473 It is important to remember that this method is called in the thread
468 so that some logic must be done to ensure that the application leve
474 so that some logic must be done to ensure that the application leve
469 handlers are called in the application thread.
475 handlers are called in the application thread.
470 """
476 """
471 raise NotImplementedError('call_handlers must be defined in a subclass.')
477 raise NotImplementedError('call_handlers must be defined in a subclass.')
472
478
473 def input(self, string):
479 def input(self, string):
474 """Send a string of raw input to the kernel."""
480 """Send a string of raw input to the kernel."""
475 content = dict(value=string)
481 content = dict(value=string)
476 msg = self.session.msg('input_reply', content)
482 msg = self.session.msg('input_reply', content)
477 self._queue_send(msg)
483 self._queue_send(msg)
478
484
479
485
480 class HBChannel(ZMQSocketChannel):
486 class HBChannel(ZMQSocketChannel):
481 """The heartbeat channel which monitors the kernel heartbeat.
487 """The heartbeat channel which monitors the kernel heartbeat.
482
488
483 Note that the heartbeat channel is paused by default. As long as you start
489 Note that the heartbeat channel is paused by default. As long as you start
484 this channel, the kernel manager will ensure that it is paused and un-paused
490 this channel, the kernel manager will ensure that it is paused and un-paused
485 as appropriate.
491 as appropriate.
486 """
492 """
487
493
488 time_to_dead = 3.0
494 time_to_dead = 3.0
489 socket = None
495 socket = None
490 poller = None
496 poller = None
491 _running = None
497 _running = None
492 _pause = None
498 _pause = None
493 _beating = None
499 _beating = None
494
500
495 def __init__(self, context, session, address):
501 def __init__(self, context, session, address):
496 super(HBChannel, self).__init__(context, session, address)
502 super(HBChannel, self).__init__(context, session, address)
497 self._running = False
503 self._running = False
498 self._pause =True
504 self._pause =True
499 self.poller = zmq.Poller()
505 self.poller = zmq.Poller()
500
506
501 def _create_socket(self):
507 def _create_socket(self):
502 if self.socket is not None:
508 if self.socket is not None:
503 # close previous socket, before opening a new one
509 # close previous socket, before opening a new one
504 self.poller.unregister(self.socket)
510 self.poller.unregister(self.socket)
505 self.socket.close()
511 self.socket.close()
506 self.socket = self.context.socket(zmq.REQ)
512 self.socket = self.context.socket(zmq.REQ)
507 self.socket.linger = 1000
513 self.socket.linger = 1000
508 self.socket.connect(self.address)
514 self.socket.connect(self.address)
509
515
510 self.poller.register(self.socket, zmq.POLLIN)
516 self.poller.register(self.socket, zmq.POLLIN)
511
517
512 def _poll(self, start_time):
518 def _poll(self, start_time):
513 """poll for heartbeat replies until we reach self.time_to_dead.
519 """poll for heartbeat replies until we reach self.time_to_dead.
514
520
515 Ignores interrupts, and returns the result of poll(), which
521 Ignores interrupts, and returns the result of poll(), which
516 will be an empty list if no messages arrived before the timeout,
522 will be an empty list if no messages arrived before the timeout,
517 or the event tuple if there is a message to receive.
523 or the event tuple if there is a message to receive.
518 """
524 """
519
525
520 until_dead = self.time_to_dead - (time.time() - start_time)
526 until_dead = self.time_to_dead - (time.time() - start_time)
521 # ensure poll at least once
527 # ensure poll at least once
522 until_dead = max(until_dead, 1e-3)
528 until_dead = max(until_dead, 1e-3)
523 events = []
529 events = []
524 while True:
530 while True:
525 try:
531 try:
526 events = self.poller.poll(1000 * until_dead)
532 events = self.poller.poll(1000 * until_dead)
527 except ZMQError as e:
533 except ZMQError as e:
528 if e.errno == errno.EINTR:
534 if e.errno == errno.EINTR:
529 # ignore interrupts during heartbeat
535 # ignore interrupts during heartbeat
530 # this may never actually happen
536 # this may never actually happen
531 until_dead = self.time_to_dead - (time.time() - start_time)
537 until_dead = self.time_to_dead - (time.time() - start_time)
532 until_dead = max(until_dead, 1e-3)
538 until_dead = max(until_dead, 1e-3)
533 pass
539 pass
534 else:
540 else:
535 raise
541 raise
536 except Exception:
542 except Exception:
537 if self._exiting:
543 if self._exiting:
538 break
544 break
539 else:
545 else:
540 raise
546 raise
541 else:
547 else:
542 break
548 break
543 return events
549 return events
544
550
545 def run(self):
551 def run(self):
546 """The thread's main activity. Call start() instead."""
552 """The thread's main activity. Call start() instead."""
547 self._create_socket()
553 self._create_socket()
548 self._running = True
554 self._running = True
549 self._beating = True
555 self._beating = True
550
556
551 while self._running:
557 while self._running:
552 if self._pause:
558 if self._pause:
553 # just sleep, and skip the rest of the loop
559 # just sleep, and skip the rest of the loop
554 time.sleep(self.time_to_dead)
560 time.sleep(self.time_to_dead)
555 continue
561 continue
556
562
557 since_last_heartbeat = 0.0
563 since_last_heartbeat = 0.0
558 # io.rprint('Ping from HB channel') # dbg
564 # io.rprint('Ping from HB channel') # dbg
559 # no need to catch EFSM here, because the previous event was
565 # no need to catch EFSM here, because the previous event was
560 # either a recv or connect, which cannot be followed by EFSM
566 # either a recv or connect, which cannot be followed by EFSM
561 self.socket.send(b'ping')
567 self.socket.send(b'ping')
562 request_time = time.time()
568 request_time = time.time()
563 ready = self._poll(request_time)
569 ready = self._poll(request_time)
564 if ready:
570 if ready:
565 self._beating = True
571 self._beating = True
566 # the poll above guarantees we have something to recv
572 # the poll above guarantees we have something to recv
567 self.socket.recv()
573 self.socket.recv()
568 # sleep the remainder of the cycle
574 # sleep the remainder of the cycle
569 remainder = self.time_to_dead - (time.time() - request_time)
575 remainder = self.time_to_dead - (time.time() - request_time)
570 if remainder > 0:
576 if remainder > 0:
571 time.sleep(remainder)
577 time.sleep(remainder)
572 continue
578 continue
573 else:
579 else:
574 # nothing was received within the time limit, signal heart failure
580 # nothing was received within the time limit, signal heart failure
575 self._beating = False
581 self._beating = False
576 since_last_heartbeat = time.time() - request_time
582 since_last_heartbeat = time.time() - request_time
577 self.call_handlers(since_last_heartbeat)
583 self.call_handlers(since_last_heartbeat)
578 # and close/reopen the socket, because the REQ/REP cycle has been broken
584 # and close/reopen the socket, because the REQ/REP cycle has been broken
579 self._create_socket()
585 self._create_socket()
580 continue
586 continue
581
587
582 def pause(self):
588 def pause(self):
583 """Pause the heartbeat."""
589 """Pause the heartbeat."""
584 self._pause = True
590 self._pause = True
585
591
586 def unpause(self):
592 def unpause(self):
587 """Unpause the heartbeat."""
593 """Unpause the heartbeat."""
588 self._pause = False
594 self._pause = False
589
595
590 def is_beating(self):
596 def is_beating(self):
591 """Is the heartbeat running and responsive (and not paused)."""
597 """Is the heartbeat running and responsive (and not paused)."""
592 if self.is_alive() and not self._pause and self._beating:
598 if self.is_alive() and not self._pause and self._beating:
593 return True
599 return True
594 else:
600 else:
595 return False
601 return False
596
602
597 def stop(self):
603 def stop(self):
598 """Stop the channel's event loop and join its thread."""
604 """Stop the channel's event loop and join its thread."""
599 self._running = False
605 self._running = False
600 super(HBChannel, self).stop()
606 super(HBChannel, self).stop()
601
607
602 def call_handlers(self, since_last_heartbeat):
608 def call_handlers(self, since_last_heartbeat):
603 """This method is called in the ioloop thread when a message arrives.
609 """This method is called in the ioloop thread when a message arrives.
604
610
605 Subclasses should override this method to handle incoming messages.
611 Subclasses should override this method to handle incoming messages.
606 It is important to remember that this method is called in the thread
612 It is important to remember that this method is called in the thread
607 so that some logic must be done to ensure that the application level
613 so that some logic must be done to ensure that the application level
608 handlers are called in the application thread.
614 handlers are called in the application thread.
609 """
615 """
610 raise NotImplementedError('call_handlers must be defined in a subclass.')
616 raise NotImplementedError('call_handlers must be defined in a subclass.')
611
617
612
618
613 #---------------------------------------------------------------------#-----------------------------------------------------------------------------
619 #---------------------------------------------------------------------#-----------------------------------------------------------------------------
614 # ABC Registration
620 # ABC Registration
615 #-----------------------------------------------------------------------------
621 #-----------------------------------------------------------------------------
616
622
617 ShellChannelABC.register(ShellChannel)
623 ShellChannelABC.register(ShellChannel)
618 IOPubChannelABC.register(IOPubChannel)
624 IOPubChannelABC.register(IOPubChannel)
619 HBChannelABC.register(HBChannel)
625 HBChannelABC.register(HBChannel)
620 StdInChannelABC.register(StdInChannel)
626 StdInChannelABC.register(StdInChannel)
@@ -1,192 +1,196 b''
1 """A kernel client for in-process kernels."""
1 """A kernel client for in-process kernels."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from IPython.kernel.channelsabc import (
6 from IPython.kernel.channelsabc import (
7 ShellChannelABC, IOPubChannelABC,
7 ShellChannelABC, IOPubChannelABC,
8 HBChannelABC, StdInChannelABC,
8 HBChannelABC, StdInChannelABC,
9 )
9 )
10
10
11 from .socket import DummySocket
11 from .socket import DummySocket
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Channel classes
14 # Channel classes
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 class InProcessChannel(object):
17 class InProcessChannel(object):
18 """Base class for in-process channels."""
18 """Base class for in-process channels."""
19 proxy_methods = []
19 proxy_methods = []
20
20
21 def __init__(self, client=None):
21 def __init__(self, client=None):
22 super(InProcessChannel, self).__init__()
22 super(InProcessChannel, self).__init__()
23 self.client = client
23 self.client = client
24 self._is_alive = False
24 self._is_alive = False
25
25
26 #--------------------------------------------------------------------------
26 #--------------------------------------------------------------------------
27 # Channel interface
27 # Channel interface
28 #--------------------------------------------------------------------------
28 #--------------------------------------------------------------------------
29
29
30 def is_alive(self):
30 def is_alive(self):
31 return self._is_alive
31 return self._is_alive
32
32
33 def start(self):
33 def start(self):
34 self._is_alive = True
34 self._is_alive = True
35
35
36 def stop(self):
36 def stop(self):
37 self._is_alive = False
37 self._is_alive = False
38
38
39 def call_handlers(self, msg):
39 def call_handlers(self, msg):
40 """ This method is called in the main thread when a message arrives.
40 """ This method is called in the main thread when a message arrives.
41
41
42 Subclasses should override this method to handle incoming messages.
42 Subclasses should override this method to handle incoming messages.
43 """
43 """
44 raise NotImplementedError('call_handlers must be defined in a subclass.')
44 raise NotImplementedError('call_handlers must be defined in a subclass.')
45
45
46 #--------------------------------------------------------------------------
46 #--------------------------------------------------------------------------
47 # InProcessChannel interface
47 # InProcessChannel interface
48 #--------------------------------------------------------------------------
48 #--------------------------------------------------------------------------
49
49
50 def call_handlers_later(self, *args, **kwds):
50 def call_handlers_later(self, *args, **kwds):
51 """ Call the message handlers later.
51 """ Call the message handlers later.
52
52
53 The default implementation just calls the handlers immediately, but this
53 The default implementation just calls the handlers immediately, but this
54 method exists so that GUI toolkits can defer calling the handlers until
54 method exists so that GUI toolkits can defer calling the handlers until
55 after the event loop has run, as expected by GUI frontends.
55 after the event loop has run, as expected by GUI frontends.
56 """
56 """
57 self.call_handlers(*args, **kwds)
57 self.call_handlers(*args, **kwds)
58
58
59 def process_events(self):
59 def process_events(self):
60 """ Process any pending GUI events.
60 """ Process any pending GUI events.
61
61
62 This method will be never be called from a frontend without an event
62 This method will be never be called from a frontend without an event
63 loop (e.g., a terminal frontend).
63 loop (e.g., a terminal frontend).
64 """
64 """
65 raise NotImplementedError
65 raise NotImplementedError
66
66
67
67
68 class InProcessShellChannel(InProcessChannel):
68 class InProcessShellChannel(InProcessChannel):
69 """See `IPython.kernel.channels.ShellChannel` for docstrings."""
69 """See `IPython.kernel.channels.ShellChannel` for docstrings."""
70
70
71 # flag for whether execute requests should be allowed to call raw_input
71 # flag for whether execute requests should be allowed to call raw_input
72 allow_stdin = True
72 allow_stdin = True
73 proxy_methods = [
73 proxy_methods = [
74 'execute',
74 'execute',
75 'complete',
75 'complete',
76 'inspect',
76 'inspect',
77 'history',
77 'history',
78 'shutdown',
78 'shutdown',
79 'kernel_info',
79 'kernel_info',
80 ]
80 ]
81
81
82 #--------------------------------------------------------------------------
82 #--------------------------------------------------------------------------
83 # ShellChannel interface
83 # ShellChannel interface
84 #--------------------------------------------------------------------------
84 #--------------------------------------------------------------------------
85
85
86 def execute(self, code, silent=False, store_history=True,
86 def execute(self, code, silent=False, store_history=True,
87 user_expressions={}, allow_stdin=None):
87 user_expressions={}, allow_stdin=None):
88 if allow_stdin is None:
88 if allow_stdin is None:
89 allow_stdin = self.allow_stdin
89 allow_stdin = self.allow_stdin
90 content = dict(code=code, silent=silent, store_history=store_history,
90 content = dict(code=code, silent=silent, store_history=store_history,
91 user_expressions=user_expressions,
91 user_expressions=user_expressions,
92 allow_stdin=allow_stdin)
92 allow_stdin=allow_stdin)
93 msg = self.client.session.msg('execute_request', content)
93 msg = self.client.session.msg('execute_request', content)
94 self._dispatch_to_kernel(msg)
94 self._dispatch_to_kernel(msg)
95 return msg['header']['msg_id']
95 return msg['header']['msg_id']
96
96
97 def complete(self, code, cursor_pos=0):
97 def complete(self, code, cursor_pos=None):
98 if cursor_pos is None:
99 cursor_pos = len(code)
98 content = dict(code=code, cursor_pos=cursor_pos)
100 content = dict(code=code, cursor_pos=cursor_pos)
99 msg = self.client.session.msg('complete_request', content)
101 msg = self.client.session.msg('complete_request', content)
100 self._dispatch_to_kernel(msg)
102 self._dispatch_to_kernel(msg)
101 return msg['header']['msg_id']
103 return msg['header']['msg_id']
102
104
103 def inspect(self, code, cursor_pos=0, detail_level=0):
105 def inspect(self, code, cursor_pos=None, detail_level=0):
106 if cursor_pos is None:
107 cursor_pos = len(code)
104 content = dict(code=code, cursor_pos=cursor_pos,
108 content = dict(code=code, cursor_pos=cursor_pos,
105 detail_level=detail_level,
109 detail_level=detail_level,
106 )
110 )
107 msg = self.client.session.msg('inspect_request', content)
111 msg = self.client.session.msg('inspect_request', content)
108 self._dispatch_to_kernel(msg)
112 self._dispatch_to_kernel(msg)
109 return msg['header']['msg_id']
113 return msg['header']['msg_id']
110
114
111 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
115 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
112 content = dict(raw=raw, output=output,
116 content = dict(raw=raw, output=output,
113 hist_access_type=hist_access_type, **kwds)
117 hist_access_type=hist_access_type, **kwds)
114 msg = self.client.session.msg('history_request', content)
118 msg = self.client.session.msg('history_request', content)
115 self._dispatch_to_kernel(msg)
119 self._dispatch_to_kernel(msg)
116 return msg['header']['msg_id']
120 return msg['header']['msg_id']
117
121
118 def shutdown(self, restart=False):
122 def shutdown(self, restart=False):
119 # FIXME: What to do here?
123 # FIXME: What to do here?
120 raise NotImplementedError('Cannot shutdown in-process kernel')
124 raise NotImplementedError('Cannot shutdown in-process kernel')
121
125
122 def kernel_info(self):
126 def kernel_info(self):
123 """Request kernel info."""
127 """Request kernel info."""
124 msg = self.client.session.msg('kernel_info_request')
128 msg = self.client.session.msg('kernel_info_request')
125 self._dispatch_to_kernel(msg)
129 self._dispatch_to_kernel(msg)
126 return msg['header']['msg_id']
130 return msg['header']['msg_id']
127
131
128 #--------------------------------------------------------------------------
132 #--------------------------------------------------------------------------
129 # Protected interface
133 # Protected interface
130 #--------------------------------------------------------------------------
134 #--------------------------------------------------------------------------
131
135
132 def _dispatch_to_kernel(self, msg):
136 def _dispatch_to_kernel(self, msg):
133 """ Send a message to the kernel and handle a reply.
137 """ Send a message to the kernel and handle a reply.
134 """
138 """
135 kernel = self.client.kernel
139 kernel = self.client.kernel
136 if kernel is None:
140 if kernel is None:
137 raise RuntimeError('Cannot send request. No kernel exists.')
141 raise RuntimeError('Cannot send request. No kernel exists.')
138
142
139 stream = DummySocket()
143 stream = DummySocket()
140 self.client.session.send(stream, msg)
144 self.client.session.send(stream, msg)
141 msg_parts = stream.recv_multipart()
145 msg_parts = stream.recv_multipart()
142 kernel.dispatch_shell(stream, msg_parts)
146 kernel.dispatch_shell(stream, msg_parts)
143
147
144 idents, reply_msg = self.client.session.recv(stream, copy=False)
148 idents, reply_msg = self.client.session.recv(stream, copy=False)
145 self.call_handlers_later(reply_msg)
149 self.call_handlers_later(reply_msg)
146
150
147
151
148 class InProcessIOPubChannel(InProcessChannel):
152 class InProcessIOPubChannel(InProcessChannel):
149 """See `IPython.kernel.channels.IOPubChannel` for docstrings."""
153 """See `IPython.kernel.channels.IOPubChannel` for docstrings."""
150
154
151 def flush(self, timeout=1.0):
155 def flush(self, timeout=1.0):
152 pass
156 pass
153
157
154
158
155 class InProcessStdInChannel(InProcessChannel):
159 class InProcessStdInChannel(InProcessChannel):
156 """See `IPython.kernel.channels.StdInChannel` for docstrings."""
160 """See `IPython.kernel.channels.StdInChannel` for docstrings."""
157
161
158 proxy_methods = ['input']
162 proxy_methods = ['input']
159
163
160 def input(self, string):
164 def input(self, string):
161 kernel = self.client.kernel
165 kernel = self.client.kernel
162 if kernel is None:
166 if kernel is None:
163 raise RuntimeError('Cannot send input reply. No kernel exists.')
167 raise RuntimeError('Cannot send input reply. No kernel exists.')
164 kernel.raw_input_str = string
168 kernel.raw_input_str = string
165
169
166
170
167 class InProcessHBChannel(InProcessChannel):
171 class InProcessHBChannel(InProcessChannel):
168 """See `IPython.kernel.channels.HBChannel` for docstrings."""
172 """See `IPython.kernel.channels.HBChannel` for docstrings."""
169
173
170 time_to_dead = 3.0
174 time_to_dead = 3.0
171
175
172 def __init__(self, *args, **kwds):
176 def __init__(self, *args, **kwds):
173 super(InProcessHBChannel, self).__init__(*args, **kwds)
177 super(InProcessHBChannel, self).__init__(*args, **kwds)
174 self._pause = True
178 self._pause = True
175
179
176 def pause(self):
180 def pause(self):
177 self._pause = True
181 self._pause = True
178
182
179 def unpause(self):
183 def unpause(self):
180 self._pause = False
184 self._pause = False
181
185
182 def is_beating(self):
186 def is_beating(self):
183 return not self._pause
187 return not self._pause
184
188
185 #-----------------------------------------------------------------------------
189 #-----------------------------------------------------------------------------
186 # ABC Registration
190 # ABC Registration
187 #-----------------------------------------------------------------------------
191 #-----------------------------------------------------------------------------
188
192
189 ShellChannelABC.register(InProcessShellChannel)
193 ShellChannelABC.register(InProcessShellChannel)
190 IOPubChannelABC.register(InProcessIOPubChannel)
194 IOPubChannelABC.register(InProcessIOPubChannel)
191 HBChannelABC.register(InProcessHBChannel)
195 HBChannelABC.register(InProcessHBChannel)
192 StdInChannelABC.register(InProcessStdInChannel)
196 StdInChannelABC.register(InProcessStdInChannel)
@@ -1,400 +1,409 b''
1 """Test suite for our zeromq-based message specification."""
1 """Test suite for our zeromq-based message specification."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import re
6 import re
7 from distutils.version import LooseVersion as V
7 from distutils.version import LooseVersion as V
8 from subprocess import PIPE
8 from subprocess import PIPE
9 try:
9 try:
10 from queue import Empty # Py 3
10 from queue import Empty # Py 3
11 except ImportError:
11 except ImportError:
12 from Queue import Empty # Py 2
12 from Queue import Empty # Py 2
13
13
14 import nose.tools as nt
14 import nose.tools as nt
15
15
16 from IPython.kernel import KernelManager
16 from IPython.kernel import KernelManager
17
17
18 from IPython.utils.traitlets import (
18 from IPython.utils.traitlets import (
19 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
19 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
20 )
20 )
21 from IPython.utils.py3compat import string_types, iteritems
21 from IPython.utils.py3compat import string_types, iteritems
22
22
23 from .utils import TIMEOUT, start_global_kernel, flush_channels, execute
23 from .utils import TIMEOUT, start_global_kernel, flush_channels, execute
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Globals
26 # Globals
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 KC = None
28 KC = None
29
29
30 def setup():
30 def setup():
31 global KC
31 global KC
32 KC = start_global_kernel()
32 KC = start_global_kernel()
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Message Spec References
35 # Message Spec References
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 class Reference(HasTraits):
38 class Reference(HasTraits):
39
39
40 """
40 """
41 Base class for message spec specification testing.
41 Base class for message spec specification testing.
42
42
43 This class is the core of the message specification test. The
43 This class is the core of the message specification test. The
44 idea is that child classes implement trait attributes for each
44 idea is that child classes implement trait attributes for each
45 message keys, so that message keys can be tested against these
45 message keys, so that message keys can be tested against these
46 traits using :meth:`check` method.
46 traits using :meth:`check` method.
47
47
48 """
48 """
49
49
50 def check(self, d):
50 def check(self, d):
51 """validate a dict against our traits"""
51 """validate a dict against our traits"""
52 for key in self.trait_names():
52 for key in self.trait_names():
53 nt.assert_in(key, d)
53 nt.assert_in(key, d)
54 # FIXME: always allow None, probably not a good idea
54 # FIXME: always allow None, probably not a good idea
55 if d[key] is None:
55 if d[key] is None:
56 continue
56 continue
57 try:
57 try:
58 setattr(self, key, d[key])
58 setattr(self, key, d[key])
59 except TraitError as e:
59 except TraitError as e:
60 assert False, str(e)
60 assert False, str(e)
61
61
62
62 class Version(Unicode):
63 class Version(Unicode):
64 def __init__(self, *args, **kwargs):
65 self.min = kwargs.pop('min', None)
66 self.max = kwargs.pop('max', None)
67 kwargs['default_value'] = self.min
68 super(Version, self).__init__(*args, **kwargs)
69
63 def validate(self, obj, value):
70 def validate(self, obj, value):
64 min_version = self.default_value
71 if self.min and V(value) < V(self.min):
65 if V(value) < V(min_version):
72 raise TraitError("bad version: %s < %s" % (value, self.min))
66 raise TraitError("bad version: %s < %s" % (value, min_version))
73 if self.max and (V(value) > V(self.max)):
74 raise TraitError("bad version: %s > %s" % (value, self.max))
75
67
76
68 class RMessage(Reference):
77 class RMessage(Reference):
69 msg_id = Unicode()
78 msg_id = Unicode()
70 msg_type = Unicode()
79 msg_type = Unicode()
71 header = Dict()
80 header = Dict()
72 parent_header = Dict()
81 parent_header = Dict()
73 content = Dict()
82 content = Dict()
74
83
75 def check(self, d):
84 def check(self, d):
76 super(RMessage, self).check(d)
85 super(RMessage, self).check(d)
77 RHeader().check(self.header)
86 RHeader().check(self.header)
78 if self.parent_header:
87 if self.parent_header:
79 RHeader().check(self.parent_header)
88 RHeader().check(self.parent_header)
80
89
81 class RHeader(Reference):
90 class RHeader(Reference):
82 msg_id = Unicode()
91 msg_id = Unicode()
83 msg_type = Unicode()
92 msg_type = Unicode()
84 session = Unicode()
93 session = Unicode()
85 username = Unicode()
94 username = Unicode()
86 version = Version('5.0')
95 version = Version(min='5.0')
87
96
88 mime_pat = re.compile(r'\w+/\w+')
97 mime_pat = re.compile(r'^[\w\-\+\.]+/[\w\-\+\.]+$')
89
98
90 class MimeBundle(Reference):
99 class MimeBundle(Reference):
91 metadata = Dict()
100 metadata = Dict()
92 data = Dict()
101 data = Dict()
93 def _data_changed(self, name, old, new):
102 def _data_changed(self, name, old, new):
94 for k,v in iteritems(new):
103 for k,v in iteritems(new):
95 assert mime_pat.match(k)
104 assert mime_pat.match(k)
96 nt.assert_is_instance(v, string_types)
105 nt.assert_is_instance(v, string_types)
97
106
98 # shell replies
107 # shell replies
99
108
100 class ExecuteReply(Reference):
109 class ExecuteReply(Reference):
101 execution_count = Integer()
110 execution_count = Integer()
102 status = Enum((u'ok', u'error'))
111 status = Enum((u'ok', u'error'))
103
112
104 def check(self, d):
113 def check(self, d):
105 Reference.check(self, d)
114 Reference.check(self, d)
106 if d['status'] == 'ok':
115 if d['status'] == 'ok':
107 ExecuteReplyOkay().check(d)
116 ExecuteReplyOkay().check(d)
108 elif d['status'] == 'error':
117 elif d['status'] == 'error':
109 ExecuteReplyError().check(d)
118 ExecuteReplyError().check(d)
110
119
111
120
112 class ExecuteReplyOkay(Reference):
121 class ExecuteReplyOkay(Reference):
113 payload = List(Dict)
122 payload = List(Dict)
114 user_expressions = Dict()
123 user_expressions = Dict()
115
124
116
125
117 class ExecuteReplyError(Reference):
126 class ExecuteReplyError(Reference):
118 ename = Unicode()
127 ename = Unicode()
119 evalue = Unicode()
128 evalue = Unicode()
120 traceback = List(Unicode)
129 traceback = List(Unicode)
121
130
122
131
123 class InspectReply(MimeBundle):
132 class InspectReply(MimeBundle):
124 found = Bool()
133 found = Bool()
125
134
126
135
127 class ArgSpec(Reference):
136 class ArgSpec(Reference):
128 args = List(Unicode)
137 args = List(Unicode)
129 varargs = Unicode()
138 varargs = Unicode()
130 varkw = Unicode()
139 varkw = Unicode()
131 defaults = List()
140 defaults = List()
132
141
133
142
134 class Status(Reference):
143 class Status(Reference):
135 execution_state = Enum((u'busy', u'idle', u'starting'))
144 execution_state = Enum((u'busy', u'idle', u'starting'))
136
145
137
146
138 class CompleteReply(Reference):
147 class CompleteReply(Reference):
139 matches = List(Unicode)
148 matches = List(Unicode)
140 cursor_start = Integer()
149 cursor_start = Integer()
141 cursor_end = Integer()
150 cursor_end = Integer()
142 status = Unicode()
151 status = Unicode()
143
152
144
153
145 class KernelInfoReply(Reference):
154 class KernelInfoReply(Reference):
146 protocol_version = Version('5.0')
155 protocol_version = Version(min='5.0')
147 implementation = Unicode('ipython')
156 implementation = Unicode('ipython')
148 implementation_version = Version('2.1')
157 implementation_version = Version(min='2.1')
149 language_version = Version('2.7')
158 language_version = Version(min='2.7')
150 language = Unicode('python')
159 language = Unicode('python')
151 banner = Unicode()
160 banner = Unicode()
152
161
153
162
154 # IOPub messages
163 # IOPub messages
155
164
156 class ExecuteInput(Reference):
165 class ExecuteInput(Reference):
157 code = Unicode()
166 code = Unicode()
158 execution_count = Integer()
167 execution_count = Integer()
159
168
160
169
161 Error = ExecuteReplyError
170 Error = ExecuteReplyError
162
171
163
172
164 class Stream(Reference):
173 class Stream(Reference):
165 name = Enum((u'stdout', u'stderr'))
174 name = Enum((u'stdout', u'stderr'))
166 data = Unicode()
175 data = Unicode()
167
176
168
177
169 class DisplayData(MimeBundle):
178 class DisplayData(MimeBundle):
170 pass
179 pass
171
180
172
181
173 class ExecuteResult(MimeBundle):
182 class ExecuteResult(MimeBundle):
174 execution_count = Integer()
183 execution_count = Integer()
175
184
176
185
177 references = {
186 references = {
178 'execute_reply' : ExecuteReply(),
187 'execute_reply' : ExecuteReply(),
179 'inspect_reply' : InspectReply(),
188 'inspect_reply' : InspectReply(),
180 'status' : Status(),
189 'status' : Status(),
181 'complete_reply' : CompleteReply(),
190 'complete_reply' : CompleteReply(),
182 'kernel_info_reply': KernelInfoReply(),
191 'kernel_info_reply': KernelInfoReply(),
183 'execute_input' : ExecuteInput(),
192 'execute_input' : ExecuteInput(),
184 'execute_result' : ExecuteResult(),
193 'execute_result' : ExecuteResult(),
185 'error' : Error(),
194 'error' : Error(),
186 'stream' : Stream(),
195 'stream' : Stream(),
187 'display_data' : DisplayData(),
196 'display_data' : DisplayData(),
188 'header' : RHeader(),
197 'header' : RHeader(),
189 }
198 }
190 """
199 """
191 Specifications of `content` part of the reply messages.
200 Specifications of `content` part of the reply messages.
192 """
201 """
193
202
194
203
195 def validate_message(msg, msg_type=None, parent=None):
204 def validate_message(msg, msg_type=None, parent=None):
196 """validate a message
205 """validate a message
197
206
198 This is a generator, and must be iterated through to actually
207 This is a generator, and must be iterated through to actually
199 trigger each test.
208 trigger each test.
200
209
201 If msg_type and/or parent are given, the msg_type and/or parent msg_id
210 If msg_type and/or parent are given, the msg_type and/or parent msg_id
202 are compared with the given values.
211 are compared with the given values.
203 """
212 """
204 RMessage().check(msg)
213 RMessage().check(msg)
205 if msg_type:
214 if msg_type:
206 nt.assert_equal(msg['msg_type'], msg_type)
215 nt.assert_equal(msg['msg_type'], msg_type)
207 if parent:
216 if parent:
208 nt.assert_equal(msg['parent_header']['msg_id'], parent)
217 nt.assert_equal(msg['parent_header']['msg_id'], parent)
209 content = msg['content']
218 content = msg['content']
210 ref = references[msg['msg_type']]
219 ref = references[msg['msg_type']]
211 ref.check(content)
220 ref.check(content)
212
221
213
222
214 #-----------------------------------------------------------------------------
223 #-----------------------------------------------------------------------------
215 # Tests
224 # Tests
216 #-----------------------------------------------------------------------------
225 #-----------------------------------------------------------------------------
217
226
218 # Shell channel
227 # Shell channel
219
228
220 def test_execute():
229 def test_execute():
221 flush_channels()
230 flush_channels()
222
231
223 msg_id = KC.execute(code='x=1')
232 msg_id = KC.execute(code='x=1')
224 reply = KC.get_shell_msg(timeout=TIMEOUT)
233 reply = KC.get_shell_msg(timeout=TIMEOUT)
225 validate_message(reply, 'execute_reply', msg_id)
234 validate_message(reply, 'execute_reply', msg_id)
226
235
227
236
228 def test_execute_silent():
237 def test_execute_silent():
229 flush_channels()
238 flush_channels()
230 msg_id, reply = execute(code='x=1', silent=True)
239 msg_id, reply = execute(code='x=1', silent=True)
231
240
232 # flush status=idle
241 # flush status=idle
233 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
242 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
234 validate_message(status, 'status', msg_id)
243 validate_message(status, 'status', msg_id)
235 nt.assert_equal(status['content']['execution_state'], 'idle')
244 nt.assert_equal(status['content']['execution_state'], 'idle')
236
245
237 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
246 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
238 count = reply['execution_count']
247 count = reply['execution_count']
239
248
240 msg_id, reply = execute(code='x=2', silent=True)
249 msg_id, reply = execute(code='x=2', silent=True)
241
250
242 # flush status=idle
251 # flush status=idle
243 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
252 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
244 validate_message(status, 'status', msg_id)
253 validate_message(status, 'status', msg_id)
245 nt.assert_equal(status['content']['execution_state'], 'idle')
254 nt.assert_equal(status['content']['execution_state'], 'idle')
246
255
247 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
256 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
248 count_2 = reply['execution_count']
257 count_2 = reply['execution_count']
249 nt.assert_equal(count_2, count)
258 nt.assert_equal(count_2, count)
250
259
251
260
252 def test_execute_error():
261 def test_execute_error():
253 flush_channels()
262 flush_channels()
254
263
255 msg_id, reply = execute(code='1/0')
264 msg_id, reply = execute(code='1/0')
256 nt.assert_equal(reply['status'], 'error')
265 nt.assert_equal(reply['status'], 'error')
257 nt.assert_equal(reply['ename'], 'ZeroDivisionError')
266 nt.assert_equal(reply['ename'], 'ZeroDivisionError')
258
267
259 error = KC.iopub_channel.get_msg(timeout=TIMEOUT)
268 error = KC.iopub_channel.get_msg(timeout=TIMEOUT)
260 validate_message(error, 'error', msg_id)
269 validate_message(error, 'error', msg_id)
261
270
262
271
263 def test_execute_inc():
272 def test_execute_inc():
264 """execute request should increment execution_count"""
273 """execute request should increment execution_count"""
265 flush_channels()
274 flush_channels()
266
275
267 msg_id, reply = execute(code='x=1')
276 msg_id, reply = execute(code='x=1')
268 count = reply['execution_count']
277 count = reply['execution_count']
269
278
270 flush_channels()
279 flush_channels()
271
280
272 msg_id, reply = execute(code='x=2')
281 msg_id, reply = execute(code='x=2')
273 count_2 = reply['execution_count']
282 count_2 = reply['execution_count']
274 nt.assert_equal(count_2, count+1)
283 nt.assert_equal(count_2, count+1)
275
284
276
285
277 def test_user_expressions():
286 def test_user_expressions():
278 flush_channels()
287 flush_channels()
279
288
280 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
289 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
281 user_expressions = reply['user_expressions']
290 user_expressions = reply['user_expressions']
282 nt.assert_equal(user_expressions, {u'foo': {
291 nt.assert_equal(user_expressions, {u'foo': {
283 u'status': u'ok',
292 u'status': u'ok',
284 u'data': {u'text/plain': u'2'},
293 u'data': {u'text/plain': u'2'},
285 u'metadata': {},
294 u'metadata': {},
286 }})
295 }})
287
296
288
297
289 def test_user_expressions_fail():
298 def test_user_expressions_fail():
290 flush_channels()
299 flush_channels()
291
300
292 msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname'))
301 msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname'))
293 user_expressions = reply['user_expressions']
302 user_expressions = reply['user_expressions']
294 foo = user_expressions['foo']
303 foo = user_expressions['foo']
295 nt.assert_equal(foo['status'], 'error')
304 nt.assert_equal(foo['status'], 'error')
296 nt.assert_equal(foo['ename'], 'NameError')
305 nt.assert_equal(foo['ename'], 'NameError')
297
306
298
307
299 def test_oinfo():
308 def test_oinfo():
300 flush_channels()
309 flush_channels()
301
310
302 msg_id = KC.inspect('a')
311 msg_id = KC.inspect('a')
303 reply = KC.get_shell_msg(timeout=TIMEOUT)
312 reply = KC.get_shell_msg(timeout=TIMEOUT)
304 validate_message(reply, 'inspect_reply', msg_id)
313 validate_message(reply, 'inspect_reply', msg_id)
305
314
306
315
307 def test_oinfo_found():
316 def test_oinfo_found():
308 flush_channels()
317 flush_channels()
309
318
310 msg_id, reply = execute(code='a=5')
319 msg_id, reply = execute(code='a=5')
311
320
312 msg_id = KC.inspect('a')
321 msg_id = KC.inspect('a')
313 reply = KC.get_shell_msg(timeout=TIMEOUT)
322 reply = KC.get_shell_msg(timeout=TIMEOUT)
314 validate_message(reply, 'inspect_reply', msg_id)
323 validate_message(reply, 'inspect_reply', msg_id)
315 content = reply['content']
324 content = reply['content']
316 assert content['found']
325 assert content['found']
317 text = content['data']['text/plain']
326 text = content['data']['text/plain']
318 nt.assert_in('Type:', text)
327 nt.assert_in('Type:', text)
319 nt.assert_in('Docstring:', text)
328 nt.assert_in('Docstring:', text)
320
329
321
330
322 def test_oinfo_detail():
331 def test_oinfo_detail():
323 flush_channels()
332 flush_channels()
324
333
325 msg_id, reply = execute(code='ip=get_ipython()')
334 msg_id, reply = execute(code='ip=get_ipython()')
326
335
327 msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1)
336 msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1)
328 reply = KC.get_shell_msg(timeout=TIMEOUT)
337 reply = KC.get_shell_msg(timeout=TIMEOUT)
329 validate_message(reply, 'inspect_reply', msg_id)
338 validate_message(reply, 'inspect_reply', msg_id)
330 content = reply['content']
339 content = reply['content']
331 assert content['found']
340 assert content['found']
332 text = content['data']['text/plain']
341 text = content['data']['text/plain']
333 nt.assert_in('Definition:', text)
342 nt.assert_in('Definition:', text)
334 nt.assert_in('Source:', text)
343 nt.assert_in('Source:', text)
335
344
336
345
337 def test_oinfo_not_found():
346 def test_oinfo_not_found():
338 flush_channels()
347 flush_channels()
339
348
340 msg_id = KC.inspect('dne')
349 msg_id = KC.inspect('dne')
341 reply = KC.get_shell_msg(timeout=TIMEOUT)
350 reply = KC.get_shell_msg(timeout=TIMEOUT)
342 validate_message(reply, 'inspect_reply', msg_id)
351 validate_message(reply, 'inspect_reply', msg_id)
343 content = reply['content']
352 content = reply['content']
344 nt.assert_false(content['found'])
353 nt.assert_false(content['found'])
345
354
346
355
347 def test_complete():
356 def test_complete():
348 flush_channels()
357 flush_channels()
349
358
350 msg_id, reply = execute(code="alpha = albert = 5")
359 msg_id, reply = execute(code="alpha = albert = 5")
351
360
352 msg_id = KC.complete('al', 2)
361 msg_id = KC.complete('al', 2)
353 reply = KC.get_shell_msg(timeout=TIMEOUT)
362 reply = KC.get_shell_msg(timeout=TIMEOUT)
354 validate_message(reply, 'complete_reply', msg_id)
363 validate_message(reply, 'complete_reply', msg_id)
355 matches = reply['content']['matches']
364 matches = reply['content']['matches']
356 for name in ('alpha', 'albert'):
365 for name in ('alpha', 'albert'):
357 nt.assert_in(name, matches)
366 nt.assert_in(name, matches)
358
367
359
368
360 def test_kernel_info_request():
369 def test_kernel_info_request():
361 flush_channels()
370 flush_channels()
362
371
363 msg_id = KC.kernel_info()
372 msg_id = KC.kernel_info()
364 reply = KC.get_shell_msg(timeout=TIMEOUT)
373 reply = KC.get_shell_msg(timeout=TIMEOUT)
365 validate_message(reply, 'kernel_info_reply', msg_id)
374 validate_message(reply, 'kernel_info_reply', msg_id)
366
375
367
376
368 def test_single_payload():
377 def test_single_payload():
369 flush_channels()
378 flush_channels()
370 msg_id, reply = execute(code="for i in range(3):\n"+
379 msg_id, reply = execute(code="for i in range(3):\n"+
371 " x=range?\n")
380 " x=range?\n")
372 payload = reply['payload']
381 payload = reply['payload']
373 next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
382 next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
374 nt.assert_equal(len(next_input_pls), 1)
383 nt.assert_equal(len(next_input_pls), 1)
375
384
376
385
377 # IOPub channel
386 # IOPub channel
378
387
379
388
380 def test_stream():
389 def test_stream():
381 flush_channels()
390 flush_channels()
382
391
383 msg_id, reply = execute("print('hi')")
392 msg_id, reply = execute("print('hi')")
384
393
385 stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT)
394 stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT)
386 validate_message(stdout, 'stream', msg_id)
395 validate_message(stdout, 'stream', msg_id)
387 content = stdout['content']
396 content = stdout['content']
388 nt.assert_equal(content['data'], u'hi\n')
397 nt.assert_equal(content['data'], u'hi\n')
389
398
390
399
391 def test_display_data():
400 def test_display_data():
392 flush_channels()
401 flush_channels()
393
402
394 msg_id, reply = execute("from IPython.core.display import display; display(1)")
403 msg_id, reply = execute("from IPython.core.display import display; display(1)")
395
404
396 display = KC.iopub_channel.get_msg(timeout=TIMEOUT)
405 display = KC.iopub_channel.get_msg(timeout=TIMEOUT)
397 validate_message(display, 'display_data', parent=msg_id)
406 validate_message(display, 'display_data', parent=msg_id)
398 data = display['content']['data']
407 data = display['content']['data']
399 nt.assert_equal(data['text/plain'], u'1')
408 nt.assert_equal(data['text/plain'], u'1')
400
409
@@ -1,853 +1,854 b''
1 """An interactive kernel that talks to frontends over 0MQ."""
1 """An interactive kernel that talks to frontends over 0MQ."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 import getpass
8 import getpass
9 import sys
9 import sys
10 import time
10 import time
11 import traceback
11 import traceback
12 import logging
12 import logging
13 import uuid
13 import uuid
14
14
15 from datetime import datetime
15 from datetime import datetime
16 from signal import (
16 from signal import (
17 signal, default_int_handler, SIGINT
17 signal, default_int_handler, SIGINT
18 )
18 )
19
19
20 import zmq
20 import zmq
21 from zmq.eventloop import ioloop
21 from zmq.eventloop import ioloop
22 from zmq.eventloop.zmqstream import ZMQStream
22 from zmq.eventloop.zmqstream import ZMQStream
23
23
24 from IPython.config.configurable import Configurable
24 from IPython.config.configurable import Configurable
25 from IPython.core.error import StdinNotImplementedError
25 from IPython.core.error import StdinNotImplementedError
26 from IPython.core import release
26 from IPython.core import release
27 from IPython.utils import py3compat
27 from IPython.utils import py3compat
28 from IPython.utils.py3compat import builtin_mod, unicode_type, string_types
28 from IPython.utils.py3compat import builtin_mod, unicode_type, string_types
29 from IPython.utils.jsonutil import json_clean
29 from IPython.utils.jsonutil import json_clean
30 from IPython.utils.tokenutil import token_at_cursor
30 from IPython.utils.tokenutil import token_at_cursor
31 from IPython.utils.traitlets import (
31 from IPython.utils.traitlets import (
32 Any, Instance, Float, Dict, List, Set, Integer, Unicode,
32 Any, Instance, Float, Dict, List, Set, Integer, Unicode,
33 Type, Bool,
33 Type, Bool,
34 )
34 )
35
35
36 from .serialize import serialize_object, unpack_apply_message
36 from .serialize import serialize_object, unpack_apply_message
37 from .session import Session
37 from .session import Session
38 from .zmqshell import ZMQInteractiveShell
38 from .zmqshell import ZMQInteractiveShell
39
39
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Main kernel class
42 # Main kernel class
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 protocol_version = release.kernel_protocol_version
45 protocol_version = release.kernel_protocol_version
46 ipython_version = release.version
46 ipython_version = release.version
47 language_version = sys.version.split()[0]
47 language_version = sys.version.split()[0]
48
48
49
49
50 class Kernel(Configurable):
50 class Kernel(Configurable):
51
51
52 #---------------------------------------------------------------------------
52 #---------------------------------------------------------------------------
53 # Kernel interface
53 # Kernel interface
54 #---------------------------------------------------------------------------
54 #---------------------------------------------------------------------------
55
55
56 # attribute to override with a GUI
56 # attribute to override with a GUI
57 eventloop = Any(None)
57 eventloop = Any(None)
58 def _eventloop_changed(self, name, old, new):
58 def _eventloop_changed(self, name, old, new):
59 """schedule call to eventloop from IOLoop"""
59 """schedule call to eventloop from IOLoop"""
60 loop = ioloop.IOLoop.instance()
60 loop = ioloop.IOLoop.instance()
61 loop.add_callback(self.enter_eventloop)
61 loop.add_callback(self.enter_eventloop)
62
62
63 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
63 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
64 shell_class = Type(ZMQInteractiveShell)
64 shell_class = Type(ZMQInteractiveShell)
65
65
66 session = Instance(Session)
66 session = Instance(Session)
67 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
67 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
68 shell_streams = List()
68 shell_streams = List()
69 control_stream = Instance(ZMQStream)
69 control_stream = Instance(ZMQStream)
70 iopub_socket = Instance(zmq.Socket)
70 iopub_socket = Instance(zmq.Socket)
71 stdin_socket = Instance(zmq.Socket)
71 stdin_socket = Instance(zmq.Socket)
72 log = Instance(logging.Logger)
72 log = Instance(logging.Logger)
73
73
74 user_module = Any()
74 user_module = Any()
75 def _user_module_changed(self, name, old, new):
75 def _user_module_changed(self, name, old, new):
76 if self.shell is not None:
76 if self.shell is not None:
77 self.shell.user_module = new
77 self.shell.user_module = new
78
78
79 user_ns = Instance(dict, args=None, allow_none=True)
79 user_ns = Instance(dict, args=None, allow_none=True)
80 def _user_ns_changed(self, name, old, new):
80 def _user_ns_changed(self, name, old, new):
81 if self.shell is not None:
81 if self.shell is not None:
82 self.shell.user_ns = new
82 self.shell.user_ns = new
83 self.shell.init_user_ns()
83 self.shell.init_user_ns()
84
84
85 # identities:
85 # identities:
86 int_id = Integer(-1)
86 int_id = Integer(-1)
87 ident = Unicode()
87 ident = Unicode()
88
88
89 def _ident_default(self):
89 def _ident_default(self):
90 return unicode_type(uuid.uuid4())
90 return unicode_type(uuid.uuid4())
91
91
92 # Private interface
92 # Private interface
93
93
94 _darwin_app_nap = Bool(True, config=True,
94 _darwin_app_nap = Bool(True, config=True,
95 help="""Whether to use appnope for compatiblity with OS X App Nap.
95 help="""Whether to use appnope for compatiblity with OS X App Nap.
96
96
97 Only affects OS X >= 10.9.
97 Only affects OS X >= 10.9.
98 """
98 """
99 )
99 )
100
100
101 # track associations with current request
101 # track associations with current request
102 _allow_stdin = Bool(False)
102 _allow_stdin = Bool(False)
103 _parent_header = Dict()
103 _parent_header = Dict()
104 _parent_ident = Any(b'')
104 _parent_ident = Any(b'')
105 # Time to sleep after flushing the stdout/err buffers in each execute
105 # Time to sleep after flushing the stdout/err buffers in each execute
106 # cycle. While this introduces a hard limit on the minimal latency of the
106 # cycle. While this introduces a hard limit on the minimal latency of the
107 # execute cycle, it helps prevent output synchronization problems for
107 # execute cycle, it helps prevent output synchronization problems for
108 # clients.
108 # clients.
109 # Units are in seconds. The minimum zmq latency on local host is probably
109 # Units are in seconds. The minimum zmq latency on local host is probably
110 # ~150 microseconds, set this to 500us for now. We may need to increase it
110 # ~150 microseconds, set this to 500us for now. We may need to increase it
111 # a little if it's not enough after more interactive testing.
111 # a little if it's not enough after more interactive testing.
112 _execute_sleep = Float(0.0005, config=True)
112 _execute_sleep = Float(0.0005, config=True)
113
113
114 # Frequency of the kernel's event loop.
114 # Frequency of the kernel's event loop.
115 # Units are in seconds, kernel subclasses for GUI toolkits may need to
115 # Units are in seconds, kernel subclasses for GUI toolkits may need to
116 # adapt to milliseconds.
116 # adapt to milliseconds.
117 _poll_interval = Float(0.05, config=True)
117 _poll_interval = Float(0.05, config=True)
118
118
119 # If the shutdown was requested over the network, we leave here the
119 # If the shutdown was requested over the network, we leave here the
120 # necessary reply message so it can be sent by our registered atexit
120 # necessary reply message so it can be sent by our registered atexit
121 # handler. This ensures that the reply is only sent to clients truly at
121 # handler. This ensures that the reply is only sent to clients truly at
122 # the end of our shutdown process (which happens after the underlying
122 # the end of our shutdown process (which happens after the underlying
123 # IPython shell's own shutdown).
123 # IPython shell's own shutdown).
124 _shutdown_message = None
124 _shutdown_message = None
125
125
126 # This is a dict of port number that the kernel is listening on. It is set
126 # This is a dict of port number that the kernel is listening on. It is set
127 # by record_ports and used by connect_request.
127 # by record_ports and used by connect_request.
128 _recorded_ports = Dict()
128 _recorded_ports = Dict()
129
129
130 # A reference to the Python builtin 'raw_input' function.
130 # A reference to the Python builtin 'raw_input' function.
131 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
131 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
132 _sys_raw_input = Any()
132 _sys_raw_input = Any()
133 _sys_eval_input = Any()
133 _sys_eval_input = Any()
134
134
135 # set of aborted msg_ids
135 # set of aborted msg_ids
136 aborted = Set()
136 aborted = Set()
137
137
138
138
139 def __init__(self, **kwargs):
139 def __init__(self, **kwargs):
140 super(Kernel, self).__init__(**kwargs)
140 super(Kernel, self).__init__(**kwargs)
141
141
142 # Initialize the InteractiveShell subclass
142 # Initialize the InteractiveShell subclass
143 self.shell = self.shell_class.instance(parent=self,
143 self.shell = self.shell_class.instance(parent=self,
144 profile_dir = self.profile_dir,
144 profile_dir = self.profile_dir,
145 user_module = self.user_module,
145 user_module = self.user_module,
146 user_ns = self.user_ns,
146 user_ns = self.user_ns,
147 kernel = self,
147 kernel = self,
148 )
148 )
149 self.shell.displayhook.session = self.session
149 self.shell.displayhook.session = self.session
150 self.shell.displayhook.pub_socket = self.iopub_socket
150 self.shell.displayhook.pub_socket = self.iopub_socket
151 self.shell.displayhook.topic = self._topic('execute_result')
151 self.shell.displayhook.topic = self._topic('execute_result')
152 self.shell.display_pub.session = self.session
152 self.shell.display_pub.session = self.session
153 self.shell.display_pub.pub_socket = self.iopub_socket
153 self.shell.display_pub.pub_socket = self.iopub_socket
154 self.shell.data_pub.session = self.session
154 self.shell.data_pub.session = self.session
155 self.shell.data_pub.pub_socket = self.iopub_socket
155 self.shell.data_pub.pub_socket = self.iopub_socket
156
156
157 # TMP - hack while developing
157 # TMP - hack while developing
158 self.shell._reply_content = None
158 self.shell._reply_content = None
159
159
160 # Build dict of handlers for message types
160 # Build dict of handlers for message types
161 msg_types = [ 'execute_request', 'complete_request',
161 msg_types = [ 'execute_request', 'complete_request',
162 'inspect_request', 'history_request',
162 'inspect_request', 'history_request',
163 'kernel_info_request',
163 'kernel_info_request',
164 'connect_request', 'shutdown_request',
164 'connect_request', 'shutdown_request',
165 'apply_request',
165 'apply_request',
166 ]
166 ]
167 self.shell_handlers = {}
167 self.shell_handlers = {}
168 for msg_type in msg_types:
168 for msg_type in msg_types:
169 self.shell_handlers[msg_type] = getattr(self, msg_type)
169 self.shell_handlers[msg_type] = getattr(self, msg_type)
170
170
171 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
171 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
172 comm_manager = self.shell.comm_manager
172 comm_manager = self.shell.comm_manager
173 for msg_type in comm_msg_types:
173 for msg_type in comm_msg_types:
174 self.shell_handlers[msg_type] = getattr(comm_manager, msg_type)
174 self.shell_handlers[msg_type] = getattr(comm_manager, msg_type)
175
175
176 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
176 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
177 self.control_handlers = {}
177 self.control_handlers = {}
178 for msg_type in control_msg_types:
178 for msg_type in control_msg_types:
179 self.control_handlers[msg_type] = getattr(self, msg_type)
179 self.control_handlers[msg_type] = getattr(self, msg_type)
180
180
181
181
182 def dispatch_control(self, msg):
182 def dispatch_control(self, msg):
183 """dispatch control requests"""
183 """dispatch control requests"""
184 idents,msg = self.session.feed_identities(msg, copy=False)
184 idents,msg = self.session.feed_identities(msg, copy=False)
185 try:
185 try:
186 msg = self.session.unserialize(msg, content=True, copy=False)
186 msg = self.session.unserialize(msg, content=True, copy=False)
187 except:
187 except:
188 self.log.error("Invalid Control Message", exc_info=True)
188 self.log.error("Invalid Control Message", exc_info=True)
189 return
189 return
190
190
191 self.log.debug("Control received: %s", msg)
191 self.log.debug("Control received: %s", msg)
192
192
193 header = msg['header']
193 header = msg['header']
194 msg_id = header['msg_id']
194 msg_id = header['msg_id']
195 msg_type = header['msg_type']
195 msg_type = header['msg_type']
196
196
197 handler = self.control_handlers.get(msg_type, None)
197 handler = self.control_handlers.get(msg_type, None)
198 if handler is None:
198 if handler is None:
199 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
199 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
200 else:
200 else:
201 try:
201 try:
202 handler(self.control_stream, idents, msg)
202 handler(self.control_stream, idents, msg)
203 except Exception:
203 except Exception:
204 self.log.error("Exception in control handler:", exc_info=True)
204 self.log.error("Exception in control handler:", exc_info=True)
205
205
206 def dispatch_shell(self, stream, msg):
206 def dispatch_shell(self, stream, msg):
207 """dispatch shell requests"""
207 """dispatch shell requests"""
208 # flush control requests first
208 # flush control requests first
209 if self.control_stream:
209 if self.control_stream:
210 self.control_stream.flush()
210 self.control_stream.flush()
211
211
212 idents,msg = self.session.feed_identities(msg, copy=False)
212 idents,msg = self.session.feed_identities(msg, copy=False)
213 try:
213 try:
214 msg = self.session.unserialize(msg, content=True, copy=False)
214 msg = self.session.unserialize(msg, content=True, copy=False)
215 except:
215 except:
216 self.log.error("Invalid Message", exc_info=True)
216 self.log.error("Invalid Message", exc_info=True)
217 return
217 return
218
218
219 header = msg['header']
219 header = msg['header']
220 msg_id = header['msg_id']
220 msg_id = header['msg_id']
221 msg_type = msg['header']['msg_type']
221 msg_type = msg['header']['msg_type']
222
222
223 # Print some info about this message and leave a '--->' marker, so it's
223 # Print some info about this message and leave a '--->' marker, so it's
224 # easier to trace visually the message chain when debugging. Each
224 # easier to trace visually the message chain when debugging. Each
225 # handler prints its message at the end.
225 # handler prints its message at the end.
226 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
226 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
227 self.log.debug(' Content: %s\n --->\n ', msg['content'])
227 self.log.debug(' Content: %s\n --->\n ', msg['content'])
228
228
229 if msg_id in self.aborted:
229 if msg_id in self.aborted:
230 self.aborted.remove(msg_id)
230 self.aborted.remove(msg_id)
231 # is it safe to assume a msg_id will not be resubmitted?
231 # is it safe to assume a msg_id will not be resubmitted?
232 reply_type = msg_type.split('_')[0] + '_reply'
232 reply_type = msg_type.split('_')[0] + '_reply'
233 status = {'status' : 'aborted'}
233 status = {'status' : 'aborted'}
234 md = {'engine' : self.ident}
234 md = {'engine' : self.ident}
235 md.update(status)
235 md.update(status)
236 reply_msg = self.session.send(stream, reply_type, metadata=md,
236 reply_msg = self.session.send(stream, reply_type, metadata=md,
237 content=status, parent=msg, ident=idents)
237 content=status, parent=msg, ident=idents)
238 return
238 return
239
239
240 handler = self.shell_handlers.get(msg_type, None)
240 handler = self.shell_handlers.get(msg_type, None)
241 if handler is None:
241 if handler is None:
242 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
242 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
243 else:
243 else:
244 # ensure default_int_handler during handler call
244 # ensure default_int_handler during handler call
245 sig = signal(SIGINT, default_int_handler)
245 sig = signal(SIGINT, default_int_handler)
246 self.log.debug("%s: %s", msg_type, msg)
246 self.log.debug("%s: %s", msg_type, msg)
247 try:
247 try:
248 handler(stream, idents, msg)
248 handler(stream, idents, msg)
249 except Exception:
249 except Exception:
250 self.log.error("Exception in message handler:", exc_info=True)
250 self.log.error("Exception in message handler:", exc_info=True)
251 finally:
251 finally:
252 signal(SIGINT, sig)
252 signal(SIGINT, sig)
253
253
254 def enter_eventloop(self):
254 def enter_eventloop(self):
255 """enter eventloop"""
255 """enter eventloop"""
256 self.log.info("entering eventloop %s", self.eventloop)
256 self.log.info("entering eventloop %s", self.eventloop)
257 for stream in self.shell_streams:
257 for stream in self.shell_streams:
258 # flush any pending replies,
258 # flush any pending replies,
259 # which may be skipped by entering the eventloop
259 # which may be skipped by entering the eventloop
260 stream.flush(zmq.POLLOUT)
260 stream.flush(zmq.POLLOUT)
261 # restore default_int_handler
261 # restore default_int_handler
262 signal(SIGINT, default_int_handler)
262 signal(SIGINT, default_int_handler)
263 while self.eventloop is not None:
263 while self.eventloop is not None:
264 try:
264 try:
265 self.eventloop(self)
265 self.eventloop(self)
266 except KeyboardInterrupt:
266 except KeyboardInterrupt:
267 # Ctrl-C shouldn't crash the kernel
267 # Ctrl-C shouldn't crash the kernel
268 self.log.error("KeyboardInterrupt caught in kernel")
268 self.log.error("KeyboardInterrupt caught in kernel")
269 continue
269 continue
270 else:
270 else:
271 # eventloop exited cleanly, this means we should stop (right?)
271 # eventloop exited cleanly, this means we should stop (right?)
272 self.eventloop = None
272 self.eventloop = None
273 break
273 break
274 self.log.info("exiting eventloop")
274 self.log.info("exiting eventloop")
275
275
276 def start(self):
276 def start(self):
277 """register dispatchers for streams"""
277 """register dispatchers for streams"""
278 self.shell.exit_now = False
278 self.shell.exit_now = False
279 if self.control_stream:
279 if self.control_stream:
280 self.control_stream.on_recv(self.dispatch_control, copy=False)
280 self.control_stream.on_recv(self.dispatch_control, copy=False)
281
281
282 def make_dispatcher(stream):
282 def make_dispatcher(stream):
283 def dispatcher(msg):
283 def dispatcher(msg):
284 return self.dispatch_shell(stream, msg)
284 return self.dispatch_shell(stream, msg)
285 return dispatcher
285 return dispatcher
286
286
287 for s in self.shell_streams:
287 for s in self.shell_streams:
288 s.on_recv(make_dispatcher(s), copy=False)
288 s.on_recv(make_dispatcher(s), copy=False)
289
289
290 # publish idle status
290 # publish idle status
291 self._publish_status('starting')
291 self._publish_status('starting')
292
292
293 def do_one_iteration(self):
293 def do_one_iteration(self):
294 """step eventloop just once"""
294 """step eventloop just once"""
295 if self.control_stream:
295 if self.control_stream:
296 self.control_stream.flush()
296 self.control_stream.flush()
297 for stream in self.shell_streams:
297 for stream in self.shell_streams:
298 # handle at most one request per iteration
298 # handle at most one request per iteration
299 stream.flush(zmq.POLLIN, 1)
299 stream.flush(zmq.POLLIN, 1)
300 stream.flush(zmq.POLLOUT)
300 stream.flush(zmq.POLLOUT)
301
301
302
302
303 def record_ports(self, ports):
303 def record_ports(self, ports):
304 """Record the ports that this kernel is using.
304 """Record the ports that this kernel is using.
305
305
306 The creator of the Kernel instance must call this methods if they
306 The creator of the Kernel instance must call this methods if they
307 want the :meth:`connect_request` method to return the port numbers.
307 want the :meth:`connect_request` method to return the port numbers.
308 """
308 """
309 self._recorded_ports = ports
309 self._recorded_ports = ports
310
310
311 #---------------------------------------------------------------------------
311 #---------------------------------------------------------------------------
312 # Kernel request handlers
312 # Kernel request handlers
313 #---------------------------------------------------------------------------
313 #---------------------------------------------------------------------------
314
314
315 def _make_metadata(self, other=None):
315 def _make_metadata(self, other=None):
316 """init metadata dict, for execute/apply_reply"""
316 """init metadata dict, for execute/apply_reply"""
317 new_md = {
317 new_md = {
318 'dependencies_met' : True,
318 'dependencies_met' : True,
319 'engine' : self.ident,
319 'engine' : self.ident,
320 'started': datetime.now(),
320 'started': datetime.now(),
321 }
321 }
322 if other:
322 if other:
323 new_md.update(other)
323 new_md.update(other)
324 return new_md
324 return new_md
325
325
326 def _publish_execute_input(self, code, parent, execution_count):
326 def _publish_execute_input(self, code, parent, execution_count):
327 """Publish the code request on the iopub stream."""
327 """Publish the code request on the iopub stream."""
328
328
329 self.session.send(self.iopub_socket, u'execute_input',
329 self.session.send(self.iopub_socket, u'execute_input',
330 {u'code':code, u'execution_count': execution_count},
330 {u'code':code, u'execution_count': execution_count},
331 parent=parent, ident=self._topic('execute_input')
331 parent=parent, ident=self._topic('execute_input')
332 )
332 )
333
333
334 def _publish_status(self, status, parent=None):
334 def _publish_status(self, status, parent=None):
335 """send status (busy/idle) on IOPub"""
335 """send status (busy/idle) on IOPub"""
336 self.session.send(self.iopub_socket,
336 self.session.send(self.iopub_socket,
337 u'status',
337 u'status',
338 {u'execution_state': status},
338 {u'execution_state': status},
339 parent=parent,
339 parent=parent,
340 ident=self._topic('status'),
340 ident=self._topic('status'),
341 )
341 )
342
342
343 def _forward_input(self, allow_stdin=False):
343 def _forward_input(self, allow_stdin=False):
344 """Forward raw_input and getpass to the current frontend.
344 """Forward raw_input and getpass to the current frontend.
345
345
346 via input_request
346 via input_request
347 """
347 """
348 self._allow_stdin = allow_stdin
348 self._allow_stdin = allow_stdin
349
349
350 if py3compat.PY3:
350 if py3compat.PY3:
351 self._sys_raw_input = builtin_mod.input
351 self._sys_raw_input = builtin_mod.input
352 builtin_mod.input = self.raw_input
352 builtin_mod.input = self.raw_input
353 else:
353 else:
354 self._sys_raw_input = builtin_mod.raw_input
354 self._sys_raw_input = builtin_mod.raw_input
355 self._sys_eval_input = builtin_mod.input
355 self._sys_eval_input = builtin_mod.input
356 builtin_mod.raw_input = self.raw_input
356 builtin_mod.raw_input = self.raw_input
357 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
357 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
358 self._save_getpass = getpass.getpass
358 self._save_getpass = getpass.getpass
359 getpass.getpass = self.getpass
359 getpass.getpass = self.getpass
360
360
361 def _restore_input(self):
361 def _restore_input(self):
362 """Restore raw_input, getpass"""
362 """Restore raw_input, getpass"""
363 if py3compat.PY3:
363 if py3compat.PY3:
364 builtin_mod.input = self._sys_raw_input
364 builtin_mod.input = self._sys_raw_input
365 else:
365 else:
366 builtin_mod.raw_input = self._sys_raw_input
366 builtin_mod.raw_input = self._sys_raw_input
367 builtin_mod.input = self._sys_eval_input
367 builtin_mod.input = self._sys_eval_input
368
368
369 getpass.getpass = self._save_getpass
369 getpass.getpass = self._save_getpass
370
370
371 def set_parent(self, ident, parent):
371 def set_parent(self, ident, parent):
372 """Record the parent state
372 """Set the current parent_header
373
373
374 For associating side effects with their requests.
374 Side effects (IOPub messages) and replies are associated with
375 the request that caused them via the parent_header.
376
377 The parent identity is used to route input_request messages
378 on the stdin channel.
375 """
379 """
376 self._parent_ident = ident
380 self._parent_ident = ident
377 self._parent_header = parent
381 self._parent_header = parent
378 self.shell.set_parent(parent)
382 self.shell.set_parent(parent)
379
383
380 def execute_request(self, stream, ident, parent):
384 def execute_request(self, stream, ident, parent):
381 """handle an execute_request"""
385 """handle an execute_request"""
382
386
383 self._publish_status(u'busy', parent)
387 self._publish_status(u'busy', parent)
384
388
385 try:
389 try:
386 content = parent[u'content']
390 content = parent[u'content']
387 code = py3compat.cast_unicode_py2(content[u'code'])
391 code = py3compat.cast_unicode_py2(content[u'code'])
388 silent = content[u'silent']
392 silent = content[u'silent']
389 store_history = content.get(u'store_history', not silent)
393 store_history = content.get(u'store_history', not silent)
390 except:
394 except:
391 self.log.error("Got bad msg: ")
395 self.log.error("Got bad msg: ")
392 self.log.error("%s", parent)
396 self.log.error("%s", parent)
393 return
397 return
394
398
395 md = self._make_metadata(parent['metadata'])
399 md = self._make_metadata(parent['metadata'])
396
400
397 shell = self.shell # we'll need this a lot here
401 shell = self.shell # we'll need this a lot here
398
402
399 self._forward_input(content.get('allow_stdin', False))
403 self._forward_input(content.get('allow_stdin', False))
400 # Set the parent message of the display hook and out streams.
404 # Set the parent message of the display hook and out streams.
401 self.set_parent(ident, parent)
405 self.set_parent(ident, parent)
402
406
403 # Re-broadcast our input for the benefit of listening clients, and
407 # Re-broadcast our input for the benefit of listening clients, and
404 # start computing output
408 # start computing output
405 if not silent:
409 if not silent:
406 self._publish_execute_input(code, parent, shell.execution_count)
410 self._publish_execute_input(code, parent, shell.execution_count)
407
411
408 reply_content = {}
412 reply_content = {}
409 # FIXME: the shell calls the exception handler itself.
413 # FIXME: the shell calls the exception handler itself.
410 shell._reply_content = None
414 shell._reply_content = None
411 try:
415 try:
412 shell.run_cell(code, store_history=store_history, silent=silent)
416 shell.run_cell(code, store_history=store_history, silent=silent)
413 except:
417 except:
414 status = u'error'
418 status = u'error'
415 # FIXME: this code right now isn't being used yet by default,
419 # FIXME: this code right now isn't being used yet by default,
416 # because the run_cell() call above directly fires off exception
420 # because the run_cell() call above directly fires off exception
417 # reporting. This code, therefore, is only active in the scenario
421 # reporting. This code, therefore, is only active in the scenario
418 # where runlines itself has an unhandled exception. We need to
422 # where runlines itself has an unhandled exception. We need to
419 # uniformize this, for all exception construction to come from a
423 # uniformize this, for all exception construction to come from a
420 # single location in the codbase.
424 # single location in the codbase.
421 etype, evalue, tb = sys.exc_info()
425 etype, evalue, tb = sys.exc_info()
422 tb_list = traceback.format_exception(etype, evalue, tb)
426 tb_list = traceback.format_exception(etype, evalue, tb)
423 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
427 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
424 else:
428 else:
425 status = u'ok'
429 status = u'ok'
426 finally:
430 finally:
427 self._restore_input()
431 self._restore_input()
428
432
429 reply_content[u'status'] = status
433 reply_content[u'status'] = status
430
434
431 # Return the execution counter so clients can display prompts
435 # Return the execution counter so clients can display prompts
432 reply_content['execution_count'] = shell.execution_count - 1
436 reply_content['execution_count'] = shell.execution_count - 1
433
437
434 # FIXME - fish exception info out of shell, possibly left there by
438 # FIXME - fish exception info out of shell, possibly left there by
435 # runlines. We'll need to clean up this logic later.
439 # runlines. We'll need to clean up this logic later.
436 if shell._reply_content is not None:
440 if shell._reply_content is not None:
437 reply_content.update(shell._reply_content)
441 reply_content.update(shell._reply_content)
438 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
442 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
439 reply_content['engine_info'] = e_info
443 reply_content['engine_info'] = e_info
440 # reset after use
444 # reset after use
441 shell._reply_content = None
445 shell._reply_content = None
442
446
443 if 'traceback' in reply_content:
447 if 'traceback' in reply_content:
444 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
448 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
445
449
446
450
447 # At this point, we can tell whether the main code execution succeeded
451 # At this point, we can tell whether the main code execution succeeded
448 # or not. If it did, we proceed to evaluate user_expressions
452 # or not. If it did, we proceed to evaluate user_expressions
449 if reply_content['status'] == 'ok':
453 if reply_content['status'] == 'ok':
450 reply_content[u'user_expressions'] = \
454 reply_content[u'user_expressions'] = \
451 shell.user_expressions(content.get(u'user_expressions', {}))
455 shell.user_expressions(content.get(u'user_expressions', {}))
452 else:
456 else:
453 # If there was an error, don't even try to compute expressions
457 # If there was an error, don't even try to compute expressions
454 reply_content[u'user_expressions'] = {}
458 reply_content[u'user_expressions'] = {}
455
459
456 # Payloads should be retrieved regardless of outcome, so we can both
460 # Payloads should be retrieved regardless of outcome, so we can both
457 # recover partial output (that could have been generated early in a
461 # recover partial output (that could have been generated early in a
458 # block, before an error) and clear the payload system always.
462 # block, before an error) and clear the payload system always.
459 reply_content[u'payload'] = shell.payload_manager.read_payload()
463 reply_content[u'payload'] = shell.payload_manager.read_payload()
460 # Be agressive about clearing the payload because we don't want
464 # Be agressive about clearing the payload because we don't want
461 # it to sit in memory until the next execute_request comes in.
465 # it to sit in memory until the next execute_request comes in.
462 shell.payload_manager.clear_payload()
466 shell.payload_manager.clear_payload()
463
467
464 # Flush output before sending the reply.
468 # Flush output before sending the reply.
465 sys.stdout.flush()
469 sys.stdout.flush()
466 sys.stderr.flush()
470 sys.stderr.flush()
467 # FIXME: on rare occasions, the flush doesn't seem to make it to the
471 # FIXME: on rare occasions, the flush doesn't seem to make it to the
468 # clients... This seems to mitigate the problem, but we definitely need
472 # clients... This seems to mitigate the problem, but we definitely need
469 # to better understand what's going on.
473 # to better understand what's going on.
470 if self._execute_sleep:
474 if self._execute_sleep:
471 time.sleep(self._execute_sleep)
475 time.sleep(self._execute_sleep)
472
476
473 # Send the reply.
477 # Send the reply.
474 reply_content = json_clean(reply_content)
478 reply_content = json_clean(reply_content)
475
479
476 md['status'] = reply_content['status']
480 md['status'] = reply_content['status']
477 if reply_content['status'] == 'error' and \
481 if reply_content['status'] == 'error' and \
478 reply_content['ename'] == 'UnmetDependency':
482 reply_content['ename'] == 'UnmetDependency':
479 md['dependencies_met'] = False
483 md['dependencies_met'] = False
480
484
481 reply_msg = self.session.send(stream, u'execute_reply',
485 reply_msg = self.session.send(stream, u'execute_reply',
482 reply_content, parent, metadata=md,
486 reply_content, parent, metadata=md,
483 ident=ident)
487 ident=ident)
484
488
485 self.log.debug("%s", reply_msg)
489 self.log.debug("%s", reply_msg)
486
490
487 if not silent and reply_msg['content']['status'] == u'error':
491 if not silent and reply_msg['content']['status'] == u'error':
488 self._abort_queues()
492 self._abort_queues()
489
493
490 self._publish_status(u'idle', parent)
494 self._publish_status(u'idle', parent)
491
495
492 def complete_request(self, stream, ident, parent):
496 def complete_request(self, stream, ident, parent):
493 content = parent['content']
497 content = parent['content']
494 code = content['code']
498 code = content['code']
495 cursor_pos = content['cursor_pos']
499 cursor_pos = content['cursor_pos']
496
500
497 txt, matches = self.shell.complete('', code, cursor_pos)
501 txt, matches = self.shell.complete('', code, cursor_pos)
498 matches = {'matches' : matches,
502 matches = {'matches' : matches,
499 'cursor_end' : cursor_pos,
503 'cursor_end' : cursor_pos,
500 'cursor_start' : cursor_pos - len(txt),
504 'cursor_start' : cursor_pos - len(txt),
501 'metadata' : {},
505 'metadata' : {},
502 'status' : 'ok'}
506 'status' : 'ok'}
503 matches = json_clean(matches)
507 matches = json_clean(matches)
504 completion_msg = self.session.send(stream, 'complete_reply',
508 completion_msg = self.session.send(stream, 'complete_reply',
505 matches, parent, ident)
509 matches, parent, ident)
506 self.log.debug("%s", completion_msg)
510 self.log.debug("%s", completion_msg)
507
511
508 def inspect_request(self, stream, ident, parent):
512 def inspect_request(self, stream, ident, parent):
509 content = parent['content']
513 content = parent['content']
510
514
511 name = token_at_cursor(content['code'], content['cursor_pos'])
515 name = token_at_cursor(content['code'], content['cursor_pos'])
512 info = self.shell.object_inspect(name)
516 info = self.shell.object_inspect(name)
513
517
514 reply_content = {'status' : 'ok'}
518 reply_content = {'status' : 'ok'}
515 reply_content['data'] = data = {}
519 reply_content['data'] = data = {}
516 reply_content['metadata'] = {}
520 reply_content['metadata'] = {}
517 reply_content['found'] = info['found']
521 reply_content['found'] = info['found']
518 if info['found']:
522 if info['found']:
519 info_text = self.shell.object_inspect_text(
523 info_text = self.shell.object_inspect_text(
520 name,
524 name,
521 detail_level=content.get('detail_level', 0),
525 detail_level=content.get('detail_level', 0),
522 )
526 )
523 reply_content['data']['text/plain'] = info_text
527 reply_content['data']['text/plain'] = info_text
524 # Before we send this object over, we scrub it for JSON usage
528 # Before we send this object over, we scrub it for JSON usage
525 reply_content = json_clean(reply_content)
529 reply_content = json_clean(reply_content)
526 msg = self.session.send(stream, 'inspect_reply',
530 msg = self.session.send(stream, 'inspect_reply',
527 reply_content, parent, ident)
531 reply_content, parent, ident)
528 self.log.debug("%s", msg)
532 self.log.debug("%s", msg)
529
533
530 def history_request(self, stream, ident, parent):
534 def history_request(self, stream, ident, parent):
531 # We need to pull these out, as passing **kwargs doesn't work with
535 # We need to pull these out, as passing **kwargs doesn't work with
532 # unicode keys before Python 2.6.5.
536 # unicode keys before Python 2.6.5.
533 hist_access_type = parent['content']['hist_access_type']
537 hist_access_type = parent['content']['hist_access_type']
534 raw = parent['content']['raw']
538 raw = parent['content']['raw']
535 output = parent['content']['output']
539 output = parent['content']['output']
536 if hist_access_type == 'tail':
540 if hist_access_type == 'tail':
537 n = parent['content']['n']
541 n = parent['content']['n']
538 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
542 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
539 include_latest=True)
543 include_latest=True)
540
544
541 elif hist_access_type == 'range':
545 elif hist_access_type == 'range':
542 session = parent['content']['session']
546 session = parent['content']['session']
543 start = parent['content']['start']
547 start = parent['content']['start']
544 stop = parent['content']['stop']
548 stop = parent['content']['stop']
545 hist = self.shell.history_manager.get_range(session, start, stop,
549 hist = self.shell.history_manager.get_range(session, start, stop,
546 raw=raw, output=output)
550 raw=raw, output=output)
547
551
548 elif hist_access_type == 'search':
552 elif hist_access_type == 'search':
549 n = parent['content'].get('n')
553 n = parent['content'].get('n')
550 unique = parent['content'].get('unique', False)
554 unique = parent['content'].get('unique', False)
551 pattern = parent['content']['pattern']
555 pattern = parent['content']['pattern']
552 hist = self.shell.history_manager.search(
556 hist = self.shell.history_manager.search(
553 pattern, raw=raw, output=output, n=n, unique=unique)
557 pattern, raw=raw, output=output, n=n, unique=unique)
554
558
555 else:
559 else:
556 hist = []
560 hist = []
557 hist = list(hist)
561 hist = list(hist)
558 content = {'history' : hist}
562 content = {'history' : hist}
559 content = json_clean(content)
563 content = json_clean(content)
560 msg = self.session.send(stream, 'history_reply',
564 msg = self.session.send(stream, 'history_reply',
561 content, parent, ident)
565 content, parent, ident)
562 self.log.debug("Sending history reply with %i entries", len(hist))
566 self.log.debug("Sending history reply with %i entries", len(hist))
563
567
564 def connect_request(self, stream, ident, parent):
568 def connect_request(self, stream, ident, parent):
565 if self._recorded_ports is not None:
569 if self._recorded_ports is not None:
566 content = self._recorded_ports.copy()
570 content = self._recorded_ports.copy()
567 else:
571 else:
568 content = {}
572 content = {}
569 msg = self.session.send(stream, 'connect_reply',
573 msg = self.session.send(stream, 'connect_reply',
570 content, parent, ident)
574 content, parent, ident)
571 self.log.debug("%s", msg)
575 self.log.debug("%s", msg)
572
576
573 def kernel_info_request(self, stream, ident, parent):
577 def kernel_info_request(self, stream, ident, parent):
574 vinfo = {
578 vinfo = {
575 'protocol_version': protocol_version,
579 'protocol_version': protocol_version,
576 'implementation': 'ipython',
580 'implementation': 'ipython',
577 'implementation_version': ipython_version,
581 'implementation_version': ipython_version,
578 'language_version': language_version,
582 'language_version': language_version,
579 'language': 'python',
583 'language': 'python',
580 'banner': self.shell.banner,
584 'banner': self.shell.banner,
581 }
585 }
582 msg = self.session.send(stream, 'kernel_info_reply',
586 msg = self.session.send(stream, 'kernel_info_reply',
583 vinfo, parent, ident)
587 vinfo, parent, ident)
584 self.log.debug("%s", msg)
588 self.log.debug("%s", msg)
585
589
586 def shutdown_request(self, stream, ident, parent):
590 def shutdown_request(self, stream, ident, parent):
587 self.shell.exit_now = True
591 self.shell.exit_now = True
588 content = dict(status='ok')
592 content = dict(status='ok')
589 content.update(parent['content'])
593 content.update(parent['content'])
590 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
594 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
591 # same content, but different msg_id for broadcasting on IOPub
595 # same content, but different msg_id for broadcasting on IOPub
592 self._shutdown_message = self.session.msg(u'shutdown_reply',
596 self._shutdown_message = self.session.msg(u'shutdown_reply',
593 content, parent
597 content, parent
594 )
598 )
595
599
596 self._at_shutdown()
600 self._at_shutdown()
597 # call sys.exit after a short delay
601 # call sys.exit after a short delay
598 loop = ioloop.IOLoop.instance()
602 loop = ioloop.IOLoop.instance()
599 loop.add_timeout(time.time()+0.1, loop.stop)
603 loop.add_timeout(time.time()+0.1, loop.stop)
600
604
601 #---------------------------------------------------------------------------
605 #---------------------------------------------------------------------------
602 # Engine methods
606 # Engine methods
603 #---------------------------------------------------------------------------
607 #---------------------------------------------------------------------------
604
608
605 def apply_request(self, stream, ident, parent):
609 def apply_request(self, stream, ident, parent):
606 try:
610 try:
607 content = parent[u'content']
611 content = parent[u'content']
608 bufs = parent[u'buffers']
612 bufs = parent[u'buffers']
609 msg_id = parent['header']['msg_id']
613 msg_id = parent['header']['msg_id']
610 except:
614 except:
611 self.log.error("Got bad msg: %s", parent, exc_info=True)
615 self.log.error("Got bad msg: %s", parent, exc_info=True)
612 return
616 return
613
617
614 self._publish_status(u'busy', parent)
618 self._publish_status(u'busy', parent)
615
619
616 # Set the parent message of the display hook and out streams.
620 # Set the parent message of the display hook and out streams.
617 shell = self.shell
621 shell = self.shell
618 shell.set_parent(parent)
622 shell.set_parent(parent)
619
623
620 # execute_input_msg = self.session.msg(u'execute_input',{u'code':code}, parent=parent)
621 # self.iopub_socket.send(execute_input_msg)
622 # self.session.send(self.iopub_socket, u'execute_input', {u'code':code},parent=parent)
623 md = self._make_metadata(parent['metadata'])
624 md = self._make_metadata(parent['metadata'])
624 try:
625 try:
625 working = shell.user_ns
626 working = shell.user_ns
626
627
627 prefix = "_"+str(msg_id).replace("-","")+"_"
628 prefix = "_"+str(msg_id).replace("-","")+"_"
628
629
629 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
630 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
630
631
631 fname = getattr(f, '__name__', 'f')
632 fname = getattr(f, '__name__', 'f')
632
633
633 fname = prefix+"f"
634 fname = prefix+"f"
634 argname = prefix+"args"
635 argname = prefix+"args"
635 kwargname = prefix+"kwargs"
636 kwargname = prefix+"kwargs"
636 resultname = prefix+"result"
637 resultname = prefix+"result"
637
638
638 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
639 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
639 # print ns
640 # print ns
640 working.update(ns)
641 working.update(ns)
641 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
642 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
642 try:
643 try:
643 exec(code, shell.user_global_ns, shell.user_ns)
644 exec(code, shell.user_global_ns, shell.user_ns)
644 result = working.get(resultname)
645 result = working.get(resultname)
645 finally:
646 finally:
646 for key in ns:
647 for key in ns:
647 working.pop(key)
648 working.pop(key)
648
649
649 result_buf = serialize_object(result,
650 result_buf = serialize_object(result,
650 buffer_threshold=self.session.buffer_threshold,
651 buffer_threshold=self.session.buffer_threshold,
651 item_threshold=self.session.item_threshold,
652 item_threshold=self.session.item_threshold,
652 )
653 )
653
654
654 except:
655 except:
655 # invoke IPython traceback formatting
656 # invoke IPython traceback formatting
656 shell.showtraceback()
657 shell.showtraceback()
657 # FIXME - fish exception info out of shell, possibly left there by
658 # FIXME - fish exception info out of shell, possibly left there by
658 # run_code. We'll need to clean up this logic later.
659 # run_code. We'll need to clean up this logic later.
659 reply_content = {}
660 reply_content = {}
660 if shell._reply_content is not None:
661 if shell._reply_content is not None:
661 reply_content.update(shell._reply_content)
662 reply_content.update(shell._reply_content)
662 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
663 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
663 reply_content['engine_info'] = e_info
664 reply_content['engine_info'] = e_info
664 # reset after use
665 # reset after use
665 shell._reply_content = None
666 shell._reply_content = None
666
667
667 self.session.send(self.iopub_socket, u'error', reply_content, parent=parent,
668 self.session.send(self.iopub_socket, u'error', reply_content, parent=parent,
668 ident=self._topic('error'))
669 ident=self._topic('error'))
669 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
670 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
670 result_buf = []
671 result_buf = []
671
672
672 if reply_content['ename'] == 'UnmetDependency':
673 if reply_content['ename'] == 'UnmetDependency':
673 md['dependencies_met'] = False
674 md['dependencies_met'] = False
674 else:
675 else:
675 reply_content = {'status' : 'ok'}
676 reply_content = {'status' : 'ok'}
676
677
677 # put 'ok'/'error' status in header, for scheduler introspection:
678 # put 'ok'/'error' status in header, for scheduler introspection:
678 md['status'] = reply_content['status']
679 md['status'] = reply_content['status']
679
680
680 # flush i/o
681 # flush i/o
681 sys.stdout.flush()
682 sys.stdout.flush()
682 sys.stderr.flush()
683 sys.stderr.flush()
683
684
684 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
685 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
685 parent=parent, ident=ident,buffers=result_buf, metadata=md)
686 parent=parent, ident=ident,buffers=result_buf, metadata=md)
686
687
687 self._publish_status(u'idle', parent)
688 self._publish_status(u'idle', parent)
688
689
689 #---------------------------------------------------------------------------
690 #---------------------------------------------------------------------------
690 # Control messages
691 # Control messages
691 #---------------------------------------------------------------------------
692 #---------------------------------------------------------------------------
692
693
693 def abort_request(self, stream, ident, parent):
694 def abort_request(self, stream, ident, parent):
694 """abort a specifig msg by id"""
695 """abort a specifig msg by id"""
695 msg_ids = parent['content'].get('msg_ids', None)
696 msg_ids = parent['content'].get('msg_ids', None)
696 if isinstance(msg_ids, string_types):
697 if isinstance(msg_ids, string_types):
697 msg_ids = [msg_ids]
698 msg_ids = [msg_ids]
698 if not msg_ids:
699 if not msg_ids:
699 self.abort_queues()
700 self.abort_queues()
700 for mid in msg_ids:
701 for mid in msg_ids:
701 self.aborted.add(str(mid))
702 self.aborted.add(str(mid))
702
703
703 content = dict(status='ok')
704 content = dict(status='ok')
704 reply_msg = self.session.send(stream, 'abort_reply', content=content,
705 reply_msg = self.session.send(stream, 'abort_reply', content=content,
705 parent=parent, ident=ident)
706 parent=parent, ident=ident)
706 self.log.debug("%s", reply_msg)
707 self.log.debug("%s", reply_msg)
707
708
708 def clear_request(self, stream, idents, parent):
709 def clear_request(self, stream, idents, parent):
709 """Clear our namespace."""
710 """Clear our namespace."""
710 self.shell.reset(False)
711 self.shell.reset(False)
711 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
712 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
712 content = dict(status='ok'))
713 content = dict(status='ok'))
713
714
714
715
715 #---------------------------------------------------------------------------
716 #---------------------------------------------------------------------------
716 # Protected interface
717 # Protected interface
717 #---------------------------------------------------------------------------
718 #---------------------------------------------------------------------------
718
719
719 def _wrap_exception(self, method=None):
720 def _wrap_exception(self, method=None):
720 # import here, because _wrap_exception is only used in parallel,
721 # import here, because _wrap_exception is only used in parallel,
721 # and parallel has higher min pyzmq version
722 # and parallel has higher min pyzmq version
722 from IPython.parallel.error import wrap_exception
723 from IPython.parallel.error import wrap_exception
723 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
724 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
724 content = wrap_exception(e_info)
725 content = wrap_exception(e_info)
725 return content
726 return content
726
727
727 def _topic(self, topic):
728 def _topic(self, topic):
728 """prefixed topic for IOPub messages"""
729 """prefixed topic for IOPub messages"""
729 if self.int_id >= 0:
730 if self.int_id >= 0:
730 base = "engine.%i" % self.int_id
731 base = "engine.%i" % self.int_id
731 else:
732 else:
732 base = "kernel.%s" % self.ident
733 base = "kernel.%s" % self.ident
733
734
734 return py3compat.cast_bytes("%s.%s" % (base, topic))
735 return py3compat.cast_bytes("%s.%s" % (base, topic))
735
736
736 def _abort_queues(self):
737 def _abort_queues(self):
737 for stream in self.shell_streams:
738 for stream in self.shell_streams:
738 if stream:
739 if stream:
739 self._abort_queue(stream)
740 self._abort_queue(stream)
740
741
741 def _abort_queue(self, stream):
742 def _abort_queue(self, stream):
742 poller = zmq.Poller()
743 poller = zmq.Poller()
743 poller.register(stream.socket, zmq.POLLIN)
744 poller.register(stream.socket, zmq.POLLIN)
744 while True:
745 while True:
745 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
746 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
746 if msg is None:
747 if msg is None:
747 return
748 return
748
749
749 self.log.info("Aborting:")
750 self.log.info("Aborting:")
750 self.log.info("%s", msg)
751 self.log.info("%s", msg)
751 msg_type = msg['header']['msg_type']
752 msg_type = msg['header']['msg_type']
752 reply_type = msg_type.split('_')[0] + '_reply'
753 reply_type = msg_type.split('_')[0] + '_reply'
753
754
754 status = {'status' : 'aborted'}
755 status = {'status' : 'aborted'}
755 md = {'engine' : self.ident}
756 md = {'engine' : self.ident}
756 md.update(status)
757 md.update(status)
757 reply_msg = self.session.send(stream, reply_type, metadata=md,
758 reply_msg = self.session.send(stream, reply_type, metadata=md,
758 content=status, parent=msg, ident=idents)
759 content=status, parent=msg, ident=idents)
759 self.log.debug("%s", reply_msg)
760 self.log.debug("%s", reply_msg)
760 # We need to wait a bit for requests to come in. This can probably
761 # We need to wait a bit for requests to come in. This can probably
761 # be set shorter for true asynchronous clients.
762 # be set shorter for true asynchronous clients.
762 poller.poll(50)
763 poller.poll(50)
763
764
764
765
765 def _no_raw_input(self):
766 def _no_raw_input(self):
766 """Raise StdinNotImplentedError if active frontend doesn't support
767 """Raise StdinNotImplentedError if active frontend doesn't support
767 stdin."""
768 stdin."""
768 raise StdinNotImplementedError("raw_input was called, but this "
769 raise StdinNotImplementedError("raw_input was called, but this "
769 "frontend does not support stdin.")
770 "frontend does not support stdin.")
770
771
771 def getpass(self, prompt=''):
772 def getpass(self, prompt=''):
772 """Forward getpass to frontends
773 """Forward getpass to frontends
773
774
774 Raises
775 Raises
775 ------
776 ------
776 StdinNotImplentedError if active frontend doesn't support stdin.
777 StdinNotImplentedError if active frontend doesn't support stdin.
777 """
778 """
778 if not self._allow_stdin:
779 if not self._allow_stdin:
779 raise StdinNotImplementedError(
780 raise StdinNotImplementedError(
780 "getpass was called, but this frontend does not support input requests."
781 "getpass was called, but this frontend does not support input requests."
781 )
782 )
782 return self._input_request(prompt,
783 return self._input_request(prompt,
783 self._parent_ident,
784 self._parent_ident,
784 self._parent_header,
785 self._parent_header,
785 password=True,
786 password=True,
786 )
787 )
787
788
788 def raw_input(self, prompt=''):
789 def raw_input(self, prompt=''):
789 """Forward raw_input to frontends
790 """Forward raw_input to frontends
790
791
791 Raises
792 Raises
792 ------
793 ------
793 StdinNotImplentedError if active frontend doesn't support stdin.
794 StdinNotImplentedError if active frontend doesn't support stdin.
794 """
795 """
795 if not self._allow_stdin:
796 if not self._allow_stdin:
796 raise StdinNotImplementedError(
797 raise StdinNotImplementedError(
797 "raw_input was called, but this frontend does not support input requests."
798 "raw_input was called, but this frontend does not support input requests."
798 )
799 )
799 return self._input_request(prompt,
800 return self._input_request(prompt,
800 self._parent_ident,
801 self._parent_ident,
801 self._parent_header,
802 self._parent_header,
802 password=False,
803 password=False,
803 )
804 )
804
805
805 def _input_request(self, prompt, ident, parent, password=False):
806 def _input_request(self, prompt, ident, parent, password=False):
806 # Flush output before making the request.
807 # Flush output before making the request.
807 sys.stderr.flush()
808 sys.stderr.flush()
808 sys.stdout.flush()
809 sys.stdout.flush()
809 # flush the stdin socket, to purge stale replies
810 # flush the stdin socket, to purge stale replies
810 while True:
811 while True:
811 try:
812 try:
812 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
813 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
813 except zmq.ZMQError as e:
814 except zmq.ZMQError as e:
814 if e.errno == zmq.EAGAIN:
815 if e.errno == zmq.EAGAIN:
815 break
816 break
816 else:
817 else:
817 raise
818 raise
818
819
819 # Send the input request.
820 # Send the input request.
820 content = json_clean(dict(prompt=prompt, password=password))
821 content = json_clean(dict(prompt=prompt, password=password))
821 self.session.send(self.stdin_socket, u'input_request', content, parent,
822 self.session.send(self.stdin_socket, u'input_request', content, parent,
822 ident=ident)
823 ident=ident)
823
824
824 # Await a response.
825 # Await a response.
825 while True:
826 while True:
826 try:
827 try:
827 ident, reply = self.session.recv(self.stdin_socket, 0)
828 ident, reply = self.session.recv(self.stdin_socket, 0)
828 except Exception:
829 except Exception:
829 self.log.warn("Invalid Message:", exc_info=True)
830 self.log.warn("Invalid Message:", exc_info=True)
830 except KeyboardInterrupt:
831 except KeyboardInterrupt:
831 # re-raise KeyboardInterrupt, to truncate traceback
832 # re-raise KeyboardInterrupt, to truncate traceback
832 raise KeyboardInterrupt
833 raise KeyboardInterrupt
833 else:
834 else:
834 break
835 break
835 try:
836 try:
836 value = py3compat.unicode_to_str(reply['content']['value'])
837 value = py3compat.unicode_to_str(reply['content']['value'])
837 except:
838 except:
838 self.log.error("Bad input_reply: %s", parent)
839 self.log.error("Bad input_reply: %s", parent)
839 value = ''
840 value = ''
840 if value == '\x04':
841 if value == '\x04':
841 # EOF
842 # EOF
842 raise EOFError
843 raise EOFError
843 return value
844 return value
844
845
845 def _at_shutdown(self):
846 def _at_shutdown(self):
846 """Actions taken at shutdown by the kernel, called by python's atexit.
847 """Actions taken at shutdown by the kernel, called by python's atexit.
847 """
848 """
848 # io.rprint("Kernel at_shutdown") # dbg
849 # io.rprint("Kernel at_shutdown") # dbg
849 if self._shutdown_message is not None:
850 if self._shutdown_message is not None:
850 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
851 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
851 self.log.debug("%s", self._shutdown_message)
852 self.log.debug("%s", self._shutdown_message)
852 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
853 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
853
854
@@ -1,825 +1,821 b''
1 """Frontend widget for the Qt Console"""
1 """Frontend widget for the Qt Console"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 from collections import namedtuple
8 from collections import namedtuple
9 import sys
9 import sys
10 import uuid
10 import uuid
11
11
12 from IPython.external import qt
12 from IPython.external import qt
13 from IPython.external.qt import QtCore, QtGui
13 from IPython.external.qt import QtCore, QtGui
14 from IPython.utils import py3compat
14 from IPython.utils import py3compat
15 from IPython.utils.importstring import import_item
15 from IPython.utils.importstring import import_item
16
16
17 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
17 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
18 from IPython.core.inputtransformer import classic_prompt
18 from IPython.core.inputtransformer import classic_prompt
19 from IPython.core.oinspect import call_tip
19 from IPython.core.oinspect import call_tip
20 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
20 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
21 from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName
21 from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName
22 from .bracket_matcher import BracketMatcher
22 from .bracket_matcher import BracketMatcher
23 from .call_tip_widget import CallTipWidget
23 from .call_tip_widget import CallTipWidget
24 from .completion_lexer import CompletionLexer
24 from .completion_lexer import CompletionLexer
25 from .history_console_widget import HistoryConsoleWidget
25 from .history_console_widget import HistoryConsoleWidget
26 from .pygments_highlighter import PygmentsHighlighter
26 from .pygments_highlighter import PygmentsHighlighter
27
27
28
28
29 class FrontendHighlighter(PygmentsHighlighter):
29 class FrontendHighlighter(PygmentsHighlighter):
30 """ A PygmentsHighlighter that understands and ignores prompts.
30 """ A PygmentsHighlighter that understands and ignores prompts.
31 """
31 """
32
32
33 def __init__(self, frontend, lexer=None):
33 def __init__(self, frontend, lexer=None):
34 super(FrontendHighlighter, self).__init__(frontend._control.document(), lexer=lexer)
34 super(FrontendHighlighter, self).__init__(frontend._control.document(), lexer=lexer)
35 self._current_offset = 0
35 self._current_offset = 0
36 self._frontend = frontend
36 self._frontend = frontend
37 self.highlighting_on = False
37 self.highlighting_on = False
38
38
39 def highlightBlock(self, string):
39 def highlightBlock(self, string):
40 """ Highlight a block of text. Reimplemented to highlight selectively.
40 """ Highlight a block of text. Reimplemented to highlight selectively.
41 """
41 """
42 if not self.highlighting_on:
42 if not self.highlighting_on:
43 return
43 return
44
44
45 # The input to this function is a unicode string that may contain
45 # The input to this function is a unicode string that may contain
46 # paragraph break characters, non-breaking spaces, etc. Here we acquire
46 # paragraph break characters, non-breaking spaces, etc. Here we acquire
47 # the string as plain text so we can compare it.
47 # the string as plain text so we can compare it.
48 current_block = self.currentBlock()
48 current_block = self.currentBlock()
49 string = self._frontend._get_block_plain_text(current_block)
49 string = self._frontend._get_block_plain_text(current_block)
50
50
51 # Decide whether to check for the regular or continuation prompt.
51 # Decide whether to check for the regular or continuation prompt.
52 if current_block.contains(self._frontend._prompt_pos):
52 if current_block.contains(self._frontend._prompt_pos):
53 prompt = self._frontend._prompt
53 prompt = self._frontend._prompt
54 else:
54 else:
55 prompt = self._frontend._continuation_prompt
55 prompt = self._frontend._continuation_prompt
56
56
57 # Only highlight if we can identify a prompt, but make sure not to
57 # Only highlight if we can identify a prompt, but make sure not to
58 # highlight the prompt.
58 # highlight the prompt.
59 if string.startswith(prompt):
59 if string.startswith(prompt):
60 self._current_offset = len(prompt)
60 self._current_offset = len(prompt)
61 string = string[len(prompt):]
61 string = string[len(prompt):]
62 super(FrontendHighlighter, self).highlightBlock(string)
62 super(FrontendHighlighter, self).highlightBlock(string)
63
63
64 def rehighlightBlock(self, block):
64 def rehighlightBlock(self, block):
65 """ Reimplemented to temporarily enable highlighting if disabled.
65 """ Reimplemented to temporarily enable highlighting if disabled.
66 """
66 """
67 old = self.highlighting_on
67 old = self.highlighting_on
68 self.highlighting_on = True
68 self.highlighting_on = True
69 super(FrontendHighlighter, self).rehighlightBlock(block)
69 super(FrontendHighlighter, self).rehighlightBlock(block)
70 self.highlighting_on = old
70 self.highlighting_on = old
71
71
72 def setFormat(self, start, count, format):
72 def setFormat(self, start, count, format):
73 """ Reimplemented to highlight selectively.
73 """ Reimplemented to highlight selectively.
74 """
74 """
75 start += self._current_offset
75 start += self._current_offset
76 super(FrontendHighlighter, self).setFormat(start, count, format)
76 super(FrontendHighlighter, self).setFormat(start, count, format)
77
77
78
78
79 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
79 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
80 """ A Qt frontend for a generic Python kernel.
80 """ A Qt frontend for a generic Python kernel.
81 """
81 """
82
82
83 # The text to show when the kernel is (re)started.
83 # The text to show when the kernel is (re)started.
84 banner = Unicode(config=True)
84 banner = Unicode(config=True)
85 kernel_banner = Unicode()
85 kernel_banner = Unicode()
86
86
87 # An option and corresponding signal for overriding the default kernel
87 # An option and corresponding signal for overriding the default kernel
88 # interrupt behavior.
88 # interrupt behavior.
89 custom_interrupt = Bool(False)
89 custom_interrupt = Bool(False)
90 custom_interrupt_requested = QtCore.Signal()
90 custom_interrupt_requested = QtCore.Signal()
91
91
92 # An option and corresponding signals for overriding the default kernel
92 # An option and corresponding signals for overriding the default kernel
93 # restart behavior.
93 # restart behavior.
94 custom_restart = Bool(False)
94 custom_restart = Bool(False)
95 custom_restart_kernel_died = QtCore.Signal(float)
95 custom_restart_kernel_died = QtCore.Signal(float)
96 custom_restart_requested = QtCore.Signal()
96 custom_restart_requested = QtCore.Signal()
97
97
98 # Whether to automatically show calltips on open-parentheses.
98 # Whether to automatically show calltips on open-parentheses.
99 enable_calltips = Bool(True, config=True,
99 enable_calltips = Bool(True, config=True,
100 help="Whether to draw information calltips on open-parentheses.")
100 help="Whether to draw information calltips on open-parentheses.")
101
101
102 clear_on_kernel_restart = Bool(True, config=True,
102 clear_on_kernel_restart = Bool(True, config=True,
103 help="Whether to clear the console when the kernel is restarted")
103 help="Whether to clear the console when the kernel is restarted")
104
104
105 confirm_restart = Bool(True, config=True,
105 confirm_restart = Bool(True, config=True,
106 help="Whether to ask for user confirmation when restarting kernel")
106 help="Whether to ask for user confirmation when restarting kernel")
107
107
108 lexer_class = DottedObjectName(config=True,
108 lexer_class = DottedObjectName(config=True,
109 help="The pygments lexer class to use."
109 help="The pygments lexer class to use."
110 )
110 )
111 def _lexer_class_changed(self, name, old, new):
111 def _lexer_class_changed(self, name, old, new):
112 lexer_class = import_item(new)
112 lexer_class = import_item(new)
113 self.lexer = lexer_class()
113 self.lexer = lexer_class()
114
114
115 def _lexer_class_default(self):
115 def _lexer_class_default(self):
116 if py3compat.PY3:
116 if py3compat.PY3:
117 return 'pygments.lexers.Python3Lexer'
117 return 'pygments.lexers.Python3Lexer'
118 else:
118 else:
119 return 'pygments.lexers.PythonLexer'
119 return 'pygments.lexers.PythonLexer'
120
120
121 lexer = Any()
121 lexer = Any()
122 def _lexer_default(self):
122 def _lexer_default(self):
123 lexer_class = import_item(self.lexer_class)
123 lexer_class = import_item(self.lexer_class)
124 return lexer_class()
124 return lexer_class()
125
125
126 # Emitted when a user visible 'execute_request' has been submitted to the
126 # Emitted when a user visible 'execute_request' has been submitted to the
127 # kernel from the FrontendWidget. Contains the code to be executed.
127 # kernel from the FrontendWidget. Contains the code to be executed.
128 executing = QtCore.Signal(object)
128 executing = QtCore.Signal(object)
129
129
130 # Emitted when a user-visible 'execute_reply' has been received from the
130 # Emitted when a user-visible 'execute_reply' has been received from the
131 # kernel and processed by the FrontendWidget. Contains the response message.
131 # kernel and processed by the FrontendWidget. Contains the response message.
132 executed = QtCore.Signal(object)
132 executed = QtCore.Signal(object)
133
133
134 # Emitted when an exit request has been received from the kernel.
134 # Emitted when an exit request has been received from the kernel.
135 exit_requested = QtCore.Signal(object)
135 exit_requested = QtCore.Signal(object)
136
136
137 # Protected class variables.
137 # Protected class variables.
138 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
138 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
139 logical_line_transforms=[],
139 logical_line_transforms=[],
140 python_line_transforms=[],
140 python_line_transforms=[],
141 )
141 )
142 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
142 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
143 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
143 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
144 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
144 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
145 _input_splitter_class = InputSplitter
145 _input_splitter_class = InputSplitter
146 _local_kernel = False
146 _local_kernel = False
147 _highlighter = Instance(FrontendHighlighter)
147 _highlighter = Instance(FrontendHighlighter)
148
148
149 #---------------------------------------------------------------------------
149 #---------------------------------------------------------------------------
150 # 'object' interface
150 # 'object' interface
151 #---------------------------------------------------------------------------
151 #---------------------------------------------------------------------------
152
152
153 def __init__(self, *args, **kw):
153 def __init__(self, *args, **kw):
154 super(FrontendWidget, self).__init__(*args, **kw)
154 super(FrontendWidget, self).__init__(*args, **kw)
155 # FIXME: remove this when PySide min version is updated past 1.0.7
155 # FIXME: remove this when PySide min version is updated past 1.0.7
156 # forcefully disable calltips if PySide is < 1.0.7, because they crash
156 # forcefully disable calltips if PySide is < 1.0.7, because they crash
157 if qt.QT_API == qt.QT_API_PYSIDE:
157 if qt.QT_API == qt.QT_API_PYSIDE:
158 import PySide
158 import PySide
159 if PySide.__version_info__ < (1,0,7):
159 if PySide.__version_info__ < (1,0,7):
160 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
160 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
161 self.enable_calltips = False
161 self.enable_calltips = False
162
162
163 # FrontendWidget protected variables.
163 # FrontendWidget protected variables.
164 self._bracket_matcher = BracketMatcher(self._control)
164 self._bracket_matcher = BracketMatcher(self._control)
165 self._call_tip_widget = CallTipWidget(self._control)
165 self._call_tip_widget = CallTipWidget(self._control)
166 self._completion_lexer = CompletionLexer(self.lexer)
166 self._completion_lexer = CompletionLexer(self.lexer)
167 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
167 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
168 self._hidden = False
168 self._hidden = False
169 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
169 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
170 self._input_splitter = self._input_splitter_class()
170 self._input_splitter = self._input_splitter_class()
171 self._kernel_manager = None
171 self._kernel_manager = None
172 self._kernel_client = None
172 self._kernel_client = None
173 self._request_info = {}
173 self._request_info = {}
174 self._request_info['execute'] = {};
174 self._request_info['execute'] = {};
175 self._callback_dict = {}
175 self._callback_dict = {}
176
176
177 # Configure the ConsoleWidget.
177 # Configure the ConsoleWidget.
178 self.tab_width = 4
178 self.tab_width = 4
179 self._set_continuation_prompt('... ')
179 self._set_continuation_prompt('... ')
180
180
181 # Configure the CallTipWidget.
181 # Configure the CallTipWidget.
182 self._call_tip_widget.setFont(self.font)
182 self._call_tip_widget.setFont(self.font)
183 self.font_changed.connect(self._call_tip_widget.setFont)
183 self.font_changed.connect(self._call_tip_widget.setFont)
184
184
185 # Configure actions.
185 # Configure actions.
186 action = self._copy_raw_action
186 action = self._copy_raw_action
187 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
187 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
188 action.setEnabled(False)
188 action.setEnabled(False)
189 action.setShortcut(QtGui.QKeySequence(key))
189 action.setShortcut(QtGui.QKeySequence(key))
190 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
190 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
191 action.triggered.connect(self.copy_raw)
191 action.triggered.connect(self.copy_raw)
192 self.copy_available.connect(action.setEnabled)
192 self.copy_available.connect(action.setEnabled)
193 self.addAction(action)
193 self.addAction(action)
194
194
195 # Connect signal handlers.
195 # Connect signal handlers.
196 document = self._control.document()
196 document = self._control.document()
197 document.contentsChange.connect(self._document_contents_change)
197 document.contentsChange.connect(self._document_contents_change)
198
198
199 # Set flag for whether we are connected via localhost.
199 # Set flag for whether we are connected via localhost.
200 self._local_kernel = kw.get('local_kernel',
200 self._local_kernel = kw.get('local_kernel',
201 FrontendWidget._local_kernel)
201 FrontendWidget._local_kernel)
202
202
203 # Whether or not a clear_output call is pending new output.
203 # Whether or not a clear_output call is pending new output.
204 self._pending_clearoutput = False
204 self._pending_clearoutput = False
205
205
206 #---------------------------------------------------------------------------
206 #---------------------------------------------------------------------------
207 # 'ConsoleWidget' public interface
207 # 'ConsoleWidget' public interface
208 #---------------------------------------------------------------------------
208 #---------------------------------------------------------------------------
209
209
210 def copy(self):
210 def copy(self):
211 """ Copy the currently selected text to the clipboard, removing prompts.
211 """ Copy the currently selected text to the clipboard, removing prompts.
212 """
212 """
213 if self._page_control is not None and self._page_control.hasFocus():
213 if self._page_control is not None and self._page_control.hasFocus():
214 self._page_control.copy()
214 self._page_control.copy()
215 elif self._control.hasFocus():
215 elif self._control.hasFocus():
216 text = self._control.textCursor().selection().toPlainText()
216 text = self._control.textCursor().selection().toPlainText()
217 if text:
217 if text:
218 text = self._prompt_transformer.transform_cell(text)
218 text = self._prompt_transformer.transform_cell(text)
219 QtGui.QApplication.clipboard().setText(text)
219 QtGui.QApplication.clipboard().setText(text)
220 else:
220 else:
221 self.log.debug("frontend widget : unknown copy target")
221 self.log.debug("frontend widget : unknown copy target")
222
222
223 #---------------------------------------------------------------------------
223 #---------------------------------------------------------------------------
224 # 'ConsoleWidget' abstract interface
224 # 'ConsoleWidget' abstract interface
225 #---------------------------------------------------------------------------
225 #---------------------------------------------------------------------------
226
226
227 def _is_complete(self, source, interactive):
227 def _is_complete(self, source, interactive):
228 """ Returns whether 'source' can be completely processed and a new
228 """ Returns whether 'source' can be completely processed and a new
229 prompt created. When triggered by an Enter/Return key press,
229 prompt created. When triggered by an Enter/Return key press,
230 'interactive' is True; otherwise, it is False.
230 'interactive' is True; otherwise, it is False.
231 """
231 """
232 self._input_splitter.reset()
232 self._input_splitter.reset()
233 try:
233 try:
234 complete = self._input_splitter.push(source)
234 complete = self._input_splitter.push(source)
235 except SyntaxError:
235 except SyntaxError:
236 return True
236 return True
237 if interactive:
237 if interactive:
238 complete = not self._input_splitter.push_accepts_more()
238 complete = not self._input_splitter.push_accepts_more()
239 return complete
239 return complete
240
240
241 def _execute(self, source, hidden):
241 def _execute(self, source, hidden):
242 """ Execute 'source'. If 'hidden', do not show any output.
242 """ Execute 'source'. If 'hidden', do not show any output.
243
243
244 See parent class :meth:`execute` docstring for full details.
244 See parent class :meth:`execute` docstring for full details.
245 """
245 """
246 msg_id = self.kernel_client.execute(source, hidden)
246 msg_id = self.kernel_client.execute(source, hidden)
247 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
247 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
248 self._hidden = hidden
248 self._hidden = hidden
249 if not hidden:
249 if not hidden:
250 self.executing.emit(source)
250 self.executing.emit(source)
251
251
252 def _prompt_started_hook(self):
252 def _prompt_started_hook(self):
253 """ Called immediately after a new prompt is displayed.
253 """ Called immediately after a new prompt is displayed.
254 """
254 """
255 if not self._reading:
255 if not self._reading:
256 self._highlighter.highlighting_on = True
256 self._highlighter.highlighting_on = True
257
257
258 def _prompt_finished_hook(self):
258 def _prompt_finished_hook(self):
259 """ Called immediately after a prompt is finished, i.e. when some input
259 """ Called immediately after a prompt is finished, i.e. when some input
260 will be processed and a new prompt displayed.
260 will be processed and a new prompt displayed.
261 """
261 """
262 # Flush all state from the input splitter so the next round of
262 # Flush all state from the input splitter so the next round of
263 # reading input starts with a clean buffer.
263 # reading input starts with a clean buffer.
264 self._input_splitter.reset()
264 self._input_splitter.reset()
265
265
266 if not self._reading:
266 if not self._reading:
267 self._highlighter.highlighting_on = False
267 self._highlighter.highlighting_on = False
268
268
269 def _tab_pressed(self):
269 def _tab_pressed(self):
270 """ Called when the tab key is pressed. Returns whether to continue
270 """ Called when the tab key is pressed. Returns whether to continue
271 processing the event.
271 processing the event.
272 """
272 """
273 # Perform tab completion if:
273 # Perform tab completion if:
274 # 1) The cursor is in the input buffer.
274 # 1) The cursor is in the input buffer.
275 # 2) There is a non-whitespace character before the cursor.
275 # 2) There is a non-whitespace character before the cursor.
276 text = self._get_input_buffer_cursor_line()
276 text = self._get_input_buffer_cursor_line()
277 if text is None:
277 if text is None:
278 return False
278 return False
279 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
279 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
280 if complete:
280 if complete:
281 self._complete()
281 self._complete()
282 return not complete
282 return not complete
283
283
284 #---------------------------------------------------------------------------
284 #---------------------------------------------------------------------------
285 # 'ConsoleWidget' protected interface
285 # 'ConsoleWidget' protected interface
286 #---------------------------------------------------------------------------
286 #---------------------------------------------------------------------------
287
287
288 def _context_menu_make(self, pos):
288 def _context_menu_make(self, pos):
289 """ Reimplemented to add an action for raw copy.
289 """ Reimplemented to add an action for raw copy.
290 """
290 """
291 menu = super(FrontendWidget, self)._context_menu_make(pos)
291 menu = super(FrontendWidget, self)._context_menu_make(pos)
292 for before_action in menu.actions():
292 for before_action in menu.actions():
293 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
293 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
294 QtGui.QKeySequence.ExactMatch:
294 QtGui.QKeySequence.ExactMatch:
295 menu.insertAction(before_action, self._copy_raw_action)
295 menu.insertAction(before_action, self._copy_raw_action)
296 break
296 break
297 return menu
297 return menu
298
298
299 def request_interrupt_kernel(self):
299 def request_interrupt_kernel(self):
300 if self._executing:
300 if self._executing:
301 self.interrupt_kernel()
301 self.interrupt_kernel()
302
302
303 def request_restart_kernel(self):
303 def request_restart_kernel(self):
304 message = 'Are you sure you want to restart the kernel?'
304 message = 'Are you sure you want to restart the kernel?'
305 self.restart_kernel(message, now=False)
305 self.restart_kernel(message, now=False)
306
306
307 def _event_filter_console_keypress(self, event):
307 def _event_filter_console_keypress(self, event):
308 """ Reimplemented for execution interruption and smart backspace.
308 """ Reimplemented for execution interruption and smart backspace.
309 """
309 """
310 key = event.key()
310 key = event.key()
311 if self._control_key_down(event.modifiers(), include_command=False):
311 if self._control_key_down(event.modifiers(), include_command=False):
312
312
313 if key == QtCore.Qt.Key_C and self._executing:
313 if key == QtCore.Qt.Key_C and self._executing:
314 self.request_interrupt_kernel()
314 self.request_interrupt_kernel()
315 return True
315 return True
316
316
317 elif key == QtCore.Qt.Key_Period:
317 elif key == QtCore.Qt.Key_Period:
318 self.request_restart_kernel()
318 self.request_restart_kernel()
319 return True
319 return True
320
320
321 elif not event.modifiers() & QtCore.Qt.AltModifier:
321 elif not event.modifiers() & QtCore.Qt.AltModifier:
322
322
323 # Smart backspace: remove four characters in one backspace if:
323 # Smart backspace: remove four characters in one backspace if:
324 # 1) everything left of the cursor is whitespace
324 # 1) everything left of the cursor is whitespace
325 # 2) the four characters immediately left of the cursor are spaces
325 # 2) the four characters immediately left of the cursor are spaces
326 if key == QtCore.Qt.Key_Backspace:
326 if key == QtCore.Qt.Key_Backspace:
327 col = self._get_input_buffer_cursor_column()
327 col = self._get_input_buffer_cursor_column()
328 cursor = self._control.textCursor()
328 cursor = self._control.textCursor()
329 if col > 3 and not cursor.hasSelection():
329 if col > 3 and not cursor.hasSelection():
330 text = self._get_input_buffer_cursor_line()[:col]
330 text = self._get_input_buffer_cursor_line()[:col]
331 if text.endswith(' ') and not text.strip():
331 if text.endswith(' ') and not text.strip():
332 cursor.movePosition(QtGui.QTextCursor.Left,
332 cursor.movePosition(QtGui.QTextCursor.Left,
333 QtGui.QTextCursor.KeepAnchor, 4)
333 QtGui.QTextCursor.KeepAnchor, 4)
334 cursor.removeSelectedText()
334 cursor.removeSelectedText()
335 return True
335 return True
336
336
337 return super(FrontendWidget, self)._event_filter_console_keypress(event)
337 return super(FrontendWidget, self)._event_filter_console_keypress(event)
338
338
339 def _insert_continuation_prompt(self, cursor):
339 def _insert_continuation_prompt(self, cursor):
340 """ Reimplemented for auto-indentation.
340 """ Reimplemented for auto-indentation.
341 """
341 """
342 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
342 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
343 cursor.insertText(' ' * self._input_splitter.indent_spaces)
343 cursor.insertText(' ' * self._input_splitter.indent_spaces)
344
344
345 #---------------------------------------------------------------------------
345 #---------------------------------------------------------------------------
346 # 'BaseFrontendMixin' abstract interface
346 # 'BaseFrontendMixin' abstract interface
347 #---------------------------------------------------------------------------
347 #---------------------------------------------------------------------------
348 def _handle_clear_output(self, msg):
348 def _handle_clear_output(self, msg):
349 """Handle clear output messages."""
349 """Handle clear output messages."""
350 if not self._hidden and self._is_from_this_session(msg):
350 if not self._hidden and self._is_from_this_session(msg):
351 wait = msg['content'].get('wait', True)
351 wait = msg['content'].get('wait', True)
352 if wait:
352 if wait:
353 self._pending_clearoutput = True
353 self._pending_clearoutput = True
354 else:
354 else:
355 self.clear_output()
355 self.clear_output()
356
356
357 def _handle_complete_reply(self, rep):
357 def _handle_complete_reply(self, rep):
358 """ Handle replies for tab completion.
358 """ Handle replies for tab completion.
359 """
359 """
360 self.log.debug("complete: %s", rep.get('content', ''))
360 self.log.debug("complete: %s", rep.get('content', ''))
361 cursor = self._get_cursor()
361 cursor = self._get_cursor()
362 info = self._request_info.get('complete')
362 info = self._request_info.get('complete')
363 if info and info.id == rep['parent_header']['msg_id'] and \
363 if info and info.id == rep['parent_header']['msg_id'] and \
364 info.pos == cursor.position():
364 info.pos == cursor.position():
365 text = '.'.join(self._get_context())
365 text = '.'.join(self._get_context())
366 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
366 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
367 self._complete_with_items(cursor, rep['content']['matches'])
367 self._complete_with_items(cursor, rep['content']['matches'])
368
368
369 def _silent_exec_callback(self, expr, callback):
369 def _silent_exec_callback(self, expr, callback):
370 """Silently execute `expr` in the kernel and call `callback` with reply
370 """Silently execute `expr` in the kernel and call `callback` with reply
371
371
372 the `expr` is evaluated silently in the kernel (without) output in
372 the `expr` is evaluated silently in the kernel (without) output in
373 the frontend. Call `callback` with the
373 the frontend. Call `callback` with the
374 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
374 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
375
375
376 Parameters
376 Parameters
377 ----------
377 ----------
378 expr : string
378 expr : string
379 valid string to be executed by the kernel.
379 valid string to be executed by the kernel.
380 callback : function
380 callback : function
381 function accepting one argument, as a string. The string will be
381 function accepting one argument, as a string. The string will be
382 the `repr` of the result of evaluating `expr`
382 the `repr` of the result of evaluating `expr`
383
383
384 The `callback` is called with the `repr()` of the result of `expr` as
384 The `callback` is called with the `repr()` of the result of `expr` as
385 first argument. To get the object, do `eval()` on the passed value.
385 first argument. To get the object, do `eval()` on the passed value.
386
386
387 See Also
387 See Also
388 --------
388 --------
389 _handle_exec_callback : private method, deal with calling callback with reply
389 _handle_exec_callback : private method, deal with calling callback with reply
390
390
391 """
391 """
392
392
393 # generate uuid, which would be used as an indication of whether or
393 # generate uuid, which would be used as an indication of whether or
394 # not the unique request originated from here (can use msg id ?)
394 # not the unique request originated from here (can use msg id ?)
395 local_uuid = str(uuid.uuid1())
395 local_uuid = str(uuid.uuid1())
396 msg_id = self.kernel_client.execute('',
396 msg_id = self.kernel_client.execute('',
397 silent=True, user_expressions={ local_uuid:expr })
397 silent=True, user_expressions={ local_uuid:expr })
398 self._callback_dict[local_uuid] = callback
398 self._callback_dict[local_uuid] = callback
399 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
399 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
400
400
401 def _handle_exec_callback(self, msg):
401 def _handle_exec_callback(self, msg):
402 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
402 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
403
403
404 Parameters
404 Parameters
405 ----------
405 ----------
406 msg : raw message send by the kernel containing an `user_expressions`
406 msg : raw message send by the kernel containing an `user_expressions`
407 and having a 'silent_exec_callback' kind.
407 and having a 'silent_exec_callback' kind.
408
408
409 Notes
409 Notes
410 -----
410 -----
411 This function will look for a `callback` associated with the
411 This function will look for a `callback` associated with the
412 corresponding message id. Association has been made by
412 corresponding message id. Association has been made by
413 `_silent_exec_callback`. `callback` is then called with the `repr()`
413 `_silent_exec_callback`. `callback` is then called with the `repr()`
414 of the value of corresponding `user_expressions` as argument.
414 of the value of corresponding `user_expressions` as argument.
415 `callback` is then removed from the known list so that any message
415 `callback` is then removed from the known list so that any message
416 coming again with the same id won't trigger it.
416 coming again with the same id won't trigger it.
417
417
418 """
418 """
419
419
420 user_exp = msg['content'].get('user_expressions')
420 user_exp = msg['content'].get('user_expressions')
421 if not user_exp:
421 if not user_exp:
422 return
422 return
423 for expression in user_exp:
423 for expression in user_exp:
424 if expression in self._callback_dict:
424 if expression in self._callback_dict:
425 self._callback_dict.pop(expression)(user_exp[expression])
425 self._callback_dict.pop(expression)(user_exp[expression])
426
426
427 def _handle_execute_reply(self, msg):
427 def _handle_execute_reply(self, msg):
428 """ Handles replies for code execution.
428 """ Handles replies for code execution.
429 """
429 """
430 self.log.debug("execute: %s", msg.get('content', ''))
430 self.log.debug("execute: %s", msg.get('content', ''))
431 msg_id = msg['parent_header']['msg_id']
431 msg_id = msg['parent_header']['msg_id']
432 info = self._request_info['execute'].get(msg_id)
432 info = self._request_info['execute'].get(msg_id)
433 # unset reading flag, because if execute finished, raw_input can't
433 # unset reading flag, because if execute finished, raw_input can't
434 # still be pending.
434 # still be pending.
435 self._reading = False
435 self._reading = False
436 if info and info.kind == 'user' and not self._hidden:
436 if info and info.kind == 'user' and not self._hidden:
437 # Make sure that all output from the SUB channel has been processed
437 # Make sure that all output from the SUB channel has been processed
438 # before writing a new prompt.
438 # before writing a new prompt.
439 self.kernel_client.iopub_channel.flush()
439 self.kernel_client.iopub_channel.flush()
440
440
441 # Reset the ANSI style information to prevent bad text in stdout
441 # Reset the ANSI style information to prevent bad text in stdout
442 # from messing up our colors. We're not a true terminal so we're
442 # from messing up our colors. We're not a true terminal so we're
443 # allowed to do this.
443 # allowed to do this.
444 if self.ansi_codes:
444 if self.ansi_codes:
445 self._ansi_processor.reset_sgr()
445 self._ansi_processor.reset_sgr()
446
446
447 content = msg['content']
447 content = msg['content']
448 status = content['status']
448 status = content['status']
449 if status == 'ok':
449 if status == 'ok':
450 self._process_execute_ok(msg)
450 self._process_execute_ok(msg)
451 elif status == 'error':
451 elif status == 'error':
452 self._process_execute_error(msg)
452 self._process_execute_error(msg)
453 elif status == 'aborted':
453 elif status == 'aborted':
454 self._process_execute_abort(msg)
454 self._process_execute_abort(msg)
455
455
456 self._show_interpreter_prompt_for_reply(msg)
456 self._show_interpreter_prompt_for_reply(msg)
457 self.executed.emit(msg)
457 self.executed.emit(msg)
458 self._request_info['execute'].pop(msg_id)
458 self._request_info['execute'].pop(msg_id)
459 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
459 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
460 self._handle_exec_callback(msg)
460 self._handle_exec_callback(msg)
461 self._request_info['execute'].pop(msg_id)
461 self._request_info['execute'].pop(msg_id)
462 else:
462 else:
463 super(FrontendWidget, self)._handle_execute_reply(msg)
463 super(FrontendWidget, self)._handle_execute_reply(msg)
464
464
465 def _handle_input_request(self, msg):
465 def _handle_input_request(self, msg):
466 """ Handle requests for raw_input.
466 """ Handle requests for raw_input.
467 """
467 """
468 self.log.debug("input: %s", msg.get('content', ''))
468 self.log.debug("input: %s", msg.get('content', ''))
469 if self._hidden:
469 if self._hidden:
470 raise RuntimeError('Request for raw input during hidden execution.')
470 raise RuntimeError('Request for raw input during hidden execution.')
471
471
472 # Make sure that all output from the SUB channel has been processed
472 # Make sure that all output from the SUB channel has been processed
473 # before entering readline mode.
473 # before entering readline mode.
474 self.kernel_client.iopub_channel.flush()
474 self.kernel_client.iopub_channel.flush()
475
475
476 def callback(line):
476 def callback(line):
477 self.kernel_client.stdin_channel.input(line)
477 self.kernel_client.stdin_channel.input(line)
478 if self._reading:
478 if self._reading:
479 self.log.debug("Got second input request, assuming first was interrupted.")
479 self.log.debug("Got second input request, assuming first was interrupted.")
480 self._reading = False
480 self._reading = False
481 self._readline(msg['content']['prompt'], callback=callback)
481 self._readline(msg['content']['prompt'], callback=callback)
482
482
483 def _kernel_restarted_message(self, died=True):
483 def _kernel_restarted_message(self, died=True):
484 msg = "Kernel died, restarting" if died else "Kernel restarting"
484 msg = "Kernel died, restarting" if died else "Kernel restarting"
485 self._append_html("<br>%s<hr><br>" % msg,
485 self._append_html("<br>%s<hr><br>" % msg,
486 before_prompt=False
486 before_prompt=False
487 )
487 )
488
488
489 def _handle_kernel_died(self, since_last_heartbeat):
489 def _handle_kernel_died(self, since_last_heartbeat):
490 """Handle the kernel's death (if we do not own the kernel).
490 """Handle the kernel's death (if we do not own the kernel).
491 """
491 """
492 self.log.warn("kernel died: %s", since_last_heartbeat)
492 self.log.warn("kernel died: %s", since_last_heartbeat)
493 if self.custom_restart:
493 if self.custom_restart:
494 self.custom_restart_kernel_died.emit(since_last_heartbeat)
494 self.custom_restart_kernel_died.emit(since_last_heartbeat)
495 else:
495 else:
496 self._kernel_restarted_message(died=True)
496 self._kernel_restarted_message(died=True)
497 self.reset()
497 self.reset()
498
498
499 def _handle_kernel_restarted(self, died=True):
499 def _handle_kernel_restarted(self, died=True):
500 """Notice that the autorestarter restarted the kernel.
500 """Notice that the autorestarter restarted the kernel.
501
501
502 There's nothing to do but show a message.
502 There's nothing to do but show a message.
503 """
503 """
504 self.log.warn("kernel restarted")
504 self.log.warn("kernel restarted")
505 self._kernel_restarted_message(died=died)
505 self._kernel_restarted_message(died=died)
506 self.reset()
506 self.reset()
507
507
508 def _handle_inspect_reply(self, rep):
508 def _handle_inspect_reply(self, rep):
509 """Handle replies for call tips."""
509 """Handle replies for call tips."""
510 self.log.debug("oinfo: %s", rep.get('content', ''))
510 self.log.debug("oinfo: %s", rep.get('content', ''))
511 cursor = self._get_cursor()
511 cursor = self._get_cursor()
512 info = self._request_info.get('call_tip')
512 info = self._request_info.get('call_tip')
513 if info and info.id == rep['parent_header']['msg_id'] and \
513 if info and info.id == rep['parent_header']['msg_id'] and \
514 info.pos == cursor.position():
514 info.pos == cursor.position():
515 # Get the information for a call tip. For now we format the call
516 # line as string, later we can pass False to format_call and
517 # syntax-highlight it ourselves for nicer formatting in the
518 # calltip.
519 content = rep['content']
515 content = rep['content']
520 if content.get('status') == 'ok':
516 if content.get('status') == 'ok':
521 self._call_tip_widget.show_inspect_data(content)
517 self._call_tip_widget.show_inspect_data(content)
522
518
523 def _handle_execute_result(self, msg):
519 def _handle_execute_result(self, msg):
524 """ Handle display hook output.
520 """ Handle display hook output.
525 """
521 """
526 self.log.debug("execute_result: %s", msg.get('content', ''))
522 self.log.debug("execute_result: %s", msg.get('content', ''))
527 if not self._hidden and self._is_from_this_session(msg):
523 if not self._hidden and self._is_from_this_session(msg):
528 self.flush_clearoutput()
524 self.flush_clearoutput()
529 text = msg['content']['data']
525 text = msg['content']['data']
530 self._append_plain_text(text + '\n', before_prompt=True)
526 self._append_plain_text(text + '\n', before_prompt=True)
531
527
532 def _handle_stream(self, msg):
528 def _handle_stream(self, msg):
533 """ Handle stdout, stderr, and stdin.
529 """ Handle stdout, stderr, and stdin.
534 """
530 """
535 self.log.debug("stream: %s", msg.get('content', ''))
531 self.log.debug("stream: %s", msg.get('content', ''))
536 if not self._hidden and self._is_from_this_session(msg):
532 if not self._hidden and self._is_from_this_session(msg):
537 self.flush_clearoutput()
533 self.flush_clearoutput()
538 self.append_stream(msg['content']['data'])
534 self.append_stream(msg['content']['data'])
539
535
540 def _handle_shutdown_reply(self, msg):
536 def _handle_shutdown_reply(self, msg):
541 """ Handle shutdown signal, only if from other console.
537 """ Handle shutdown signal, only if from other console.
542 """
538 """
543 self.log.warn("shutdown: %s", msg.get('content', ''))
539 self.log.warn("shutdown: %s", msg.get('content', ''))
544 restart = msg.get('content', {}).get('restart', False)
540 restart = msg.get('content', {}).get('restart', False)
545 if not self._hidden and not self._is_from_this_session(msg):
541 if not self._hidden and not self._is_from_this_session(msg):
546 # got shutdown reply, request came from session other than ours
542 # got shutdown reply, request came from session other than ours
547 if restart:
543 if restart:
548 # someone restarted the kernel, handle it
544 # someone restarted the kernel, handle it
549 self._handle_kernel_restarted(died=False)
545 self._handle_kernel_restarted(died=False)
550 else:
546 else:
551 # kernel was shutdown permanently
547 # kernel was shutdown permanently
552 # this triggers exit_requested if the kernel was local,
548 # this triggers exit_requested if the kernel was local,
553 # and a dialog if the kernel was remote,
549 # and a dialog if the kernel was remote,
554 # so we don't suddenly clear the qtconsole without asking.
550 # so we don't suddenly clear the qtconsole without asking.
555 if self._local_kernel:
551 if self._local_kernel:
556 self.exit_requested.emit(self)
552 self.exit_requested.emit(self)
557 else:
553 else:
558 title = self.window().windowTitle()
554 title = self.window().windowTitle()
559 reply = QtGui.QMessageBox.question(self, title,
555 reply = QtGui.QMessageBox.question(self, title,
560 "Kernel has been shutdown permanently. "
556 "Kernel has been shutdown permanently. "
561 "Close the Console?",
557 "Close the Console?",
562 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
558 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
563 if reply == QtGui.QMessageBox.Yes:
559 if reply == QtGui.QMessageBox.Yes:
564 self.exit_requested.emit(self)
560 self.exit_requested.emit(self)
565
561
566 def _handle_status(self, msg):
562 def _handle_status(self, msg):
567 """Handle status message"""
563 """Handle status message"""
568 # This is where a busy/idle indicator would be triggered,
564 # This is where a busy/idle indicator would be triggered,
569 # when we make one.
565 # when we make one.
570 state = msg['content'].get('execution_state', '')
566 state = msg['content'].get('execution_state', '')
571 if state == 'starting':
567 if state == 'starting':
572 # kernel started while we were running
568 # kernel started while we were running
573 if self._executing:
569 if self._executing:
574 self._handle_kernel_restarted(died=True)
570 self._handle_kernel_restarted(died=True)
575 elif state == 'idle':
571 elif state == 'idle':
576 pass
572 pass
577 elif state == 'busy':
573 elif state == 'busy':
578 pass
574 pass
579
575
580 def _started_channels(self):
576 def _started_channels(self):
581 """ Called when the KernelManager channels have started listening or
577 """ Called when the KernelManager channels have started listening or
582 when the frontend is assigned an already listening KernelManager.
578 when the frontend is assigned an already listening KernelManager.
583 """
579 """
584 self.reset(clear=True)
580 self.reset(clear=True)
585
581
586 #---------------------------------------------------------------------------
582 #---------------------------------------------------------------------------
587 # 'FrontendWidget' public interface
583 # 'FrontendWidget' public interface
588 #---------------------------------------------------------------------------
584 #---------------------------------------------------------------------------
589
585
590 def copy_raw(self):
586 def copy_raw(self):
591 """ Copy the currently selected text to the clipboard without attempting
587 """ Copy the currently selected text to the clipboard without attempting
592 to remove prompts or otherwise alter the text.
588 to remove prompts or otherwise alter the text.
593 """
589 """
594 self._control.copy()
590 self._control.copy()
595
591
596 def execute_file(self, path, hidden=False):
592 def execute_file(self, path, hidden=False):
597 """ Attempts to execute file with 'path'. If 'hidden', no output is
593 """ Attempts to execute file with 'path'. If 'hidden', no output is
598 shown.
594 shown.
599 """
595 """
600 self.execute('execfile(%r)' % path, hidden=hidden)
596 self.execute('execfile(%r)' % path, hidden=hidden)
601
597
602 def interrupt_kernel(self):
598 def interrupt_kernel(self):
603 """ Attempts to interrupt the running kernel.
599 """ Attempts to interrupt the running kernel.
604
600
605 Also unsets _reading flag, to avoid runtime errors
601 Also unsets _reading flag, to avoid runtime errors
606 if raw_input is called again.
602 if raw_input is called again.
607 """
603 """
608 if self.custom_interrupt:
604 if self.custom_interrupt:
609 self._reading = False
605 self._reading = False
610 self.custom_interrupt_requested.emit()
606 self.custom_interrupt_requested.emit()
611 elif self.kernel_manager:
607 elif self.kernel_manager:
612 self._reading = False
608 self._reading = False
613 self.kernel_manager.interrupt_kernel()
609 self.kernel_manager.interrupt_kernel()
614 else:
610 else:
615 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
611 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
616
612
617 def reset(self, clear=False):
613 def reset(self, clear=False):
618 """ Resets the widget to its initial state if ``clear`` parameter
614 """ Resets the widget to its initial state if ``clear`` parameter
619 is True, otherwise
615 is True, otherwise
620 prints a visual indication of the fact that the kernel restarted, but
616 prints a visual indication of the fact that the kernel restarted, but
621 does not clear the traces from previous usage of the kernel before it
617 does not clear the traces from previous usage of the kernel before it
622 was restarted. With ``clear=True``, it is similar to ``%clear``, but
618 was restarted. With ``clear=True``, it is similar to ``%clear``, but
623 also re-writes the banner and aborts execution if necessary.
619 also re-writes the banner and aborts execution if necessary.
624 """
620 """
625 if self._executing:
621 if self._executing:
626 self._executing = False
622 self._executing = False
627 self._request_info['execute'] = {}
623 self._request_info['execute'] = {}
628 self._reading = False
624 self._reading = False
629 self._highlighter.highlighting_on = False
625 self._highlighter.highlighting_on = False
630
626
631 if clear:
627 if clear:
632 self._control.clear()
628 self._control.clear()
633 self._append_plain_text(self.banner)
629 self._append_plain_text(self.banner)
634 if self.kernel_banner:
630 if self.kernel_banner:
635 self._append_plain_text(self.kernel_banner)
631 self._append_plain_text(self.kernel_banner)
636
632
637 # update output marker for stdout/stderr, so that startup
633 # update output marker for stdout/stderr, so that startup
638 # messages appear after banner:
634 # messages appear after banner:
639 self._append_before_prompt_pos = self._get_cursor().position()
635 self._append_before_prompt_pos = self._get_cursor().position()
640 self._show_interpreter_prompt()
636 self._show_interpreter_prompt()
641
637
642 def restart_kernel(self, message, now=False):
638 def restart_kernel(self, message, now=False):
643 """ Attempts to restart the running kernel.
639 """ Attempts to restart the running kernel.
644 """
640 """
645 # FIXME: now should be configurable via a checkbox in the dialog. Right
641 # FIXME: now should be configurable via a checkbox in the dialog. Right
646 # now at least the heartbeat path sets it to True and the manual restart
642 # now at least the heartbeat path sets it to True and the manual restart
647 # to False. But those should just be the pre-selected states of a
643 # to False. But those should just be the pre-selected states of a
648 # checkbox that the user could override if so desired. But I don't know
644 # checkbox that the user could override if so desired. But I don't know
649 # enough Qt to go implementing the checkbox now.
645 # enough Qt to go implementing the checkbox now.
650
646
651 if self.custom_restart:
647 if self.custom_restart:
652 self.custom_restart_requested.emit()
648 self.custom_restart_requested.emit()
653 return
649 return
654
650
655 if self.kernel_manager:
651 if self.kernel_manager:
656 # Pause the heart beat channel to prevent further warnings.
652 # Pause the heart beat channel to prevent further warnings.
657 self.kernel_client.hb_channel.pause()
653 self.kernel_client.hb_channel.pause()
658
654
659 # Prompt the user to restart the kernel. Un-pause the heartbeat if
655 # Prompt the user to restart the kernel. Un-pause the heartbeat if
660 # they decline. (If they accept, the heartbeat will be un-paused
656 # they decline. (If they accept, the heartbeat will be un-paused
661 # automatically when the kernel is restarted.)
657 # automatically when the kernel is restarted.)
662 if self.confirm_restart:
658 if self.confirm_restart:
663 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
659 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
664 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
660 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
665 message, buttons)
661 message, buttons)
666 do_restart = result == QtGui.QMessageBox.Yes
662 do_restart = result == QtGui.QMessageBox.Yes
667 else:
663 else:
668 # confirm_restart is False, so we don't need to ask user
664 # confirm_restart is False, so we don't need to ask user
669 # anything, just do the restart
665 # anything, just do the restart
670 do_restart = True
666 do_restart = True
671 if do_restart:
667 if do_restart:
672 try:
668 try:
673 self.kernel_manager.restart_kernel(now=now)
669 self.kernel_manager.restart_kernel(now=now)
674 except RuntimeError as e:
670 except RuntimeError as e:
675 self._append_plain_text(
671 self._append_plain_text(
676 'Error restarting kernel: %s\n' % e,
672 'Error restarting kernel: %s\n' % e,
677 before_prompt=True
673 before_prompt=True
678 )
674 )
679 else:
675 else:
680 self._append_html("<br>Restarting kernel...\n<hr><br>",
676 self._append_html("<br>Restarting kernel...\n<hr><br>",
681 before_prompt=True,
677 before_prompt=True,
682 )
678 )
683 else:
679 else:
684 self.kernel_client.hb_channel.unpause()
680 self.kernel_client.hb_channel.unpause()
685
681
686 else:
682 else:
687 self._append_plain_text(
683 self._append_plain_text(
688 'Cannot restart a Kernel I did not start\n',
684 'Cannot restart a Kernel I did not start\n',
689 before_prompt=True
685 before_prompt=True
690 )
686 )
691
687
692 def append_stream(self, text):
688 def append_stream(self, text):
693 """Appends text to the output stream."""
689 """Appends text to the output stream."""
694 # Most consoles treat tabs as being 8 space characters. Convert tabs
690 # Most consoles treat tabs as being 8 space characters. Convert tabs
695 # to spaces so that output looks as expected regardless of this
691 # to spaces so that output looks as expected regardless of this
696 # widget's tab width.
692 # widget's tab width.
697 text = text.expandtabs(8)
693 text = text.expandtabs(8)
698 self._append_plain_text(text, before_prompt=True)
694 self._append_plain_text(text, before_prompt=True)
699 self._control.moveCursor(QtGui.QTextCursor.End)
695 self._control.moveCursor(QtGui.QTextCursor.End)
700
696
701 def flush_clearoutput(self):
697 def flush_clearoutput(self):
702 """If a clearoutput is pending, execute it."""
698 """If a clearoutput is pending, execute it."""
703 if self._pending_clearoutput:
699 if self._pending_clearoutput:
704 self._pending_clearoutput = False
700 self._pending_clearoutput = False
705 self.clear_output()
701 self.clear_output()
706
702
707 def clear_output(self):
703 def clear_output(self):
708 """Clears the current line of output."""
704 """Clears the current line of output."""
709 cursor = self._control.textCursor()
705 cursor = self._control.textCursor()
710 cursor.beginEditBlock()
706 cursor.beginEditBlock()
711 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
707 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
712 cursor.insertText('')
708 cursor.insertText('')
713 cursor.endEditBlock()
709 cursor.endEditBlock()
714
710
715 #---------------------------------------------------------------------------
711 #---------------------------------------------------------------------------
716 # 'FrontendWidget' protected interface
712 # 'FrontendWidget' protected interface
717 #---------------------------------------------------------------------------
713 #---------------------------------------------------------------------------
718
714
719 def _call_tip(self):
715 def _call_tip(self):
720 """ Shows a call tip, if appropriate, at the current cursor location.
716 """ Shows a call tip, if appropriate, at the current cursor location.
721 """
717 """
722 # Decide if it makes sense to show a call tip
718 # Decide if it makes sense to show a call tip
723 if not self.enable_calltips:
719 if not self.enable_calltips:
724 return False
720 return False
725 cursor_pos = self._get_input_buffer_cursor_pos()
721 cursor_pos = self._get_input_buffer_cursor_pos()
726 code = self.input_buffer
722 code = self.input_buffer
727 # Send the metadata request to the kernel
723 # Send the metadata request to the kernel
728 msg_id = self.kernel_client.inspect(code, cursor_pos)
724 msg_id = self.kernel_client.inspect(code, cursor_pos)
729 pos = self._get_cursor().position()
725 pos = self._get_cursor().position()
730 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
726 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
731 return True
727 return True
732
728
733 def _complete(self):
729 def _complete(self):
734 """ Performs completion at the current cursor location.
730 """ Performs completion at the current cursor location.
735 """
731 """
736 context = self._get_context()
732 context = self._get_context()
737 if context:
733 if context:
738 # Send the completion request to the kernel
734 # Send the completion request to the kernel
739 msg_id = self.kernel_client.complete(
735 msg_id = self.kernel_client.complete(
740 code=self.input_buffer,
736 code=self.input_buffer,
741 cursor_pos=self._get_input_buffer_cursor_pos(),
737 cursor_pos=self._get_input_buffer_cursor_pos(),
742 )
738 )
743 pos = self._get_cursor().position()
739 pos = self._get_cursor().position()
744 info = self._CompletionRequest(msg_id, pos)
740 info = self._CompletionRequest(msg_id, pos)
745 self._request_info['complete'] = info
741 self._request_info['complete'] = info
746
742
747 def _get_context(self, cursor=None):
743 def _get_context(self, cursor=None):
748 """ Gets the context for the specified cursor (or the current cursor
744 """ Gets the context for the specified cursor (or the current cursor
749 if none is specified).
745 if none is specified).
750 """
746 """
751 if cursor is None:
747 if cursor is None:
752 cursor = self._get_cursor()
748 cursor = self._get_cursor()
753 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
749 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
754 QtGui.QTextCursor.KeepAnchor)
750 QtGui.QTextCursor.KeepAnchor)
755 text = cursor.selection().toPlainText()
751 text = cursor.selection().toPlainText()
756 return self._completion_lexer.get_context(text)
752 return self._completion_lexer.get_context(text)
757
753
758 def _process_execute_abort(self, msg):
754 def _process_execute_abort(self, msg):
759 """ Process a reply for an aborted execution request.
755 """ Process a reply for an aborted execution request.
760 """
756 """
761 self._append_plain_text("ERROR: execution aborted\n")
757 self._append_plain_text("ERROR: execution aborted\n")
762
758
763 def _process_execute_error(self, msg):
759 def _process_execute_error(self, msg):
764 """ Process a reply for an execution request that resulted in an error.
760 """ Process a reply for an execution request that resulted in an error.
765 """
761 """
766 content = msg['content']
762 content = msg['content']
767 # If a SystemExit is passed along, this means exit() was called - also
763 # If a SystemExit is passed along, this means exit() was called - also
768 # all the ipython %exit magic syntax of '-k' to be used to keep
764 # all the ipython %exit magic syntax of '-k' to be used to keep
769 # the kernel running
765 # the kernel running
770 if content['ename']=='SystemExit':
766 if content['ename']=='SystemExit':
771 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
767 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
772 self._keep_kernel_on_exit = keepkernel
768 self._keep_kernel_on_exit = keepkernel
773 self.exit_requested.emit(self)
769 self.exit_requested.emit(self)
774 else:
770 else:
775 traceback = ''.join(content['traceback'])
771 traceback = ''.join(content['traceback'])
776 self._append_plain_text(traceback)
772 self._append_plain_text(traceback)
777
773
778 def _process_execute_ok(self, msg):
774 def _process_execute_ok(self, msg):
779 """ Process a reply for a successful execution request.
775 """ Process a reply for a successful execution request.
780 """
776 """
781 payload = msg['content']['payload']
777 payload = msg['content']['payload']
782 for item in payload:
778 for item in payload:
783 if not self._process_execute_payload(item):
779 if not self._process_execute_payload(item):
784 warning = 'Warning: received unknown payload of type %s'
780 warning = 'Warning: received unknown payload of type %s'
785 print(warning % repr(item['source']))
781 print(warning % repr(item['source']))
786
782
787 def _process_execute_payload(self, item):
783 def _process_execute_payload(self, item):
788 """ Process a single payload item from the list of payload items in an
784 """ Process a single payload item from the list of payload items in an
789 execution reply. Returns whether the payload was handled.
785 execution reply. Returns whether the payload was handled.
790 """
786 """
791 # The basic FrontendWidget doesn't handle payloads, as they are a
787 # The basic FrontendWidget doesn't handle payloads, as they are a
792 # mechanism for going beyond the standard Python interpreter model.
788 # mechanism for going beyond the standard Python interpreter model.
793 return False
789 return False
794
790
795 def _show_interpreter_prompt(self):
791 def _show_interpreter_prompt(self):
796 """ Shows a prompt for the interpreter.
792 """ Shows a prompt for the interpreter.
797 """
793 """
798 self._show_prompt('>>> ')
794 self._show_prompt('>>> ')
799
795
800 def _show_interpreter_prompt_for_reply(self, msg):
796 def _show_interpreter_prompt_for_reply(self, msg):
801 """ Shows a prompt for the interpreter given an 'execute_reply' message.
797 """ Shows a prompt for the interpreter given an 'execute_reply' message.
802 """
798 """
803 self._show_interpreter_prompt()
799 self._show_interpreter_prompt()
804
800
805 #------ Signal handlers ----------------------------------------------------
801 #------ Signal handlers ----------------------------------------------------
806
802
807 def _document_contents_change(self, position, removed, added):
803 def _document_contents_change(self, position, removed, added):
808 """ Called whenever the document's content changes. Display a call tip
804 """ Called whenever the document's content changes. Display a call tip
809 if appropriate.
805 if appropriate.
810 """
806 """
811 # Calculate where the cursor should be *after* the change:
807 # Calculate where the cursor should be *after* the change:
812 position += added
808 position += added
813
809
814 document = self._control.document()
810 document = self._control.document()
815 if position == self._get_cursor().position():
811 if position == self._get_cursor().position():
816 self._call_tip()
812 self._call_tip()
817
813
818 #------ Trait default initializers -----------------------------------------
814 #------ Trait default initializers -----------------------------------------
819
815
820 def _banner_default(self):
816 def _banner_default(self):
821 """ Returns the standard Python banner.
817 """ Returns the standard Python banner.
822 """
818 """
823 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
819 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
824 '"license" for more information.'
820 '"license" for more information.'
825 return banner % (sys.version, sys.platform)
821 return banner % (sys.version, sys.platform)
@@ -1,70 +1,64 b''
1 .. _execution_semantics:
1 .. _execution_semantics:
2
2
3 Execution semantics in the IPython kernel
3 Execution semantics in the IPython kernel
4 =========================================
4 =========================================
5
5
6 The execution of use code consists of the following phases:
6 The execution of use code consists of the following phases:
7
7
8 1. Fire the ``pre_execute`` event.
8 1. Fire the ``pre_execute`` event.
9 2. Fire the ``pre_run_cell`` event unless silent is True.
9 2. Fire the ``pre_run_cell`` event unless silent is True.
10 3. Execute the ``code`` field, see below for details.
10 3. Execute the ``code`` field, see below for details.
11 4. If execution succeeds, expressions in ``user_expressions`` are computed.
11 4. If execution succeeds, expressions in ``user_expressions`` are computed.
12 This ensures that any error in the expressions don't affect the main code execution.
12 This ensures that any error in the expressions don't affect the main code execution.
13 5. Fire the post_execute eventCall any method registered with :meth:`register_post_execute`.
13 5. Fire the post_execute event.
14
14
15 .. warning::
15 .. seealso::
16
17 :doc:`config/callbacks`
16
18
17 The API for running code before/after the main code block is likely to
18 change soon. Both the ``pre_runcode_hook`` and the
19 :meth:`register_post_execute` are susceptible to modification, as we find a
20 consistent model for both.
21
19
22 To understand how the ``code`` field is executed, one must know that Python
20 To understand how the ``code`` field is executed, one must know that Python
23 code can be compiled in one of three modes (controlled by the ``mode`` argument
21 code can be compiled in one of three modes (controlled by the ``mode`` argument
24 to the :func:`compile` builtin):
22 to the :func:`compile` builtin):
25
23
26 *single*
24 *single*
27 Valid for a single interactive statement (though the source can contain
25 Valid for a single interactive statement (though the source can contain
28 multiple lines, such as a for loop). When compiled in this mode, the
26 multiple lines, such as a for loop). When compiled in this mode, the
29 generated bytecode contains special instructions that trigger the calling of
27 generated bytecode contains special instructions that trigger the calling of
30 :func:`sys.displayhook` for any expression in the block that returns a value.
28 :func:`sys.displayhook` for any expression in the block that returns a value.
31 This means that a single statement can actually produce multiple calls to
29 This means that a single statement can actually produce multiple calls to
32 :func:`sys.displayhook`, if for example it contains a loop where each
30 :func:`sys.displayhook`, if for example it contains a loop where each
33 iteration computes an unassigned expression would generate 10 calls::
31 iteration computes an unassigned expression would generate 10 calls::
34
32
35 for i in range(10):
33 for i in range(10):
36 i**2
34 i**2
37
35
38 *exec*
36 *exec*
39 An arbitrary amount of source code, this is how modules are compiled.
37 An arbitrary amount of source code, this is how modules are compiled.
40 :func:`sys.displayhook` is *never* implicitly called.
38 :func:`sys.displayhook` is *never* implicitly called.
41
39
42 *eval*
40 *eval*
43 A single expression that returns a value. :func:`sys.displayhook` is *never*
41 A single expression that returns a value. :func:`sys.displayhook` is *never*
44 implicitly called.
42 implicitly called.
45
43
46
44
47 The ``code`` field is split into individual blocks each of which is valid for
45 The ``code`` field is split into individual blocks each of which is valid for
48 execution in 'single' mode, and then:
46 execution in 'single' mode, and then:
49
47
50 - If there is only a single block: it is executed in 'single' mode.
48 - If there is only a single block: it is executed in 'single' mode.
51
49
52 - If there is more than one block:
50 - If there is more than one block:
53
51
54 * if the last one is a single line long, run all but the last in 'exec' mode
52 * if the last one is a single line long, run all but the last in 'exec' mode
55 and the very last one in 'single' mode. This makes it easy to type simple
53 and the very last one in 'single' mode. This makes it easy to type simple
56 expressions at the end to see computed values.
54 expressions at the end to see computed values.
57
55
58 * if the last one is no more than two lines long, run all but the last in
56 * if the last one is no more than two lines long, run all but the last in
59 'exec' mode and the very last one in 'single' mode. This makes it easy to
57 'exec' mode and the very last one in 'single' mode. This makes it easy to
60 type simple expressions at the end to see computed values. - otherwise
58 type simple expressions at the end to see computed values. - otherwise
61 (last one is also multiline), run all in 'exec' mode
59 (last one is also multiline), run all in 'exec' mode
62
60
63 * otherwise (last one is also multiline), run all in 'exec' mode as a single
61 * otherwise (last one is also multiline), run all in 'exec' mode as a single
64 unit.
62 unit.
65
63
66
64
67 Errors in any registered post_execute functions are reported,
68 and the failing function is removed from the post_execution set so that it does
69 not continue triggering failures.
70
@@ -1,1052 +1,1052 b''
1 .. _messaging:
1 .. _messaging:
2
2
3 ======================
3 ======================
4 Messaging in IPython
4 Messaging in IPython
5 ======================
5 ======================
6
6
7
7
8 Versioning
8 Versioning
9 ==========
9 ==========
10
10
11 The IPython message specification is versioned independently of IPython.
11 The IPython message specification is versioned independently of IPython.
12 The current version of the specification is 5.0.0.
12 The current version of the specification is 5.0.
13
13
14
14
15 Introduction
15 Introduction
16 ============
16 ============
17
17
18 This document explains the basic communications design and messaging
18 This document explains the basic communications design and messaging
19 specification for how the various IPython objects interact over a network
19 specification for how the various IPython objects interact over a network
20 transport. The current implementation uses the ZeroMQ_ library for messaging
20 transport. The current implementation uses the ZeroMQ_ library for messaging
21 within and between hosts.
21 within and between hosts.
22
22
23 .. Note::
23 .. Note::
24
24
25 This document should be considered the authoritative description of the
25 This document should be considered the authoritative description of the
26 IPython messaging protocol, and all developers are strongly encouraged to
26 IPython messaging protocol, and all developers are strongly encouraged to
27 keep it updated as the implementation evolves, so that we have a single
27 keep it updated as the implementation evolves, so that we have a single
28 common reference for all protocol details.
28 common reference for all protocol details.
29
29
30 The basic design is explained in the following diagram:
30 The basic design is explained in the following diagram:
31
31
32 .. image:: figs/frontend-kernel.png
32 .. image:: figs/frontend-kernel.png
33 :width: 450px
33 :width: 450px
34 :alt: IPython kernel/frontend messaging architecture.
34 :alt: IPython kernel/frontend messaging architecture.
35 :align: center
35 :align: center
36 :target: ../_images/frontend-kernel.png
36 :target: ../_images/frontend-kernel.png
37
37
38 A single kernel can be simultaneously connected to one or more frontends. The
38 A single kernel can be simultaneously connected to one or more frontends. The
39 kernel has three sockets that serve the following functions:
39 kernel has three sockets that serve the following functions:
40
40
41 1. Shell: this single ROUTER socket allows multiple incoming connections from
41 1. Shell: this single ROUTER socket allows multiple incoming connections from
42 frontends, and this is the socket where requests for code execution, object
42 frontends, and this is the socket where requests for code execution, object
43 information, prompts, etc. are made to the kernel by any frontend. The
43 information, prompts, etc. are made to the kernel by any frontend. The
44 communication on this socket is a sequence of request/reply actions from
44 communication on this socket is a sequence of request/reply actions from
45 each frontend and the kernel.
45 each frontend and the kernel.
46
46
47 2. IOPub: this socket is the 'broadcast channel' where the kernel publishes all
47 2. IOPub: this socket is the 'broadcast channel' where the kernel publishes all
48 side effects (stdout, stderr, etc.) as well as the requests coming from any
48 side effects (stdout, stderr, etc.) as well as the requests coming from any
49 client over the shell socket and its own requests on the stdin socket. There
49 client over the shell socket and its own requests on the stdin socket. There
50 are a number of actions in Python which generate side effects: :func:`print`
50 are a number of actions in Python which generate side effects: :func:`print`
51 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
51 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
52 a multi-client scenario, we want all frontends to be able to know what each
52 a multi-client scenario, we want all frontends to be able to know what each
53 other has sent to the kernel (this can be useful in collaborative scenarios,
53 other has sent to the kernel (this can be useful in collaborative scenarios,
54 for example). This socket allows both side effects and the information
54 for example). This socket allows both side effects and the information
55 about communications taking place with one client over the shell channel
55 about communications taking place with one client over the shell channel
56 to be made available to all clients in a uniform manner.
56 to be made available to all clients in a uniform manner.
57
57
58 3. stdin: this ROUTER socket is connected to all frontends, and it allows
58 3. stdin: this ROUTER socket is connected to all frontends, and it allows
59 the kernel to request input from the active frontend when :func:`raw_input` is called.
59 the kernel to request input from the active frontend when :func:`raw_input` is called.
60 The frontend that executed the code has a DEALER socket that acts as a 'virtual keyboard'
60 The frontend that executed the code has a DEALER socket that acts as a 'virtual keyboard'
61 for the kernel while this communication is happening (illustrated in the
61 for the kernel while this communication is happening (illustrated in the
62 figure by the black outline around the central keyboard). In practice,
62 figure by the black outline around the central keyboard). In practice,
63 frontends may display such kernel requests using a special input widget or
63 frontends may display such kernel requests using a special input widget or
64 otherwise indicating that the user is to type input for the kernel instead
64 otherwise indicating that the user is to type input for the kernel instead
65 of normal commands in the frontend.
65 of normal commands in the frontend.
66
66
67 All messages are tagged with enough information (details below) for clients
67 All messages are tagged with enough information (details below) for clients
68 to know which messages come from their own interaction with the kernel and
68 to know which messages come from their own interaction with the kernel and
69 which ones are from other clients, so they can display each type
69 which ones are from other clients, so they can display each type
70 appropriately.
70 appropriately.
71
71
72 4. Control: This channel is identical to Shell, but operates on a separate socket,
72 4. Control: This channel is identical to Shell, but operates on a separate socket,
73 to allow important messages to avoid queueing behind execution requests (e.g. shutdown or abort).
73 to allow important messages to avoid queueing behind execution requests (e.g. shutdown or abort).
74
74
75 The actual format of the messages allowed on each of these channels is
75 The actual format of the messages allowed on each of these channels is
76 specified below. Messages are dicts of dicts with string keys and values that
76 specified below. Messages are dicts of dicts with string keys and values that
77 are reasonably representable in JSON. Our current implementation uses JSON
77 are reasonably representable in JSON. Our current implementation uses JSON
78 explicitly as its message format, but this shouldn't be considered a permanent
78 explicitly as its message format, but this shouldn't be considered a permanent
79 feature. As we've discovered that JSON has non-trivial performance issues due
79 feature. As we've discovered that JSON has non-trivial performance issues due
80 to excessive copying, we may in the future move to a pure pickle-based raw
80 to excessive copying, we may in the future move to a pure pickle-based raw
81 message format. However, it should be possible to easily convert from the raw
81 message format. However, it should be possible to easily convert from the raw
82 objects to JSON, since we may have non-python clients (e.g. a web frontend).
82 objects to JSON, since we may have non-python clients (e.g. a web frontend).
83 As long as it's easy to make a JSON version of the objects that is a faithful
83 As long as it's easy to make a JSON version of the objects that is a faithful
84 representation of all the data, we can communicate with such clients.
84 representation of all the data, we can communicate with such clients.
85
85
86 .. Note::
86 .. Note::
87
87
88 Not all of these have yet been fully fleshed out, but the key ones are, see
88 Not all of these have yet been fully fleshed out, but the key ones are, see
89 kernel and frontend files for actual implementation details.
89 kernel and frontend files for actual implementation details.
90
90
91 General Message Format
91 General Message Format
92 ======================
92 ======================
93
93
94 A message is defined by the following four-dictionary structure::
94 A message is defined by the following four-dictionary structure::
95
95
96 {
96 {
97 # The message header contains a pair of unique identifiers for the
97 # The message header contains a pair of unique identifiers for the
98 # originating session and the actual message id, in addition to the
98 # originating session and the actual message id, in addition to the
99 # username for the process that generated the message. This is useful in
99 # username for the process that generated the message. This is useful in
100 # collaborative settings where multiple users may be interacting with the
100 # collaborative settings where multiple users may be interacting with the
101 # same kernel simultaneously, so that frontends can label the various
101 # same kernel simultaneously, so that frontends can label the various
102 # messages in a meaningful way.
102 # messages in a meaningful way.
103 'header' : {
103 'header' : {
104 'msg_id' : uuid,
104 'msg_id' : uuid,
105 'username' : str,
105 'username' : str,
106 'session' : uuid,
106 'session' : uuid,
107 # All recognized message type strings are listed below.
107 # All recognized message type strings are listed below.
108 'msg_type' : str,
108 'msg_type' : str,
109 # the message protocol version
109 # the message protocol version
110 'version' : '5.0.0',
110 'version' : '5.0',
111 },
111 },
112
112
113 # In a chain of messages, the header from the parent is copied so that
113 # In a chain of messages, the header from the parent is copied so that
114 # clients can track where messages come from.
114 # clients can track where messages come from.
115 'parent_header' : dict,
115 'parent_header' : dict,
116
116
117 # Any metadata associated with the message.
117 # Any metadata associated with the message.
118 'metadata' : dict,
118 'metadata' : dict,
119
119
120 # The actual content of the message must be a dict, whose structure
120 # The actual content of the message must be a dict, whose structure
121 # depends on the message type.
121 # depends on the message type.
122 'content' : dict,
122 'content' : dict,
123 }
123 }
124
124
125 .. versionchanged:: 5.0.0
125 .. versionchanged:: 5.0
126
126
127 ``version`` key added to the header.
127 ``version`` key added to the header.
128
128
129 The Wire Protocol
129 The Wire Protocol
130 =================
130 =================
131
131
132
132
133 This message format exists at a high level,
133 This message format exists at a high level,
134 but does not describe the actual *implementation* at the wire level in zeromq.
134 but does not describe the actual *implementation* at the wire level in zeromq.
135 The canonical implementation of the message spec is our :class:`~IPython.kernel.zmq.session.Session` class.
135 The canonical implementation of the message spec is our :class:`~IPython.kernel.zmq.session.Session` class.
136
136
137 .. note::
137 .. note::
138
138
139 This section should only be relevant to non-Python consumers of the protocol.
139 This section should only be relevant to non-Python consumers of the protocol.
140 Python consumers should simply import and use IPython's own implementation of the wire protocol
140 Python consumers should simply import and use IPython's own implementation of the wire protocol
141 in the :class:`IPython.kernel.zmq.session.Session` object.
141 in the :class:`IPython.kernel.zmq.session.Session` object.
142
142
143 Every message is serialized to a sequence of at least six blobs of bytes:
143 Every message is serialized to a sequence of at least six blobs of bytes:
144
144
145 .. sourcecode:: python
145 .. sourcecode:: python
146
146
147 [
147 [
148 b'u-u-i-d', # zmq identity(ies)
148 b'u-u-i-d', # zmq identity(ies)
149 b'<IDS|MSG>', # delimiter
149 b'<IDS|MSG>', # delimiter
150 b'baddad42', # HMAC signature
150 b'baddad42', # HMAC signature
151 b'{header}', # serialized header dict
151 b'{header}', # serialized header dict
152 b'{parent_header}', # serialized parent header dict
152 b'{parent_header}', # serialized parent header dict
153 b'{metadata}', # serialized metadata dict
153 b'{metadata}', # serialized metadata dict
154 b'{content}, # serialized content dict
154 b'{content}, # serialized content dict
155 b'blob', # extra raw data buffer(s)
155 b'blob', # extra raw data buffer(s)
156 ...
156 ...
157 ]
157 ]
158
158
159 The front of the message is the ZeroMQ routing prefix,
159 The front of the message is the ZeroMQ routing prefix,
160 which can be zero or more socket identities.
160 which can be zero or more socket identities.
161 This is every piece of the message prior to the delimiter key ``<IDS|MSG>``.
161 This is every piece of the message prior to the delimiter key ``<IDS|MSG>``.
162 In the case of IOPub, there should be just one prefix component,
162 In the case of IOPub, there should be just one prefix component,
163 which is the topic for IOPub subscribers, e.g. ``execute_result``, ``display_data``.
163 which is the topic for IOPub subscribers, e.g. ``execute_result``, ``display_data``.
164
164
165 .. note::
165 .. note::
166
166
167 In most cases, the IOPub topics are irrelevant and completely ignored,
167 In most cases, the IOPub topics are irrelevant and completely ignored,
168 because frontends just subscribe to all topics.
168 because frontends just subscribe to all topics.
169 The convention used in the IPython kernel is to use the msg_type as the topic,
169 The convention used in the IPython kernel is to use the msg_type as the topic,
170 and possibly extra information about the message, e.g. ``execute_result`` or ``stream.stdout``
170 and possibly extra information about the message, e.g. ``execute_result`` or ``stream.stdout``
171
171
172 After the delimiter is the `HMAC`_ signature of the message, used for authentication.
172 After the delimiter is the `HMAC`_ signature of the message, used for authentication.
173 If authentication is disabled, this should be an empty string.
173 If authentication is disabled, this should be an empty string.
174 By default, the hashing function used for computing these signatures is sha256.
174 By default, the hashing function used for computing these signatures is sha256.
175
175
176 .. _HMAC: http://en.wikipedia.org/wiki/HMAC
176 .. _HMAC: http://en.wikipedia.org/wiki/HMAC
177
177
178 .. note::
178 .. note::
179
179
180 To disable authentication and signature checking,
180 To disable authentication and signature checking,
181 set the `key` field of a connection file to an empty string.
181 set the `key` field of a connection file to an empty string.
182
182
183 The signature is the HMAC hex digest of the concatenation of:
183 The signature is the HMAC hex digest of the concatenation of:
184
184
185 - A shared key (typically the ``key`` field of a connection file)
185 - A shared key (typically the ``key`` field of a connection file)
186 - The serialized header dict
186 - The serialized header dict
187 - The serialized parent header dict
187 - The serialized parent header dict
188 - The serialized metadata dict
188 - The serialized metadata dict
189 - The serialized content dict
189 - The serialized content dict
190
190
191 In Python, this is implemented via:
191 In Python, this is implemented via:
192
192
193 .. sourcecode:: python
193 .. sourcecode:: python
194
194
195 # once:
195 # once:
196 digester = HMAC(key, digestmod=hashlib.sha256)
196 digester = HMAC(key, digestmod=hashlib.sha256)
197
197
198 # for each message
198 # for each message
199 d = digester.copy()
199 d = digester.copy()
200 for serialized_dict in (header, parent, metadata, content):
200 for serialized_dict in (header, parent, metadata, content):
201 d.update(serialized_dict)
201 d.update(serialized_dict)
202 signature = d.hexdigest()
202 signature = d.hexdigest()
203
203
204 After the signature is the actual message, always in four frames of bytes.
204 After the signature is the actual message, always in four frames of bytes.
205 The four dictionaries that compose a message are serialized separately,
205 The four dictionaries that compose a message are serialized separately,
206 in the order of header, parent header, metadata, and content.
206 in the order of header, parent header, metadata, and content.
207 These can be serialized by any function that turns a dict into bytes.
207 These can be serialized by any function that turns a dict into bytes.
208 The default and most common serialization is JSON, but msgpack and pickle
208 The default and most common serialization is JSON, but msgpack and pickle
209 are common alternatives.
209 are common alternatives.
210
210
211 After the serialized dicts are zero to many raw data buffers,
211 After the serialized dicts are zero to many raw data buffers,
212 which can be used by message types that support binary data (mainly apply and data_pub).
212 which can be used by message types that support binary data (mainly apply and data_pub).
213
213
214
214
215 Python functional API
215 Python functional API
216 =====================
216 =====================
217
217
218 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
218 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
219 should develop, at a few key points, functional forms of all the requests that
219 should develop, at a few key points, functional forms of all the requests that
220 take arguments in this manner and automatically construct the necessary dict
220 take arguments in this manner and automatically construct the necessary dict
221 for sending.
221 for sending.
222
222
223 In addition, the Python implementation of the message specification extends
223 In addition, the Python implementation of the message specification extends
224 messages upon deserialization to the following form for convenience::
224 messages upon deserialization to the following form for convenience::
225
225
226 {
226 {
227 'header' : dict,
227 'header' : dict,
228 # The msg's unique identifier and type are always stored in the header,
228 # The msg's unique identifier and type are always stored in the header,
229 # but the Python implementation copies them to the top level.
229 # but the Python implementation copies them to the top level.
230 'msg_id' : uuid,
230 'msg_id' : uuid,
231 'msg_type' : str,
231 'msg_type' : str,
232 'parent_header' : dict,
232 'parent_header' : dict,
233 'content' : dict,
233 'content' : dict,
234 'metadata' : dict,
234 'metadata' : dict,
235 }
235 }
236
236
237 All messages sent to or received by any IPython process should have this
237 All messages sent to or received by any IPython process should have this
238 extended structure.
238 extended structure.
239
239
240
240
241 Messages on the shell ROUTER/DEALER sockets
241 Messages on the shell ROUTER/DEALER sockets
242 ===========================================
242 ===========================================
243
243
244 .. _execute:
244 .. _execute:
245
245
246 Execute
246 Execute
247 -------
247 -------
248
248
249 This message type is used by frontends to ask the kernel to execute code on
249 This message type is used by frontends to ask the kernel to execute code on
250 behalf of the user, in a namespace reserved to the user's variables (and thus
250 behalf of the user, in a namespace reserved to the user's variables (and thus
251 separate from the kernel's own internal code and variables).
251 separate from the kernel's own internal code and variables).
252
252
253 Message type: ``execute_request``::
253 Message type: ``execute_request``::
254
254
255 content = {
255 content = {
256 # Source code to be executed by the kernel, one or more lines.
256 # Source code to be executed by the kernel, one or more lines.
257 'code' : str,
257 'code' : str,
258
258
259 # A boolean flag which, if True, signals the kernel to execute
259 # A boolean flag which, if True, signals the kernel to execute
260 # this code as quietly as possible.
260 # this code as quietly as possible.
261 # silent=True forces store_history to be False,
261 # silent=True forces store_history to be False,
262 # and will *not*:
262 # and will *not*:
263 # - broadcast output on the IOPUB channel
263 # - broadcast output on the IOPUB channel
264 # - have an execute_result
264 # - have an execute_result
265 # The default is False.
265 # The default is False.
266 'silent' : bool,
266 'silent' : bool,
267
267
268 # A boolean flag which, if True, signals the kernel to populate history
268 # A boolean flag which, if True, signals the kernel to populate history
269 # The default is True if silent is False. If silent is True, store_history
269 # The default is True if silent is False. If silent is True, store_history
270 # is forced to be False.
270 # is forced to be False.
271 'store_history' : bool,
271 'store_history' : bool,
272
272
273 # A dict mapping names to expressions to be evaluated in the
273 # A dict mapping names to expressions to be evaluated in the
274 # user's dict. The rich display-data representation of each will be evaluated after execution.
274 # user's dict. The rich display-data representation of each will be evaluated after execution.
275 # See the display_data content for the structure of the representation data.
275 # See the display_data content for the structure of the representation data.
276 'user_expressions' : dict,
276 'user_expressions' : dict,
277
277
278 # Some frontends do not support stdin requests.
278 # Some frontends do not support stdin requests.
279 # If raw_input is called from code executed from such a frontend,
279 # If raw_input is called from code executed from such a frontend,
280 # a StdinNotImplementedError will be raised.
280 # a StdinNotImplementedError will be raised.
281 'allow_stdin' : True,
281 'allow_stdin' : True,
282 }
282 }
283
283
284 .. versionchanged:: 5.0.0
284 .. versionchanged:: 5.0
285
285
286 ``user_variables`` removed, because it is redundant with user_expressions.
286 ``user_variables`` removed, because it is redundant with user_expressions.
287
287
288 The ``code`` field contains a single string (possibly multiline) to be executed.
288 The ``code`` field contains a single string (possibly multiline) to be executed.
289
289
290 The ``user_expressions`` field deserves a detailed explanation. In the past, IPython had
290 The ``user_expressions`` field deserves a detailed explanation. In the past, IPython had
291 the notion of a prompt string that allowed arbitrary code to be evaluated, and
291 the notion of a prompt string that allowed arbitrary code to be evaluated, and
292 this was put to good use by many in creating prompts that displayed system
292 this was put to good use by many in creating prompts that displayed system
293 status, path information, and even more esoteric uses like remote instrument
293 status, path information, and even more esoteric uses like remote instrument
294 status acquired over the network. But now that IPython has a clean separation
294 status acquired over the network. But now that IPython has a clean separation
295 between the kernel and the clients, the kernel has no prompt knowledge; prompts
295 between the kernel and the clients, the kernel has no prompt knowledge; prompts
296 are a frontend feature, and it should be even possible for different
296 are a frontend feature, and it should be even possible for different
297 frontends to display different prompts while interacting with the same kernel.
297 frontends to display different prompts while interacting with the same kernel.
298 ``user_expressions`` can be used to retrieve this information.
298 ``user_expressions`` can be used to retrieve this information.
299
299
300 Any error in evaluating any expression in ``user_expressions`` will result in
300 Any error in evaluating any expression in ``user_expressions`` will result in
301 only that key containing a standard error message, of the form::
301 only that key containing a standard error message, of the form::
302
302
303 {
303 {
304 'status' : 'error',
304 'status' : 'error',
305 'ename' : 'NameError',
305 'ename' : 'NameError',
306 'evalue' : 'foo',
306 'evalue' : 'foo',
307 'traceback' : ...
307 'traceback' : ...
308 }
308 }
309
309
310 .. Note::
310 .. Note::
311
311
312 In order to obtain the current execution counter for the purposes of
312 In order to obtain the current execution counter for the purposes of
313 displaying input prompts, frontends may make an execution request with an
313 displaying input prompts, frontends may make an execution request with an
314 empty code string and ``silent=True``.
314 empty code string and ``silent=True``.
315
315
316 Upon completion of the execution request, the kernel *always* sends a reply,
316 Upon completion of the execution request, the kernel *always* sends a reply,
317 with a status code indicating what happened and additional data depending on
317 with a status code indicating what happened and additional data depending on
318 the outcome. See :ref:`below <execution_results>` for the possible return
318 the outcome. See :ref:`below <execution_results>` for the possible return
319 codes and associated data.
319 codes and associated data.
320
320
321 .. seealso::
321 .. seealso::
322
322
323 :ref:`execution_semantics`
323 :ref:`execution_semantics`
324
324
325 .. _execution_counter:
325 .. _execution_counter:
326
326
327 Execution counter (prompt number)
327 Execution counter (prompt number)
328 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
328 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
329
329
330 The kernel should have a single, monotonically increasing counter of all execution
330 The kernel should have a single, monotonically increasing counter of all execution
331 requests that are made with ``store_history=True``. This counter is used to populate
331 requests that are made with ``store_history=True``. This counter is used to populate
332 the ``In[n]`` and ``Out[n]`` prompts. The value of this counter will be returned as the
332 the ``In[n]`` and ``Out[n]`` prompts. The value of this counter will be returned as the
333 ``execution_count`` field of all ``execute_reply`` and ``execute_input`` messages.
333 ``execution_count`` field of all ``execute_reply`` and ``execute_input`` messages.
334
334
335 .. _execution_results:
335 .. _execution_results:
336
336
337 Execution results
337 Execution results
338 ~~~~~~~~~~~~~~~~~
338 ~~~~~~~~~~~~~~~~~
339
339
340 Message type: ``execute_reply``::
340 Message type: ``execute_reply``::
341
341
342 content = {
342 content = {
343 # One of: 'ok' OR 'error' OR 'abort'
343 # One of: 'ok' OR 'error' OR 'abort'
344 'status' : str,
344 'status' : str,
345
345
346 # The global kernel counter that increases by one with each request that
346 # The global kernel counter that increases by one with each request that
347 # stores history. This will typically be used by clients to display
347 # stores history. This will typically be used by clients to display
348 # prompt numbers to the user. If the request did not store history, this will
348 # prompt numbers to the user. If the request did not store history, this will
349 # be the current value of the counter in the kernel.
349 # be the current value of the counter in the kernel.
350 'execution_count' : int,
350 'execution_count' : int,
351 }
351 }
352
352
353 When status is 'ok', the following extra fields are present::
353 When status is 'ok', the following extra fields are present::
354
354
355 {
355 {
356 # 'payload' will be a list of payload dicts.
356 # 'payload' will be a list of payload dicts.
357 # Each execution payload is a dict with string keys that may have been
357 # Each execution payload is a dict with string keys that may have been
358 # produced by the code being executed. It is retrieved by the kernel at
358 # produced by the code being executed. It is retrieved by the kernel at
359 # the end of the execution and sent back to the front end, which can take
359 # the end of the execution and sent back to the front end, which can take
360 # action on it as needed.
360 # action on it as needed.
361 # The only requirement of each payload dict is that it have a 'source' key,
361 # The only requirement of each payload dict is that it have a 'source' key,
362 # which is a string classifying the payload (e.g. 'pager').
362 # which is a string classifying the payload (e.g. 'pager').
363 'payload' : list(dict),
363 'payload' : list(dict),
364
364
365 # Results for the user_expressions.
365 # Results for the user_expressions.
366 'user_expressions' : dict,
366 'user_expressions' : dict,
367 }
367 }
368
368
369 .. versionchanged:: 5.0.0
369 .. versionchanged:: 5.0
370
370
371 ``user_variables`` is removed, use user_expressions instead.
371 ``user_variables`` is removed, use user_expressions instead.
372
372
373 .. admonition:: Execution payloads
373 .. admonition:: Execution payloads
374
374
375 The notion of an 'execution payload' is different from a return value of a
375 The notion of an 'execution payload' is different from a return value of a
376 given set of code, which normally is just displayed on the execute_result stream
376 given set of code, which normally is just displayed on the execute_result stream
377 through the PUB socket. The idea of a payload is to allow special types of
377 through the PUB socket. The idea of a payload is to allow special types of
378 code, typically magics, to populate a data container in the IPython kernel
378 code, typically magics, to populate a data container in the IPython kernel
379 that will be shipped back to the caller via this channel. The kernel
379 that will be shipped back to the caller via this channel. The kernel
380 has an API for this in the PayloadManager::
380 has an API for this in the PayloadManager::
381
381
382 ip.payload_manager.write_payload(payload_dict)
382 ip.payload_manager.write_payload(payload_dict)
383
383
384 which appends a dictionary to the list of payloads.
384 which appends a dictionary to the list of payloads.
385
385
386 The payload API is not yet stabilized,
386 The payload API is not yet stabilized,
387 and should probably not be supported by non-Python kernels at this time.
387 and should probably not be supported by non-Python kernels at this time.
388 In such cases, the payload list should always be empty.
388 In such cases, the payload list should always be empty.
389
389
390
390
391 When status is 'error', the following extra fields are present::
391 When status is 'error', the following extra fields are present::
392
392
393 {
393 {
394 'ename' : str, # Exception name, as a string
394 'ename' : str, # Exception name, as a string
395 'evalue' : str, # Exception value, as a string
395 'evalue' : str, # Exception value, as a string
396
396
397 # The traceback will contain a list of frames, represented each as a
397 # The traceback will contain a list of frames, represented each as a
398 # string. For now we'll stick to the existing design of ultraTB, which
398 # string. For now we'll stick to the existing design of ultraTB, which
399 # controls exception level of detail statefully. But eventually we'll
399 # controls exception level of detail statefully. But eventually we'll
400 # want to grow into a model where more information is collected and
400 # want to grow into a model where more information is collected and
401 # packed into the traceback object, with clients deciding how little or
401 # packed into the traceback object, with clients deciding how little or
402 # how much of it to unpack. But for now, let's start with a simple list
402 # how much of it to unpack. But for now, let's start with a simple list
403 # of strings, since that requires only minimal changes to ultratb as
403 # of strings, since that requires only minimal changes to ultratb as
404 # written.
404 # written.
405 'traceback' : list,
405 'traceback' : list,
406 }
406 }
407
407
408
408
409 When status is 'abort', there are for now no additional data fields. This
409 When status is 'abort', there are for now no additional data fields. This
410 happens when the kernel was interrupted by a signal.
410 happens when the kernel was interrupted by a signal.
411
411
412
412
413 Introspection
413 Introspection
414 -------------
414 -------------
415
415
416 Code can be inspected to show useful information to the user.
416 Code can be inspected to show useful information to the user.
417 It is up to the Kernel to decide what information should be displayed, and its formatting.
417 It is up to the Kernel to decide what information should be displayed, and its formatting.
418
418
419 Message type: ``inspect_request``::
419 Message type: ``inspect_request``::
420
420
421 content = {
421 content = {
422 # The code context in which introspection is requested
422 # The code context in which introspection is requested
423 # this may be up to an entire multiline cell.
423 # this may be up to an entire multiline cell.
424 'code' : str,
424 'code' : str,
425
425
426 # The cursor position within 'code' (in unicode characters) where inspection is requested
426 # The cursor position within 'code' (in unicode characters) where inspection is requested
427 'cursor_pos' : int,
427 'cursor_pos' : int,
428
428
429 # The level of detail desired. In IPython, the default (0) is equivalent to typing
429 # The level of detail desired. In IPython, the default (0) is equivalent to typing
430 # 'x?' at the prompt, 1 is equivalent to 'x??'.
430 # 'x?' at the prompt, 1 is equivalent to 'x??'.
431 # The difference is up to kernels, but in IPython level 1 includes the source code
431 # The difference is up to kernels, but in IPython level 1 includes the source code
432 # if available.
432 # if available.
433 'detail_level' : 0 or 1,
433 'detail_level' : 0 or 1,
434 }
434 }
435
435
436 .. versionchanged:: 5.0.0
436 .. versionchanged:: 5.0
437
437
438 ``object_info_request`` renamed to ``inspect_request``.
438 ``object_info_request`` renamed to ``inspect_request``.
439
439
440 .. versionchanged:: 5.0.0
440 .. versionchanged:: 5.0
441
441
442 ``name`` key replaced with ``code`` and ``cursor_pos``,
442 ``name`` key replaced with ``code`` and ``cursor_pos``,
443 moving the lexing responsibility to the kernel.
443 moving the lexing responsibility to the kernel.
444
444
445 The reply is a mime-bundle, like a `display_data`_ message,
445 The reply is a mime-bundle, like a `display_data`_ message,
446 which should be a formatted representation of information about the context.
446 which should be a formatted representation of information about the context.
447 In the notebook, this is used to show tooltips over function calls, etc.
447 In the notebook, this is used to show tooltips over function calls, etc.
448
448
449 Message type: ``inspect_reply``::
449 Message type: ``inspect_reply``::
450
450
451 content = {
451 content = {
452 # 'ok' if the request succeeded or 'error', with error information as in all other replies.
452 # 'ok' if the request succeeded or 'error', with error information as in all other replies.
453 'status' : 'ok',
453 'status' : 'ok',
454
454
455 # data can be empty if nothing is found
455 # data can be empty if nothing is found
456 'data' : dict,
456 'data' : dict,
457 'metadata' : dict,
457 'metadata' : dict,
458 }
458 }
459
459
460 .. versionchanged:: 5.0.0
460 .. versionchanged:: 5.0
461
461
462 ``object_info_reply`` renamed to ``inspect_reply``.
462 ``object_info_reply`` renamed to ``inspect_reply``.
463
463
464 .. versionchanged:: 5.0.0
464 .. versionchanged:: 5.0
465
465
466 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
466 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
467
467
468 Completion
468 Completion
469 ----------
469 ----------
470
470
471 Message type: ``complete_request``::
471 Message type: ``complete_request``::
472
472
473 content = {
473 content = {
474 # The code context in which completion is requested
474 # The code context in which completion is requested
475 # this may be up to an entire multiline cell, such as
475 # this may be up to an entire multiline cell, such as
476 # 'foo = a.isal'
476 # 'foo = a.isal'
477 'code' : str,
477 'code' : str,
478
478
479 # The cursor position within 'code' (in unicode characters) where completion is requested
479 # The cursor position within 'code' (in unicode characters) where completion is requested
480 'cursor_pos' : int,
480 'cursor_pos' : int,
481 }
481 }
482
482
483 .. versionchanged:: 5.0.0
483 .. versionchanged:: 5.0
484
484
485 ``line``, ``block``, and ``text`` keys are removed in favor of a single ``code`` for context.
485 ``line``, ``block``, and ``text`` keys are removed in favor of a single ``code`` for context.
486 Lexing is up to the kernel.
486 Lexing is up to the kernel.
487
487
488
488
489 Message type: ``complete_reply``::
489 Message type: ``complete_reply``::
490
490
491 content = {
491 content = {
492 # The list of all matches to the completion request, such as
492 # The list of all matches to the completion request, such as
493 # ['a.isalnum', 'a.isalpha'] for the above example.
493 # ['a.isalnum', 'a.isalpha'] for the above example.
494 'matches' : list,
494 'matches' : list,
495
495
496 # The range of text that should be replaced by the above matches when a completion is accepted.
496 # The range of text that should be replaced by the above matches when a completion is accepted.
497 # typically cursor_end is the same as cursor_pos in the request.
497 # typically cursor_end is the same as cursor_pos in the request.
498 'cursor_start' : int,
498 'cursor_start' : int,
499 'cursor_end' : int,
499 'cursor_end' : int,
500
500
501 # Information that frontend plugins might use for extra display information about completions.
501 # Information that frontend plugins might use for extra display information about completions.
502 'metadata' : dict,
502 'metadata' : dict,
503
503
504 # status should be 'ok' unless an exception was raised during the request,
504 # status should be 'ok' unless an exception was raised during the request,
505 # in which case it should be 'error', along with the usual error message content
505 # in which case it should be 'error', along with the usual error message content
506 # in other messages.
506 # in other messages.
507 'status' : 'ok'
507 'status' : 'ok'
508 }
508 }
509
509
510 .. versionchanged:: 5.0.0
510 .. versionchanged:: 5.0
511
511
512 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
512 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
513 - ``metadata`` is added for extended information.
513 - ``metadata`` is added for extended information.
514
514
515
515
516 History
516 History
517 -------
517 -------
518
518
519 For clients to explicitly request history from a kernel. The kernel has all
519 For clients to explicitly request history from a kernel. The kernel has all
520 the actual execution history stored in a single location, so clients can
520 the actual execution history stored in a single location, so clients can
521 request it from the kernel when needed.
521 request it from the kernel when needed.
522
522
523 Message type: ``history_request``::
523 Message type: ``history_request``::
524
524
525 content = {
525 content = {
526
526
527 # If True, also return output history in the resulting dict.
527 # If True, also return output history in the resulting dict.
528 'output' : bool,
528 'output' : bool,
529
529
530 # If True, return the raw input history, else the transformed input.
530 # If True, return the raw input history, else the transformed input.
531 'raw' : bool,
531 'raw' : bool,
532
532
533 # So far, this can be 'range', 'tail' or 'search'.
533 # So far, this can be 'range', 'tail' or 'search'.
534 'hist_access_type' : str,
534 'hist_access_type' : str,
535
535
536 # If hist_access_type is 'range', get a range of input cells. session can
536 # If hist_access_type is 'range', get a range of input cells. session can
537 # be a positive session number, or a negative number to count back from
537 # be a positive session number, or a negative number to count back from
538 # the current session.
538 # the current session.
539 'session' : int,
539 'session' : int,
540 # start and stop are line numbers within that session.
540 # start and stop are line numbers within that session.
541 'start' : int,
541 'start' : int,
542 'stop' : int,
542 'stop' : int,
543
543
544 # If hist_access_type is 'tail' or 'search', get the last n cells.
544 # If hist_access_type is 'tail' or 'search', get the last n cells.
545 'n' : int,
545 'n' : int,
546
546
547 # If hist_access_type is 'search', get cells matching the specified glob
547 # If hist_access_type is 'search', get cells matching the specified glob
548 # pattern (with * and ? as wildcards).
548 # pattern (with * and ? as wildcards).
549 'pattern' : str,
549 'pattern' : str,
550
550
551 # If hist_access_type is 'search' and unique is true, do not
551 # If hist_access_type is 'search' and unique is true, do not
552 # include duplicated history. Default is false.
552 # include duplicated history. Default is false.
553 'unique' : bool,
553 'unique' : bool,
554
554
555 }
555 }
556
556
557 .. versionadded:: 4.0
557 .. versionadded:: 4.0
558 The key ``unique`` for ``history_request``.
558 The key ``unique`` for ``history_request``.
559
559
560 Message type: ``history_reply``::
560 Message type: ``history_reply``::
561
561
562 content = {
562 content = {
563 # A list of 3 tuples, either:
563 # A list of 3 tuples, either:
564 # (session, line_number, input) or
564 # (session, line_number, input) or
565 # (session, line_number, (input, output)),
565 # (session, line_number, (input, output)),
566 # depending on whether output was False or True, respectively.
566 # depending on whether output was False or True, respectively.
567 'history' : list,
567 'history' : list,
568 }
568 }
569
569
570
570
571 Connect
571 Connect
572 -------
572 -------
573
573
574 When a client connects to the request/reply socket of the kernel, it can issue
574 When a client connects to the request/reply socket of the kernel, it can issue
575 a connect request to get basic information about the kernel, such as the ports
575 a connect request to get basic information about the kernel, such as the ports
576 the other ZeroMQ sockets are listening on. This allows clients to only have
576 the other ZeroMQ sockets are listening on. This allows clients to only have
577 to know about a single port (the shell channel) to connect to a kernel.
577 to know about a single port (the shell channel) to connect to a kernel.
578
578
579 Message type: ``connect_request``::
579 Message type: ``connect_request``::
580
580
581 content = {
581 content = {
582 }
582 }
583
583
584 Message type: ``connect_reply``::
584 Message type: ``connect_reply``::
585
585
586 content = {
586 content = {
587 'shell_port' : int, # The port the shell ROUTER socket is listening on.
587 'shell_port' : int, # The port the shell ROUTER socket is listening on.
588 'iopub_port' : int, # The port the PUB socket is listening on.
588 'iopub_port' : int, # The port the PUB socket is listening on.
589 'stdin_port' : int, # The port the stdin ROUTER socket is listening on.
589 'stdin_port' : int, # The port the stdin ROUTER socket is listening on.
590 'hb_port' : int, # The port the heartbeat socket is listening on.
590 'hb_port' : int, # The port the heartbeat socket is listening on.
591 }
591 }
592
592
593
593
594 Kernel info
594 Kernel info
595 -----------
595 -----------
596
596
597 If a client needs to know information about the kernel, it can
597 If a client needs to know information about the kernel, it can
598 make a request of the kernel's information.
598 make a request of the kernel's information.
599 This message can be used to fetch core information of the
599 This message can be used to fetch core information of the
600 kernel, including language (e.g., Python), language version number and
600 kernel, including language (e.g., Python), language version number and
601 IPython version number, and the IPython message spec version number.
601 IPython version number, and the IPython message spec version number.
602
602
603 Message type: ``kernel_info_request``::
603 Message type: ``kernel_info_request``::
604
604
605 content = {
605 content = {
606 }
606 }
607
607
608 Message type: ``kernel_info_reply``::
608 Message type: ``kernel_info_reply``::
609
609
610 content = {
610 content = {
611 # Version of messaging protocol.
611 # Version of messaging protocol.
612 # The first integer indicates major version. It is incremented when
612 # The first integer indicates major version. It is incremented when
613 # there is any backward incompatible change.
613 # there is any backward incompatible change.
614 # The second integer indicates minor version. It is incremented when
614 # The second integer indicates minor version. It is incremented when
615 # there is any backward compatible change.
615 # there is any backward compatible change.
616 'protocol_version': 'X.Y.Z',
616 'protocol_version': 'X.Y.Z',
617
617
618 # The kernel implementation name
618 # The kernel implementation name
619 # (e.g. 'ipython' for the IPython kernel)
619 # (e.g. 'ipython' for the IPython kernel)
620 'implementation': str,
620 'implementation': str,
621
621
622 # Implementation version number.
622 # Implementation version number.
623 # The version number of the kernel's implementation
623 # The version number of the kernel's implementation
624 # (e.g. IPython.__version__ for the IPython kernel)
624 # (e.g. IPython.__version__ for the IPython kernel)
625 'implementation_version': 'X.Y.Z',
625 'implementation_version': 'X.Y.Z',
626
626
627 # Programming language in which kernel is implemented.
627 # Programming language in which kernel is implemented.
628 # Kernel included in IPython returns 'python'.
628 # Kernel included in IPython returns 'python'.
629 'language': str,
629 'language': str,
630
630
631 # Language version number.
631 # Language version number.
632 # It is Python version number (e.g., '2.7.3') for the kernel
632 # It is Python version number (e.g., '2.7.3') for the kernel
633 # included in IPython.
633 # included in IPython.
634 'language_version': 'X.Y.Z',
634 'language_version': 'X.Y.Z',
635
635
636 # A banner of information about the kernel,
636 # A banner of information about the kernel,
637 # which may be desplayed in console environments.
637 # which may be desplayed in console environments.
638 'banner' : str,
638 'banner' : str,
639 }
639 }
640
640
641 .. versionchanged:: 5.0.0
641 .. versionchanged:: 5.0
642
642
643 Versions changed from lists of integers to strings.
643 Versions changed from lists of integers to strings.
644
644
645 .. versionchanged:: 5.0.0
645 .. versionchanged:: 5.0
646
646
647 ``ipython_version`` is removed.
647 ``ipython_version`` is removed.
648
648
649 .. versionchanged:: 5.0.0
649 .. versionchanged:: 5.0
650
650
651 ``implementation``, ``implementation_version``, and ``banner`` keys are added.
651 ``implementation``, ``implementation_version``, and ``banner`` keys are added.
652
652
653
653
654 Kernel shutdown
654 Kernel shutdown
655 ---------------
655 ---------------
656
656
657 The clients can request the kernel to shut itself down; this is used in
657 The clients can request the kernel to shut itself down; this is used in
658 multiple cases:
658 multiple cases:
659
659
660 - when the user chooses to close the client application via a menu or window
660 - when the user chooses to close the client application via a menu or window
661 control.
661 control.
662 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
662 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
663 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
663 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
664 IPythonQt client) to force a kernel restart to get a clean kernel without
664 IPythonQt client) to force a kernel restart to get a clean kernel without
665 losing client-side state like history or inlined figures.
665 losing client-side state like history or inlined figures.
666
666
667 The client sends a shutdown request to the kernel, and once it receives the
667 The client sends a shutdown request to the kernel, and once it receives the
668 reply message (which is otherwise empty), it can assume that the kernel has
668 reply message (which is otherwise empty), it can assume that the kernel has
669 completed shutdown safely.
669 completed shutdown safely.
670
670
671 Upon their own shutdown, client applications will typically execute a last
671 Upon their own shutdown, client applications will typically execute a last
672 minute sanity check and forcefully terminate any kernel that is still alive, to
672 minute sanity check and forcefully terminate any kernel that is still alive, to
673 avoid leaving stray processes in the user's machine.
673 avoid leaving stray processes in the user's machine.
674
674
675 Message type: ``shutdown_request``::
675 Message type: ``shutdown_request``::
676
676
677 content = {
677 content = {
678 'restart' : bool # whether the shutdown is final, or precedes a restart
678 'restart' : bool # whether the shutdown is final, or precedes a restart
679 }
679 }
680
680
681 Message type: ``shutdown_reply``::
681 Message type: ``shutdown_reply``::
682
682
683 content = {
683 content = {
684 'restart' : bool # whether the shutdown is final, or precedes a restart
684 'restart' : bool # whether the shutdown is final, or precedes a restart
685 }
685 }
686
686
687 .. Note::
687 .. Note::
688
688
689 When the clients detect a dead kernel thanks to inactivity on the heartbeat
689 When the clients detect a dead kernel thanks to inactivity on the heartbeat
690 socket, they simply send a forceful process termination signal, since a dead
690 socket, they simply send a forceful process termination signal, since a dead
691 process is unlikely to respond in any useful way to messages.
691 process is unlikely to respond in any useful way to messages.
692
692
693
693
694 Messages on the PUB/SUB socket
694 Messages on the PUB/SUB socket
695 ==============================
695 ==============================
696
696
697 Streams (stdout, stderr, etc)
697 Streams (stdout, stderr, etc)
698 ------------------------------
698 ------------------------------
699
699
700 Message type: ``stream``::
700 Message type: ``stream``::
701
701
702 content = {
702 content = {
703 # The name of the stream is one of 'stdout', 'stderr'
703 # The name of the stream is one of 'stdout', 'stderr'
704 'name' : str,
704 'name' : str,
705
705
706 # The data is an arbitrary string to be written to that stream
706 # The data is an arbitrary string to be written to that stream
707 'data' : str,
707 'data' : str,
708 }
708 }
709
709
710 Display Data
710 Display Data
711 ------------
711 ------------
712
712
713 This type of message is used to bring back data that should be displayed (text,
713 This type of message is used to bring back data that should be displayed (text,
714 html, svg, etc.) in the frontends. This data is published to all frontends.
714 html, svg, etc.) in the frontends. This data is published to all frontends.
715 Each message can have multiple representations of the data; it is up to the
715 Each message can have multiple representations of the data; it is up to the
716 frontend to decide which to use and how. A single message should contain all
716 frontend to decide which to use and how. A single message should contain all
717 possible representations of the same information. Each representation should
717 possible representations of the same information. Each representation should
718 be a JSON'able data structure, and should be a valid MIME type.
718 be a JSON'able data structure, and should be a valid MIME type.
719
719
720 Some questions remain about this design:
720 Some questions remain about this design:
721
721
722 * Do we use this message type for execute_result/displayhook? Probably not, because
722 * Do we use this message type for execute_result/displayhook? Probably not, because
723 the displayhook also has to handle the Out prompt display. On the other hand
723 the displayhook also has to handle the Out prompt display. On the other hand
724 we could put that information into the metadata section.
724 we could put that information into the metadata section.
725
725
726 .. _display_data:
726 .. _display_data:
727
727
728 Message type: ``display_data``::
728 Message type: ``display_data``::
729
729
730 content = {
730 content = {
731
731
732 # Who create the data
732 # Who create the data
733 'source' : str,
733 'source' : str,
734
734
735 # The data dict contains key/value pairs, where the keys are MIME
735 # The data dict contains key/value pairs, where the keys are MIME
736 # types and the values are the raw data of the representation in that
736 # types and the values are the raw data of the representation in that
737 # format.
737 # format.
738 'data' : dict,
738 'data' : dict,
739
739
740 # Any metadata that describes the data
740 # Any metadata that describes the data
741 'metadata' : dict
741 'metadata' : dict
742 }
742 }
743
743
744
744
745 The ``metadata`` contains any metadata that describes the output.
745 The ``metadata`` contains any metadata that describes the output.
746 Global keys are assumed to apply to the output as a whole.
746 Global keys are assumed to apply to the output as a whole.
747 The ``metadata`` dict can also contain mime-type keys, which will be sub-dictionaries,
747 The ``metadata`` dict can also contain mime-type keys, which will be sub-dictionaries,
748 which are interpreted as applying only to output of that type.
748 which are interpreted as applying only to output of that type.
749 Third parties should put any data they write into a single dict
749 Third parties should put any data they write into a single dict
750 with a reasonably unique name to avoid conflicts.
750 with a reasonably unique name to avoid conflicts.
751
751
752 The only metadata keys currently defined in IPython are the width and height
752 The only metadata keys currently defined in IPython are the width and height
753 of images::
753 of images::
754
754
755 metadata = {
755 metadata = {
756 'image/png' : {
756 'image/png' : {
757 'width': 640,
757 'width': 640,
758 'height': 480
758 'height': 480
759 }
759 }
760 }
760 }
761
761
762
762
763 .. versionchanged:: 5.0.0
763 .. versionchanged:: 5.0
764
764
765 `application/json` data should be unpacked JSON data,
765 `application/json` data should be unpacked JSON data,
766 not double-serialized as a JSON string.
766 not double-serialized as a JSON string.
767
767
768
768
769 Raw Data Publication
769 Raw Data Publication
770 --------------------
770 --------------------
771
771
772 ``display_data`` lets you publish *representations* of data, such as images and html.
772 ``display_data`` lets you publish *representations* of data, such as images and html.
773 This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers.
773 This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers.
774
774
775 data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function:
775 data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function:
776
776
777 .. sourcecode:: python
777 .. sourcecode:: python
778
778
779 from IPython.kernel.zmq.datapub import publish_data
779 from IPython.kernel.zmq.datapub import publish_data
780 ns = dict(x=my_array)
780 ns = dict(x=my_array)
781 publish_data(ns)
781 publish_data(ns)
782
782
783
783
784 Message type: ``data_pub``::
784 Message type: ``data_pub``::
785
785
786 content = {
786 content = {
787 # the keys of the data dict, after it has been unserialized
787 # the keys of the data dict, after it has been unserialized
788 'keys' : ['a', 'b']
788 'keys' : ['a', 'b']
789 }
789 }
790 # the namespace dict will be serialized in the message buffers,
790 # the namespace dict will be serialized in the message buffers,
791 # which will have a length of at least one
791 # which will have a length of at least one
792 buffers = [b'pdict', ...]
792 buffers = [b'pdict', ...]
793
793
794
794
795 The interpretation of a sequence of data_pub messages for a given parent request should be
795 The interpretation of a sequence of data_pub messages for a given parent request should be
796 to update a single namespace with subsequent results.
796 to update a single namespace with subsequent results.
797
797
798 .. note::
798 .. note::
799
799
800 No frontends directly handle data_pub messages at this time.
800 No frontends directly handle data_pub messages at this time.
801 It is currently only used by the client/engines in :mod:`IPython.parallel`,
801 It is currently only used by the client/engines in :mod:`IPython.parallel`,
802 where engines may publish *data* to the Client,
802 where engines may publish *data* to the Client,
803 of which the Client can then publish *representations* via ``display_data``
803 of which the Client can then publish *representations* via ``display_data``
804 to various frontends.
804 to various frontends.
805
805
806 Code inputs
806 Code inputs
807 -----------
807 -----------
808
808
809 To let all frontends know what code is being executed at any given time, these
809 To let all frontends know what code is being executed at any given time, these
810 messages contain a re-broadcast of the ``code`` portion of an
810 messages contain a re-broadcast of the ``code`` portion of an
811 :ref:`execute_request <execute>`, along with the :ref:`execution_count
811 :ref:`execute_request <execute>`, along with the :ref:`execution_count
812 <execution_counter>`.
812 <execution_counter>`.
813
813
814 Message type: ``execute_input``::
814 Message type: ``execute_input``::
815
815
816 content = {
816 content = {
817 'code' : str, # Source code to be executed, one or more lines
817 'code' : str, # Source code to be executed, one or more lines
818
818
819 # The counter for this execution is also provided so that clients can
819 # The counter for this execution is also provided so that clients can
820 # display it, since IPython automatically creates variables called _iN
820 # display it, since IPython automatically creates variables called _iN
821 # (for input prompt In[N]).
821 # (for input prompt In[N]).
822 'execution_count' : int
822 'execution_count' : int
823 }
823 }
824
824
825 .. versionchanged:: 5.0.0
825 .. versionchanged:: 5.0
826
826
827 ``pyin`` is renamed to ``execute_input``.
827 ``pyin`` is renamed to ``execute_input``.
828
828
829
829
830 Execution results
830 Execution results
831 -----------------
831 -----------------
832
832
833 Results of an execution are published as an ``execute_result``.
833 Results of an execution are published as an ``execute_result``.
834 These are identical to `display_data`_ messages, with the addition of an ``execution_count`` key.
834 These are identical to `display_data`_ messages, with the addition of an ``execution_count`` key.
835
835
836 Results can have multiple simultaneous formats depending on its
836 Results can have multiple simultaneous formats depending on its
837 configuration. A plain text representation should always be provided
837 configuration. A plain text representation should always be provided
838 in the ``text/plain`` mime-type. Frontends are free to display any or all of these
838 in the ``text/plain`` mime-type. Frontends are free to display any or all of these
839 according to its capabilities.
839 according to its capabilities.
840 Frontends should ignore mime-types they do not understand. The data itself is
840 Frontends should ignore mime-types they do not understand. The data itself is
841 any JSON object and depends on the format. It is often, but not always a string.
841 any JSON object and depends on the format. It is often, but not always a string.
842
842
843 Message type: ``execute_result``::
843 Message type: ``execute_result``::
844
844
845 content = {
845 content = {
846
846
847 # The counter for this execution is also provided so that clients can
847 # The counter for this execution is also provided so that clients can
848 # display it, since IPython automatically creates variables called _N
848 # display it, since IPython automatically creates variables called _N
849 # (for prompt N).
849 # (for prompt N).
850 'execution_count' : int,
850 'execution_count' : int,
851
851
852 # data and metadata are identical to a display_data message.
852 # data and metadata are identical to a display_data message.
853 # the object being displayed is that passed to the display hook,
853 # the object being displayed is that passed to the display hook,
854 # i.e. the *result* of the execution.
854 # i.e. the *result* of the execution.
855 'data' : dict,
855 'data' : dict,
856 'metadata' : dict,
856 'metadata' : dict,
857 }
857 }
858
858
859 Execution errors
859 Execution errors
860 ----------------
860 ----------------
861
861
862 When an error occurs during code execution
862 When an error occurs during code execution
863
863
864 Message type: ``error``::
864 Message type: ``error``::
865
865
866 content = {
866 content = {
867 # Similar content to the execute_reply messages for the 'error' case,
867 # Similar content to the execute_reply messages for the 'error' case,
868 # except the 'status' field is omitted.
868 # except the 'status' field is omitted.
869 }
869 }
870
870
871 .. versionchanged:: 5.0.0
871 .. versionchanged:: 5.0
872
872
873 ``pyerr`` renamed to ``error``
873 ``pyerr`` renamed to ``error``
874
874
875 Kernel status
875 Kernel status
876 -------------
876 -------------
877
877
878 This message type is used by frontends to monitor the status of the kernel.
878 This message type is used by frontends to monitor the status of the kernel.
879
879
880 Message type: ``status``::
880 Message type: ``status``::
881
881
882 content = {
882 content = {
883 # When the kernel starts to execute code, it will enter the 'busy'
883 # When the kernel starts to execute code, it will enter the 'busy'
884 # state and when it finishes, it will enter the 'idle' state.
884 # state and when it finishes, it will enter the 'idle' state.
885 # The kernel will publish state 'starting' exactly once at process startup.
885 # The kernel will publish state 'starting' exactly once at process startup.
886 execution_state : ('busy', 'idle', 'starting')
886 execution_state : ('busy', 'idle', 'starting')
887 }
887 }
888
888
889 Clear output
889 Clear output
890 ------------
890 ------------
891
891
892 This message type is used to clear the output that is visible on the frontend.
892 This message type is used to clear the output that is visible on the frontend.
893
893
894 Message type: ``clear_output``::
894 Message type: ``clear_output``::
895
895
896 content = {
896 content = {
897
897
898 # Wait to clear the output until new output is available. Clears the
898 # Wait to clear the output until new output is available. Clears the
899 # existing output immediately before the new output is displayed.
899 # existing output immediately before the new output is displayed.
900 # Useful for creating simple animations with minimal flickering.
900 # Useful for creating simple animations with minimal flickering.
901 'wait' : bool,
901 'wait' : bool,
902 }
902 }
903
903
904 .. versionchanged:: 4.1
904 .. versionchanged:: 4.1
905
905
906 ``stdout``, ``stderr``, and ``display`` boolean keys for selective clearing are removed,
906 ``stdout``, ``stderr``, and ``display`` boolean keys for selective clearing are removed,
907 and ``wait`` is added.
907 and ``wait`` is added.
908 The selective clearing keys are ignored in v4 and the default behavior remains the same,
908 The selective clearing keys are ignored in v4 and the default behavior remains the same,
909 so v4 clear_output messages will be safely handled by a v4.1 frontend.
909 so v4 clear_output messages will be safely handled by a v4.1 frontend.
910
910
911
911
912 Messages on the stdin ROUTER/DEALER sockets
912 Messages on the stdin ROUTER/DEALER sockets
913 ===========================================
913 ===========================================
914
914
915 This is a socket where the request/reply pattern goes in the opposite direction:
915 This is a socket where the request/reply pattern goes in the opposite direction:
916 from the kernel to a *single* frontend, and its purpose is to allow
916 from the kernel to a *single* frontend, and its purpose is to allow
917 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
917 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
918 to be fulfilled by the client. The request should be made to the frontend that
918 to be fulfilled by the client. The request should be made to the frontend that
919 made the execution request that prompted ``raw_input`` to be called. For now we
919 made the execution request that prompted ``raw_input`` to be called. For now we
920 will keep these messages as simple as possible, since they only mean to convey
920 will keep these messages as simple as possible, since they only mean to convey
921 the ``raw_input(prompt)`` call.
921 the ``raw_input(prompt)`` call.
922
922
923 Message type: ``input_request``::
923 Message type: ``input_request``::
924
924
925 content = {
925 content = {
926 # the text to show at the prompt
926 # the text to show at the prompt
927 'prompt' : str,
927 'prompt' : str,
928 # Is the request for a password?
928 # Is the request for a password?
929 # If so, the frontend shouldn't echo input.
929 # If so, the frontend shouldn't echo input.
930 'password' : bool
930 'password' : bool
931 }
931 }
932
932
933 Message type: ``input_reply``::
933 Message type: ``input_reply``::
934
934
935 content = { 'value' : str }
935 content = { 'value' : str }
936
936
937
937
938 When ``password`` is True, the frontend should not echo the input as it is entered.
938 When ``password`` is True, the frontend should not echo the input as it is entered.
939
939
940 .. versionchanged:: 5.0.0
940 .. versionchanged:: 5.0
941
941
942 ``password`` key added.
942 ``password`` key added.
943
943
944 .. note::
944 .. note::
945
945
946 The stdin socket of the client is required to have the same zmq IDENTITY
946 The stdin socket of the client is required to have the same zmq IDENTITY
947 as the client's shell socket.
947 as the client's shell socket.
948 Because of this, the ``input_request`` must be sent with the same IDENTITY
948 Because of this, the ``input_request`` must be sent with the same IDENTITY
949 routing prefix as the ``execute_reply`` in order for the frontend to receive
949 routing prefix as the ``execute_reply`` in order for the frontend to receive
950 the message.
950 the message.
951
951
952 .. note::
952 .. note::
953
953
954 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
954 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
955 practice the kernel should behave like an interactive program. When a
955 practice the kernel should behave like an interactive program. When a
956 program is opened on the console, the keyboard effectively takes over the
956 program is opened on the console, the keyboard effectively takes over the
957 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
957 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
958 Since the IPython kernel effectively behaves like a console program (albeit
958 Since the IPython kernel effectively behaves like a console program (albeit
959 one whose "keyboard" is actually living in a separate process and
959 one whose "keyboard" is actually living in a separate process and
960 transported over the zmq connection), raw ``stdin`` isn't expected to be
960 transported over the zmq connection), raw ``stdin`` isn't expected to be
961 available.
961 available.
962
962
963
963
964 Heartbeat for kernels
964 Heartbeat for kernels
965 =====================
965 =====================
966
966
967 Clients send ping messages on a REQ socket, which are echoed right back
967 Clients send ping messages on a REQ socket, which are echoed right back
968 from the Kernel's REP socket. These are simple bytestrings, not full JSON messages described above.
968 from the Kernel's REP socket. These are simple bytestrings, not full JSON messages described above.
969
969
970
970
971 Custom Messages
971 Custom Messages
972 ===============
972 ===============
973
973
974 .. versionadded:: 4.1
974 .. versionadded:: 4.1
975
975
976 IPython 2.0 (msgspec v4.1) adds a messaging system for developers to add their own objects with Frontend
976 IPython 2.0 (msgspec v4.1) adds a messaging system for developers to add their own objects with Frontend
977 and Kernel-side components, and allow them to communicate with each other.
977 and Kernel-side components, and allow them to communicate with each other.
978 To do this, IPython adds a notion of a ``Comm``, which exists on both sides,
978 To do this, IPython adds a notion of a ``Comm``, which exists on both sides,
979 and can communicate in either direction.
979 and can communicate in either direction.
980
980
981 These messages are fully symmetrical - both the Kernel and the Frontend can send each message,
981 These messages are fully symmetrical - both the Kernel and the Frontend can send each message,
982 and no messages expect a reply.
982 and no messages expect a reply.
983 The Kernel listens for these messages on the Shell channel,
983 The Kernel listens for these messages on the Shell channel,
984 and the Frontend listens for them on the IOPub channel.
984 and the Frontend listens for them on the IOPub channel.
985
985
986 Opening a Comm
986 Opening a Comm
987 --------------
987 --------------
988
988
989 Opening a Comm produces a ``comm_open`` message, to be sent to the other side::
989 Opening a Comm produces a ``comm_open`` message, to be sent to the other side::
990
990
991 {
991 {
992 'comm_id' : 'u-u-i-d',
992 'comm_id' : 'u-u-i-d',
993 'target_name' : 'my_comm',
993 'target_name' : 'my_comm',
994 'data' : {}
994 'data' : {}
995 }
995 }
996
996
997 Every Comm has an ID and a target name.
997 Every Comm has an ID and a target name.
998 The code handling the message on the receiving side is responsible for maintaining a mapping
998 The code handling the message on the receiving side is responsible for maintaining a mapping
999 of target_name keys to constructors.
999 of target_name keys to constructors.
1000 After a ``comm_open`` message has been sent,
1000 After a ``comm_open`` message has been sent,
1001 there should be a corresponding Comm instance on both sides.
1001 there should be a corresponding Comm instance on both sides.
1002 The ``data`` key is always a dict and can be any extra JSON information used in initialization of the comm.
1002 The ``data`` key is always a dict and can be any extra JSON information used in initialization of the comm.
1003
1003
1004 If the ``target_name`` key is not found on the receiving side,
1004 If the ``target_name`` key is not found on the receiving side,
1005 then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state.
1005 then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state.
1006
1006
1007 Comm Messages
1007 Comm Messages
1008 -------------
1008 -------------
1009
1009
1010 Comm messages are one-way communications to update comm state,
1010 Comm messages are one-way communications to update comm state,
1011 used for synchronizing widget state, or simply requesting actions of a comm's counterpart.
1011 used for synchronizing widget state, or simply requesting actions of a comm's counterpart.
1012
1012
1013 Essentially, each comm pair defines their own message specification implemented inside the ``data`` dict.
1013 Essentially, each comm pair defines their own message specification implemented inside the ``data`` dict.
1014
1014
1015 There are no expected replies (of course, one side can send another ``comm_msg`` in reply).
1015 There are no expected replies (of course, one side can send another ``comm_msg`` in reply).
1016
1016
1017 Message type: ``comm_msg``::
1017 Message type: ``comm_msg``::
1018
1018
1019 {
1019 {
1020 'comm_id' : 'u-u-i-d',
1020 'comm_id' : 'u-u-i-d',
1021 'data' : {}
1021 'data' : {}
1022 }
1022 }
1023
1023
1024 Tearing Down Comms
1024 Tearing Down Comms
1025 ------------------
1025 ------------------
1026
1026
1027 Since comms live on both sides, when a comm is destroyed the other side must be notified.
1027 Since comms live on both sides, when a comm is destroyed the other side must be notified.
1028 This is done with a ``comm_close`` message.
1028 This is done with a ``comm_close`` message.
1029
1029
1030 Message type: ``comm_close``::
1030 Message type: ``comm_close``::
1031
1031
1032 {
1032 {
1033 'comm_id' : 'u-u-i-d',
1033 'comm_id' : 'u-u-i-d',
1034 'data' : {}
1034 'data' : {}
1035 }
1035 }
1036
1036
1037 Output Side Effects
1037 Output Side Effects
1038 -------------------
1038 -------------------
1039
1039
1040 Since comm messages can execute arbitrary user code,
1040 Since comm messages can execute arbitrary user code,
1041 handlers should set the parent header and publish status busy / idle,
1041 handlers should set the parent header and publish status busy / idle,
1042 just like an execute request.
1042 just like an execute request.
1043
1043
1044
1044
1045 To Do
1045 To Do
1046 =====
1046 =====
1047
1047
1048 Missing things include:
1048 Missing things include:
1049
1049
1050 * Important: finish thinking through the payload concept and API.
1050 * Important: finish thinking through the payload concept and API.
1051
1051
1052 .. include:: ../links.txt
1052 .. include:: ../links.txt
General Comments 0
You need to be logged in to leave comments. Login now