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