##// END OF EJS Templates
Add support for "finally" event callbacks....
Fabio Niephaus -
Show More
@@ -0,0 +1,6 b''
1 Two new event callbacks have been added: ``finally_execute`` and ``finally_run_cell``.
2 They work similar to the corresponding *post* callbacks, but are guaranteed to be triggered (even when, for example, a ``SyntaxError`` was raised).
3 Also, the execution result is provided as an argument for further inspection.
4
5 * `GitHub issue <https://github.com/ipython/ipython/issues/10774>`__
6 * `Updated docs <http://ipython.readthedocs.io/en/stable/config/callbacks.html?highlight=finally>`__
@@ -116,6 +116,29 b' def post_run_cell():'
116 116 pass
117 117
118 118 @_define_event
119 def finally_execute(result):
120 """Always fires after code is executed in response to user/frontend action.
121
122 This includes comm and widget messages and silent execution, as well as user
123 code cells.
124
125 Parameters
126 ----------
127 result : :class:`~IPython.core.interactiveshell.ExecutionResult`
128 """
129 pass
130
131 @_define_event
132 def finally_run_cell(result):
133 """Always fires after user-entered code runs.
134
135 Parameters
136 ----------
137 result : :class:`~IPython.core.interactiveshell.ExecutionResult`
138 """
139 pass
140
141 @_define_event
119 142 def shell_initialized(ip):
120 143 """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`.
121 144
@@ -1879,7 +1879,7 b' class InteractiveShell(SingletonConfigurable):'
1879 1879 # This is overridden in TerminalInteractiveShell to show a message about
1880 1880 # the %paste magic.
1881 1881 def showindentationerror(self):
1882 """Called by run_cell when there's an IndentationError in code entered
1882 """Called by _run_cell when there's an IndentationError in code entered
1883 1883 at the prompt.
1884 1884
1885 1885 This is overridden in TerminalInteractiveShell to show a message about
@@ -2621,6 +2621,29 b' class InteractiveShell(SingletonConfigurable):'
2621 2621 -------
2622 2622 result : :class:`ExecutionResult`
2623 2623 """
2624 try:
2625 result = self._run_cell(
2626 raw_cell, store_history, silent, shell_futures)
2627 finally:
2628 self.events.trigger('finally_execute', result)
2629 if not silent:
2630 self.events.trigger('finally_run_cell', result)
2631 return result
2632
2633 def _run_cell(self, raw_cell, store_history, silent, shell_futures):
2634 """Internal method to run a complete IPython cell.
2635
2636 Parameters
2637 ----------
2638 raw_cell : str
2639 store_history : bool
2640 silent : bool
2641 shell_futures : bool
2642
2643 Returns
2644 -------
2645 result : :class:`ExecutionResult`
2646 """
2624 2647 result = ExecutionResult()
2625 2648
2626 2649 if (not raw_cell) or raw_cell.isspace():
@@ -263,11 +263,17 b' class InteractiveShellTestCase(unittest.TestCase):'
263 263 pre_always = mock.Mock()
264 264 post_explicit = mock.Mock()
265 265 post_always = mock.Mock()
266 finally_explicit = mock.Mock()
267 finally_always = mock.Mock()
268 all_mocks = [pre_explicit, pre_always, post_explicit, post_always,
269 finally_explicit,finally_always]
266 270
267 271 ip.events.register('pre_run_cell', pre_explicit)
268 272 ip.events.register('pre_execute', pre_always)
269 273 ip.events.register('post_run_cell', post_explicit)
270 274 ip.events.register('post_execute', post_always)
275 ip.events.register('finally_run_cell', finally_explicit)
276 ip.events.register('finally_execute', finally_always)
271 277
272 278 try:
273 279 ip.run_cell("1", silent=True)
@@ -275,17 +281,31 b' class InteractiveShellTestCase(unittest.TestCase):'
275 281 assert not pre_explicit.called
276 282 assert post_always.called
277 283 assert not post_explicit.called
284 assert finally_always.called
285 assert not finally_explicit.called
278 286 # double-check that non-silent exec did what we expected
279 287 # silent to avoid
280 288 ip.run_cell("1")
281 289 assert pre_explicit.called
282 290 assert post_explicit.called
291 assert finally_explicit.called
292 # check that finally hooks are always called
293 [m.reset_mock() for m in all_mocks]
294 ip.run_cell("syntax error")
295 assert pre_always.called
296 assert pre_explicit.called
297 assert not post_always.called # because of `SyntaxError`
298 assert not post_explicit.called
299 assert finally_explicit.called
300 assert finally_always.called
283 301 finally:
284 302 # remove post-exec
285 303 ip.events.unregister('pre_run_cell', pre_explicit)
286 304 ip.events.unregister('pre_execute', pre_always)
287 305 ip.events.unregister('post_run_cell', post_explicit)
288 306 ip.events.unregister('post_execute', post_always)
307 ip.events.unregister('finally_run_cell', finally_explicit)
308 ip.events.unregister('finally_execute', finally_always)
289 309
290 310 def test_silent_noadvance(self):
291 311 """run_cell(silent=True) doesn't advance execution_count"""
@@ -24,11 +24,18 b' For example::'
24 24 def post_execute(self):
25 25 if self.shell.user_ns.get('x', None) != self.last_x:
26 26 print("x changed!")
27
28 def finally_execute(self, result):
29 if result.error_before_exec:
30 print('Error before execution: %s' % result.error_before_exec)
31 else:
32 print('Execution result: %s', result.result)
27 33
28 34 def load_ipython_extension(ip):
29 35 vw = VarWatcher(ip)
30 36 ip.events.register('pre_execute', vw.pre_execute)
31 37 ip.events.register('post_execute', vw.post_execute)
38 ip.events.register('finally_execute', vw.finally_execute)
32 39
33 40
34 41 Events
@@ -64,16 +71,32 b' skipping the history/display mechanisms, in which cases ``pre_run_cell`` will no'
64 71 post_run_cell
65 72 -------------
66 73
67 ``post_run_cell`` runs after interactive execution (e.g. a cell in a notebook).
68 It can be used to cleanup or notify or perform operations on any side effects produced during execution.
69 For instance, the inline matplotlib backend uses this event to display any figures created but not explicitly displayed during the course of the cell.
70
74 ``post_run_cell`` runs after successful interactive execution (e.g. a cell in a
75 notebook, but, for example, not when a ``SyntaxError`` was raised).
76 It can be used to cleanup or notify or perform operations on any side effects
77 produced during execution.
78 For instance, the inline matplotlib backend uses this event to display any
79 figures created but not explicitly displayed during the course of the cell.
71 80
72 81 post_execute
73 82 ------------
74 83
75 84 The same as ``pre_execute``, ``post_execute`` is like ``post_run_cell``,
76 but fires for *all* executions, not just interactive ones.
85 but fires for *all* successful executions, not just interactive ones.
86
87 finally_run_cell
88 -------------
89
90 ``finally_run_cell`` is like ``post_run_cell``, but fires after *all* executions
91 (even when, for example, a ``SyntaxError`` was raised).
92 Additionally, the execution result is provided as an argument.
93
94 finally_execute
95 ------------
96
97 ``finally_execute`` is like ``post_execute``, but fires after *all* executions
98 (even when, for example, a ``SyntaxError`` was raised).
99 Additionally, the execution result is provided as an argument.
77 100
78 101
79 102 .. seealso::
@@ -6,11 +6,14 b' Execution semantics in the IPython kernel'
6 6 The execution of user code consists of the following phases:
7 7
8 8 1. Fire the ``pre_execute`` event.
9 2. Fire the ``pre_run_cell`` event unless silent is True.
9 2. Fire the ``pre_run_cell`` event unless silent is ``True``.
10 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 event.
13 5. Fire the ``post_execute`` event unless the execution failed.
14 6. Fire the ``post_run_cell`` event unless the execution failed or silent is ``True``.
15 7. Fire the ``finally_execute`` event.
16 8. Fire the ``finally_run_cell`` event unless silent is ``True``.
14 17
15 18 .. seealso::
16 19
General Comments 0
You need to be logged in to leave comments. Login now