##// END OF EJS Templates
Added a first_reply signal to the heartbeat channel.
Brian Granger -
Show More
@@ -1,185 +1,202 b''
1 1 """ Defines a KernelManager that provides signals and slots.
2 2 """
3 3
4 4 # System library imports.
5 5 from PyQt4 import QtCore
6 import zmq
7 6
8 7 # IPython imports.
9 8 from IPython.utils.traitlets import Type
10 9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
11 10 XReqSocketChannel, RepSocketChannel, HBSocketChannel
12 11 from util import MetaQObjectHasTraits, SuperQObject
13 12
14 13
15 14 class SocketChannelQObject(SuperQObject):
16 15
17 16 # Emitted when the channel is started.
18 17 started = QtCore.pyqtSignal()
19 18
20 19 # Emitted when the channel is stopped.
21 20 stopped = QtCore.pyqtSignal()
22 21
23 22 #---------------------------------------------------------------------------
24 23 # 'ZmqSocketChannel' interface
25 24 #---------------------------------------------------------------------------
26 25
27 26 def start(self):
28 27 """ Reimplemented to emit signal.
29 28 """
30 29 super(SocketChannelQObject, self).start()
31 30 self.started.emit()
32 31
33 32 def stop(self):
34 33 """ Reimplemented to emit signal.
35 34 """
36 35 super(SocketChannelQObject, self).stop()
37 36 self.stopped.emit()
38 37
39 38
40 39 class QtXReqSocketChannel(SocketChannelQObject, XReqSocketChannel):
41 40
42 41 # Emitted when any message is received.
43 42 message_received = QtCore.pyqtSignal(object)
44 43
45 # Emitted when a reply has been received for the corresponding request type.
44 # Emitted when a reply has been received for the corresponding request
45 # type.
46 46 execute_reply = QtCore.pyqtSignal(object)
47 47 complete_reply = QtCore.pyqtSignal(object)
48 48 object_info_reply = QtCore.pyqtSignal(object)
49 49
50 # Emitted when the first reply comes back
51 first_reply = QtCore.pyqtSignal(object)
52
53 # Used by the first_reply signal logic to determine if a reply is the
54 # first.
55 _handlers_called = False
56
50 57 #---------------------------------------------------------------------------
51 58 # 'XReqSocketChannel' interface
52 59 #---------------------------------------------------------------------------
53 60
54 61 def call_handlers(self, msg):
55 62 """ Reimplemented to emit signals instead of making callbacks.
56 63 """
57 64 # Emit the generic signal.
58 65 self.message_received.emit(msg)
59 66
60 67 # Emit signals for specialized message types.
61 68 msg_type = msg['msg_type']
62 69 signal = getattr(self, msg_type, None)
63 70 if signal:
64 71 signal.emit(msg)
65 72
73 if not self._handlers_called:
74 self.first_reply.emit()
75
76 self._handlers_called = True
77
78 def reset_first_reply(self):
79 """ Reset the first_reply signal to fire again on the next reply.
80 """
81 self._handlers_called = False
82
66 83
67 84 class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):
68 85
69 86 # Emitted when any message is received.
70 87 message_received = QtCore.pyqtSignal(object)
71 88
72 89 # Emitted when a message of type 'stream' is received.
73 90 stream_received = QtCore.pyqtSignal(object)
74 91
75 92 # Emitted when a message of type 'pyin' is received.
76 93 pyin_received = QtCore.pyqtSignal(object)
77 94
78 95 # Emitted when a message of type 'pyout' is received.
79 96 pyout_received = QtCore.pyqtSignal(object)
80 97
81 98 # Emitted when a message of type 'pyerr' is received.
82 99 pyerr_received = QtCore.pyqtSignal(object)
83 100
84 101 # Emitted when a crash report message is received from the kernel's
85 102 # last-resort sys.excepthook.
86 103 crash_received = QtCore.pyqtSignal(object)
87 104
88 105 #---------------------------------------------------------------------------
89 106 # 'SubSocketChannel' interface
90 107 #---------------------------------------------------------------------------
91 108
92 109 def call_handlers(self, msg):
93 110 """ Reimplemented to emit signals instead of making callbacks.
94 111 """
95 112 # Emit the generic signal.
96 113 self.message_received.emit(msg)
97 114
98 115 # Emit signals for specialized message types.
99 116 msg_type = msg['msg_type']
100 117 signal = getattr(self, msg_type + '_received', None)
101 118 if signal:
102 119 signal.emit(msg)
103 120 elif msg_type in ('stdout', 'stderr'):
104 121 self.stream_received.emit(msg)
105 122
106 123 def flush(self):
107 124 """ Reimplemented to ensure that signals are dispatched immediately.
108 125 """
109 126 super(QtSubSocketChannel, self).flush()
110 127 QtCore.QCoreApplication.instance().processEvents()
111 128
112 129
113 130 class QtRepSocketChannel(SocketChannelQObject, RepSocketChannel):
114 131
115 132 # Emitted when any message is received.
116 133 message_received = QtCore.pyqtSignal(object)
117 134
118 135 # Emitted when an input request is received.
119 136 input_requested = QtCore.pyqtSignal(object)
120 137
121 138 #---------------------------------------------------------------------------
122 139 # 'RepSocketChannel' interface
123 140 #---------------------------------------------------------------------------
124 141
125 142 def call_handlers(self, msg):
126 143 """ Reimplemented to emit signals instead of making callbacks.
127 144 """
128 145 # Emit the generic signal.
129 146 self.message_received.emit(msg)
130 147
131 148 # Emit signals for specialized message types.
132 149 msg_type = msg['msg_type']
133 150 if msg_type == 'input_request':
134 151 self.input_requested.emit(msg)
135 152
136 153
137 154 class QtHBSocketChannel(SocketChannelQObject, HBSocketChannel):
138 155
139 156 # Emitted when the kernel has died.
140 157 kernel_died = QtCore.pyqtSignal(object)
141 158
142 159 #---------------------------------------------------------------------------
143 160 # 'HBSocketChannel' interface
144 161 #---------------------------------------------------------------------------
145 162
146 163 def call_handlers(self, since_last_heartbeat):
147 164 """ Reimplemented to emit signals instead of making callbacks.
148 165 """
149 166 # Emit the generic signal.
150 167 self.kernel_died.emit(since_last_heartbeat)
151 168
152 169
153 170 class QtKernelManager(KernelManager, SuperQObject):
154 171 """ A KernelManager that provides signals and slots.
155 172 """
156 173
157 174 __metaclass__ = MetaQObjectHasTraits
158 175
159 176 # Emitted when the kernel manager has started listening.
160 177 started_channels = QtCore.pyqtSignal()
161 178
162 179 # Emitted when the kernel manager has stopped listening.
163 180 stopped_channels = QtCore.pyqtSignal()
164 181
165 182 # Use Qt-specific channel classes that emit signals.
166 183 sub_channel_class = Type(QtSubSocketChannel)
167 184 xreq_channel_class = Type(QtXReqSocketChannel)
168 185 rep_channel_class = Type(QtRepSocketChannel)
169 186 hb_channel_class = Type(QtHBSocketChannel)
170 187
171 188 #---------------------------------------------------------------------------
172 189 # 'KernelManager' interface
173 190 #---------------------------------------------------------------------------
174 191
175 192 def start_channels(self, *args, **kw):
176 193 """ Reimplemented to emit signal.
177 194 """
178 195 super(QtKernelManager, self).start_channels(*args, **kw)
179 196 self.started_channels.emit()
180 197
181 198 def stop_channels(self):
182 199 """ Reimplemented to emit signal.
183 200 """
184 201 super(QtKernelManager, self).stop_channels()
185 202 self.stopped_channels.emit()
@@ -1,612 +1,612 b''
1 1 #!/usr/bin/env python
2 2 """A simple interactive kernel that talks to a frontend over 0MQ.
3 3
4 4 Things to do:
5 5
6 6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 7 call set_parent on all the PUB objects with the message about to be executed.
8 8 * Implement random port and security key logic.
9 9 * Implement control messages.
10 10 * Implement event loop and poll version.
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Standard library imports.
19 19 import __builtin__
20 20 import atexit
21 21 import sys
22 22 import time
23 23 import traceback
24 24
25 25 # System library imports.
26 26 import zmq
27 27
28 28 # Local imports.
29 29 from IPython.config.configurable import Configurable
30 30 from IPython.utils import io
31 31 from IPython.utils.jsonutil import json_clean
32 32 from IPython.lib import pylabtools
33 33 from IPython.utils.traitlets import Instance, Float
34 34 from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
35 35 start_kernel)
36 36 from iostream import OutStream
37 37 from session import Session, Message
38 38 from zmqshell import ZMQInteractiveShell
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Main kernel class
42 42 #-----------------------------------------------------------------------------
43 43
44 44 class Kernel(Configurable):
45 45
46 46 #---------------------------------------------------------------------------
47 47 # Kernel interface
48 48 #---------------------------------------------------------------------------
49 49
50 50 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
51 51 session = Instance(Session)
52 52 reply_socket = Instance('zmq.Socket')
53 53 pub_socket = Instance('zmq.Socket')
54 54 req_socket = Instance('zmq.Socket')
55 55
56 56 # Private interface
57 57
58 58 # Time to sleep after flushing the stdout/err buffers in each execute
59 59 # cycle. While this introduces a hard limit on the minimal latency of the
60 60 # execute cycle, it helps prevent output synchronization problems for
61 61 # clients.
62 62 # Units are in seconds. The minimum zmq latency on local host is probably
63 63 # ~150 microseconds, set this to 500us for now. We may need to increase it
64 64 # a little if it's not enough after more interactive testing.
65 65 _execute_sleep = Float(0.0005, config=True)
66 66
67 67 # Frequency of the kernel's event loop.
68 68 # Units are in seconds, kernel subclasses for GUI toolkits may need to
69 69 # adapt to milliseconds.
70 70 _poll_interval = Float(0.05, config=True)
71 71
72 72 # If the shutdown was requested over the network, we leave here the
73 73 # necessary reply message so it can be sent by our registered atexit
74 74 # handler. This ensures that the reply is only sent to clients truly at
75 75 # the end of our shutdown process (which happens after the underlying
76 76 # IPython shell's own shutdown).
77 77 _shutdown_message = None
78 78
79 79 # This is a dict of port number that the kernel is listening on. It is set
80 80 # by record_ports and used by connect_request.
81 81 _recorded_ports = None
82 82
83 83 def __init__(self, **kwargs):
84 84 super(Kernel, self).__init__(**kwargs)
85 85
86 86 # Before we even start up the shell, register *first* our exit handlers
87 87 # so they come before the shell's
88 88 atexit.register(self._at_shutdown)
89 89
90 90 # Initialize the InteractiveShell subclass
91 91 self.shell = ZMQInteractiveShell.instance()
92 92 self.shell.displayhook.session = self.session
93 93 self.shell.displayhook.pub_socket = self.pub_socket
94 94
95 95 # TMP - hack while developing
96 96 self.shell._reply_content = None
97 97
98 98 # Build dict of handlers for message types
99 99 msg_types = [ 'execute_request', 'complete_request',
100 100 'object_info_request', 'history_request',
101 'shutdown_request']
101 'connect_request', 'shutdown_request']
102 102 self.handlers = {}
103 103 for msg_type in msg_types:
104 104 self.handlers[msg_type] = getattr(self, msg_type)
105 105
106 106 def do_one_iteration(self):
107 107 """Do one iteration of the kernel's evaluation loop.
108 108 """
109 109 try:
110 110 ident = self.reply_socket.recv(zmq.NOBLOCK)
111 111 except zmq.ZMQError, e:
112 112 if e.errno == zmq.EAGAIN:
113 113 return
114 114 else:
115 115 raise
116 116 # FIXME: Bug in pyzmq/zmq?
117 117 # assert self.reply_socket.rcvmore(), "Missing message part."
118 118 msg = self.reply_socket.recv_json()
119 119
120 120 # Print some info about this message and leave a '--->' marker, so it's
121 121 # easier to trace visually the message chain when debugging. Each
122 122 # handler prints its message at the end.
123 123 # Eventually we'll move these from stdout to a logger.
124 124 io.raw_print('\n*** MESSAGE TYPE:', msg['msg_type'], '***')
125 125 io.raw_print(' Content: ', msg['content'],
126 126 '\n --->\n ', sep='', end='')
127 127
128 128 # Find and call actual handler for message
129 129 handler = self.handlers.get(msg['msg_type'], None)
130 130 if handler is None:
131 131 io.raw_print_err("UNKNOWN MESSAGE TYPE:", msg)
132 132 else:
133 133 handler(ident, msg)
134 134
135 135 # Check whether we should exit, in case the incoming message set the
136 136 # exit flag on
137 137 if self.shell.exit_now:
138 138 io.raw_print('\nExiting IPython kernel...')
139 139 # We do a normal, clean exit, which allows any actions registered
140 140 # via atexit (such as history saving) to take place.
141 141 sys.exit(0)
142 142
143 143
144 144 def start(self):
145 145 """ Start the kernel main loop.
146 146 """
147 147 while True:
148 148 time.sleep(self._poll_interval)
149 149 self.do_one_iteration()
150 150
151 151 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
152 152 """Record the ports that this kernel is using.
153 153
154 154 The creator of the Kernel instance must call this methods if they
155 155 want the :meth:`connect_request` method to return the port numbers.
156 156 """
157 157 self._recorded_ports = {
158 158 'xrep_port' : xrep_port,
159 159 'pub_port' : pub_port,
160 160 'req_port' : req_port,
161 161 'hb_port' : hb_port
162 162 }
163 163
164 164 #---------------------------------------------------------------------------
165 165 # Kernel request handlers
166 166 #---------------------------------------------------------------------------
167 167
168 168 def _publish_pyin(self, code, parent):
169 169 """Publish the code request on the pyin stream."""
170 170
171 171 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
172 172 self.pub_socket.send_json(pyin_msg)
173 173
174 174 def execute_request(self, ident, parent):
175 175 try:
176 176 content = parent[u'content']
177 177 code = content[u'code']
178 178 silent = content[u'silent']
179 179 except:
180 180 io.raw_print_err("Got bad msg: ")
181 181 io.raw_print_err(Message(parent))
182 182 return
183 183
184 184 shell = self.shell # we'll need this a lot here
185 185
186 186 # Replace raw_input. Note that is not sufficient to replace
187 187 # raw_input in the user namespace.
188 188 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
189 189 __builtin__.raw_input = raw_input
190 190
191 191 # Set the parent message of the display hook and out streams.
192 192 shell.displayhook.set_parent(parent)
193 193 sys.stdout.set_parent(parent)
194 194 sys.stderr.set_parent(parent)
195 195
196 196 # Re-broadcast our input for the benefit of listening clients, and
197 197 # start computing output
198 198 if not silent:
199 199 self._publish_pyin(code, parent)
200 200
201 201 reply_content = {}
202 202 try:
203 203 if silent:
204 204 # runcode uses 'exec' mode, so no displayhook will fire, and it
205 205 # doesn't call logging or history manipulations. Print
206 206 # statements in that code will obviously still execute.
207 207 shell.runcode(code)
208 208 else:
209 209 # FIXME: runlines calls the exception handler itself.
210 210 shell._reply_content = None
211 211
212 212 # For now leave this here until we're sure we can stop using it
213 213 #shell.runlines(code)
214 214
215 215 # Experimental: cell mode! Test more before turning into
216 216 # default and removing the hacks around runlines.
217 217 shell.run_cell(code)
218 218 except:
219 219 status = u'error'
220 220 # FIXME: this code right now isn't being used yet by default,
221 221 # because the runlines() call above directly fires off exception
222 222 # reporting. This code, therefore, is only active in the scenario
223 223 # where runlines itself has an unhandled exception. We need to
224 224 # uniformize this, for all exception construction to come from a
225 225 # single location in the codbase.
226 226 etype, evalue, tb = sys.exc_info()
227 227 tb_list = traceback.format_exception(etype, evalue, tb)
228 228 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
229 229 else:
230 230 status = u'ok'
231 231
232 232 reply_content[u'status'] = status
233 233 # Compute the execution counter so clients can display prompts
234 234 reply_content['execution_count'] = shell.displayhook.prompt_count
235 235
236 236 # FIXME - fish exception info out of shell, possibly left there by
237 237 # runlines. We'll need to clean up this logic later.
238 238 if shell._reply_content is not None:
239 239 reply_content.update(shell._reply_content)
240 240
241 241 # At this point, we can tell whether the main code execution succeeded
242 242 # or not. If it did, we proceed to evaluate user_variables/expressions
243 243 if reply_content['status'] == 'ok':
244 244 reply_content[u'user_variables'] = \
245 245 shell.get_user_variables(content[u'user_variables'])
246 246 reply_content[u'user_expressions'] = \
247 247 shell.eval_expressions(content[u'user_expressions'])
248 248 else:
249 249 # If there was an error, don't even try to compute variables or
250 250 # expressions
251 251 reply_content[u'user_variables'] = {}
252 252 reply_content[u'user_expressions'] = {}
253 253
254 254 # Payloads should be retrieved regardless of outcome, so we can both
255 255 # recover partial output (that could have been generated early in a
256 256 # block, before an error) and clear the payload system always.
257 257 reply_content[u'payload'] = shell.payload_manager.read_payload()
258 258 # Be agressive about clearing the payload because we don't want
259 259 # it to sit in memory until the next execute_request comes in.
260 260 shell.payload_manager.clear_payload()
261 261
262 262 # Send the reply.
263 263 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
264 264 io.raw_print(reply_msg)
265 265
266 266 # Flush output before sending the reply.
267 267 sys.stdout.flush()
268 268 sys.stderr.flush()
269 269 # FIXME: on rare occasions, the flush doesn't seem to make it to the
270 270 # clients... This seems to mitigate the problem, but we definitely need
271 271 # to better understand what's going on.
272 272 if self._execute_sleep:
273 273 time.sleep(self._execute_sleep)
274 274
275 275 self.reply_socket.send(ident, zmq.SNDMORE)
276 276 self.reply_socket.send_json(reply_msg)
277 277 if reply_msg['content']['status'] == u'error':
278 278 self._abort_queue()
279 279
280 280 def complete_request(self, ident, parent):
281 281 txt, matches = self._complete(parent)
282 282 matches = {'matches' : matches,
283 283 'matched_text' : txt,
284 284 'status' : 'ok'}
285 285 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
286 286 matches, parent, ident)
287 287 io.raw_print(completion_msg)
288 288
289 289 def object_info_request(self, ident, parent):
290 290 object_info = self.shell.object_inspect(parent['content']['oname'])
291 291 # Before we send this object over, we turn it into a dict and we scrub
292 292 # it for JSON usage
293 293 oinfo = json_clean(object_info._asdict())
294 294 msg = self.session.send(self.reply_socket, 'object_info_reply',
295 295 oinfo, parent, ident)
296 296 io.raw_print(msg)
297 297
298 298 def history_request(self, ident, parent):
299 299 output = parent['content']['output']
300 300 index = parent['content']['index']
301 301 raw = parent['content']['raw']
302 302 hist = self.shell.get_history(index=index, raw=raw, output=output)
303 303 content = {'history' : hist}
304 304 msg = self.session.send(self.reply_socket, 'history_reply',
305 305 content, parent, ident)
306 306 io.raw_print(msg)
307 307
308 308 def connect_request(self, ident, parent):
309 309 if self._recorded_ports is not None:
310 310 content = self._recorded_ports.copy()
311 311 else:
312 312 content = {}
313 313 msg = self.session.send(self.reply_socket, 'connect_reply',
314 314 content, parent, ident)
315 315 io.raw_print(msg)
316 316
317 317 def shutdown_request(self, ident, parent):
318 318 self.shell.exit_now = True
319 319 self._shutdown_message = self.session.msg(u'shutdown_reply', {}, parent)
320 320 sys.exit(0)
321 321
322 322 #---------------------------------------------------------------------------
323 323 # Protected interface
324 324 #---------------------------------------------------------------------------
325 325
326 326 def _abort_queue(self):
327 327 while True:
328 328 try:
329 329 ident = self.reply_socket.recv(zmq.NOBLOCK)
330 330 except zmq.ZMQError, e:
331 331 if e.errno == zmq.EAGAIN:
332 332 break
333 333 else:
334 334 assert self.reply_socket.rcvmore(), \
335 335 "Unexpected missing message part."
336 336 msg = self.reply_socket.recv_json()
337 337 io.raw_print("Aborting:\n", Message(msg))
338 338 msg_type = msg['msg_type']
339 339 reply_type = msg_type.split('_')[0] + '_reply'
340 340 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
341 341 io.raw_print(reply_msg)
342 342 self.reply_socket.send(ident,zmq.SNDMORE)
343 343 self.reply_socket.send_json(reply_msg)
344 344 # We need to wait a bit for requests to come in. This can probably
345 345 # be set shorter for true asynchronous clients.
346 346 time.sleep(0.1)
347 347
348 348 def _raw_input(self, prompt, ident, parent):
349 349 # Flush output before making the request.
350 350 sys.stderr.flush()
351 351 sys.stdout.flush()
352 352
353 353 # Send the input request.
354 354 content = dict(prompt=prompt)
355 355 msg = self.session.msg(u'input_request', content, parent)
356 356 self.req_socket.send_json(msg)
357 357
358 358 # Await a response.
359 359 reply = self.req_socket.recv_json()
360 360 try:
361 361 value = reply['content']['value']
362 362 except:
363 363 io.raw_print_err("Got bad raw_input reply: ")
364 364 io.raw_print_err(Message(parent))
365 365 value = ''
366 366 return value
367 367
368 368 def _complete(self, msg):
369 369 c = msg['content']
370 370 try:
371 371 cpos = int(c['cursor_pos'])
372 372 except:
373 373 # If we don't get something that we can convert to an integer, at
374 374 # least attempt the completion guessing the cursor is at the end of
375 375 # the text, if there's any, and otherwise of the line
376 376 cpos = len(c['text'])
377 377 if cpos==0:
378 378 cpos = len(c['line'])
379 379 return self.shell.complete(c['text'], c['line'], cpos)
380 380
381 381 def _object_info(self, context):
382 382 symbol, leftover = self._symbol_from_context(context)
383 383 if symbol is not None and not leftover:
384 384 doc = getattr(symbol, '__doc__', '')
385 385 else:
386 386 doc = ''
387 387 object_info = dict(docstring = doc)
388 388 return object_info
389 389
390 390 def _symbol_from_context(self, context):
391 391 if not context:
392 392 return None, context
393 393
394 394 base_symbol_string = context[0]
395 395 symbol = self.shell.user_ns.get(base_symbol_string, None)
396 396 if symbol is None:
397 397 symbol = __builtin__.__dict__.get(base_symbol_string, None)
398 398 if symbol is None:
399 399 return None, context
400 400
401 401 context = context[1:]
402 402 for i, name in enumerate(context):
403 403 new_symbol = getattr(symbol, name, None)
404 404 if new_symbol is None:
405 405 return symbol, context[i:]
406 406 else:
407 407 symbol = new_symbol
408 408
409 409 return symbol, []
410 410
411 411 def _at_shutdown(self):
412 412 """Actions taken at shutdown by the kernel, called by python's atexit.
413 413 """
414 414 # io.rprint("Kernel at_shutdown") # dbg
415 415 if self._shutdown_message is not None:
416 416 self.reply_socket.send_json(self._shutdown_message)
417 417 io.raw_print(self._shutdown_message)
418 418 # A very short sleep to give zmq time to flush its message buffers
419 419 # before Python truly shuts down.
420 420 time.sleep(0.01)
421 421
422 422
423 423 class QtKernel(Kernel):
424 424 """A Kernel subclass with Qt support."""
425 425
426 426 def start(self):
427 427 """Start a kernel with QtPy4 event loop integration."""
428 428
429 429 from PyQt4 import QtCore
430 430 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
431 431
432 432 self.app = get_app_qt4([" "])
433 433 self.app.setQuitOnLastWindowClosed(False)
434 434 self.timer = QtCore.QTimer()
435 435 self.timer.timeout.connect(self.do_one_iteration)
436 436 # Units for the timer are in milliseconds
437 437 self.timer.start(1000*self._poll_interval)
438 438 start_event_loop_qt4(self.app)
439 439
440 440
441 441 class WxKernel(Kernel):
442 442 """A Kernel subclass with Wx support."""
443 443
444 444 def start(self):
445 445 """Start a kernel with wx event loop support."""
446 446
447 447 import wx
448 448 from IPython.lib.guisupport import start_event_loop_wx
449 449
450 450 doi = self.do_one_iteration
451 451 # Wx uses milliseconds
452 452 poll_interval = int(1000*self._poll_interval)
453 453
454 454 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
455 455 # We make the Frame hidden when we create it in the main app below.
456 456 class TimerFrame(wx.Frame):
457 457 def __init__(self, func):
458 458 wx.Frame.__init__(self, None, -1)
459 459 self.timer = wx.Timer(self)
460 460 # Units for the timer are in milliseconds
461 461 self.timer.Start(poll_interval)
462 462 self.Bind(wx.EVT_TIMER, self.on_timer)
463 463 self.func = func
464 464
465 465 def on_timer(self, event):
466 466 self.func()
467 467
468 468 # We need a custom wx.App to create our Frame subclass that has the
469 469 # wx.Timer to drive the ZMQ event loop.
470 470 class IPWxApp(wx.App):
471 471 def OnInit(self):
472 472 self.frame = TimerFrame(doi)
473 473 self.frame.Show(False)
474 474 return True
475 475
476 476 # The redirect=False here makes sure that wx doesn't replace
477 477 # sys.stdout/stderr with its own classes.
478 478 self.app = IPWxApp(redirect=False)
479 479 start_event_loop_wx(self.app)
480 480
481 481
482 482 class TkKernel(Kernel):
483 483 """A Kernel subclass with Tk support."""
484 484
485 485 def start(self):
486 486 """Start a Tk enabled event loop."""
487 487
488 488 import Tkinter
489 489 doi = self.do_one_iteration
490 490 # Tk uses milliseconds
491 491 poll_interval = int(1000*self._poll_interval)
492 492 # For Tkinter, we create a Tk object and call its withdraw method.
493 493 class Timer(object):
494 494 def __init__(self, func):
495 495 self.app = Tkinter.Tk()
496 496 self.app.withdraw()
497 497 self.func = func
498 498
499 499 def on_timer(self):
500 500 self.func()
501 501 self.app.after(poll_interval, self.on_timer)
502 502
503 503 def start(self):
504 504 self.on_timer() # Call it once to get things going.
505 505 self.app.mainloop()
506 506
507 507 self.timer = Timer(doi)
508 508 self.timer.start()
509 509
510 510
511 511 class GTKKernel(Kernel):
512 512 """A Kernel subclass with GTK support."""
513 513
514 514 def start(self):
515 515 """Start the kernel, coordinating with the GTK event loop"""
516 516 from .gui.gtkembed import GTKEmbed
517 517
518 518 gtk_kernel = GTKEmbed(self)
519 519 gtk_kernel.start()
520 520
521 521
522 522 #-----------------------------------------------------------------------------
523 523 # Kernel main and launch functions
524 524 #-----------------------------------------------------------------------------
525 525
526 526 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
527 527 independent=False, pylab=False):
528 528 """Launches a localhost kernel, binding to the specified ports.
529 529
530 530 Parameters
531 531 ----------
532 532 xrep_port : int, optional
533 533 The port to use for XREP channel.
534 534
535 535 pub_port : int, optional
536 536 The port to use for the SUB channel.
537 537
538 538 req_port : int, optional
539 539 The port to use for the REQ (raw input) channel.
540 540
541 541 hb_port : int, optional
542 542 The port to use for the hearbeat REP channel.
543 543
544 544 independent : bool, optional (default False)
545 545 If set, the kernel process is guaranteed to survive if this process
546 546 dies. If not set, an effort is made to ensure that the kernel is killed
547 547 when this process dies. Note that in this case it is still good practice
548 548 to kill kernels manually before exiting.
549 549
550 550 pylab : bool or string, optional (default False)
551 551 If not False, the kernel will be launched with pylab enabled. If a
552 552 string is passed, matplotlib will use the specified backend. Otherwise,
553 553 matplotlib's default backend will be used.
554 554
555 555 Returns
556 556 -------
557 557 A tuple of form:
558 558 (kernel_process, xrep_port, pub_port, req_port)
559 559 where kernel_process is a Popen object and the ports are integers.
560 560 """
561 561 extra_arguments = []
562 562 if pylab:
563 563 extra_arguments.append('--pylab')
564 564 if isinstance(pylab, basestring):
565 565 extra_arguments.append(pylab)
566 566 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
567 567 xrep_port, pub_port, req_port, hb_port,
568 568 independent, extra_arguments)
569 569
570 570
571 571 def main():
572 572 """ The IPython kernel main entry point.
573 573 """
574 574 parser = make_argument_parser()
575 575 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
576 576 const='auto', help = \
577 577 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
578 578 given, the GUI backend is matplotlib's, otherwise use one of: \
579 579 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
580 580 namespace = parser.parse_args()
581 581
582 582 kernel_class = Kernel
583 583
584 584 kernel_classes = {
585 585 'qt' : QtKernel,
586 586 'qt4': QtKernel,
587 587 'inline': Kernel,
588 588 'wx' : WxKernel,
589 589 'tk' : TkKernel,
590 590 'gtk': GTKKernel,
591 591 }
592 592 if namespace.pylab:
593 593 if namespace.pylab == 'auto':
594 594 gui, backend = pylabtools.find_gui_and_backend()
595 595 else:
596 596 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
597 597 kernel_class = kernel_classes.get(gui)
598 598 if kernel_class is None:
599 599 raise ValueError('GUI is not supported: %r' % gui)
600 600 pylabtools.activate_matplotlib(backend)
601 601
602 602 kernel = make_kernel(namespace, kernel_class, OutStream)
603 603
604 604 if namespace.pylab:
605 605 pylabtools.import_pylab(kernel.shell.user_ns, backend,
606 606 shell=kernel.shell)
607 607
608 608 start_kernel(namespace, kernel)
609 609
610 610
611 611 if __name__ == '__main__':
612 612 main()
General Comments 0
You need to be logged in to leave comments. Login now