From 7de483b5fdaa99fa289b418ce0efc5102287ba64 2017-10-18 09:36:20 From: Fabio Niephaus Date: 2017-10-18 09:36:20 Subject: [PATCH] Rename ExecutionRequest to ExecutionInfo; refactor --- diff --git a/IPython/core/events.py b/IPython/core/events.py index 7b897bb..de250d6 100644 --- a/IPython/core/events.py +++ b/IPython/core/events.py @@ -40,7 +40,7 @@ class EventManager(object): self.callbacks = {n:[] for n in available_events} def register(self, event, function): - """Register a new event callback + """Register a new event callback. Parameters ---------- @@ -59,12 +59,21 @@ class EventManager(object): """ if not callable(function): raise TypeError('Need a callable, got %r' % function) - self.callbacks[event].append(_adapt_function(event, function)) + callback_proto = available_events.get(event) + self.callbacks[event].append(callback_proto.adapt(function)) def unregister(self, event, function): """Remove a callback from the given event.""" - self.callbacks[event].remove(_adapt_function(event, function)) - + if function in self.callbacks[event]: + return self.callbacks[event].remove(function) + + # Remove callback in case ``function`` was adapted by `backcall`. + for callback in self.callbacks[event]: + if callback.__wrapped__ is function: + return self.callbacks[event].remove(callback) + + raise ValueError('Function {!r} is not registered as a {} callback'.format(function, event)) + def trigger(self, event, *args, **kwargs): """Call callbacks for ``event``. @@ -81,30 +90,11 @@ class EventManager(object): # event_name -> prototype mapping available_events = {} -# (event, function) -> adapted function mapping -adapted_functions = {} - - -def _define_event(callback_proto): - available_events[callback_proto.__name__] = callback_proto +def _define_event(callback_function): + callback_proto = callback_prototype(callback_function) + available_events[callback_function.__name__] = callback_proto return callback_proto - -def _adapt_function(event, function): - """Adapts and caches a function using `backcall` to provide compatibility. - - Function adaptations depend not only on the function but also on the event, - as events may expect different arguments (e.g. `request` vs. `result`). - Hence, `(event, function)` is used as the cache key. - """ - if (event, function) in adapted_functions: - return adapted_functions[(event, function)] - callback_proto = available_events.get(event) - adapted_function = callback_proto.adapt(function) - adapted_functions[(event, function)] = adapted_function - return adapted_function - - # ------------------------------------------------------------------------------ # Callback prototypes # @@ -113,49 +103,35 @@ def _adapt_function(event, function): # ------------------------------------------------------------------------------ @_define_event -@callback_prototype -def pre_execute(request): +def pre_execute(): """Fires before code is executed in response to user/frontend action. This includes comm and widget messages and silent execution, as well as user code cells. - - Parameters - ---------- - request : :class:`~IPython.core.interactiveshell.ExecutionRequest` - The object representing the code execution request. """ pass @_define_event -@callback_prototype -def pre_run_cell(request): +def pre_run_cell(info): """Fires before user-entered code runs. Parameters ---------- - request : :class:`~IPython.core.interactiveshell.ExecutionRequest` - The object representing the code execution request. + info : :class:`~IPython.core.interactiveshell.ExecutionInfo` + An object containing information used for the code execution. """ pass @_define_event -@callback_prototype -def post_execute(result): +def post_execute(): """Fires after code is executed in response to user/frontend action. This includes comm and widget messages and silent execution, as well as user code cells. - - Parameters - ---------- - result : :class:`~IPython.core.interactiveshell.ExecutionResult` - The object which will be returned as the execution result. """ pass @_define_event -@callback_prototype def post_run_cell(result): """Fires after user-entered code runs. diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 601c4e3..1d4fed5 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -173,8 +173,8 @@ class DummyMod(object): pass -class ExecutionRequest(object): - """The request of a call to :meth:`InteractiveShell.run_cell` +class ExecutionInfo(object): + """The arguments used for a call to :meth:`InteractiveShell.run_cell` Stores information about what is going to happen. """ @@ -205,11 +205,11 @@ class ExecutionResult(object): execution_count = None error_before_exec = None error_in_exec = None - request = None + info = None result = None - def __init__(self, request): - self.request = request + def __init__(self, info): + self.info = info @property def success(self): @@ -224,8 +224,8 @@ class ExecutionResult(object): def __repr__(self): name = self.__class__.__qualname__ - return '<%s object at %x, execution_count=%s error_before_exec=%s error_in_exec=%s request=%s result=%s>' %\ - (name, id(self), self.execution_count, self.error_before_exec, self.error_in_exec, repr(self.request), repr(self.result)) + return '<%s object at %x, execution_count=%s error_before_exec=%s error_in_exec=%s info=%s result=%s>' %\ + (name, id(self), self.execution_count, self.error_before_exec, self.error_in_exec, repr(self.info), repr(self.result)) class InteractiveShell(SingletonConfigurable): @@ -894,7 +894,7 @@ class InteractiveShell(SingletonConfigurable): "ip.events.register('post_run_cell', func) instead.", stacklevel=2) self.events.register('post_run_cell', func) - def _clear_warning_registry(self, request): + def _clear_warning_registry(self): # clear the warning registry, so that different code blocks with # overlapping line number ranges don't cause spurious suppression of # warnings (see gh-6611 for details) @@ -2653,7 +2653,7 @@ class InteractiveShell(SingletonConfigurable): result = self._run_cell( raw_cell, store_history, silent, shell_futures) finally: - self.events.trigger('post_execute', result) + self.events.trigger('post_execute') if not silent: self.events.trigger('post_run_cell', result) return result @@ -2672,9 +2672,9 @@ class InteractiveShell(SingletonConfigurable): ------- result : :class:`ExecutionResult` """ - request = ExecutionRequest( + info = ExecutionInfo( raw_cell, store_history, silent, shell_futures) - result = ExecutionResult(request) + result = ExecutionResult(info) if (not raw_cell) or raw_cell.isspace(): self.last_execution_succeeded = True @@ -2693,9 +2693,9 @@ class InteractiveShell(SingletonConfigurable): self.last_execution_result = result return result - self.events.trigger('pre_execute', request) + self.events.trigger('pre_execute') if not silent: - self.events.trigger('pre_run_cell', request) + self.events.trigger('pre_run_cell', info) # If any of our input transformation (input_transformer_manager or # prefilter_manager) raises an exception, we store it in this variable diff --git a/IPython/core/tests/test_events.py b/IPython/core/tests/test_events.py index fbf38cd..a2ece35 100644 --- a/IPython/core/tests/test_events.py +++ b/IPython/core/tests/test_events.py @@ -7,13 +7,11 @@ import IPython.testing.tools as tt @events._define_event -@callback_prototype def ping_received(): pass @events._define_event -@callback_prototype def event_with_argument(argument): pass diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index 07bdef2..d275f96 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -276,14 +276,14 @@ class InteractiveShellTestCase(unittest.TestCase): assert not pre_explicit.called assert post_always.called assert not post_explicit.called - request, = pre_always.call_args[0] - result, = post_always.call_args[0] - self.assertEqual(request, result.request) # double-check that non-silent exec did what we expected # silent to avoid ip.run_cell("1") assert pre_explicit.called assert post_explicit.called + info, = pre_explicit.call_args[0] + result, = post_explicit.call_args[0] + self.assertEqual(info, result.info) # check that post hooks are always called [m.reset_mock() for m in all_mocks] ip.run_cell("syntax error") @@ -291,9 +291,9 @@ class InteractiveShellTestCase(unittest.TestCase): assert pre_explicit.called assert post_always.called assert post_explicit.called - request, = pre_always.call_args[0] - result, = post_always.call_args[0] - self.assertEqual(request, result.request) + info, = pre_explicit.call_args[0] + result, = post_explicit.call_args[0] + self.assertEqual(info, result.info) finally: # remove post-exec ip.events.unregister('pre_run_cell', pre_explicit) diff --git a/docs/source/config/callbacks.rst b/docs/source/config/callbacks.rst index 2ff6936..bc56ab8 100644 --- a/docs/source/config/callbacks.rst +++ b/docs/source/config/callbacks.rst @@ -18,21 +18,27 @@ For example:: self.shell = ip self.last_x = None - def pre_execute(self, request): - print('Cell code: "%s"' % request.raw_cell) + def pre_execute(self): self.last_x = self.shell.user_ns.get('x', None) - def post_execute(self, result): - print('Cell code: "%s"' % result.request.raw_cell) - if result.error_before_exec: - print('Error before execution: %s' % result.error_before_exec) + def pre_run_cell(self, info): + print('Cell code: "%s"' % info.raw_cell) + + def post_execute(self): if self.shell.user_ns.get('x', None) != self.last_x: print("x changed!") + def post_run_cell(self, result): + print('Cell code: "%s"' % result.info.raw_cell) + if result.error_before_exec: + print('Error before execution: %s' % result.error_before_exec) + def load_ipython_extension(ip): vw = VarWatcher(ip) ip.events.register('pre_execute', vw.pre_execute) + ip.events.register('pre_run_cell', vw.pre_run_cell) ip.events.register('post_execute', vw.post_execute) + ip.events.register('post_run_cell', vw.post_run_cell) Events @@ -57,7 +63,7 @@ pre_run_cell ``pre_run_cell`` fires prior to interactive execution (e.g. a cell in a notebook). It can be used to note the state prior to execution, and keep track of changes. -The object representing the code execution request is provided as an argument. +An object containing information used for the code execution is provided as an argument. pre_execute ----------- @@ -65,7 +71,6 @@ pre_execute ``pre_execute`` is like ``pre_run_cell``, but is triggered prior to *any* execution. Sometimes code can be executed by libraries, etc. which skipping the history/display mechanisms, in which cases ``pre_run_cell`` will not fire. -The object representing the code execution request is provided as an argument. post_run_cell ------------- diff --git a/docs/source/whatsnew/pr/event-callbacks-updates.rst b/docs/source/whatsnew/pr/event-callbacks-updates.rst index d00c85e..7a28a04 100644 --- a/docs/source/whatsnew/pr/event-callbacks-updates.rst +++ b/docs/source/whatsnew/pr/event-callbacks-updates.rst @@ -1,7 +1,7 @@ The *post* event callbacks are now always called, even when the execution failed (for example because of a ``SyntaxError``). -Additionally, the execution request and result objects are now made available in -the corresponding *pre* or *post* event callbacks in a backward compatible -manner. +Additionally, the execution info and result objects are now made available in +the corresponding *pre* or *post* ``*_run_cell`` event callbacks in a backward +compatible manner. * `Related GitHub issue `__