##// END OF EJS Templates
Merge pull request #3089 from minrk/stdin...
Brian E. Granger -
r10380:c3a90443 merge
parent child Browse files
Show More
@@ -507,6 +507,12 b' class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):'
507 507 # under Python 2.x for some reason
508 508 msg = msg.encode('utf8', 'replace')
509 509 try:
510 identity, msg = msg.split(':', 1)
511 self.session.session = identity.decode('ascii')
512 except Exception:
513 logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
514
515 try:
510 516 self.request._cookies = Cookie.SimpleCookie(msg)
511 517 except:
512 518 self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
@@ -519,23 +525,28 b' class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):'
519 525 self.on_message = self.save_on_message
520 526
521 527
522 class IOPubHandler(AuthenticatedZMQStreamHandler):
528 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
529
530 @property
531 def max_msg_size(self):
532 return self.settings.get('max_msg_size', 65535)
533
534 def create_stream(self):
535 km = self.kernel_manager
536 meth = getattr(km, 'connect_%s' % self.channel)
537 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
523 538
524 539 def initialize(self, *args, **kwargs):
525 self.iopub_stream = None
540 self.zmq_stream = None
526 541
527 542 def on_first_message(self, msg):
528 543 try:
529 super(IOPubHandler, self).on_first_message(msg)
544 super(ZMQChannelHandler, self).on_first_message(msg)
530 545 except web.HTTPError:
531 546 self.close()
532 547 return
533 km = self.kernel_manager
534 kernel_id = self.kernel_id
535 km.add_restart_callback(kernel_id, self.on_kernel_restarted)
536 km.add_restart_callback(kernel_id, self.on_restart_failed, 'dead')
537 548 try:
538 self.iopub_stream = km.connect_iopub(kernel_id)
549 self.create_stream()
539 550 except web.HTTPError:
540 551 # WebSockets don't response to traditional error codes so we
541 552 # close the connection.
@@ -543,29 +554,32 b' class IOPubHandler(AuthenticatedZMQStreamHandler):'
543 554 self.stream.close()
544 555 self.close()
545 556 else:
546 self.iopub_stream.on_recv(self._on_zmq_reply)
557 self.zmq_stream.on_recv(self._on_zmq_reply)
547 558
548 559 def on_message(self, msg):
549 pass
550
551 def _send_status_message(self, status):
552 msg = self.session.msg("status",
553 {'execution_state': status}
554 )
555 self.write_message(jsonapi.dumps(msg, default=date_default))
556
557 def on_kernel_restarted(self):
558 self.log.warn("kernel %s restarted", self.kernel_id)
559 self._send_status_message('restarting')
560
561 def on_restart_failed(self):
562 self.log.error("kernel %s restarted failed!", self.kernel_id)
563 self._send_status_message('dead')
560 if len(msg) < self.max_msg_size:
561 msg = jsonapi.loads(msg)
562 self.session.send(self.zmq_stream, msg)
564 563
565 564 def on_close(self):
566 565 # This method can be called twice, once by self.kernel_died and once
567 566 # from the WebSocket close event. If the WebSocket connection is
568 567 # closed before the ZMQ streams are setup, they could be None.
568 if self.zmq_stream is not None and not self.zmq_stream.closed():
569 self.zmq_stream.on_recv(None)
570 self.zmq_stream.close()
571
572
573 class IOPubHandler(ZMQChannelHandler):
574 channel = 'iopub'
575
576 def create_stream(self):
577 super(IOPubHandler, self).create_stream()
578 km = self.kernel_manager
579 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
580 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
581
582 def on_close(self):
569 583 km = self.kernel_manager
570 584 if self.kernel_id in km:
571 585 km.remove_restart_callback(
@@ -574,48 +588,31 b' class IOPubHandler(AuthenticatedZMQStreamHandler):'
574 588 km.remove_restart_callback(
575 589 self.kernel_id, self.on_restart_failed, 'dead',
576 590 )
577 if self.iopub_stream is not None and not self.iopub_stream.closed():
578 self.iopub_stream.on_recv(None)
579 self.iopub_stream.close()
580
581
582 class ShellHandler(AuthenticatedZMQStreamHandler):
591 super(IOPubHandler, self).on_close()
583 592
584 @property
585 def max_msg_size(self):
586 return self.settings.get('max_msg_size', 65535)
593 def _send_status_message(self, status):
594 msg = self.session.msg("status",
595 {'execution_state': status}
596 )
597 self.write_message(jsonapi.dumps(msg, default=date_default))
587 598
588 def initialize(self, *args, **kwargs):
589 self.shell_stream = None
599 def on_kernel_restarted(self):
600 logging.warn("kernel %s restarted", self.kernel_id)
601 self._send_status_message('restarting')
590 602
591 def on_first_message(self, msg):
592 try:
593 super(ShellHandler, self).on_first_message(msg)
594 except web.HTTPError:
595 self.close()
596 return
597 km = self.kernel_manager
598 kernel_id = self.kernel_id
599 try:
600 self.shell_stream = km.connect_shell(kernel_id)
601 except web.HTTPError:
602 # WebSockets don't response to traditional error codes so we
603 # close the connection.
604 if not self.stream.closed():
605 self.stream.close()
606 self.close()
607 else:
608 self.shell_stream.on_recv(self._on_zmq_reply)
603 def on_restart_failed(self):
604 logging.error("kernel %s restarted failed!", self.kernel_id)
605 self._send_status_message('dead')
609 606
610 607 def on_message(self, msg):
611 if len(msg) < self.max_msg_size:
612 msg = jsonapi.loads(msg)
613 self.session.send(self.shell_stream, msg)
608 """IOPub messages make no sense"""
609 pass
614 610
615 def on_close(self):
616 # Make sure the stream exists and is not already closed.
617 if self.shell_stream is not None and not self.shell_stream.closed():
618 self.shell_stream.close()
611 class ShellHandler(ZMQChannelHandler):
612 channel = 'shell'
613
614 class StdinHandler(ZMQChannelHandler):
615 channel = 'stdin'
619 616
620 617
621 618 #-----------------------------------------------------------------------------
@@ -66,7 +66,7 b' from IPython.frontend.html.notebook import DEFAULT_STATIC_FILES_PATH'
66 66 from .kernelmanager import MappingKernelManager
67 67 from .handlers import (LoginHandler, LogoutHandler,
68 68 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
69 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
69 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler, StdinHandler,
70 70 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
71 71 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
72 72 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
@@ -160,6 +160,7 b' class NotebookWebApplication(web.Application):'
160 160 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
161 161 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
162 162 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
163 (r"/kernels/%s/stdin" % _kernel_id_regex, StdinHandler),
163 164 (r"/notebooks", NotebookRootHandler),
164 165 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
165 166 (r"/rstservice/render", RSTHandler),
@@ -936,6 +936,9 b' pre,code,kbd,samp{white-space:pre-wrap;}'
936 936 a{text-decoration:underline;}
937 937 p{margin-bottom:0;}
938 938 a.heading-anchor:link,a.heading-anchor:visited{text-decoration:none;color:inherit;}
939 div.raw_input{padding-top:0px;padding-bottom:0px;height:1em;line-height:1em;font-family:monospace;}
940 span.input_prompt{font-family:inherit;}
941 input.raw_input{font-family:inherit;font-size:inherit;color:inherit;width:auto;margin:-2px 0px 0px 1px;padding-left:1px;padding-top:2px;height:1em;}
939 942 @media print{body{overflow:visible !important;} div#notebook{overflow:visible !important;} .ui-widget-content{border:0px;} #save_widget{margin:0px !important;} #header,#pager,#pager_splitter,#menubar,#toolbar{display:none !important;} .cell{border:none !important;} .toolbar{display:none;}}.rendered_html{color:black;}.rendered_html em{font-style:italic;}
940 943 .rendered_html strong{font-weight:bold;}
941 944 .rendered_html u{text-decoration:underline;}
@@ -245,7 +245,8 b' var IPython = (function (IPython) {'
245 245 'execute_reply': $.proxy(this._handle_execute_reply, this),
246 246 'output': $.proxy(this.output_area.handle_output, this.output_area),
247 247 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
248 'set_next_input': $.proxy(this._handle_set_next_input, this)
248 'set_next_input': $.proxy(this._handle_set_next_input, this),
249 'input_request': $.proxy(this._handle_input_request, this)
249 250 };
250 251 var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false});
251 252 };
@@ -260,11 +261,24 b' var IPython = (function (IPython) {'
260 261 $([IPython.events]).trigger('set_dirty.Notebook', {'value': true});
261 262 }
262 263
264 /**
265 * @method _handle_set_next_input
266 * @private
267 */
263 268 CodeCell.prototype._handle_set_next_input = function (text) {
264 269 var data = {'cell': this, 'text': text}
265 270 $([IPython.events]).trigger('set_next_input.Notebook', data);
266 271 }
267 272
273 /**
274 * @method _handle_input_request
275 * @private
276 */
277 CodeCell.prototype._handle_input_request = function (content) {
278 this.output_area.append_raw_input(content);
279 }
280
281
268 282 // Basic cell manipulation.
269 283
270 284 CodeCell.prototype.select = function () {
@@ -28,6 +28,7 b' var IPython = (function (IPython) {'
28 28 this.kernel_id = null;
29 29 this.shell_channel = null;
30 30 this.iopub_channel = null;
31 this.stdin_channel = null;
31 32 this.base_url = base_url;
32 33 this.running = false;
33 34 this.username = "username";
@@ -127,9 +128,12 b' var IPython = (function (IPython) {'
127 128 var ws_url = this.ws_url + this.kernel_url;
128 129 console.log("Starting WebSockets:", ws_url);
129 130 this.shell_channel = new this.WebSocket(ws_url + "/shell");
131 this.stdin_channel = new this.WebSocket(ws_url + "/stdin");
130 132 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
131 133 send_cookie = function(){
132 this.send(document.cookie);
134 // send the session id so the Session object Python-side
135 // has the same identity
136 this.send(that.session_id + ':' + document.cookie);
133 137 };
134 138 var already_called_onclose = false; // only alert once
135 139 var ws_closed_early = function(evt){
@@ -150,21 +154,26 b' var IPython = (function (IPython) {'
150 154 that._websocket_closed(ws_url, false);
151 155 }
152 156 };
153 this.shell_channel.onopen = send_cookie;
154 this.shell_channel.onclose = ws_closed_early;
155 this.iopub_channel.onopen = send_cookie;
156 this.iopub_channel.onclose = ws_closed_early;
157 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
158 for (var i=0; i < channels.length; i++) {
159 channels[i].onopen = send_cookie;
160 channels[i].onclose = ws_closed_early;
161 }
157 162 // switch from early-close to late-close message after 1s
158 163 setTimeout(function() {
159 if (that.shell_channel !== null) {
160 that.shell_channel.onclose = ws_closed_late;
164 for (var i=0; i < channels.length; i++) {
165 if (channels[i] !== null) {
166 channels[i].onclose = ws_closed_late;
161 167 }
162 if (that.iopub_channel !== null) {
163 that.iopub_channel.onclose = ws_closed_late;
164 168 }
165 169 }, 1000);
166 170 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
167 171 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
172 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
173
174 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
175 that.send_input_reply(data);
176 });
168 177 };
169 178
170 179 /**
@@ -172,16 +181,14 b' var IPython = (function (IPython) {'
172 181 * @method stop_channels
173 182 */
174 183 Kernel.prototype.stop_channels = function () {
175 if (this.shell_channel !== null) {
176 this.shell_channel.onclose = function (evt) {};
177 this.shell_channel.close();
178 this.shell_channel = null;
179 };
180 if (this.iopub_channel !== null) {
181 this.iopub_channel.onclose = function (evt) {};
182 this.iopub_channel.close();
183 this.iopub_channel = null;
184 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
185 for (var i=0; i < channels.length; i++) {
186 if ( channels[i] !== null ) {
187 channels[i].onclose = function (evt) {};
188 channels[i].close();
189 }
184 190 };
191 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
185 192 };
186 193
187 194 // Main public methods.
@@ -284,6 +291,9 b' var IPython = (function (IPython) {'
284 291 user_expressions : {},
285 292 allow_stdin : false
286 293 };
294 if (callbacks.input_request !== undefined) {
295 content.allow_stdin = true;
296 }
287 297 $.extend(true, content, options)
288 298 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
289 299 var msg = this._get_msg("execute_request", content);
@@ -343,8 +353,18 b' var IPython = (function (IPython) {'
343 353 };
344 354 };
345 355
356 Kernel.prototype.send_input_reply = function (input) {
357 var content = {
358 value : input,
359 };
360 $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
361 var msg = this._get_msg("input_reply", content);
362 this.stdin_channel.send(JSON.stringify(msg));
363 return msg.header.msg_id;
364 };
365
346 366
347 // Reply handlers.
367 // Reply handlers
348 368
349 369 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
350 370 var callbacks = this._msg_callbacks[msg_id];
@@ -433,6 +453,26 b' var IPython = (function (IPython) {'
433 453 };
434 454
435 455
456 Kernel.prototype._handle_input_request = function (e) {
457 var request = $.parseJSON(e.data);
458 var header = request.header;
459 var content = request.content;
460 var metadata = request.metadata;
461 var msg_type = header.msg_type;
462 if (msg_type !== 'input_request') {
463 console.log("Invalid input request!", request);
464 return;
465 }
466 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
467 if (callbacks !== undefined) {
468 var cb = callbacks[msg_type];
469 if (cb !== undefined) {
470 cb(content, metadata);
471 }
472 };
473 };
474
475
436 476 IPython.Kernel = Kernel;
437 477
438 478 return IPython;
@@ -449,6 +449,55 b' var IPython = (function (IPython) {'
449 449 element.append(toinsert);
450 450 };
451 451
452 OutputArea.prototype.append_raw_input = function (content) {
453 var that = this;
454 this.expand();
455 this.flush_clear_timeout();
456 var area = this.create_output_area();
457
458 area.append(
459 $("<div/>")
460 .addClass("box-flex1 output_subarea raw_input")
461 .append(
462 $("<span/>")
463 .addClass("input_prompt")
464 .text(content.prompt)
465 )
466 .append(
467 $("<input/>")
468 .addClass("raw_input")
469 .attr('type', 'text')
470 .attr("size", 80)
471 .keydown(function (event, ui) {
472 // make sure we submit on enter,
473 // and don't re-execute the *cell* on shift-enter
474 if (event.which === utils.keycodes.ENTER) {
475 that._submit_raw_input();
476 return false;
477 }
478 })
479 )
480 );
481 this.element.append(area);
482 area.find("input.raw_input").focus();
483 }
484 OutputArea.prototype._submit_raw_input = function (evt) {
485 var container = this.element.find("div.raw_input");
486 var theprompt = container.find("span.input_prompt");
487 var theinput = container.find("input.raw_input");
488 var value = theinput.attr("value");
489 var content = {
490 output_type : 'stream',
491 name : 'stdout',
492 text : theprompt.text() + value + '\n'
493 }
494 // remove form container
495 container.parent().remove();
496 // replace with plaintext version in stdout
497 this.append_output(content, false);
498 $([IPython.events]).trigger('send_input_reply.Kernel', value);
499 }
500
452 501
453 502 OutputArea.prototype.handle_clear_output = function (content) {
454 503 this.clear_output(content.stdout, content.stderr, content.other);
@@ -477,3 +477,26 b' a.heading-anchor:link, a.heading-anchor:visited {'
477 477 text-decoration: none;
478 478 color: inherit;
479 479 }
480
481 /* raw_input styles */
482
483 div.raw_input {
484 padding-top: 0px;
485 padding-bottom: 0px;
486 height: 1em;
487 line-height: 1em;
488 font-family: monospace;
489 }
490 span.input_prompt {
491 font-family: inherit;
492 }
493 input.raw_input {
494 font-family: inherit;
495 font-size: inherit;
496 color: inherit;
497 width: auto;
498 margin: -2px 0px 0px 1px;
499 padding-left: 1px;
500 padding-top: 2px;
501 height: 1em;
502 }
@@ -746,6 +746,15 b' class Kernel(Configurable):'
746 746 # Flush output before making the request.
747 747 sys.stderr.flush()
748 748 sys.stdout.flush()
749 # flush the stdin socket, to purge stale replies
750 while True:
751 try:
752 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
753 except zmq.ZMQError as e:
754 if e.errno == zmq.EAGAIN:
755 break
756 else:
757 raise
749 758
750 759 # Send the input request.
751 760 content = json_clean(dict(prompt=prompt))
General Comments 0
You need to be logged in to leave comments. Login now