Show More
@@ -1,4 +1,4 b'' | |||||
1 | """Infrastructure for registering and firing callbacks. |
|
1 | """Infrastructure for registering and firing callbacks on application events. | |
2 |
|
2 | |||
3 | Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to |
|
3 | Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to | |
4 | be called at specific times, or a collection of alternative methods to try, |
|
4 | be called at specific times, or a collection of alternative methods to try, | |
@@ -10,13 +10,13 b' events and the arguments which will be passed to them.' | |||||
10 | """ |
|
10 | """ | |
11 | from __future__ import print_function |
|
11 | from __future__ import print_function | |
12 |
|
12 | |||
13 |
class |
|
13 | class EventManager(object): | |
14 | """Manage a collection of events and a sequence of callbacks for each. |
|
14 | """Manage a collection of events and a sequence of callbacks for each. | |
15 |
|
15 | |||
16 | This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell` |
|
16 | This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell` | |
17 | instances as a ``callbacks`` attribute. |
|
17 | instances as a ``callbacks`` attribute. | |
18 | """ |
|
18 | """ | |
19 |
def __init__(self, shell, available_ |
|
19 | def __init__(self, shell, available_events): | |
20 | """Initialise the :class:`CallbackManager`. |
|
20 | """Initialise the :class:`CallbackManager`. | |
21 |
|
21 | |||
22 | Parameters |
|
22 | Parameters | |
@@ -27,14 +27,14 b' class CallbackManager(object):' | |||||
27 | An iterable of names for callback events. |
|
27 | An iterable of names for callback events. | |
28 | """ |
|
28 | """ | |
29 | self.shell = shell |
|
29 | self.shell = shell | |
30 |
self.callbacks = {n:[] for n in available_ |
|
30 | self.callbacks = {n:[] for n in available_events} | |
31 |
|
31 | |||
32 |
def register(self, |
|
32 | def register(self, event, function): | |
33 | """Register a new callback |
|
33 | """Register a new callback | |
34 |
|
34 | |||
35 | Parameters |
|
35 | Parameters | |
36 | ---------- |
|
36 | ---------- | |
37 |
|
|
37 | event : str | |
38 | The event for which to register this callback. |
|
38 | The event for which to register this callback. | |
39 | function : callable |
|
39 | function : callable | |
40 | A function to be called on the given event. It should take the same |
|
40 | A function to be called on the given event. It should take the same | |
@@ -45,42 +45,42 b' class CallbackManager(object):' | |||||
45 | TypeError |
|
45 | TypeError | |
46 | If ``function`` is not callable. |
|
46 | If ``function`` is not callable. | |
47 | KeyError |
|
47 | KeyError | |
48 |
If `` |
|
48 | If ``event`` is not one of the known events. | |
49 | """ |
|
49 | """ | |
50 | if not callable(function): |
|
50 | if not callable(function): | |
51 | raise TypeError('Need a callable, got %r' % function) |
|
51 | raise TypeError('Need a callable, got %r' % function) | |
52 |
self.callbacks[ |
|
52 | self.callbacks[event].append(function) | |
53 |
|
53 | |||
54 |
def unregister(self, |
|
54 | def unregister(self, event, function): | |
55 | """Remove a callback from the given event.""" |
|
55 | """Remove a callback from the given event.""" | |
56 |
self.callbacks[ |
|
56 | self.callbacks[event].remove(function) | |
57 |
|
57 | |||
58 |
def reset(self, |
|
58 | def reset(self, event): | |
59 | """Clear all callbacks for the given event.""" |
|
59 | """Clear all callbacks for the given event.""" | |
60 |
self.callbacks[ |
|
60 | self.callbacks[event] = [] | |
61 |
|
61 | |||
62 | def reset_all(self): |
|
62 | def reset_all(self): | |
63 | """Clear all callbacks for all events.""" |
|
63 | """Clear all callbacks for all events.""" | |
64 | self.callbacks = {n:[] for n in self.callbacks} |
|
64 | self.callbacks = {n:[] for n in self.callbacks} | |
65 |
|
65 | |||
66 |
def |
|
66 | def trigger(self, event, *args, **kwargs): | |
67 |
"""Call callbacks for |
|
67 | """Call callbacks for ``event``. | |
68 |
|
68 | |||
69 | Any additional arguments are passed to all callbacks registered for this |
|
69 | Any additional arguments are passed to all callbacks registered for this | |
70 | event. Exceptions raised by callbacks are caught, and a message printed. |
|
70 | event. Exceptions raised by callbacks are caught, and a message printed. | |
71 | """ |
|
71 | """ | |
72 |
for func in self.callbacks[ |
|
72 | for func in self.callbacks[event]: | |
73 | try: |
|
73 | try: | |
74 | func(*args, **kwargs) |
|
74 | func(*args, **kwargs) | |
75 | except Exception: |
|
75 | except Exception: | |
76 |
print("Error in callback {} (for {}):".format(func, |
|
76 | print("Error in callback {} (for {}):".format(func, event)) | |
77 | self.shell.showtraceback() |
|
77 | self.shell.showtraceback() | |
78 |
|
78 | |||
79 | # event_name -> prototype mapping |
|
79 | # event_name -> prototype mapping | |
80 |
available_ |
|
80 | available_events = {} | |
81 |
|
81 | |||
82 | def _collect(callback_proto): |
|
82 | def _collect(callback_proto): | |
83 |
available_ |
|
83 | available_events[callback_proto.__name__] = callback_proto | |
84 | return callback_proto |
|
84 | return callback_proto | |
85 |
|
85 | |||
86 | # ------------------------------------------------------------------------------ |
|
86 | # ------------------------------------------------------------------------------ |
@@ -41,7 +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. |
|
44 | from IPython.core.events import EventManager, available_events | |
45 | from IPython.core.compilerop import CachingCompiler, check_linecache_ipython |
|
45 | from IPython.core.compilerop import CachingCompiler, check_linecache_ipython | |
46 | from IPython.core.display_trap import DisplayTrap |
|
46 | from IPython.core.display_trap import DisplayTrap | |
47 | from IPython.core.displayhook import DisplayHook |
|
47 | from IPython.core.displayhook import DisplayHook | |
@@ -468,7 +468,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
468 |
|
468 | |||
469 | self.init_syntax_highlighting() |
|
469 | self.init_syntax_highlighting() | |
470 | self.init_hooks() |
|
470 | self.init_hooks() | |
471 |
self.init_ |
|
471 | self.init_events() | |
472 | self.init_pushd_popd_magic() |
|
472 | self.init_pushd_popd_magic() | |
473 | # 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 | |
474 | # because it and init_io have to come after init_readline. |
|
474 | # because it and init_io have to come after init_readline. | |
@@ -512,7 +512,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
512 | self.init_payload() |
|
512 | self.init_payload() | |
513 | self.init_comms() |
|
513 | self.init_comms() | |
514 | self.hooks.late_startup_hook() |
|
514 | self.hooks.late_startup_hook() | |
515 |
self. |
|
515 | self.events.trigger('shell_initialised', self) | |
516 | atexit.register(self.atexit_operations) |
|
516 | atexit.register(self.atexit_operations) | |
517 |
|
517 | |||
518 | def get_ipython(self): |
|
518 | def get_ipython(self): | |
@@ -840,8 +840,8 b' class InteractiveShell(SingletonConfigurable):' | |||||
840 | # Things related to callbacks |
|
840 | # Things related to callbacks | |
841 | #------------------------------------------------------------------------- |
|
841 | #------------------------------------------------------------------------- | |
842 |
|
842 | |||
843 |
def init_ |
|
843 | def init_events(self): | |
844 |
self. |
|
844 | self.events = EventManager(self, available_events) | |
845 |
|
845 | |||
846 | def register_post_execute(self, func): |
|
846 | def register_post_execute(self, func): | |
847 | """DEPRECATED: Use ip.callbacks.register('post_execute_explicit', func) |
|
847 | """DEPRECATED: Use ip.callbacks.register('post_execute_explicit', func) | |
@@ -850,7 +850,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
850 | """ |
|
850 | """ | |
851 | warn("ip.register_post_execute is deprecated, use " |
|
851 | warn("ip.register_post_execute is deprecated, use " | |
852 | "ip.callbacks.register('post_execute_explicit', func) instead.") |
|
852 | "ip.callbacks.register('post_execute_explicit', func) instead.") | |
853 |
self. |
|
853 | self.events.register('post_execute_explicit', func) | |
854 |
|
854 | |||
855 | #------------------------------------------------------------------------- |
|
855 | #------------------------------------------------------------------------- | |
856 | # Things related to the "main" module |
|
856 | # Things related to the "main" module | |
@@ -2667,9 +2667,9 b' class InteractiveShell(SingletonConfigurable):' | |||||
2667 | if silent: |
|
2667 | if silent: | |
2668 | store_history = False |
|
2668 | store_history = False | |
2669 |
|
2669 | |||
2670 |
self. |
|
2670 | self.events.trigger('pre_execute') | |
2671 | if not silent: |
|
2671 | if not silent: | |
2672 |
self. |
|
2672 | self.events.trigger('pre_execute_explicit') | |
2673 |
|
2673 | |||
2674 | # If any of our input transformation (input_transformer_manager or |
|
2674 | # If any of our input transformation (input_transformer_manager or | |
2675 | # prefilter_manager) raises an exception, we store it in this variable |
|
2675 | # prefilter_manager) raises an exception, we store it in this variable | |
@@ -2740,9 +2740,9 b' class InteractiveShell(SingletonConfigurable):' | |||||
2740 | self.run_ast_nodes(code_ast.body, cell_name, |
|
2740 | self.run_ast_nodes(code_ast.body, cell_name, | |
2741 | interactivity=interactivity, compiler=compiler) |
|
2741 | interactivity=interactivity, compiler=compiler) | |
2742 |
|
2742 | |||
2743 |
self. |
|
2743 | self.events.trigger('post_execute') | |
2744 | if not silent: |
|
2744 | if not silent: | |
2745 |
self. |
|
2745 | self.events.trigger('post_execute_explicit') | |
2746 |
|
2746 | |||
2747 | if store_history: |
|
2747 | if store_history: | |
2748 | # Write output to the database. Does nothing unless |
|
2748 | # Write output to the database. Does nothing unless |
@@ -4,7 +4,7 b' try: # Python 3.3 +' | |||||
4 | except ImportError: |
|
4 | except ImportError: | |
5 | from mock import Mock |
|
5 | from mock import Mock | |
6 |
|
6 | |||
7 |
from IPython.core import |
|
7 | from IPython.core import events | |
8 | import IPython.testing.tools as tt |
|
8 | import IPython.testing.tools as tt | |
9 |
|
9 | |||
10 | def ping_received(): |
|
10 | def ping_received(): | |
@@ -12,35 +12,35 b' def ping_received():' | |||||
12 |
|
12 | |||
13 | class CallbackTests(unittest.TestCase): |
|
13 | class CallbackTests(unittest.TestCase): | |
14 | def setUp(self): |
|
14 | def setUp(self): | |
15 |
self. |
|
15 | self.em = events.EventManager(get_ipython(), {'ping_received': ping_received}) | |
16 |
|
16 | |||
17 | def test_register_unregister(self): |
|
17 | def test_register_unregister(self): | |
18 | cb = Mock() |
|
18 | cb = Mock() | |
19 |
|
19 | |||
20 |
self. |
|
20 | self.em.register('ping_received', cb) | |
21 |
self. |
|
21 | self.em.trigger('ping_received') | |
22 | self.assertEqual(cb.call_count, 1) |
|
22 | self.assertEqual(cb.call_count, 1) | |
23 |
|
23 | |||
24 |
self. |
|
24 | self.em.unregister('ping_received', cb) | |
25 |
self. |
|
25 | self.em.trigger('ping_received') | |
26 | self.assertEqual(cb.call_count, 1) |
|
26 | self.assertEqual(cb.call_count, 1) | |
27 |
|
27 | |||
28 | def test_reset(self): |
|
28 | def test_reset(self): | |
29 | cb = Mock() |
|
29 | cb = Mock() | |
30 |
self. |
|
30 | self.em.register('ping_received', cb) | |
31 |
self. |
|
31 | self.em.reset('ping_received') | |
32 |
self. |
|
32 | self.em.trigger('ping_received') | |
33 | assert not cb.called |
|
33 | assert not cb.called | |
34 |
|
34 | |||
35 | def test_reset_all(self): |
|
35 | def test_reset_all(self): | |
36 | cb = Mock() |
|
36 | cb = Mock() | |
37 |
self. |
|
37 | self.em.register('ping_received', cb) | |
38 |
self. |
|
38 | self.em.reset_all() | |
39 |
self. |
|
39 | self.em.trigger('ping_received') | |
40 | assert not cb.called |
|
40 | assert not cb.called | |
41 |
|
41 | |||
42 | def test_cb_error(self): |
|
42 | def test_cb_error(self): | |
43 | cb = Mock(side_effect=ValueError) |
|
43 | cb = Mock(side_effect=ValueError) | |
44 |
self. |
|
44 | self.em.register('ping_received', cb) | |
45 | with tt.AssertPrints("Error in callback"): |
|
45 | with tt.AssertPrints("Error in callback"): | |
46 |
self. |
|
46 | self.em.trigger('ping_received') No newline at end of file |
@@ -288,10 +288,10 b' class InteractiveShellTestCase(unittest.TestCase):' | |||||
288 | post_explicit = mock.Mock() |
|
288 | post_explicit = mock.Mock() | |
289 | post_always = mock.Mock() |
|
289 | post_always = mock.Mock() | |
290 |
|
290 | |||
291 |
ip. |
|
291 | ip.events.register('pre_execute_explicit', pre_explicit) | |
292 |
ip. |
|
292 | ip.events.register('pre_execute', pre_always) | |
293 |
ip. |
|
293 | ip.events.register('post_execute_explicit', post_explicit) | |
294 |
ip. |
|
294 | ip.events.register('post_execute', post_always) | |
295 |
|
295 | |||
296 | try: |
|
296 | try: | |
297 | ip.run_cell("1", silent=True) |
|
297 | ip.run_cell("1", silent=True) | |
@@ -306,7 +306,7 b' class InteractiveShellTestCase(unittest.TestCase):' | |||||
306 | assert post_explicit.called |
|
306 | assert post_explicit.called | |
307 | finally: |
|
307 | finally: | |
308 | # remove post-exec |
|
308 | # remove post-exec | |
309 |
ip. |
|
309 | ip.events.reset_all() | |
310 |
|
310 | |||
311 | def test_silent_noadvance(self): |
|
311 | def test_silent_noadvance(self): | |
312 | """run_cell(silent=True) doesn't advance execution_count""" |
|
312 | """run_cell(silent=True) doesn't advance execution_count""" |
@@ -502,4 +502,4 b' def load_ipython_extension(ip):' | |||||
502 | """Load the extension in IPython.""" |
|
502 | """Load the extension in IPython.""" | |
503 | auto_reload = AutoreloadMagics(ip) |
|
503 | auto_reload = AutoreloadMagics(ip) | |
504 | ip.register_magics(auto_reload) |
|
504 | ip.register_magics(auto_reload) | |
505 |
ip. |
|
505 | ip.events.register('pre_execute_explicit', auto_reload.pre_execute_explicit) |
@@ -23,7 +23,7 b' import nose.tools as nt' | |||||
23 | import IPython.testing.tools as tt |
|
23 | import IPython.testing.tools as tt | |
24 |
|
24 | |||
25 | from IPython.extensions.autoreload import AutoreloadMagics |
|
25 | from IPython.extensions.autoreload import AutoreloadMagics | |
26 |
from IPython.core. |
|
26 | from IPython.core.events import EventManager, pre_execute_explicit | |
27 | from IPython.utils.py3compat import PY3 |
|
27 | from IPython.utils.py3compat import PY3 | |
28 |
|
28 | |||
29 | if PY3: |
|
29 | if PY3: | |
@@ -41,14 +41,14 b' class FakeShell(object):' | |||||
41 |
|
41 | |||
42 | def __init__(self): |
|
42 | def __init__(self): | |
43 | self.ns = {} |
|
43 | self.ns = {} | |
44 |
self. |
|
44 | self.events = EventManager(self, {'pre_execute_explicit', pre_execute_explicit}) | |
45 | self.auto_magics = AutoreloadMagics(shell=self) |
|
45 | self.auto_magics = AutoreloadMagics(shell=self) | |
46 |
self. |
|
46 | self.events.register('pre_execute_explicit', self.auto_magics.pre_execute_explicit) | |
47 |
|
47 | |||
48 | register_magics = set_hook = noop |
|
48 | register_magics = set_hook = noop | |
49 |
|
49 | |||
50 | def run_code(self, code): |
|
50 | def run_code(self, code): | |
51 |
self. |
|
51 | self.events.trigger('pre_execute_explicit') | |
52 | exec(code, self.ns) |
|
52 | exec(code, self.ns) | |
53 |
|
53 | |||
54 | def push(self, items): |
|
54 | def push(self, items): |
@@ -134,9 +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. |
|
137 | self.shell.events.trigger('pre_execute') | |
138 | self._msg_callback(msg) |
|
138 | self._msg_callback(msg) | |
139 |
self.shell. |
|
139 | self.shell.events.trigger('post_execute') | |
140 |
|
140 | |||
141 |
|
141 | |||
142 | __all__ = ['Comm'] |
|
142 | __all__ = ['Comm'] |
General Comments 0
You need to be logged in to leave comments.
Login now