Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

add stdin to the notebook #3089

Merged
merged 13 commits into from

2 participants

@minrk
Owner

adds a simple stdin handler to the notebook, so things like raw_input and %debug work.

%debug in action:

debug

This PR depends on #3011 and #3088. Incentive!

@minrk
Owner

The main thing remaining to do here is to prevent shift-enter from executing code while the raw_input filed is active. That can mess things up.

@ellisonbg
Owner

Minor UI comments:

  • The text area used to type stdin should have the font properties as the regular monospace we are using, namely black.
  • We should probably think about how to size the width of the text area.
  • When a user hits return for stdin, it inserts a static version of the prompt and typed text below the input cell. This static version should have the same margins/padding as the actual prompt/text area, so there isn't a jump when a user presses return.
minrk added some commits
@minrk minrk cleanup stdin event submission
follow example in rename notebook: remove form, bind keydown for enter,
avoiding shift-enter submitting the cell again.
5631528
@minrk minrk tweak raw-input styling
should fix alignment of raw_input and prompt
5a8093e
IPython/frontend/html/notebook/handlers.py
@@ -507,6 +507,12 @@ def _inject_cookie_message(self, msg):
# under Python 2.x for some reason
msg = msg.encode('utf8', 'replace')
try:
+ bsession, msg = msg.split(':', 1)
@ellisonbg Owner

I think the identity should be handled at the level of the handler rather than in the JS code and sent to the handler.

@minrk Owner
minrk added a note

Discussed on HipChat, this isn't possible - the identity of the StdIn and Shell sockets must match for input_request routing, and the only thing that has a handle on both at the same time is the Javascript.

@ellisonbg Owner

The name "bsession" is a bit confusing. Can you rename the variable to emphasize that this is the socket identity and provide some comments reminding us why we have to pass this and what it is for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ellisonbg
Owner

Shouldn't some of the channels have an on_message method that is different from the base class (like a pass)?

@ellisonbg
Owner

I really like the cleanup/refactor of the zmq handlers. Much cleaner and easy to maintain. Amazing how simple it is!

@ellisonbg ellisonbg commented on the diff
IPython/frontend/html/notebook/static/js/kernel.js
((24 lines not shown))
}
}, 1000);
- this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
- this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
+ this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
+ this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this);
+ this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
@ellisonbg Owner

Maybe use stdin rather than input to be consistent in the naming?

@ellisonbg Owner

Also below...

@minrk Owner
minrk added a note

it's called an input_request, that's the message type. See our message spec

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ellisonbg ellisonbg commented on the diff
IPython/frontend/html/notebook/static/js/kernel.js
@@ -433,6 +451,26 @@ var IPython = (function (IPython) {
};
+ Kernel.prototype._handle_input_request = function (e) {
+ var request = $.parseJSON(e.data);
+ var header = request.header;
+ var content = request.content;
+ var metadata = request.metadata;
+ var msg_type = header.msg_type;
+ if (msg_type !== 'input_request') {
@ellisonbg Owner

Then again, maybe "input" is better as it is the name of the message.

@minrk Owner
minrk added a note

it is a request, it has a reply, just like execute, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ellisonbg
Owner

OK I have reviewed all of the code. Mostly minor comments and a bit of UI cleanup.

@minrk
Owner

When a user hits return for stdin, it inserts a static version of the prompt and typed text below the input cell. This static version should have the same margins/padding as the actual prompt/text area, so there isn't a jump when a user presses return.

I disagree - it should be a plain pre tag for this, but I'm not sure that I can make an input area with zero margins, so I don't think this is possible.

@minrk
Owner

I spoke too soon, the layout should match reasonably well now.

@ellisonbg
Owner

OK, the look of the input area matches the pre quite well. Great work! Right now the input area is statically sized to 80 cols. When the notebook window is narrower than that, the layout gets messed up. It looks like it would be difficult to make it variable width. Do you think we need the full 80 cols for this?

IPython/frontend/html/notebook/handlers.py
@@ -507,6 +507,12 @@ def _inject_cookie_message(self, msg):
# under Python 2.x for some reason
msg = msg.encode('utf8', 'replace')
try:
+ bsession, msg = msg.split(':', 1)
+ self.session.session = bsession.decode('ascii')
+ except Exception:
+ logging.error("No bsession!", exc_info=True)
@ellisonbg Owner

This error message is a bit cryptic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ellisonbg
Owner

Any response to the on_message behavior?

@minrk
Owner

As for 80 cols - no, I don't expect it to be needed often, but I don't know how to make it auto-width.

Sorry I didn't comment, but I did set the IOPub on_message to pass, since IOPub downstream messages don't make any sense.

I also fixed the bsession / error message when the first message is not given.

@ellisonbg ellisonbg merged commit c3a9044 into from
@tkf tkf referenced this pull request in tkf/emacs-ipython-notebook
Open

Implement stdin channel #115

@tkf tkf referenced this pull request from a commit in tkf/emacs-ipython-notebook
@tkf tkf Use IPython 1.0-compatible "handshake"
IPython changed the way to start channel [1].
fixes #121

[1] see commit ipython/ipython@24dcb6b
in ipython/ipython#3089
146fc45
@tkf tkf referenced this pull request in tkf/emacs-ipython-notebook
Open

Use IPython 1.0-compatible "handshake" to open kernel channels #122

@minrk minrk deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 25, 2013
  1. @minrk
  2. @minrk

    add stdin channel to NotebookApp

    minrk authored
  3. @minrk

    specify socket identity from kernel.js

    minrk authored
    required for stdin routing
  4. @minrk

    add stdin to notebook

    minrk authored
    dumb / gross / ugly jQuery modal dialog for now,
    but it works!
  5. @minrk
  6. @minrk
  7. @minrk

    cleanup stdin event submission

    minrk authored
    follow example in rename notebook: remove form, bind keydown for enter,
    avoiding shift-enter submitting the cell again.
  8. @minrk

    tweak raw-input styling

    minrk authored
    should fix alignment of raw_input and prompt
  9. @minrk

    add no-op on_message for iopub

    minrk authored
  10. @minrk

    fix color in raw_input

    minrk authored
Commits on Apr 26, 2013
  1. @minrk

    tweak raw_input style

    minrk authored
    more crowded, less jumpy
  2. @minrk
  3. @minrk
This page is out of date. Refresh to see the latest.
View
123 IPython/frontend/html/notebook/handlers.py
@@ -507,6 +507,12 @@ def _inject_cookie_message(self, msg):
# under Python 2.x for some reason
msg = msg.encode('utf8', 'replace')
try:
+ identity, msg = msg.split(':', 1)
+ self.session.session = identity.decode('ascii')
+ except Exception:
+ logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
+
+ try:
self.request._cookies = Cookie.SimpleCookie(msg)
except:
self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
@@ -519,23 +525,28 @@ def on_first_message(self, msg):
self.on_message = self.save_on_message
-class IOPubHandler(AuthenticatedZMQStreamHandler):
-
+class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
+
+ @property
+ def max_msg_size(self):
+ return self.settings.get('max_msg_size', 65535)
+
+ def create_stream(self):
+ km = self.kernel_manager
+ meth = getattr(km, 'connect_%s' % self.channel)
+ self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
+
def initialize(self, *args, **kwargs):
- self.iopub_stream = None
-
+ self.zmq_stream = None
+
def on_first_message(self, msg):
try:
- super(IOPubHandler, self).on_first_message(msg)
+ super(ZMQChannelHandler, self).on_first_message(msg)
except web.HTTPError:
self.close()
return
- km = self.kernel_manager
- kernel_id = self.kernel_id
- km.add_restart_callback(kernel_id, self.on_kernel_restarted)
- km.add_restart_callback(kernel_id, self.on_restart_failed, 'dead')
try:
- self.iopub_stream = km.connect_iopub(kernel_id)
+ self.create_stream()
except web.HTTPError:
# WebSockets don't response to traditional error codes so we
# close the connection.
@@ -543,29 +554,32 @@ def on_first_message(self, msg):
self.stream.close()
self.close()
else:
- self.iopub_stream.on_recv(self._on_zmq_reply)
+ self.zmq_stream.on_recv(self._on_zmq_reply)
def on_message(self, msg):
- pass
-
- def _send_status_message(self, status):
- msg = self.session.msg("status",
- {'execution_state': status}
- )
- self.write_message(jsonapi.dumps(msg, default=date_default))
-
- def on_kernel_restarted(self):
- self.log.warn("kernel %s restarted", self.kernel_id)
- self._send_status_message('restarting')
-
- def on_restart_failed(self):
- self.log.error("kernel %s restarted failed!", self.kernel_id)
- self._send_status_message('dead')
+ if len(msg) < self.max_msg_size:
+ msg = jsonapi.loads(msg)
+ self.session.send(self.zmq_stream, msg)
def on_close(self):
# This method can be called twice, once by self.kernel_died and once
# from the WebSocket close event. If the WebSocket connection is
# closed before the ZMQ streams are setup, they could be None.
+ if self.zmq_stream is not None and not self.zmq_stream.closed():
+ self.zmq_stream.on_recv(None)
+ self.zmq_stream.close()
+
+
+class IOPubHandler(ZMQChannelHandler):
+ channel = 'iopub'
+
+ def create_stream(self):
+ super(IOPubHandler, self).create_stream()
+ km = self.kernel_manager
+ km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
+ km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
+
+ def on_close(self):
km = self.kernel_manager
if self.kernel_id in km:
km.remove_restart_callback(
@@ -574,48 +588,31 @@ def on_close(self):
km.remove_restart_callback(
self.kernel_id, self.on_restart_failed, 'dead',
)
- if self.iopub_stream is not None and not self.iopub_stream.closed():
- self.iopub_stream.on_recv(None)
- self.iopub_stream.close()
-
-
-class ShellHandler(AuthenticatedZMQStreamHandler):
+ super(IOPubHandler, self).on_close()
- @property
- def max_msg_size(self):
- return self.settings.get('max_msg_size', 65535)
-
- def initialize(self, *args, **kwargs):
- self.shell_stream = None
+ def _send_status_message(self, status):
+ msg = self.session.msg("status",
+ {'execution_state': status}
+ )
+ self.write_message(jsonapi.dumps(msg, default=date_default))
- def on_first_message(self, msg):
- try:
- super(ShellHandler, self).on_first_message(msg)
- except web.HTTPError:
- self.close()
- return
- km = self.kernel_manager
- kernel_id = self.kernel_id
- try:
- self.shell_stream = km.connect_shell(kernel_id)
- except web.HTTPError:
- # WebSockets don't response to traditional error codes so we
- # close the connection.
- if not self.stream.closed():
- self.stream.close()
- self.close()
- else:
- self.shell_stream.on_recv(self._on_zmq_reply)
+ def on_kernel_restarted(self):
+ logging.warn("kernel %s restarted", self.kernel_id)
+ self._send_status_message('restarting')
+ def on_restart_failed(self):
+ logging.error("kernel %s restarted failed!", self.kernel_id)
+ self._send_status_message('dead')
+
def on_message(self, msg):
- if len(msg) < self.max_msg_size:
- msg = jsonapi.loads(msg)
- self.session.send(self.shell_stream, msg)
+ """IOPub messages make no sense"""
+ pass
- def on_close(self):
- # Make sure the stream exists and is not already closed.
- if self.shell_stream is not None and not self.shell_stream.closed():
- self.shell_stream.close()
+class ShellHandler(ZMQChannelHandler):
+ channel = 'shell'
+
+class StdinHandler(ZMQChannelHandler):
+ channel = 'stdin'
#-----------------------------------------------------------------------------
View
3  IPython/frontend/html/notebook/notebookapp.py
@@ -66,7 +66,7 @@
from .kernelmanager import MappingKernelManager
from .handlers import (LoginHandler, LogoutHandler,
ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
- MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
+ MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler, StdinHandler,
ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
@@ -160,6 +160,7 @@ def __init__(self, ipython_app, kernel_manager, notebook_manager,
(r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
(r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
(r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
+ (r"/kernels/%s/stdin" % _kernel_id_regex, StdinHandler),
(r"/notebooks", NotebookRootHandler),
(r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
(r"/rstservice/render", RSTHandler),
View
3  IPython/frontend/html/notebook/static/css/style.min.css
@@ -936,6 +936,9 @@ pre,code,kbd,samp{white-space:pre-wrap;}
a{text-decoration:underline;}
p{margin-bottom:0;}
a.heading-anchor:link,a.heading-anchor:visited{text-decoration:none;color:inherit;}
+div.raw_input{padding-top:0px;padding-bottom:0px;height:1em;line-height:1em;font-family:monospace;}
+span.input_prompt{font-family:inherit;}
+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;}
@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;}
.rendered_html strong{font-weight:bold;}
.rendered_html u{text-decoration:underline;}
View
16 IPython/frontend/html/notebook/static/js/codecell.js
@@ -245,7 +245,8 @@ var IPython = (function (IPython) {
'execute_reply': $.proxy(this._handle_execute_reply, this),
'output': $.proxy(this.output_area.handle_output, this.output_area),
'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
- 'set_next_input': $.proxy(this._handle_set_next_input, this)
+ 'set_next_input': $.proxy(this._handle_set_next_input, this),
+ 'input_request': $.proxy(this._handle_input_request, this)
};
var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false});
};
@@ -260,10 +261,23 @@ var IPython = (function (IPython) {
$([IPython.events]).trigger('set_dirty.Notebook', {'value': true});
}
+ /**
+ * @method _handle_set_next_input
+ * @private
+ */
CodeCell.prototype._handle_set_next_input = function (text) {
var data = {'cell': this, 'text': text}
$([IPython.events]).trigger('set_next_input.Notebook', data);
}
+
+ /**
+ * @method _handle_input_request
+ * @private
+ */
+ CodeCell.prototype._handle_input_request = function (content) {
+ this.output_area.append_raw_input(content);
+ }
+
// Basic cell manipulation.
View
84 IPython/frontend/html/notebook/static/js/kernel.js
@@ -28,6 +28,7 @@ var IPython = (function (IPython) {
this.kernel_id = null;
this.shell_channel = null;
this.iopub_channel = null;
+ this.stdin_channel = null;
this.base_url = base_url;
this.running = false;
this.username = "username";
@@ -127,9 +128,12 @@ var IPython = (function (IPython) {
var ws_url = this.ws_url + this.kernel_url;
console.log("Starting WebSockets:", ws_url);
this.shell_channel = new this.WebSocket(ws_url + "/shell");
+ this.stdin_channel = new this.WebSocket(ws_url + "/stdin");
this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
send_cookie = function(){
- this.send(document.cookie);
+ // send the session id so the Session object Python-side
+ // has the same identity
+ this.send(that.session_id + ':' + document.cookie);
};
var already_called_onclose = false; // only alert once
var ws_closed_early = function(evt){
@@ -150,21 +154,26 @@ var IPython = (function (IPython) {
that._websocket_closed(ws_url, false);
}
};
- this.shell_channel.onopen = send_cookie;
- this.shell_channel.onclose = ws_closed_early;
- this.iopub_channel.onopen = send_cookie;
- this.iopub_channel.onclose = ws_closed_early;
+ var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
+ for (var i=0; i < channels.length; i++) {
+ channels[i].onopen = send_cookie;
+ channels[i].onclose = ws_closed_early;
+ }
// switch from early-close to late-close message after 1s
setTimeout(function() {
- if (that.shell_channel !== null) {
- that.shell_channel.onclose = ws_closed_late;
- }
- if (that.iopub_channel !== null) {
- that.iopub_channel.onclose = ws_closed_late;
+ for (var i=0; i < channels.length; i++) {
+ if (channels[i] !== null) {
+ channels[i].onclose = ws_closed_late;
+ }
}
}, 1000);
- this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
- this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
+ this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
+ this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this);
+ this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
@ellisonbg Owner

Maybe use stdin rather than input to be consistent in the naming?

@ellisonbg Owner

Also below...

@minrk Owner
minrk added a note

it's called an input_request, that's the message type. See our message spec

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
+ that.send_input_reply(data);
+ });
};
/**
@@ -172,16 +181,14 @@ var IPython = (function (IPython) {
* @method stop_channels
*/
Kernel.prototype.stop_channels = function () {
- if (this.shell_channel !== null) {
- this.shell_channel.onclose = function (evt) {};
- this.shell_channel.close();
- this.shell_channel = null;
- };
- if (this.iopub_channel !== null) {
- this.iopub_channel.onclose = function (evt) {};
- this.iopub_channel.close();
- this.iopub_channel = null;
+ var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
+ for (var i=0; i < channels.length; i++) {
+ if ( channels[i] !== null ) {
+ channels[i].onclose = function (evt) {};
+ channels[i].close();
+ }
};
+ this.shell_channel = this.iopub_channel = this.stdin_channel = null;
};
// Main public methods.
@@ -284,6 +291,9 @@ var IPython = (function (IPython) {
user_expressions : {},
allow_stdin : false
};
+ if (callbacks.input_request !== undefined) {
+ content.allow_stdin = true;
+ }
$.extend(true, content, options)
$([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
var msg = this._get_msg("execute_request", content);
@@ -343,8 +353,18 @@ var IPython = (function (IPython) {
};
};
+ Kernel.prototype.send_input_reply = function (input) {
+ var content = {
+ value : input,
+ };
+ $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
+ var msg = this._get_msg("input_reply", content);
+ this.stdin_channel.send(JSON.stringify(msg));
+ return msg.header.msg_id;
+ };
+
- // Reply handlers.
+ // Reply handlers
Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
var callbacks = this._msg_callbacks[msg_id];
@@ -433,6 +453,26 @@ var IPython = (function (IPython) {
};
+ Kernel.prototype._handle_input_request = function (e) {
+ var request = $.parseJSON(e.data);
+ var header = request.header;
+ var content = request.content;
+ var metadata = request.metadata;
+ var msg_type = header.msg_type;
+ if (msg_type !== 'input_request') {
@ellisonbg Owner

Then again, maybe "input" is better as it is the name of the message.

@minrk Owner
minrk added a note

it is a request, it has a reply, just like execute, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ console.log("Invalid input request!", request);
+ return;
+ }
+ var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
+ if (callbacks !== undefined) {
+ var cb = callbacks[msg_type];
+ if (cb !== undefined) {
+ cb(content, metadata);
+ }
+ };
+ };
+
+
IPython.Kernel = Kernel;
return IPython;
View
49 IPython/frontend/html/notebook/static/js/outputarea.js
@@ -448,6 +448,55 @@ var IPython = (function (IPython) {
toinsert.append(latex);
element.append(toinsert);
};
+
+ OutputArea.prototype.append_raw_input = function (content) {
+ var that = this;
+ this.expand();
+ this.flush_clear_timeout();
+ var area = this.create_output_area();
+
+ area.append(
+ $("<div/>")
+ .addClass("box-flex1 output_subarea raw_input")
+ .append(
+ $("<span/>")
+ .addClass("input_prompt")
+ .text(content.prompt)
+ )
+ .append(
+ $("<input/>")
+ .addClass("raw_input")
+ .attr('type', 'text')
+ .attr("size", 80)
+ .keydown(function (event, ui) {
+ // make sure we submit on enter,
+ // and don't re-execute the *cell* on shift-enter
+ if (event.which === utils.keycodes.ENTER) {
+ that._submit_raw_input();
+ return false;
+ }
+ })
+ )
+ );
+ this.element.append(area);
+ area.find("input.raw_input").focus();
+ }
+ OutputArea.prototype._submit_raw_input = function (evt) {
+ var container = this.element.find("div.raw_input");
+ var theprompt = container.find("span.input_prompt");
+ var theinput = container.find("input.raw_input");
+ var value = theinput.attr("value");
+ var content = {
+ output_type : 'stream',
+ name : 'stdout',
+ text : theprompt.text() + value + '\n'
+ }
+ // remove form container
+ container.parent().remove();
+ // replace with plaintext version in stdout
+ this.append_output(content, false);
+ $([IPython.events]).trigger('send_input_reply.Kernel', value);
+ }
OutputArea.prototype.handle_clear_output = function (content) {
View
23 IPython/frontend/html/notebook/static/less/notebook.less
@@ -477,3 +477,26 @@ a.heading-anchor:link, a.heading-anchor:visited {
text-decoration: none;
color: inherit;
}
+
+/* raw_input styles */
+
+div.raw_input {
+ padding-top: 0px;
+ padding-bottom: 0px;
+ height: 1em;
+ line-height: 1em;
+ font-family: monospace;
+}
+span.input_prompt {
+ font-family: inherit;
+}
+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;
+}
View
11 IPython/kernel/zmq/ipkernel.py
@@ -746,7 +746,16 @@ def _raw_input(self, prompt, ident, parent):
# Flush output before making the request.
sys.stderr.flush()
sys.stdout.flush()
-
+ # flush the stdin socket, to purge stale replies
+ while True:
+ try:
+ self.stdin_socket.recv_multipart(zmq.NOBLOCK)
+ except zmq.ZMQError as e:
+ if e.errno == zmq.EAGAIN:
+ break
+ else:
+ raise
+
# Send the input request.
content = json_clean(dict(prompt=prompt))
self.session.send(self.stdin_socket, u'input_request', content, parent,
Something went wrong with that request. Please try again.