diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js index 1404956..527b9ca 100644 --- a/IPython/html/static/notebook/js/codecell.js +++ b/IPython/html/static/notebook/js/codecell.js @@ -403,14 +403,18 @@ define([ * Execute current code cell to the kernel * @method execute */ - CodeCell.prototype.execute = function () { + CodeCell.prototype.execute = function (skip_exceptions) { if (!this.kernel || !this.kernel.is_connected()) { console.log("Can't execute, kernel is not connected."); return; } this.active_output_area.clear_output(false, true); - + + if (skip_exceptions === undefined) { + skip_exceptions = false; + } + // Clear widget area for (var i = 0; i < this.widget_views.length; i++) { var view = this.widget_views[i]; @@ -434,7 +438,8 @@ define([ var callbacks = this.get_callbacks(); var old_msg_id = this.last_msg_id; - this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true}); + this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true, + skip_exceptions : skip_exceptions}); if (old_msg_id) { delete CodeCell.msg_cells[old_msg_id]; } diff --git a/IPython/html/tests/notebook/execute_code.js b/IPython/html/tests/notebook/execute_code.js index 8374fc1..ca80b9f 100644 --- a/IPython/html/tests/notebook/execute_code.js +++ b/IPython/html/tests/notebook/execute_code.js @@ -75,4 +75,39 @@ casper.notebook_test(function () { var result = this.get_output_cell(0); this.test.assertEquals(result.text, '13\n', 'cell execute (using "play" toolbar button)') }); + + // run code with skip_exception + this.thenEvaluate(function () { + var cell0 = IPython.notebook.get_cell(0); + cell0.set_text('raise IOError'); + IPython.notebook.insert_cell_below('code',0); + var cell1 = IPython.notebook.get_cell(1); + cell1.set_text('a=14; print(a)'); + cell0.execute(skip_exception=true); + cell1.execute(); + }); + + this.wait_for_output(1); + + this.then(function () { + var result = this.get_output_cell(1); + this.test.assertEquals(result.text, '14\n', 'cell execute, skip exceptions'); + }); + + this.thenEvaluate(function () { + var cell0 = IPython.notebook.get_cell(0); + cell0.set_text('raise IOError'); + IPython.notebook.insert_cell_below('code',0); + var cell1 = IPython.notebook.get_cell(1); + cell1.set_text('a=14; print(a)'); + cell0.execute(); + cell1.execute(); + }); + + this.wait_for_output(1); + + this.then(function () { + var result = this.get_output_cell(1); + this.test.assertNotEquals(result.text, '14\n', 'cell execute, skip exceptions'); + }); }); diff --git a/IPython/kernel/client.py b/IPython/kernel/client.py index 16d5e2f..5977f3f 100644 --- a/IPython/kernel/client.py +++ b/IPython/kernel/client.py @@ -196,7 +196,7 @@ class KernelClient(ConnectionFileMixin): # Methods to send specific messages on channels def execute(self, code, silent=False, store_history=True, - user_expressions=None, allow_stdin=None): + user_expressions=None, allow_stdin=None, skip_exceptions=False): """Execute code in the kernel. Parameters @@ -224,6 +224,9 @@ class KernelClient(ConnectionFileMixin): If raw_input is called from code executed from such a frontend, a StdinNotImplementedError will be raised. + skip_exceptions: bool, optional (default False) + Flag whether to abort the execution queue, if an exception is encountered. + Returns ------- The msg_id of the message sent. @@ -243,7 +246,7 @@ class KernelClient(ConnectionFileMixin): # not in Session. content = dict(code=code, silent=silent, store_history=store_history, user_expressions=user_expressions, - allow_stdin=allow_stdin, + allow_stdin=allow_stdin, skip_exceptions=skip_exceptions ) msg = self.session.msg('execute_request', content) self.shell_channel.send(msg) diff --git a/IPython/kernel/tests/test_message_spec.py b/IPython/kernel/tests/test_message_spec.py index a2a6d3f..b3fad59 100644 --- a/IPython/kernel/tests/test_message_spec.py +++ b/IPython/kernel/tests/test_message_spec.py @@ -303,6 +303,24 @@ def test_execute_inc(): count_2 = reply['execution_count'] nt.assert_equal(count_2, count+1) +def test_execute_skip_exceptions(): + """execute request should not abort execution queue with skip_exceptions""" + flush_channels() + + KC.execute(code='raise IOError') + msg_id = KC.execute(code='print("Hallo")') + KC.get_shell_msg(timeout=TIMEOUT) + reply = KC.get_shell_msg(timeout=TIMEOUT) + nt.assert_equal(reply['content']['status'], 'aborted') + + flush_channels() + + KC.execute(code='raise IOError', skip_exceptions=True) + msg_id = KC.execute(code='print("Hallo")') + KC.get_shell_msg(timeout=TIMEOUT) + reply = KC.get_shell_msg(timeout=TIMEOUT) + nt.assert_equal(reply['content']['status'], 'ok') + def test_user_expressions(): flush_channels() diff --git a/IPython/kernel/zmq/kernelbase.py b/IPython/kernel/zmq/kernelbase.py index 396d1d5..10e4d52 100755 --- a/IPython/kernel/zmq/kernelbase.py +++ b/IPython/kernel/zmq/kernelbase.py @@ -348,6 +348,11 @@ class Kernel(SingletonConfigurable): self.log.error("%s", parent) return + if u'skip_exceptions' in content and content[u'skip_exceptions'] is True: + skip_exceptions = True + else: + skip_exceptions = False + md = self._make_metadata(parent['metadata']) # Re-broadcast our input for the benefit of listening clients, and @@ -382,11 +387,11 @@ class Kernel(SingletonConfigurable): self.log.debug("%s", reply_msg) - if not silent and reply_msg['content']['status'] == u'error': + if not silent and reply_msg['content']['status'] == u'error' and not skip_exceptions: self._abort_queues() def do_execute(self, code, silent, store_history=True, - user_experssions=None, allow_stdin=False): + user_expressions=None, allow_stdin=False): """Execute user code. Must be overridden by subclasses. """ raise NotImplementedError diff --git a/docs/source/development/messaging.rst b/docs/source/development/messaging.rst index 4c33ce4..4a50898 100644 --- a/docs/source/development/messaging.rst +++ b/docs/source/development/messaging.rst @@ -281,6 +281,10 @@ Message type: ``execute_request``:: # If raw_input is called from code executed from such a frontend, # a StdinNotImplementedError will be raised. 'allow_stdin' : True, + + # A boolean flag, which, if True, does not abort the execution queue, if an exception is encountered. + # This allows the queued execution of multiple execute_requests, even if they generate exceptions. + 'skip_exceptions' : True, } .. versionchanged:: 5.0