##// END OF EJS Templates
Don't register the same callabck multiple times...
Quentin Peter -
Show More
@@ -1,160 +1,161 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 backcall import callback_prototype
16 from backcall import callback_prototype
17
17
18
18
19 class EventManager(object):
19 class EventManager(object):
20 """Manage a collection of events and a sequence of callbacks for each.
20 """Manage a collection of events and a sequence of callbacks for each.
21
21
22 This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell`
22 This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell`
23 instances as an ``events`` attribute.
23 instances as an ``events`` attribute.
24
24
25 .. note::
25 .. note::
26
26
27 This API is experimental in IPython 2.0, and may be revised in future versions.
27 This API is experimental in IPython 2.0, and may be revised in future versions.
28 """
28 """
29 def __init__(self, shell, available_events):
29 def __init__(self, shell, available_events):
30 """Initialise the :class:`CallbackManager`.
30 """Initialise the :class:`CallbackManager`.
31
31
32 Parameters
32 Parameters
33 ----------
33 ----------
34 shell
34 shell
35 The :class:`~IPython.core.interactiveshell.InteractiveShell` instance
35 The :class:`~IPython.core.interactiveshell.InteractiveShell` instance
36 available_callbacks
36 available_callbacks
37 An iterable of names for callback events.
37 An iterable of names for callback events.
38 """
38 """
39 self.shell = shell
39 self.shell = shell
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 ----------
47 event : str
47 event : str
48 The event for which to register this callback.
48 The event for which to register this callback.
49 function : callable
49 function : callable
50 A function to be called on the given event. It should take the same
50 A function to be called on the given event. It should take the same
51 parameters as the appropriate callback prototype.
51 parameters as the appropriate callback prototype.
52
52
53 Raises
53 Raises
54 ------
54 ------
55 TypeError
55 TypeError
56 If ``function`` is not callable.
56 If ``function`` is not callable.
57 KeyError
57 KeyError
58 If ``event`` is not one of the known events.
58 If ``event`` is not one of the known events.
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 callback_proto = available_events.get(event)
62 callback_proto = available_events.get(event)
63 self.callbacks[event].append(callback_proto.adapt(function))
63 if function not in self.callbacks[event]:
64 self.callbacks[event].append(callback_proto.adapt(function))
64
65
65 def unregister(self, event, function):
66 def unregister(self, event, function):
66 """Remove a callback from the given event."""
67 """Remove a callback from the given event."""
67 if function in self.callbacks[event]:
68 if function in self.callbacks[event]:
68 return self.callbacks[event].remove(function)
69 return self.callbacks[event].remove(function)
69
70
70 # Remove callback in case ``function`` was adapted by `backcall`.
71 # Remove callback in case ``function`` was adapted by `backcall`.
71 for callback in self.callbacks[event]:
72 for callback in self.callbacks[event]:
72 try:
73 try:
73 if callback.__wrapped__ is function:
74 if callback.__wrapped__ is function:
74 return self.callbacks[event].remove(callback)
75 return self.callbacks[event].remove(callback)
75 except AttributeError:
76 except AttributeError:
76 pass
77 pass
77
78
78 raise ValueError('Function {!r} is not registered as a {} callback'.format(function, event))
79 raise ValueError('Function {!r} is not registered as a {} callback'.format(function, event))
79
80
80 def trigger(self, event, *args, **kwargs):
81 def trigger(self, event, *args, **kwargs):
81 """Call callbacks for ``event``.
82 """Call callbacks for ``event``.
82
83
83 Any additional arguments are passed to all callbacks registered for this
84 Any additional arguments are passed to all callbacks registered for this
84 event. Exceptions raised by callbacks are caught, and a message printed.
85 event. Exceptions raised by callbacks are caught, and a message printed.
85 """
86 """
86 for func in self.callbacks[event][:]:
87 for func in self.callbacks[event][:]:
87 try:
88 try:
88 func(*args, **kwargs)
89 func(*args, **kwargs)
89 except (Exception, KeyboardInterrupt):
90 except (Exception, KeyboardInterrupt):
90 print("Error in callback {} (for {}):".format(func, event))
91 print("Error in callback {} (for {}):".format(func, event))
91 self.shell.showtraceback()
92 self.shell.showtraceback()
92
93
93 # event_name -> prototype mapping
94 # event_name -> prototype mapping
94 available_events = {}
95 available_events = {}
95
96
96 def _define_event(callback_function):
97 def _define_event(callback_function):
97 callback_proto = callback_prototype(callback_function)
98 callback_proto = callback_prototype(callback_function)
98 available_events[callback_function.__name__] = callback_proto
99 available_events[callback_function.__name__] = callback_proto
99 return callback_proto
100 return callback_proto
100
101
101 # ------------------------------------------------------------------------------
102 # ------------------------------------------------------------------------------
102 # Callback prototypes
103 # Callback prototypes
103 #
104 #
104 # No-op functions which describe the names of available events and the
105 # No-op functions which describe the names of available events and the
105 # signatures of callbacks for those events.
106 # signatures of callbacks for those events.
106 # ------------------------------------------------------------------------------
107 # ------------------------------------------------------------------------------
107
108
108 @_define_event
109 @_define_event
109 def pre_execute():
110 def pre_execute():
110 """Fires before code is executed in response to user/frontend action.
111 """Fires before code is executed in response to user/frontend action.
111
112
112 This includes comm and widget messages and silent execution, as well as user
113 This includes comm and widget messages and silent execution, as well as user
113 code cells.
114 code cells.
114 """
115 """
115 pass
116 pass
116
117
117 @_define_event
118 @_define_event
118 def pre_run_cell(info):
119 def pre_run_cell(info):
119 """Fires before user-entered code runs.
120 """Fires before user-entered code runs.
120
121
121 Parameters
122 Parameters
122 ----------
123 ----------
123 info : :class:`~IPython.core.interactiveshell.ExecutionInfo`
124 info : :class:`~IPython.core.interactiveshell.ExecutionInfo`
124 An object containing information used for the code execution.
125 An object containing information used for the code execution.
125 """
126 """
126 pass
127 pass
127
128
128 @_define_event
129 @_define_event
129 def post_execute():
130 def post_execute():
130 """Fires after code is executed in response to user/frontend action.
131 """Fires after code is executed in response to user/frontend action.
131
132
132 This includes comm and widget messages and silent execution, as well as user
133 This includes comm and widget messages and silent execution, as well as user
133 code cells.
134 code cells.
134 """
135 """
135 pass
136 pass
136
137
137 @_define_event
138 @_define_event
138 def post_run_cell(result):
139 def post_run_cell(result):
139 """Fires after user-entered code runs.
140 """Fires after user-entered code runs.
140
141
141 Parameters
142 Parameters
142 ----------
143 ----------
143 result : :class:`~IPython.core.interactiveshell.ExecutionResult`
144 result : :class:`~IPython.core.interactiveshell.ExecutionResult`
144 The object which will be returned as the execution result.
145 The object which will be returned as the execution result.
145 """
146 """
146 pass
147 pass
147
148
148 @_define_event
149 @_define_event
149 def shell_initialized(ip):
150 def shell_initialized(ip):
150 """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`.
151 """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`.
151
152
152 This is before extensions and startup scripts are loaded, so it can only be
153 This is before extensions and startup scripts are loaded, so it can only be
153 set by subclassing.
154 set by subclassing.
154
155
155 Parameters
156 Parameters
156 ----------
157 ----------
157 ip : :class:`~IPython.core.interactiveshell.InteractiveShell`
158 ip : :class:`~IPython.core.interactiveshell.InteractiveShell`
158 The newly initialised shell.
159 The newly initialised shell.
159 """
160 """
160 pass
161 pass
General Comments 0
You need to be logged in to leave comments. Login now