##// END OF EJS Templates
Rename ExecutionRequest to ExecutionInfo; refactor
Fabio Niephaus -
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 request : :class:`~IPython.core.interactiveshell.ExecutionRequest`
138 The object representing the code execution request.
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 ExecutionRequest(object):
177 """The request of a call to :meth:`InteractiveShell.run_cell`
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 request = None
208 info = None
209 209 result = None
210 210
211 def __init__(self, request):
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 request=%s result=%s>' %\
228 (name, id(self), self.execution_count, self.error_before_exec, self.error_in_exec, repr(self.request), repr(self.result))
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, request):
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', result)
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 request = ExecutionRequest(
2675 info = ExecutionInfo(
2676 2676 raw_cell, store_history, silent, shell_futures)
2677 result = ExecutionResult(request)
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', request)
2696 self.events.trigger('pre_execute')
2697 2697 if not silent:
2698 self.events.trigger('pre_run_cell', request)
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 request, = pre_always.call_args[0]
295 result, = post_always.call_args[0]
296 self.assertEqual(request, result.request)
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, request):
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 post_execute(self, result):
26 print('Cell code: "%s"' % result.request.raw_cell)
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 The object representing the code execution request is provided as an argument.
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 request and result objects are now made available in
4 the corresponding *pre* or *post* event callbacks in a backward compatible
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