##// END OF EJS Templates
Start of new callback system
Thomas Kluyver -
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 # unless we are silent
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_nopostexec(self):
284 def test_silent_postexec(self):
281 """run_cell(silent=True) doesn't invoke post-exec funcs"""
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