##// END OF EJS Templates
more backcall removal
Matthias Bussonnier -
Show More
@@ -1,155 +1,154 b''
1 1 """Infrastructure for registering and firing callbacks on application events.
2 2
3 3 Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to
4 4 be called at specific times, or a collection of alternative methods to try,
5 5 callbacks are designed to be used by extension authors. A number of callbacks
6 6 can be registered for the same event without needing to be aware of one another.
7 7
8 8 The functions defined in this module are no-ops indicating the names of available
9 9 events and the arguments which will be passed to them.
10 10
11 11 .. note::
12 12
13 13 This API is experimental in IPython 2.0, and may be revised in future versions.
14 14 """
15 15
16 16
17 17 class EventManager(object):
18 18 """Manage a collection of events and a sequence of callbacks for each.
19 19
20 20 This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell`
21 21 instances as an ``events`` attribute.
22 22
23 23 .. note::
24 24
25 25 This API is experimental in IPython 2.0, and may be revised in future versions.
26 26 """
27 27
28 28 def __init__(self, shell, available_events, print_on_error=True):
29 29 """Initialise the :class:`CallbackManager`.
30 30
31 31 Parameters
32 32 ----------
33 33 shell
34 34 The :class:`~IPython.core.interactiveshell.InteractiveShell` instance
35 35 available_events
36 36 An iterable of names for callback events.
37 37 print_on_error:
38 38 A boolean flag to set whether the EventManager will print a warning which a event errors.
39 39 """
40 40 self.shell = shell
41 41 self.callbacks = {n:[] for n in available_events}
42 42 self.print_on_error = print_on_error
43 43
44 44 def register(self, event, function):
45 45 """Register a new event callback.
46 46
47 47 Parameters
48 48 ----------
49 49 event : str
50 50 The event for which to register this callback.
51 51 function : callable
52 52 A function to be called on the given event. It should take the same
53 53 parameters as the appropriate callback prototype.
54 54
55 55 Raises
56 56 ------
57 57 TypeError
58 58 If ``function`` is not callable.
59 59 KeyError
60 60 If ``event`` is not one of the known events.
61 61 """
62 62 if not callable(function):
63 63 raise TypeError('Need a callable, got %r' % function)
64 64 if function not in self.callbacks[event]:
65 65 self.callbacks[event].append(function)
66 66
67 67 def unregister(self, event, function):
68 68 """Remove a callback from the given event."""
69 69 if function in self.callbacks[event]:
70 70 return self.callbacks[event].remove(function)
71 71
72 72 raise ValueError('Function {!r} is not registered as a {} callback'.format(function, event))
73 73
74 74 def trigger(self, event, *args, **kwargs):
75 75 """Call callbacks for ``event``.
76 76
77 77 Any additional arguments are passed to all callbacks registered for this
78 78 event. Exceptions raised by callbacks are caught, and a message printed.
79 79 """
80 80 for func in self.callbacks[event][:]:
81 81 try:
82 82 func(*args, **kwargs)
83 83 except (Exception, KeyboardInterrupt):
84 84 if self.print_on_error:
85 85 print("Error in callback {} (for {}):".format(func, event))
86 86 self.shell.showtraceback()
87 87
88 88 # event_name -> prototype mapping
89 89 available_events = {}
90 90
91 91 def _define_event(callback_function):
92 callback_proto = callback_prototype(callback_function)
93 available_events[callback_function.__name__] = callback_proto
94 return callback_proto
92 available_events[callback_function.__name__] = callback_function
93 return callback_function
95 94
96 95 # ------------------------------------------------------------------------------
97 96 # Callback prototypes
98 97 #
99 98 # No-op functions which describe the names of available events and the
100 99 # signatures of callbacks for those events.
101 100 # ------------------------------------------------------------------------------
102 101
103 102 @_define_event
104 103 def pre_execute():
105 104 """Fires before code is executed in response to user/frontend action.
106 105
107 106 This includes comm and widget messages and silent execution, as well as user
108 107 code cells.
109 108 """
110 109 pass
111 110
112 111 @_define_event
113 112 def pre_run_cell(info):
114 113 """Fires before user-entered code runs.
115 114
116 115 Parameters
117 116 ----------
118 117 info : :class:`~IPython.core.interactiveshell.ExecutionInfo`
119 118 An object containing information used for the code execution.
120 119 """
121 120 pass
122 121
123 122 @_define_event
124 123 def post_execute():
125 124 """Fires after code is executed in response to user/frontend action.
126 125
127 126 This includes comm and widget messages and silent execution, as well as user
128 127 code cells.
129 128 """
130 129 pass
131 130
132 131 @_define_event
133 132 def post_run_cell(result):
134 133 """Fires after user-entered code runs.
135 134
136 135 Parameters
137 136 ----------
138 137 result : :class:`~IPython.core.interactiveshell.ExecutionResult`
139 138 The object which will be returned as the execution result.
140 139 """
141 140 pass
142 141
143 142 @_define_event
144 143 def shell_initialized(ip):
145 144 """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`.
146 145
147 146 This is before extensions and startup scripts are loaded, so it can only be
148 147 set by subclassing.
149 148
150 149 Parameters
151 150 ----------
152 151 ip : :class:`~IPython.core.interactiveshell.InteractiveShell`
153 152 The newly initialised shell.
154 153 """
155 154 pass
@@ -1,91 +1,78 b''
1 1 import unittest
2 2 from unittest.mock import Mock
3 3
4 4 from IPython.core import events
5 5 import IPython.testing.tools as tt
6 6
7 7
8 8 @events._define_event
9 9 def ping_received():
10 10 pass
11 11
12 12
13 13 @events._define_event
14 14 def event_with_argument(argument):
15 15 pass
16 16
17 17
18 18 class CallbackTests(unittest.TestCase):
19 19 def setUp(self):
20 20 self.em = events.EventManager(get_ipython(),
21 21 {'ping_received': ping_received,
22 22 'event_with_argument': event_with_argument})
23 23
24 24 def test_register_unregister(self):
25 25 cb = Mock()
26 26
27 27 self.em.register('ping_received', cb)
28 28 self.em.trigger('ping_received')
29 29 self.assertEqual(cb.call_count, 1)
30 30
31 31 self.em.unregister('ping_received', cb)
32 32 self.em.trigger('ping_received')
33 33 self.assertEqual(cb.call_count, 1)
34 34
35 35 def test_bare_function_missed_unregister(self):
36 36 def cb1():
37 37 ...
38 38
39 39 def cb2():
40 40 ...
41 41
42 42 self.em.register("ping_received", cb1)
43 43 self.assertRaises(ValueError, self.em.unregister, "ping_received", cb2)
44 44 self.em.unregister("ping_received", cb1)
45 45
46 46 def test_cb_error(self):
47 47 cb = Mock(side_effect=ValueError)
48 48 self.em.register('ping_received', cb)
49 49 with tt.AssertPrints("Error in callback"):
50 50 self.em.trigger('ping_received')
51 51
52 52 def test_cb_keyboard_interrupt(self):
53 53 cb = Mock(side_effect=KeyboardInterrupt)
54 54 self.em.register('ping_received', cb)
55 55 with tt.AssertPrints("Error in callback"):
56 56 self.em.trigger('ping_received')
57 57
58 58 def test_unregister_during_callback(self):
59 59 invoked = [False] * 3
60 60
61 61 def func1(*_):
62 62 invoked[0] = True
63 63 self.em.unregister('ping_received', func1)
64 64 self.em.register('ping_received', func3)
65 65
66 66 def func2(*_):
67 67 invoked[1] = True
68 68 self.em.unregister('ping_received', func2)
69 69
70 70 def func3(*_):
71 71 invoked[2] = True
72 72
73 73 self.em.register('ping_received', func1)
74 74 self.em.register('ping_received', func2)
75 75
76 76 self.em.trigger('ping_received')
77 77 self.assertEqual([True, True, False], invoked)
78 78 self.assertEqual([func3], self.em.callbacks['ping_received'])
79
80 def test_ignore_event_arguments_if_no_argument_required(self):
81 call_count = [0]
82 def event_with_no_argument():
83 call_count[0] += 1
84
85 self.em.register('event_with_argument', event_with_no_argument)
86 self.em.trigger('event_with_argument', 'the argument')
87 self.assertEqual(call_count[0], 1)
88
89 self.em.unregister('event_with_argument', event_with_no_argument)
90 self.em.trigger('ping_received')
91 self.assertEqual(call_count[0], 1)
General Comments 0
You need to be logged in to leave comments. Login now