Show More
@@ -40,7 +40,7 b' class EventManager(object):' | |||
|
40 | 40 | self.callbacks = {n:[] for n in available_events} |
|
41 | 41 | |
|
42 | 42 | def register(self, event, function): |
|
43 | """Register a new event callback | |
|
43 | """Register a new event callback. | |
|
44 | 44 | |
|
45 | 45 | Parameters |
|
46 | 46 | ---------- |
@@ -59,12 +59,21 b' class EventManager(object):' | |||
|
59 | 59 | """ |
|
60 | 60 | if not callable(function): |
|
61 | 61 | raise TypeError('Need a callable, got %r' % function) |
|
62 | self.callbacks[event].append(_adapt_function(event, function)) | |
|
62 | callback_proto = available_events.get(event) | |
|
63 | self.callbacks[event].append(callback_proto.adapt(function)) | |
|
63 | 64 | |
|
64 | 65 | def unregister(self, event, function): |
|
65 | 66 | """Remove a callback from the given event.""" |
|
66 | self.callbacks[event].remove(_adapt_function(event, function)) | |
|
67 | ||
|
67 | if function in self.callbacks[event]: | |
|
68 | return self.callbacks[event].remove(function) | |
|
69 | ||
|
70 | # Remove callback in case ``function`` was adapted by `backcall`. | |
|
71 | for callback in self.callbacks[event]: | |
|
72 | if callback.__wrapped__ is function: | |
|
73 | return self.callbacks[event].remove(callback) | |
|
74 | ||
|
75 | raise ValueError('Function {!r} is not registered as a {} callback'.format(function, event)) | |
|
76 | ||
|
68 | 77 | def trigger(self, event, *args, **kwargs): |
|
69 | 78 | """Call callbacks for ``event``. |
|
70 | 79 | |
@@ -81,30 +90,11 b' class EventManager(object):' | |||
|
81 | 90 | # event_name -> prototype mapping |
|
82 | 91 | available_events = {} |
|
83 | 92 | |
|
84 | # (event, function) -> adapted function mapping | |
|
85 | adapted_functions = {} | |
|
86 | ||
|
87 | ||
|
88 | def _define_event(callback_proto): | |
|
89 | available_events[callback_proto.__name__] = callback_proto | |
|
93 | def _define_event(callback_function): | |
|
94 | callback_proto = callback_prototype(callback_function) | |
|
95 | available_events[callback_function.__name__] = callback_proto | |
|
90 | 96 | return callback_proto |
|
91 | 97 | |
|
92 | ||
|
93 | def _adapt_function(event, function): | |
|
94 | """Adapts and caches a function using `backcall` to provide compatibility. | |
|
95 | ||
|
96 | Function adaptations depend not only on the function but also on the event, | |
|
97 | as events may expect different arguments (e.g. `request` vs. `result`). | |
|
98 | Hence, `(event, function)` is used as the cache key. | |
|
99 | """ | |
|
100 | if (event, function) in adapted_functions: | |
|
101 | return adapted_functions[(event, function)] | |
|
102 | callback_proto = available_events.get(event) | |
|
103 | adapted_function = callback_proto.adapt(function) | |
|
104 | adapted_functions[(event, function)] = adapted_function | |
|
105 | return adapted_function | |
|
106 | ||
|
107 | ||
|
108 | 98 | # ------------------------------------------------------------------------------ |
|
109 | 99 | # Callback prototypes |
|
110 | 100 | # |
@@ -113,49 +103,35 b' def _adapt_function(event, function):' | |||
|
113 | 103 | # ------------------------------------------------------------------------------ |
|
114 | 104 | |
|
115 | 105 | @_define_event |
|
116 | @callback_prototype | |
|
117 | def pre_execute(request): | |
|
106 | def pre_execute(): | |
|
118 | 107 | """Fires before code is executed in response to user/frontend action. |
|
119 | 108 | |
|
120 | 109 | This includes comm and widget messages and silent execution, as well as user |
|
121 | 110 | code cells. |
|
122 | ||
|
123 | Parameters | |
|
124 | ---------- | |
|
125 | request : :class:`~IPython.core.interactiveshell.ExecutionRequest` | |
|
126 | The object representing the code execution request. | |
|
127 | 111 | """ |
|
128 | 112 | pass |
|
129 | 113 | |
|
130 | 114 | @_define_event |
|
131 | @callback_prototype | |
|
132 | def pre_run_cell(request): | |
|
115 | def pre_run_cell(info): | |
|
133 | 116 | """Fires before user-entered code runs. |
|
134 | 117 | |
|
135 | 118 | Parameters |
|
136 | 119 | ---------- |
|
137 |
|
|
|
138 |
|
|
|
120 | info : :class:`~IPython.core.interactiveshell.ExecutionInfo` | |
|
121 | An object containing information used for the code execution. | |
|
139 | 122 | """ |
|
140 | 123 | pass |
|
141 | 124 | |
|
142 | 125 | @_define_event |
|
143 | @callback_prototype | |
|
144 | def post_execute(result): | |
|
126 | def post_execute(): | |
|
145 | 127 | """Fires after code is executed in response to user/frontend action. |
|
146 | 128 | |
|
147 | 129 | This includes comm and widget messages and silent execution, as well as user |
|
148 | 130 | code cells. |
|
149 | ||
|
150 | Parameters | |
|
151 | ---------- | |
|
152 | result : :class:`~IPython.core.interactiveshell.ExecutionResult` | |
|
153 | The object which will be returned as the execution result. | |
|
154 | 131 | """ |
|
155 | 132 | pass |
|
156 | 133 | |
|
157 | 134 | @_define_event |
|
158 | @callback_prototype | |
|
159 | 135 | def post_run_cell(result): |
|
160 | 136 | """Fires after user-entered code runs. |
|
161 | 137 |
@@ -173,8 +173,8 b' class DummyMod(object):' | |||
|
173 | 173 | pass |
|
174 | 174 | |
|
175 | 175 | |
|
176 |
class Execution |
|
|
177 |
"""The |
|
|
176 | class ExecutionInfo(object): | |
|
177 | """The arguments used for a call to :meth:`InteractiveShell.run_cell` | |
|
178 | 178 | |
|
179 | 179 | Stores information about what is going to happen. |
|
180 | 180 | """ |
@@ -205,11 +205,11 b' class ExecutionResult(object):' | |||
|
205 | 205 | execution_count = None |
|
206 | 206 | error_before_exec = None |
|
207 | 207 | error_in_exec = None |
|
208 |
|
|
|
208 | info = None | |
|
209 | 209 | result = None |
|
210 | 210 | |
|
211 |
def __init__(self, |
|
|
212 | self.request = request | |
|
211 | def __init__(self, info): | |
|
212 | self.info = info | |
|
213 | 213 | |
|
214 | 214 | @property |
|
215 | 215 | def success(self): |
@@ -224,8 +224,8 b' class ExecutionResult(object):' | |||
|
224 | 224 | |
|
225 | 225 | def __repr__(self): |
|
226 | 226 | name = self.__class__.__qualname__ |
|
227 |
return '<%s object at %x, execution_count=%s error_before_exec=%s error_in_exec=%s |
|
|
228 |
(name, id(self), self.execution_count, self.error_before_exec, self.error_in_exec, repr(self. |
|
|
227 | return '<%s object at %x, execution_count=%s error_before_exec=%s error_in_exec=%s info=%s result=%s>' %\ | |
|
228 | (name, id(self), self.execution_count, self.error_before_exec, self.error_in_exec, repr(self.info), repr(self.result)) | |
|
229 | 229 | |
|
230 | 230 | |
|
231 | 231 | class InteractiveShell(SingletonConfigurable): |
@@ -894,7 +894,7 b' class InteractiveShell(SingletonConfigurable):' | |||
|
894 | 894 | "ip.events.register('post_run_cell', func) instead.", stacklevel=2) |
|
895 | 895 | self.events.register('post_run_cell', func) |
|
896 | 896 | |
|
897 |
def _clear_warning_registry(self |
|
|
897 | def _clear_warning_registry(self): | |
|
898 | 898 | # clear the warning registry, so that different code blocks with |
|
899 | 899 | # overlapping line number ranges don't cause spurious suppression of |
|
900 | 900 | # warnings (see gh-6611 for details) |
@@ -2653,7 +2653,7 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2653 | 2653 | result = self._run_cell( |
|
2654 | 2654 | raw_cell, store_history, silent, shell_futures) |
|
2655 | 2655 | finally: |
|
2656 |
self.events.trigger('post_execute' |
|
|
2656 | self.events.trigger('post_execute') | |
|
2657 | 2657 | if not silent: |
|
2658 | 2658 | self.events.trigger('post_run_cell', result) |
|
2659 | 2659 | return result |
@@ -2672,9 +2672,9 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2672 | 2672 | ------- |
|
2673 | 2673 | result : :class:`ExecutionResult` |
|
2674 | 2674 | """ |
|
2675 |
|
|
|
2675 | info = ExecutionInfo( | |
|
2676 | 2676 | raw_cell, store_history, silent, shell_futures) |
|
2677 |
result = ExecutionResult( |
|
|
2677 | result = ExecutionResult(info) | |
|
2678 | 2678 | |
|
2679 | 2679 | if (not raw_cell) or raw_cell.isspace(): |
|
2680 | 2680 | self.last_execution_succeeded = True |
@@ -2693,9 +2693,9 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2693 | 2693 | self.last_execution_result = result |
|
2694 | 2694 | return result |
|
2695 | 2695 | |
|
2696 |
self.events.trigger('pre_execute' |
|
|
2696 | self.events.trigger('pre_execute') | |
|
2697 | 2697 | if not silent: |
|
2698 |
self.events.trigger('pre_run_cell', |
|
|
2698 | self.events.trigger('pre_run_cell', info) | |
|
2699 | 2699 | |
|
2700 | 2700 | # If any of our input transformation (input_transformer_manager or |
|
2701 | 2701 | # prefilter_manager) raises an exception, we store it in this variable |
@@ -7,13 +7,11 b' import IPython.testing.tools as tt' | |||
|
7 | 7 | |
|
8 | 8 | |
|
9 | 9 | @events._define_event |
|
10 | @callback_prototype | |
|
11 | 10 | def ping_received(): |
|
12 | 11 | pass |
|
13 | 12 | |
|
14 | 13 | |
|
15 | 14 | @events._define_event |
|
16 | @callback_prototype | |
|
17 | 15 | def event_with_argument(argument): |
|
18 | 16 | pass |
|
19 | 17 |
@@ -276,14 +276,14 b' class InteractiveShellTestCase(unittest.TestCase):' | |||
|
276 | 276 | assert not pre_explicit.called |
|
277 | 277 | assert post_always.called |
|
278 | 278 | assert not post_explicit.called |
|
279 | request, = pre_always.call_args[0] | |
|
280 | result, = post_always.call_args[0] | |
|
281 | self.assertEqual(request, result.request) | |
|
282 | 279 | # double-check that non-silent exec did what we expected |
|
283 | 280 | # silent to avoid |
|
284 | 281 | ip.run_cell("1") |
|
285 | 282 | assert pre_explicit.called |
|
286 | 283 | assert post_explicit.called |
|
284 | info, = pre_explicit.call_args[0] | |
|
285 | result, = post_explicit.call_args[0] | |
|
286 | self.assertEqual(info, result.info) | |
|
287 | 287 | # check that post hooks are always called |
|
288 | 288 | [m.reset_mock() for m in all_mocks] |
|
289 | 289 | ip.run_cell("syntax error") |
@@ -291,9 +291,9 b' class InteractiveShellTestCase(unittest.TestCase):' | |||
|
291 | 291 | assert pre_explicit.called |
|
292 | 292 | assert post_always.called |
|
293 | 293 | assert post_explicit.called |
|
294 |
|
|
|
295 |
result, = post_ |
|
|
296 |
self.assertEqual( |
|
|
294 | info, = pre_explicit.call_args[0] | |
|
295 | result, = post_explicit.call_args[0] | |
|
296 | self.assertEqual(info, result.info) | |
|
297 | 297 | finally: |
|
298 | 298 | # remove post-exec |
|
299 | 299 | ip.events.unregister('pre_run_cell', pre_explicit) |
@@ -18,21 +18,27 b' For example::' | |||
|
18 | 18 | self.shell = ip |
|
19 | 19 | self.last_x = None |
|
20 | 20 | |
|
21 |
def pre_execute(self |
|
|
22 | print('Cell code: "%s"' % request.raw_cell) | |
|
21 | def pre_execute(self): | |
|
23 | 22 | self.last_x = self.shell.user_ns.get('x', None) |
|
24 | 23 | |
|
25 |
def p |
|
|
26 |
print('Cell code: "%s"' % |
|
|
27 | if result.error_before_exec: | |
|
28 | print('Error before execution: %s' % result.error_before_exec) | |
|
24 | def pre_run_cell(self, info): | |
|
25 | print('Cell code: "%s"' % info.raw_cell) | |
|
26 | ||
|
27 | def post_execute(self): | |
|
29 | 28 | if self.shell.user_ns.get('x', None) != self.last_x: |
|
30 | 29 | print("x changed!") |
|
31 | 30 | |
|
31 | def post_run_cell(self, result): | |
|
32 | print('Cell code: "%s"' % result.info.raw_cell) | |
|
33 | if result.error_before_exec: | |
|
34 | print('Error before execution: %s' % result.error_before_exec) | |
|
35 | ||
|
32 | 36 | def load_ipython_extension(ip): |
|
33 | 37 | vw = VarWatcher(ip) |
|
34 | 38 | ip.events.register('pre_execute', vw.pre_execute) |
|
39 | ip.events.register('pre_run_cell', vw.pre_run_cell) | |
|
35 | 40 | ip.events.register('post_execute', vw.post_execute) |
|
41 | ip.events.register('post_run_cell', vw.post_run_cell) | |
|
36 | 42 | |
|
37 | 43 | |
|
38 | 44 | Events |
@@ -57,7 +63,7 b' pre_run_cell' | |||
|
57 | 63 | |
|
58 | 64 | ``pre_run_cell`` fires prior to interactive execution (e.g. a cell in a notebook). |
|
59 | 65 | It can be used to note the state prior to execution, and keep track of changes. |
|
60 |
|
|
|
66 | An object containing information used for the code execution is provided as an argument. | |
|
61 | 67 | |
|
62 | 68 | pre_execute |
|
63 | 69 | ----------- |
@@ -65,7 +71,6 b' pre_execute' | |||
|
65 | 71 | ``pre_execute`` is like ``pre_run_cell``, but is triggered prior to *any* execution. |
|
66 | 72 | Sometimes code can be executed by libraries, etc. which |
|
67 | 73 | skipping the history/display mechanisms, in which cases ``pre_run_cell`` will not fire. |
|
68 | The object representing the code execution request is provided as an argument. | |
|
69 | 74 | |
|
70 | 75 | post_run_cell |
|
71 | 76 | ------------- |
@@ -1,7 +1,7 b'' | |||
|
1 | 1 | The *post* event callbacks are now always called, even when the execution failed |
|
2 | 2 | (for example because of a ``SyntaxError``). |
|
3 |
Additionally, the execution |
|
|
4 |
the corresponding *pre* or *post* event callbacks in a backward |
|
|
5 | manner. | |
|
3 | Additionally, the execution info and result objects are now made available in | |
|
4 | the corresponding *pre* or *post* ``*_run_cell`` event callbacks in a backward | |
|
5 | compatible manner. | |
|
6 | 6 | |
|
7 | 7 | * `Related GitHub issue <https://github.com/ipython/ipython/issues/10774>`__ |
General Comments 0
You need to be logged in to leave comments.
Login now