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 |
|
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