Show More
@@ -1,188 +1,189 b'' | |||||
1 | """Infrastructure for registering and firing callbacks on application events. |
|
1 | """Infrastructure for registering and firing callbacks on application events. | |
2 |
|
2 | |||
3 | Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to |
|
3 | Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to | |
4 | be called at specific times, or a collection of alternative methods to try, |
|
4 | be called at specific times, or a collection of alternative methods to try, | |
5 | callbacks are designed to be used by extension authors. A number of callbacks |
|
5 | callbacks are designed to be used by extension authors. A number of callbacks | |
6 | can be registered for the same event without needing to be aware of one another. |
|
6 | can be registered for the same event without needing to be aware of one another. | |
7 |
|
7 | |||
8 | The functions defined in this module are no-ops indicating the names of available |
|
8 | The functions defined in this module are no-ops indicating the names of available | |
9 | events and the arguments which will be passed to them. |
|
9 | events and the arguments which will be passed to them. | |
10 |
|
10 | |||
11 | .. note:: |
|
11 | .. note:: | |
12 |
|
12 | |||
13 | This API is experimental in IPython 2.0, and may be revised in future versions. |
|
13 | This API is experimental in IPython 2.0, and may be revised in future versions. | |
14 | """ |
|
14 | """ | |
15 |
|
15 | |||
16 | from functools import wraps |
|
16 | from functools import wraps | |
|
17 | from inspect import isfunction | |||
17 | try: |
|
18 | try: | |
18 | from inspect import getfullargspec |
|
19 | from inspect import getfullargspec | |
19 | except: |
|
20 | except: | |
20 | from inspect import getargspec as getfullargspec # for Python2 compatibility. |
|
21 | from inspect import getargspec as getfullargspec # for Python2 compatibility. | |
21 |
|
22 | |||
22 | # original function -> wrapper function mapping |
|
23 | # original function -> wrapper function mapping | |
23 | compatibility_wrapper_functions = {} |
|
24 | compatibility_wrapper_functions = {} | |
24 |
|
25 | |||
25 | def _compatibility_wrapper_for(function): |
|
26 | def _compatibility_wrapper_for(function): | |
26 | """Returns a wrapper for a function without args that accepts any args.""" |
|
27 | """Returns a wrapper for a function without args that accepts any args.""" | |
27 | if len(getfullargspec(function).args) > 0: |
|
28 | if len(getfullargspec(function).args) > 0: | |
28 | raise TypeError('%s cannot have arguments' % function) |
|
29 | raise TypeError('%s cannot have arguments' % function) | |
29 | if function in compatibility_wrapper_functions: |
|
30 | if function in compatibility_wrapper_functions: | |
30 | return compatibility_wrapper_functions[function] |
|
31 | return compatibility_wrapper_functions[function] | |
31 | @wraps(function) |
|
32 | @wraps(function) | |
32 | def wrapper(*args, **kwargs): |
|
33 | def wrapper(*args, **kwargs): | |
33 | function() |
|
34 | function() | |
34 | compatibility_wrapper_functions[function] = wrapper |
|
35 | compatibility_wrapper_functions[function] = wrapper | |
35 | return wrapper |
|
36 | return wrapper | |
36 |
|
37 | |||
37 | class EventManager(object): |
|
38 | class EventManager(object): | |
38 | """Manage a collection of events and a sequence of callbacks for each. |
|
39 | """Manage a collection of events and a sequence of callbacks for each. | |
39 |
|
40 | |||
40 | This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell` |
|
41 | This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell` | |
41 | instances as an ``events`` attribute. |
|
42 | instances as an ``events`` attribute. | |
42 |
|
43 | |||
43 | .. note:: |
|
44 | .. note:: | |
44 |
|
45 | |||
45 | This API is experimental in IPython 2.0, and may be revised in future versions. |
|
46 | This API is experimental in IPython 2.0, and may be revised in future versions. | |
46 | """ |
|
47 | """ | |
47 | def __init__(self, shell, available_events): |
|
48 | def __init__(self, shell, available_events): | |
48 | """Initialise the :class:`CallbackManager`. |
|
49 | """Initialise the :class:`CallbackManager`. | |
49 |
|
50 | |||
50 | Parameters |
|
51 | Parameters | |
51 | ---------- |
|
52 | ---------- | |
52 | shell |
|
53 | shell | |
53 | The :class:`~IPython.core.interactiveshell.InteractiveShell` instance |
|
54 | The :class:`~IPython.core.interactiveshell.InteractiveShell` instance | |
54 | available_callbacks |
|
55 | available_callbacks | |
55 | An iterable of names for callback events. |
|
56 | An iterable of names for callback events. | |
56 | """ |
|
57 | """ | |
57 | self.shell = shell |
|
58 | self.shell = shell | |
58 | self.callbacks = {n:[] for n in available_events} |
|
59 | self.callbacks = {n:[] for n in available_events} | |
59 |
|
60 | |||
60 | def register(self, event, function): |
|
61 | def register(self, event, function): | |
61 | """Register a new event callback |
|
62 | """Register a new event callback | |
62 |
|
63 | |||
63 | Parameters |
|
64 | Parameters | |
64 | ---------- |
|
65 | ---------- | |
65 | event : str |
|
66 | event : str | |
66 | The event for which to register this callback. |
|
67 | The event for which to register this callback. | |
67 | function : callable |
|
68 | function : callable | |
68 | A function to be called on the given event. It should take the same |
|
69 | A function to be called on the given event. It should take the same | |
69 | parameters as the appropriate callback prototype. |
|
70 | parameters as the appropriate callback prototype. | |
70 |
|
71 | |||
71 | Raises |
|
72 | Raises | |
72 | ------ |
|
73 | ------ | |
73 | TypeError |
|
74 | TypeError | |
74 | If ``function`` is not callable. |
|
75 | If ``function`` is not callable. | |
75 | KeyError |
|
76 | KeyError | |
76 | If ``event`` is not one of the known events. |
|
77 | If ``event`` is not one of the known events. | |
77 | """ |
|
78 | """ | |
78 | if not callable(function): |
|
79 | if not callable(function): | |
79 | raise TypeError('Need a callable, got %r' % function) |
|
80 | raise TypeError('Need a callable, got %r' % function) | |
80 |
|
81 | |||
81 | callback_proto = available_events.get(event) |
|
82 | callback_proto = available_events.get(event) | |
82 |
if ( |
|
83 | if (isfunction(callback_proto) and isfunction(function) and | |
83 | len(getfullargspec(callback_proto).args) > 0 and |
|
84 | len(getfullargspec(callback_proto).args) > 0 and | |
84 | len(getfullargspec(function).args) == 0): |
|
85 | len(getfullargspec(function).args) == 0): | |
85 |
# `callback_proto` |
|
86 | # `callback_proto` has args but `function` does not, so a | |
86 | # compatibility wrapper is needed. |
|
87 | # compatibility wrapper is needed. | |
87 | self.callbacks[event].append(_compatibility_wrapper_for(function)) |
|
88 | self.callbacks[event].append(_compatibility_wrapper_for(function)) | |
88 | else: |
|
89 | else: | |
89 | self.callbacks[event].append(function) |
|
90 | self.callbacks[event].append(function) | |
90 |
|
91 | |||
91 | def unregister(self, event, function): |
|
92 | def unregister(self, event, function): | |
92 | """Remove a callback from the given event.""" |
|
93 | """Remove a callback from the given event.""" | |
93 | wrapper = compatibility_wrapper_functions.get(function) |
|
94 | wrapper = compatibility_wrapper_functions.get(function) | |
94 | if wrapper: |
|
95 | if wrapper: | |
95 | self.callbacks[event].remove(wrapper) |
|
96 | self.callbacks[event].remove(wrapper) | |
96 | else: |
|
97 | else: | |
97 | self.callbacks[event].remove(function) |
|
98 | self.callbacks[event].remove(function) | |
98 |
|
99 | |||
99 | def trigger(self, event, *args, **kwargs): |
|
100 | def trigger(self, event, *args, **kwargs): | |
100 | """Call callbacks for ``event``. |
|
101 | """Call callbacks for ``event``. | |
101 |
|
102 | |||
102 | Any additional arguments are passed to all callbacks registered for this |
|
103 | Any additional arguments are passed to all callbacks registered for this | |
103 | event. Exceptions raised by callbacks are caught, and a message printed. |
|
104 | event. Exceptions raised by callbacks are caught, and a message printed. | |
104 | """ |
|
105 | """ | |
105 | for func in self.callbacks[event][:]: |
|
106 | for func in self.callbacks[event][:]: | |
106 | try: |
|
107 | try: | |
107 | func(*args, **kwargs) |
|
108 | func(*args, **kwargs) | |
108 | except Exception: |
|
109 | except Exception: | |
109 | print("Error in callback {} (for {}):".format(func, event)) |
|
110 | print("Error in callback {} (for {}):".format(func, event)) | |
110 | self.shell.showtraceback() |
|
111 | self.shell.showtraceback() | |
111 |
|
112 | |||
112 | # event_name -> prototype mapping |
|
113 | # event_name -> prototype mapping | |
113 | available_events = {} |
|
114 | available_events = {} | |
114 |
|
115 | |||
115 | def _define_event(callback_proto): |
|
116 | def _define_event(callback_proto): | |
116 | available_events[callback_proto.__name__] = callback_proto |
|
117 | available_events[callback_proto.__name__] = callback_proto | |
117 | return callback_proto |
|
118 | return callback_proto | |
118 |
|
119 | |||
119 | # ------------------------------------------------------------------------------ |
|
120 | # ------------------------------------------------------------------------------ | |
120 | # Callback prototypes |
|
121 | # Callback prototypes | |
121 | # |
|
122 | # | |
122 | # No-op functions which describe the names of available events and the |
|
123 | # No-op functions which describe the names of available events and the | |
123 | # signatures of callbacks for those events. |
|
124 | # signatures of callbacks for those events. | |
124 | # ------------------------------------------------------------------------------ |
|
125 | # ------------------------------------------------------------------------------ | |
125 |
|
126 | |||
126 | @_define_event |
|
127 | @_define_event | |
127 | def pre_execute(result): |
|
128 | def pre_execute(result): | |
128 | """Fires before code is executed in response to user/frontend action. |
|
129 | """Fires before code is executed in response to user/frontend action. | |
129 |
|
130 | |||
130 | This includes comm and widget messages and silent execution, as well as user |
|
131 | This includes comm and widget messages and silent execution, as well as user | |
131 | code cells. |
|
132 | code cells. | |
132 |
|
133 | |||
133 | Parameters |
|
134 | Parameters | |
134 | ---------- |
|
135 | ---------- | |
135 | result : :class:`~IPython.core.interactiveshell.ExecutionResult` |
|
136 | result : :class:`~IPython.core.interactiveshell.ExecutionResult` | |
136 | The object which will be returned as the execution result. |
|
137 | The object which will be returned as the execution result. | |
137 | """ |
|
138 | """ | |
138 | pass |
|
139 | pass | |
139 |
|
140 | |||
140 | @_define_event |
|
141 | @_define_event | |
141 | def pre_run_cell(result): |
|
142 | def pre_run_cell(result): | |
142 | """Fires before user-entered code runs. |
|
143 | """Fires before user-entered code runs. | |
143 |
|
144 | |||
144 | Parameters |
|
145 | Parameters | |
145 | ---------- |
|
146 | ---------- | |
146 | result : :class:`~IPython.core.interactiveshell.ExecutionResult` |
|
147 | result : :class:`~IPython.core.interactiveshell.ExecutionResult` | |
147 | The object which will be returned as the execution result. |
|
148 | The object which will be returned as the execution result. | |
148 | """ |
|
149 | """ | |
149 | pass |
|
150 | pass | |
150 |
|
151 | |||
151 | @_define_event |
|
152 | @_define_event | |
152 | def post_execute(result): |
|
153 | def post_execute(result): | |
153 | """Fires after code is executed in response to user/frontend action. |
|
154 | """Fires after code is executed in response to user/frontend action. | |
154 |
|
155 | |||
155 | This includes comm and widget messages and silent execution, as well as user |
|
156 | This includes comm and widget messages and silent execution, as well as user | |
156 | code cells. |
|
157 | code cells. | |
157 |
|
158 | |||
158 | Parameters |
|
159 | Parameters | |
159 | ---------- |
|
160 | ---------- | |
160 | result : :class:`~IPython.core.interactiveshell.ExecutionResult` |
|
161 | result : :class:`~IPython.core.interactiveshell.ExecutionResult` | |
161 | The object which will be returned as the execution result. |
|
162 | The object which will be returned as the execution result. | |
162 | """ |
|
163 | """ | |
163 | pass |
|
164 | pass | |
164 |
|
165 | |||
165 | @_define_event |
|
166 | @_define_event | |
166 | def post_run_cell(result): |
|
167 | def post_run_cell(result): | |
167 | """Fires after user-entered code runs. |
|
168 | """Fires after user-entered code runs. | |
168 |
|
169 | |||
169 | Parameters |
|
170 | Parameters | |
170 | ---------- |
|
171 | ---------- | |
171 | result : :class:`~IPython.core.interactiveshell.ExecutionResult` |
|
172 | result : :class:`~IPython.core.interactiveshell.ExecutionResult` | |
172 | The object which will be returned as the execution result. |
|
173 | The object which will be returned as the execution result. | |
173 | """ |
|
174 | """ | |
174 | pass |
|
175 | pass | |
175 |
|
176 | |||
176 | @_define_event |
|
177 | @_define_event | |
177 | def shell_initialized(ip): |
|
178 | def shell_initialized(ip): | |
178 | """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`. |
|
179 | """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`. | |
179 |
|
180 | |||
180 | This is before extensions and startup scripts are loaded, so it can only be |
|
181 | This is before extensions and startup scripts are loaded, so it can only be | |
181 | set by subclassing. |
|
182 | set by subclassing. | |
182 |
|
183 | |||
183 | Parameters |
|
184 | Parameters | |
184 | ---------- |
|
185 | ---------- | |
185 | ip : :class:`~IPython.core.interactiveshell.InteractiveShell` |
|
186 | ip : :class:`~IPython.core.interactiveshell.InteractiveShell` | |
186 | The newly initialised shell. |
|
187 | The newly initialised shell. | |
187 | """ |
|
188 | """ | |
188 | pass |
|
189 | pass |
@@ -1,110 +1,94 b'' | |||||
1 | .. _events: |
|
1 | .. _events: | |
2 | .. _callbacks: |
|
2 | .. _callbacks: | |
3 |
|
3 | |||
4 | ============== |
|
4 | ============== | |
5 | IPython Events |
|
5 | IPython Events | |
6 | ============== |
|
6 | ============== | |
7 |
|
7 | |||
8 | Extension code can register callbacks functions which will be called on specific |
|
8 | Extension code can register callbacks functions which will be called on specific | |
9 | events within the IPython code. You can see the current list of available |
|
9 | events within the IPython code. You can see the current list of available | |
10 | callbacks, and the parameters that will be passed with each, in the callback |
|
10 | callbacks, and the parameters that will be passed with each, in the callback | |
11 | prototype functions defined in :mod:`IPython.core.callbacks`. |
|
11 | prototype functions defined in :mod:`IPython.core.callbacks`. | |
12 |
|
12 | |||
13 | To register callbacks, use :meth:`IPython.core.events.EventManager.register`. |
|
13 | To register callbacks, use :meth:`IPython.core.events.EventManager.register`. | |
14 | For example:: |
|
14 | For example:: | |
15 |
|
15 | |||
16 | class VarWatcher(object): |
|
16 | class VarWatcher(object): | |
17 | def __init__(self, ip): |
|
17 | def __init__(self, ip): | |
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): |
|
21 | def pre_execute(self): | |
22 | self.last_x = self.shell.user_ns.get('x', None) |
|
22 | self.last_x = self.shell.user_ns.get('x', None) | |
23 |
|
23 | |||
24 | def post_execute(self, result): |
|
24 | def post_execute(self, result): | |
25 | if result.error_before_exec: |
|
25 | if result.error_before_exec: | |
26 | print('Error before execution: %s' % result.error_before_exec) |
|
26 | print('Error before execution: %s' % result.error_before_exec) | |
27 | if self.shell.user_ns.get('x', None) != self.last_x: |
|
27 | if self.shell.user_ns.get('x', None) != self.last_x: | |
28 | print("x changed!") |
|
28 | print("x changed!") | |
29 |
|
29 | |||
30 | def load_ipython_extension(ip): |
|
30 | def load_ipython_extension(ip): | |
31 | vw = VarWatcher(ip) |
|
31 | vw = VarWatcher(ip) | |
32 | ip.events.register('pre_execute', vw.pre_execute) |
|
32 | ip.events.register('pre_execute', vw.pre_execute) | |
33 | ip.events.register('post_execute', vw.post_execute) |
|
33 | ip.events.register('post_execute', vw.post_execute) | |
34 |
|
34 | |||
35 |
|
35 | |||
36 | Events |
|
36 | Events | |
37 | ====== |
|
37 | ====== | |
38 |
|
38 | |||
39 | These are the events IPython will emit. Callbacks will be passed no arguments, unless otherwise specified. |
|
39 | These are the events IPython will emit. Callbacks will be passed no arguments, unless otherwise specified. | |
40 |
|
40 | |||
41 | shell_initialized |
|
41 | shell_initialized | |
42 | ----------------- |
|
42 | ----------------- | |
43 |
|
43 | |||
44 | .. code-block:: python |
|
44 | .. code-block:: python | |
45 |
|
45 | |||
46 | def shell_initialized(ipython): |
|
46 | def shell_initialized(ipython): | |
47 | ... |
|
47 | ... | |
48 |
|
48 | |||
49 | This event is triggered only once, at the end of setting up IPython. |
|
49 | This event is triggered only once, at the end of setting up IPython. | |
50 | Extensions registered to load by default as part of configuration can use this to execute code to finalize setup. |
|
50 | Extensions registered to load by default as part of configuration can use this to execute code to finalize setup. | |
51 | Callbacks will be passed the InteractiveShell instance. |
|
51 | Callbacks will be passed the InteractiveShell instance. | |
52 |
|
52 | |||
53 | pre_run_cell |
|
53 | pre_run_cell | |
54 | ------------ |
|
54 | ------------ | |
55 |
|
55 | |||
56 | ``pre_run_cell`` fires prior to interactive execution (e.g. a cell in a notebook). |
|
56 | ``pre_run_cell`` fires prior to interactive execution (e.g. a cell in a notebook). | |
57 | It can be used to note the state prior to execution, and keep track of changes. |
|
57 | It can be used to note the state prior to execution, and keep track of changes. | |
58 | The object which will be returned as the execution result is provided as an |
|
58 | The object which will be returned as the execution result is provided as an | |
59 | argument, even though the actual result is not yet available. |
|
59 | argument, even though the actual result is not yet available. | |
60 |
|
60 | |||
61 | pre_execute |
|
61 | pre_execute | |
62 | ----------- |
|
62 | ----------- | |
63 |
|
63 | |||
64 | ``pre_execute`` is like ``pre_run_cell``, but is triggered prior to *any* execution. |
|
64 | ``pre_execute`` is like ``pre_run_cell``, but is triggered prior to *any* execution. | |
65 | Sometimes code can be executed by libraries, etc. which |
|
65 | Sometimes code can be executed by libraries, etc. which | |
66 | skipping the history/display mechanisms, in which cases ``pre_run_cell`` will not fire. |
|
66 | skipping the history/display mechanisms, in which cases ``pre_run_cell`` will not fire. | |
67 | The object which will be returned as the execution result is provided as an |
|
67 | The object which will be returned as the execution result is provided as an | |
68 | argument, even though the actual result is not yet available. |
|
68 | argument, even though the actual result is not yet available. | |
69 |
|
69 | |||
70 | post_run_cell |
|
70 | post_run_cell | |
71 | ------------- |
|
71 | ------------- | |
72 |
|
72 | |||
73 | ``post_run_cell`` runs after interactive execution. |
|
73 | ``post_run_cell`` runs after interactive execution (e.g. a cell in a notebook). | |
74 | It can be used to cleanup or notify or perform operations on any side effects |
|
74 | It can be used to cleanup or notify or perform operations on any side effects produced during execution. | |
75 | produced during execution. |
|
75 | For instance, the inline matplotlib backend uses this event to display any figures created but not explicitly displayed during the course of the cell. | |
76 | For instance, the inline matplotlib backend uses this event to display any |
|
|||
77 | figures created but not explicitly displayed during the course of the cell. |
|
|||
78 | The object which will be returned as the execution result is provided as an |
|
76 | The object which will be returned as the execution result is provided as an | |
79 | argument. |
|
77 | argument. | |
80 |
|
78 | |||
81 | post_execute |
|
79 | post_execute | |
82 | ------------ |
|
80 | ------------ | |
83 |
|
81 | |||
84 | The same as ``pre_execute``, ``post_execute`` is like ``post_run_cell``, |
|
82 | The same as ``pre_execute``, ``post_execute`` is like ``post_run_cell``, | |
85 | but fires for *all* executions, not just interactive ones. |
|
83 | but fires for *all* executions, not just interactive ones. | |
86 |
|
84 | |||
87 | finally_run_cell |
|
|||
88 | ------------- |
|
|||
89 |
|
||||
90 | ``finally_run_cell`` is like ``post_run_cell``, but fires after *all* executions |
|
|||
91 | (even when, for example, a ``SyntaxError`` was raised). |
|
|||
92 | Additionally, the execution result is provided as an argument. |
|
|||
93 |
|
||||
94 | finally_execute |
|
|||
95 | ------------ |
|
|||
96 |
|
||||
97 | ``finally_execute`` is like ``post_execute``, but fires after *all* executions |
|
|||
98 | (even when, for example, a ``SyntaxError`` was raised). |
|
|||
99 | Additionally, the execution result is provided as an argument. |
|
|||
100 |
|
||||
101 |
|
85 | |||
102 | .. seealso:: |
|
86 | .. seealso:: | |
103 |
|
87 | |||
104 | Module :mod:`IPython.core.hooks` |
|
88 | Module :mod:`IPython.core.hooks` | |
105 | The older 'hooks' system allows end users to customise some parts of |
|
89 | The older 'hooks' system allows end users to customise some parts of | |
106 | IPython's behaviour. |
|
90 | IPython's behaviour. | |
107 |
|
91 | |||
108 | :doc:`inputtransforms` |
|
92 | :doc:`inputtransforms` | |
109 | By registering input transformers that don't change code, you can monitor |
|
93 | By registering input transformers that don't change code, you can monitor | |
110 | what is being executed. |
|
94 | what is being executed. |
General Comments 0
You need to be logged in to leave comments.
Login now