##// 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 pass
116 pass
117
117
118 @_define_event
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 def shell_initialized(ip):
142 def shell_initialized(ip):
120 """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`.
143 """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`.
121
144
@@ -1879,7 +1879,7 b' class InteractiveShell(SingletonConfigurable):'
1879 # This is overridden in TerminalInteractiveShell to show a message about
1879 # This is overridden in TerminalInteractiveShell to show a message about
1880 # the %paste magic.
1880 # the %paste magic.
1881 def showindentationerror(self):
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 at the prompt.
1883 at the prompt.
1884
1884
1885 This is overridden in TerminalInteractiveShell to show a message about
1885 This is overridden in TerminalInteractiveShell to show a message about
@@ -2621,6 +2621,29 b' class InteractiveShell(SingletonConfigurable):'
2621 -------
2621 -------
2622 result : :class:`ExecutionResult`
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 result = ExecutionResult()
2647 result = ExecutionResult()
2625
2648
2626 if (not raw_cell) or raw_cell.isspace():
2649 if (not raw_cell) or raw_cell.isspace():
@@ -263,11 +263,17 b' class InteractiveShellTestCase(unittest.TestCase):'
263 pre_always = mock.Mock()
263 pre_always = mock.Mock()
264 post_explicit = mock.Mock()
264 post_explicit = mock.Mock()
265 post_always = mock.Mock()
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 ip.events.register('pre_run_cell', pre_explicit)
271 ip.events.register('pre_run_cell', pre_explicit)
268 ip.events.register('pre_execute', pre_always)
272 ip.events.register('pre_execute', pre_always)
269 ip.events.register('post_run_cell', post_explicit)
273 ip.events.register('post_run_cell', post_explicit)
270 ip.events.register('post_execute', post_always)
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 try:
278 try:
273 ip.run_cell("1", silent=True)
279 ip.run_cell("1", silent=True)
@@ -275,17 +281,31 b' class InteractiveShellTestCase(unittest.TestCase):'
275 assert not pre_explicit.called
281 assert not pre_explicit.called
276 assert post_always.called
282 assert post_always.called
277 assert not post_explicit.called
283 assert not post_explicit.called
284 assert finally_always.called
285 assert not finally_explicit.called
278 # double-check that non-silent exec did what we expected
286 # double-check that non-silent exec did what we expected
279 # silent to avoid
287 # silent to avoid
280 ip.run_cell("1")
288 ip.run_cell("1")
281 assert pre_explicit.called
289 assert pre_explicit.called
282 assert post_explicit.called
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 finally:
301 finally:
284 # remove post-exec
302 # remove post-exec
285 ip.events.unregister('pre_run_cell', pre_explicit)
303 ip.events.unregister('pre_run_cell', pre_explicit)
286 ip.events.unregister('pre_execute', pre_always)
304 ip.events.unregister('pre_execute', pre_always)
287 ip.events.unregister('post_run_cell', post_explicit)
305 ip.events.unregister('post_run_cell', post_explicit)
288 ip.events.unregister('post_execute', post_always)
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 def test_silent_noadvance(self):
310 def test_silent_noadvance(self):
291 """run_cell(silent=True) doesn't advance execution_count"""
311 """run_cell(silent=True) doesn't advance execution_count"""
@@ -24,11 +24,18 b' For example::'
24 def post_execute(self):
24 def post_execute(self):
25 if self.shell.user_ns.get('x', None) != self.last_x:
25 if self.shell.user_ns.get('x', None) != self.last_x:
26 print("x changed!")
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 def load_ipython_extension(ip):
34 def load_ipython_extension(ip):
29 vw = VarWatcher(ip)
35 vw = VarWatcher(ip)
30 ip.events.register('pre_execute', vw.pre_execute)
36 ip.events.register('pre_execute', vw.pre_execute)
31 ip.events.register('post_execute', vw.post_execute)
37 ip.events.register('post_execute', vw.post_execute)
38 ip.events.register('finally_execute', vw.finally_execute)
32
39
33
40
34 Events
41 Events
@@ -64,16 +71,32 b' skipping the history/display mechanisms, in which cases ``pre_run_cell`` will no'
64 post_run_cell
71 post_run_cell
65 -------------
72 -------------
66
73
67 ``post_run_cell`` runs after interactive execution (e.g. a cell in a notebook).
74 ``post_run_cell`` runs after successful interactive execution (e.g. a cell in a
68 It can be used to cleanup or notify or perform operations on any side effects produced during execution.
75 notebook, but, for example, not when a ``SyntaxError`` was raised).
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.
76 It can be used to cleanup or notify or perform operations on any side effects
70
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 post_execute
81 post_execute
73 ------------
82 ------------
74
83
75 The same as ``pre_execute``, ``post_execute`` is like ``post_run_cell``,
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 .. seealso::
102 .. seealso::
@@ -6,11 +6,14 b' Execution semantics in the IPython kernel'
6 The execution of user code consists of the following phases:
6 The execution of user code consists of the following phases:
7
7
8 1. Fire the ``pre_execute`` event.
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 3. Execute the ``code`` field, see below for details.
10 3. Execute the ``code`` field, see below for details.
11 4. If execution succeeds, expressions in ``user_expressions`` are computed.
11 4. If execution succeeds, expressions in ``user_expressions`` are computed.
12 This ensures that any error in the expressions don't affect the main code execution.
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 .. seealso::
18 .. seealso::
16
19
General Comments 0
You need to be logged in to leave comments. Login now