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