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