Show More
@@ -0,0 +1,57 b'' | |||||
|
1 | from __future__ import print_function | |||
|
2 | ||||
|
3 | class CallbackManager(object): | |||
|
4 | def __init__(self, shell, available_callbacks): | |||
|
5 | self.shell = shell | |||
|
6 | self.callbacks = {n:[] for n in available_callbacks} | |||
|
7 | ||||
|
8 | def register(self, name, function): | |||
|
9 | if not callable(function): | |||
|
10 | raise TypeError('Need a callable, got %r' % function) | |||
|
11 | self.callbacks[name].append(function) | |||
|
12 | ||||
|
13 | def unregister(self, name, function): | |||
|
14 | self.callbacks[name].remove(function) | |||
|
15 | ||||
|
16 | def reset(self, name): | |||
|
17 | self.callbacks[name] = [] | |||
|
18 | ||||
|
19 | def reset_all(self): | |||
|
20 | self.callbacks = {n:[] for n in self.callbacks} | |||
|
21 | ||||
|
22 | def fire(self, name, *args, **kwargs): | |||
|
23 | for func in self.callbacks[name]: | |||
|
24 | try: | |||
|
25 | func(*args, **kwargs) | |||
|
26 | except Exception: | |||
|
27 | print("Error in callback {} (for {}):".format(func, name)) | |||
|
28 | self.shell.showtraceback() | |||
|
29 | ||||
|
30 | available_callbacks = {} | |||
|
31 | def _collect(callback_proto): | |||
|
32 | available_callbacks[callback_proto.__name__] = callback_proto | |||
|
33 | return callback_proto | |||
|
34 | ||||
|
35 | @_collect | |||
|
36 | def pre_execute(): | |||
|
37 | """Fires before code is executed in response to user/frontend action. | |||
|
38 | ||||
|
39 | This includes comm and widget messages.""" | |||
|
40 | pass | |||
|
41 | ||||
|
42 | @_collect | |||
|
43 | def pre_execute_explicit(): | |||
|
44 | """Fires before user-entered code runs.""" | |||
|
45 | pass | |||
|
46 | ||||
|
47 | @_collect | |||
|
48 | def post_execute(): | |||
|
49 | """Fires after code is executed in response to user/frontend action. | |||
|
50 | ||||
|
51 | This includes comm and widget messages.""" | |||
|
52 | pass | |||
|
53 | ||||
|
54 | @_collect | |||
|
55 | def post_execute_explicit(): | |||
|
56 | """Fires after user-entered code runs.""" | |||
|
57 | pass No newline at end of file |
@@ -41,6 +41,7 b' from IPython.core import ultratb' | |||||
41 | from IPython.core.alias import AliasManager, AliasError |
|
41 | from IPython.core.alias import AliasManager, AliasError | |
42 | from IPython.core.autocall import ExitAutocall |
|
42 | from IPython.core.autocall import ExitAutocall | |
43 | from IPython.core.builtin_trap import BuiltinTrap |
|
43 | from IPython.core.builtin_trap import BuiltinTrap | |
|
44 | from IPython.core.callbacks import CallbackManager, available_callbacks | |||
44 | from IPython.core.compilerop import CachingCompiler, check_linecache_ipython |
|
45 | from IPython.core.compilerop import CachingCompiler, check_linecache_ipython | |
45 | from IPython.core.display_trap import DisplayTrap |
|
46 | from IPython.core.display_trap import DisplayTrap | |
46 | from IPython.core.displayhook import DisplayHook |
|
47 | from IPython.core.displayhook import DisplayHook | |
@@ -467,6 +468,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
467 |
|
468 | |||
468 | self.init_syntax_highlighting() |
|
469 | self.init_syntax_highlighting() | |
469 | self.init_hooks() |
|
470 | self.init_hooks() | |
|
471 | self.init_callbacks() | |||
470 | self.init_pushd_popd_magic() |
|
472 | self.init_pushd_popd_magic() | |
471 | # self.init_traceback_handlers use to be here, but we moved it below |
|
473 | # self.init_traceback_handlers use to be here, but we moved it below | |
472 | # because it and init_io have to come after init_readline. |
|
474 | # because it and init_io have to come after init_readline. | |
@@ -827,12 +829,21 b' class InteractiveShell(SingletonConfigurable):' | |||||
827 |
|
829 | |||
828 | setattr(self.hooks,name, dp) |
|
830 | setattr(self.hooks,name, dp) | |
829 |
|
831 | |||
|
832 | #------------------------------------------------------------------------- | |||
|
833 | # Things related to callbacks | |||
|
834 | #------------------------------------------------------------------------- | |||
|
835 | ||||
|
836 | def init_callbacks(self): | |||
|
837 | self.callbacks = CallbackManager(self, available_callbacks) | |||
|
838 | ||||
830 | def register_post_execute(self, func): |
|
839 | def register_post_execute(self, func): | |
831 | """Register a function for calling after code execution. |
|
840 | """DEPRECATED: Use ip.callbacks.register('post_execute_explicit', func) | |
|
841 | ||||
|
842 | Register a function for calling after code execution. | |||
832 | """ |
|
843 | """ | |
833 | if not callable(func): |
|
844 | warn("ip.register_post_execute is deprecated, use " | |
834 | raise ValueError('argument %s must be callable' % func) |
|
845 | "ip.callbacks.register('post_execute_explicit', func) instead.") | |
835 | self._post_execute[func] = True |
|
846 | self.callbacks.register('post_execute_explicit', func) | |
836 |
|
847 | |||
837 | #------------------------------------------------------------------------- |
|
848 | #------------------------------------------------------------------------- | |
838 | # Things related to the "main" module |
|
849 | # Things related to the "main" module | |
@@ -2649,6 +2660,10 b' class InteractiveShell(SingletonConfigurable):' | |||||
2649 | if silent: |
|
2660 | if silent: | |
2650 | store_history = False |
|
2661 | store_history = False | |
2651 |
|
2662 | |||
|
2663 | self.callbacks.fire('pre_execute') | |||
|
2664 | if not silent: | |||
|
2665 | self.callbacks.fire('pre_execute_explicit') | |||
|
2666 | ||||
2652 | # If any of our input transformation (input_transformer_manager or |
|
2667 | # If any of our input transformation (input_transformer_manager or | |
2653 | # prefilter_manager) raises an exception, we store it in this variable |
|
2668 | # prefilter_manager) raises an exception, we store it in this variable | |
2654 | # so that we can display the error after logging the input and storing |
|
2669 | # so that we can display the error after logging the input and storing | |
@@ -2717,28 +2732,10 b' class InteractiveShell(SingletonConfigurable):' | |||||
2717 | interactivity = "none" if silent else self.ast_node_interactivity |
|
2732 | interactivity = "none" if silent else self.ast_node_interactivity | |
2718 | self.run_ast_nodes(code_ast.body, cell_name, |
|
2733 | self.run_ast_nodes(code_ast.body, cell_name, | |
2719 | interactivity=interactivity, compiler=compiler) |
|
2734 | interactivity=interactivity, compiler=compiler) | |
2720 |
|
2735 | |||
2721 | # Execute any registered post-execution functions. |
|
2736 | self.callbacks.fire('post_execute') | |
2722 |
|
|
2737 | if not silent: | |
2723 | post_exec = [] if silent else iteritems(self._post_execute) |
|
2738 | self.callbacks.fire('post_execute_explicit') | |
2724 |
|
||||
2725 | for func, status in post_exec: |
|
|||
2726 | if self.disable_failing_post_execute and not status: |
|
|||
2727 | continue |
|
|||
2728 | try: |
|
|||
2729 | func() |
|
|||
2730 | except KeyboardInterrupt: |
|
|||
2731 | print("\nKeyboardInterrupt", file=io.stderr) |
|
|||
2732 | except Exception: |
|
|||
2733 | # register as failing: |
|
|||
2734 | self._post_execute[func] = False |
|
|||
2735 | self.showtraceback() |
|
|||
2736 | print('\n'.join([ |
|
|||
2737 | "post-execution function %r produced an error." % func, |
|
|||
2738 | "If this problem persists, you can disable failing post-exec functions with:", |
|
|||
2739 | "", |
|
|||
2740 | " get_ipython().disable_failing_post_execute = True" |
|
|||
2741 | ]), file=io.stderr) |
|
|||
2742 |
|
2739 | |||
2743 | if store_history: |
|
2740 | if store_history: | |
2744 | # Write output to the database. Does nothing unless |
|
2741 | # Write output to the database. Does nothing unless |
@@ -27,6 +27,10 b' import shutil' | |||||
27 | import sys |
|
27 | import sys | |
28 | import tempfile |
|
28 | import tempfile | |
29 | import unittest |
|
29 | import unittest | |
|
30 | try: | |||
|
31 | from unittest import mock | |||
|
32 | except ImportError: | |||
|
33 | import mock | |||
30 | from os.path import join |
|
34 | from os.path import join | |
31 |
|
35 | |||
32 | # third-party |
|
36 | # third-party | |
@@ -277,21 +281,32 b' class InteractiveShellTestCase(unittest.TestCase):' | |||||
277 | # ZeroDivisionError |
|
281 | # ZeroDivisionError | |
278 | self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}") |
|
282 | self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}") | |
279 |
|
283 | |||
280 |
def test_silent_ |
|
284 | def test_silent_postexec(self): | |
281 |
"""run_cell(silent=True) doesn't invoke p |
|
285 | """run_cell(silent=True) doesn't invoke pre/post_execute_explicit callbacks""" | |
282 | d = dict(called=False) |
|
286 | pre_explicit = mock.Mock() | |
283 | def set_called(): |
|
287 | pre_always = mock.Mock() | |
284 | d['called'] = True |
|
288 | post_explicit = mock.Mock() | |
|
289 | post_always = mock.Mock() | |||
285 |
|
290 | |||
286 | ip.register_post_execute(set_called) |
|
291 | ip.callbacks.register('pre_execute_explicit', pre_explicit) | |
287 | ip.run_cell("1", silent=True) |
|
292 | ip.callbacks.register('pre_execute', pre_always) | |
288 | self.assertFalse(d['called']) |
|
293 | ip.callbacks.register('post_execute_explicit', post_explicit) | |
289 | # double-check that non-silent exec did what we expected |
|
294 | ip.callbacks.register('post_execute', post_always) | |
290 | # silent to avoid |
|
295 | ||
291 | ip.run_cell("1") |
|
296 | try: | |
292 | self.assertTrue(d['called']) |
|
297 | ip.run_cell("1", silent=True) | |
293 | # remove post-exec |
|
298 | assert pre_always.called | |
294 | ip._post_execute.pop(set_called) |
|
299 | assert not pre_explicit.called | |
|
300 | assert post_always.called | |||
|
301 | assert not post_explicit.called | |||
|
302 | # double-check that non-silent exec did what we expected | |||
|
303 | # silent to avoid | |||
|
304 | ip.run_cell("1") | |||
|
305 | assert pre_explicit.called | |||
|
306 | assert post_explicit.called | |||
|
307 | finally: | |||
|
308 | # remove post-exec | |||
|
309 | ip.callbacks.reset_all() | |||
295 |
|
310 | |||
296 | def test_silent_noadvance(self): |
|
311 | def test_silent_noadvance(self): | |
297 | """run_cell(silent=True) doesn't advance execution_count""" |
|
312 | """run_cell(silent=True) doesn't advance execution_count""" |
@@ -134,7 +134,9 b' class Comm(LoggingConfigurable):' | |||||
134 | """Handle a comm_msg message""" |
|
134 | """Handle a comm_msg message""" | |
135 | self.log.debug("handle_msg[%s](%s)", self.comm_id, msg) |
|
135 | self.log.debug("handle_msg[%s](%s)", self.comm_id, msg) | |
136 | if self._msg_callback: |
|
136 | if self._msg_callback: | |
|
137 | self.shell.callbacks.fire('pre_execute') | |||
137 | self._msg_callback(msg) |
|
138 | self._msg_callback(msg) | |
|
139 | self.shell.callbacks.fire('post_execute') | |||
138 |
|
140 | |||
139 |
|
141 | |||
140 | __all__ = ['Comm'] |
|
142 | __all__ = ['Comm'] |
General Comments 0
You need to be logged in to leave comments.
Login now