##// END OF EJS Templates
re-enter kernel.eventloop after catching SIGINT
MinRK -
Show More
@@ -1,652 +1,657 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 import logging
25 25 from signal import (
26 26 signal, default_int_handler, SIGINT, SIG_IGN
27 27 )
28 28 # System library imports.
29 29 import zmq
30 30
31 31 # Local imports.
32 32 from IPython.core import pylabtools
33 33 from IPython.config.configurable import Configurable
34 34 from IPython.config.application import boolean_flag, catch_config_error
35 35 from IPython.core.application import ProfileDir
36 36 from IPython.core.error import StdinNotImplementedError
37 37 from IPython.core.shellapp import (
38 38 InteractiveShellApp, shell_flags, shell_aliases
39 39 )
40 40 from IPython.utils import io
41 41 from IPython.utils import py3compat
42 42 from IPython.utils.jsonutil import json_clean
43 43 from IPython.utils.traitlets import (
44 44 Any, Instance, Float, Dict, CaselessStrEnum
45 45 )
46 46
47 47 from entry_point import base_launch_kernel
48 48 from kernelapp import KernelApp, kernel_flags, kernel_aliases
49 49 from session import Session, Message
50 50 from zmqshell import ZMQInteractiveShell
51 51
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Main kernel class
55 55 #-----------------------------------------------------------------------------
56 56
57 57 class Kernel(Configurable):
58 58
59 59 #---------------------------------------------------------------------------
60 60 # Kernel interface
61 61 #---------------------------------------------------------------------------
62 62
63 63 # attribute to override with a GUI
64 64 eventloop = Any(None)
65 65
66 66 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
67 67 session = Instance(Session)
68 68 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
69 69 shell_socket = Instance('zmq.Socket')
70 70 iopub_socket = Instance('zmq.Socket')
71 71 stdin_socket = Instance('zmq.Socket')
72 72 log = Instance(logging.Logger)
73 73
74 74 # Private interface
75 75
76 76 # Time to sleep after flushing the stdout/err buffers in each execute
77 77 # cycle. While this introduces a hard limit on the minimal latency of the
78 78 # execute cycle, it helps prevent output synchronization problems for
79 79 # clients.
80 80 # Units are in seconds. The minimum zmq latency on local host is probably
81 81 # ~150 microseconds, set this to 500us for now. We may need to increase it
82 82 # a little if it's not enough after more interactive testing.
83 83 _execute_sleep = Float(0.0005, config=True)
84 84
85 85 # Frequency of the kernel's event loop.
86 86 # Units are in seconds, kernel subclasses for GUI toolkits may need to
87 87 # adapt to milliseconds.
88 88 _poll_interval = Float(0.05, config=True)
89 89
90 90 # If the shutdown was requested over the network, we leave here the
91 91 # necessary reply message so it can be sent by our registered atexit
92 92 # handler. This ensures that the reply is only sent to clients truly at
93 93 # the end of our shutdown process (which happens after the underlying
94 94 # IPython shell's own shutdown).
95 95 _shutdown_message = None
96 96
97 97 # This is a dict of port number that the kernel is listening on. It is set
98 98 # by record_ports and used by connect_request.
99 99 _recorded_ports = Dict()
100 100
101 101
102 102
103 103 def __init__(self, **kwargs):
104 104 super(Kernel, self).__init__(**kwargs)
105 105
106 106 # Before we even start up the shell, register *first* our exit handlers
107 107 # so they come before the shell's
108 108 atexit.register(self._at_shutdown)
109 109
110 110 # Initialize the InteractiveShell subclass
111 111 self.shell = ZMQInteractiveShell.instance(config=self.config,
112 112 profile_dir = self.profile_dir,
113 113 )
114 114 self.shell.displayhook.session = self.session
115 115 self.shell.displayhook.pub_socket = self.iopub_socket
116 116 self.shell.display_pub.session = self.session
117 117 self.shell.display_pub.pub_socket = self.iopub_socket
118 118
119 119 # TMP - hack while developing
120 120 self.shell._reply_content = None
121 121
122 122 # Build dict of handlers for message types
123 123 msg_types = [ 'execute_request', 'complete_request',
124 124 'object_info_request', 'history_request',
125 125 'connect_request', 'shutdown_request']
126 126 self.handlers = {}
127 127 for msg_type in msg_types:
128 128 self.handlers[msg_type] = getattr(self, msg_type)
129 129
130 130 def do_one_iteration(self):
131 131 """Do one iteration of the kernel's evaluation loop.
132 132 """
133 133 try:
134 134 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
135 135 except Exception:
136 136 self.log.warn("Invalid Message:", exc_info=True)
137 137 return
138 138 if msg is None:
139 139 return
140 140
141 141 msg_type = msg['header']['msg_type']
142 142
143 143 # This assert will raise in versions of zeromq 2.0.7 and lesser.
144 144 # We now require 2.0.8 or above, so we can uncomment for safety.
145 145 # print(ident,msg, file=sys.__stdout__)
146 146 assert ident is not None, "Missing message part."
147 147
148 148 # Print some info about this message and leave a '--->' marker, so it's
149 149 # easier to trace visually the message chain when debugging. Each
150 150 # handler prints its message at the end.
151 151 self.log.debug('\n*** MESSAGE TYPE:'+str(msg_type)+'***')
152 152 self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
153 153
154 154 # Find and call actual handler for message
155 155 handler = self.handlers.get(msg_type, None)
156 156 if handler is None:
157 157 self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
158 158 else:
159 159 handler(ident, msg)
160 160
161 161 # Check whether we should exit, in case the incoming message set the
162 162 # exit flag on
163 163 if self.shell.exit_now:
164 164 self.log.debug('\nExiting IPython kernel...')
165 165 # We do a normal, clean exit, which allows any actions registered
166 166 # via atexit (such as history saving) to take place.
167 167 sys.exit(0)
168 168
169 169
170 170 def start(self):
171 171 """ Start the kernel main loop.
172 172 """
173 173 # a KeyboardInterrupt (SIGINT) can occur on any python statement, so
174 174 # let's ignore (SIG_IGN) them until we're in a place to handle them properly
175 175 signal(SIGINT,SIG_IGN)
176 176 poller = zmq.Poller()
177 177 poller.register(self.shell_socket, zmq.POLLIN)
178 178 # loop while self.eventloop has not been overridden
179 179 while self.eventloop is None:
180 180 try:
181 181 # scale by extra factor of 10, because there is no
182 182 # reason for this to be anything less than ~ 0.1s
183 183 # since it is a real poller and will respond
184 184 # to events immediately
185 185
186 186 # double nested try/except, to properly catch KeyboardInterrupt
187 187 # due to pyzmq Issue #130
188 188 try:
189 189 poller.poll(10*1000*self._poll_interval)
190 190 # restore raising of KeyboardInterrupt
191 191 signal(SIGINT, default_int_handler)
192 192 self.do_one_iteration()
193 193 except:
194 194 raise
195 195 finally:
196 196 # prevent raising of KeyboardInterrupt
197 197 signal(SIGINT,SIG_IGN)
198 198 except KeyboardInterrupt:
199 199 # Ctrl-C shouldn't crash the kernel
200 200 io.raw_print("KeyboardInterrupt caught in kernel")
201 201 # stop ignoring sigint, now that we are out of our own loop,
202 202 # we don't want to prevent future code from handling it
203 203 signal(SIGINT, default_int_handler)
204 if self.eventloop is not None:
204 while self.eventloop is not None:
205 205 try:
206 206 self.eventloop(self)
207 207 except KeyboardInterrupt:
208 208 # Ctrl-C shouldn't crash the kernel
209 209 io.raw_print("KeyboardInterrupt caught in kernel")
210 continue
211 else:
212 # eventloop exited cleanly, this means we should stop (right?)
213 self.eventloop = None
214 break
210 215
211 216
212 217 def record_ports(self, ports):
213 218 """Record the ports that this kernel is using.
214 219
215 220 The creator of the Kernel instance must call this methods if they
216 221 want the :meth:`connect_request` method to return the port numbers.
217 222 """
218 223 self._recorded_ports = ports
219 224
220 225 #---------------------------------------------------------------------------
221 226 # Kernel request handlers
222 227 #---------------------------------------------------------------------------
223 228
224 229 def _publish_pyin(self, code, parent):
225 230 """Publish the code request on the pyin stream."""
226 231
227 232 self.session.send(self.iopub_socket, u'pyin', {u'code':code},
228 233 parent=parent)
229 234
230 235 def execute_request(self, ident, parent):
231 236
232 237 self.session.send(self.iopub_socket,
233 238 u'status',
234 239 {u'execution_state':u'busy'},
235 240 parent=parent )
236 241
237 242 try:
238 243 content = parent[u'content']
239 244 code = content[u'code']
240 245 silent = content[u'silent']
241 246 except:
242 247 self.log.error("Got bad msg: ")
243 248 self.log.error(str(Message(parent)))
244 249 return
245 250
246 251 shell = self.shell # we'll need this a lot here
247 252
248 253 # Replace raw_input. Note that is not sufficient to replace
249 254 # raw_input in the user namespace.
250 255 if content.get('allow_stdin', False):
251 256 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
252 257 else:
253 258 raw_input = lambda prompt='' : self._no_raw_input()
254 259
255 260 if py3compat.PY3:
256 261 __builtin__.input = raw_input
257 262 else:
258 263 __builtin__.raw_input = raw_input
259 264
260 265 # Set the parent message of the display hook and out streams.
261 266 shell.displayhook.set_parent(parent)
262 267 shell.display_pub.set_parent(parent)
263 268 sys.stdout.set_parent(parent)
264 269 sys.stderr.set_parent(parent)
265 270
266 271 # Re-broadcast our input for the benefit of listening clients, and
267 272 # start computing output
268 273 if not silent:
269 274 self._publish_pyin(code, parent)
270 275
271 276 reply_content = {}
272 277 try:
273 278 if silent:
274 279 # run_code uses 'exec' mode, so no displayhook will fire, and it
275 280 # doesn't call logging or history manipulations. Print
276 281 # statements in that code will obviously still execute.
277 282 shell.run_code(code)
278 283 else:
279 284 # FIXME: the shell calls the exception handler itself.
280 285 shell.run_cell(code, store_history=True)
281 286 except:
282 287 status = u'error'
283 288 # FIXME: this code right now isn't being used yet by default,
284 289 # because the run_cell() call above directly fires off exception
285 290 # reporting. This code, therefore, is only active in the scenario
286 291 # where runlines itself has an unhandled exception. We need to
287 292 # uniformize this, for all exception construction to come from a
288 293 # single location in the codbase.
289 294 etype, evalue, tb = sys.exc_info()
290 295 tb_list = traceback.format_exception(etype, evalue, tb)
291 296 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
292 297 else:
293 298 status = u'ok'
294 299
295 300 reply_content[u'status'] = status
296 301
297 302 # Return the execution counter so clients can display prompts
298 303 reply_content['execution_count'] = shell.execution_count -1
299 304
300 305 # FIXME - fish exception info out of shell, possibly left there by
301 306 # runlines. We'll need to clean up this logic later.
302 307 if shell._reply_content is not None:
303 308 reply_content.update(shell._reply_content)
304 309 # reset after use
305 310 shell._reply_content = None
306 311
307 312 # At this point, we can tell whether the main code execution succeeded
308 313 # or not. If it did, we proceed to evaluate user_variables/expressions
309 314 if reply_content['status'] == 'ok':
310 315 reply_content[u'user_variables'] = \
311 316 shell.user_variables(content[u'user_variables'])
312 317 reply_content[u'user_expressions'] = \
313 318 shell.user_expressions(content[u'user_expressions'])
314 319 else:
315 320 # If there was an error, don't even try to compute variables or
316 321 # expressions
317 322 reply_content[u'user_variables'] = {}
318 323 reply_content[u'user_expressions'] = {}
319 324
320 325 # Payloads should be retrieved regardless of outcome, so we can both
321 326 # recover partial output (that could have been generated early in a
322 327 # block, before an error) and clear the payload system always.
323 328 reply_content[u'payload'] = shell.payload_manager.read_payload()
324 329 # Be agressive about clearing the payload because we don't want
325 330 # it to sit in memory until the next execute_request comes in.
326 331 shell.payload_manager.clear_payload()
327 332
328 333 # Flush output before sending the reply.
329 334 sys.stdout.flush()
330 335 sys.stderr.flush()
331 336 # FIXME: on rare occasions, the flush doesn't seem to make it to the
332 337 # clients... This seems to mitigate the problem, but we definitely need
333 338 # to better understand what's going on.
334 339 if self._execute_sleep:
335 340 time.sleep(self._execute_sleep)
336 341
337 342 # Send the reply.
338 343 reply_content = json_clean(reply_content)
339 344 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
340 345 reply_content, parent, ident=ident)
341 346 self.log.debug(str(reply_msg))
342 347
343 348 if reply_msg['content']['status'] == u'error':
344 349 self._abort_queue()
345 350
346 351 self.session.send(self.iopub_socket,
347 352 u'status',
348 353 {u'execution_state':u'idle'},
349 354 parent=parent )
350 355
351 356 def complete_request(self, ident, parent):
352 357 txt, matches = self._complete(parent)
353 358 matches = {'matches' : matches,
354 359 'matched_text' : txt,
355 360 'status' : 'ok'}
356 361 matches = json_clean(matches)
357 362 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
358 363 matches, parent, ident)
359 364 self.log.debug(str(completion_msg))
360 365
361 366 def object_info_request(self, ident, parent):
362 367 object_info = self.shell.object_inspect(parent['content']['oname'])
363 368 # Before we send this object over, we scrub it for JSON usage
364 369 oinfo = json_clean(object_info)
365 370 msg = self.session.send(self.shell_socket, 'object_info_reply',
366 371 oinfo, parent, ident)
367 372 self.log.debug(msg)
368 373
369 374 def history_request(self, ident, parent):
370 375 # We need to pull these out, as passing **kwargs doesn't work with
371 376 # unicode keys before Python 2.6.5.
372 377 hist_access_type = parent['content']['hist_access_type']
373 378 raw = parent['content']['raw']
374 379 output = parent['content']['output']
375 380 if hist_access_type == 'tail':
376 381 n = parent['content']['n']
377 382 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
378 383 include_latest=True)
379 384
380 385 elif hist_access_type == 'range':
381 386 session = parent['content']['session']
382 387 start = parent['content']['start']
383 388 stop = parent['content']['stop']
384 389 hist = self.shell.history_manager.get_range(session, start, stop,
385 390 raw=raw, output=output)
386 391
387 392 elif hist_access_type == 'search':
388 393 pattern = parent['content']['pattern']
389 394 hist = self.shell.history_manager.search(pattern, raw=raw,
390 395 output=output)
391 396
392 397 else:
393 398 hist = []
394 399 hist = list(hist)
395 400 content = {'history' : hist}
396 401 content = json_clean(content)
397 402 msg = self.session.send(self.shell_socket, 'history_reply',
398 403 content, parent, ident)
399 404 self.log.debug("Sending history reply with %i entries", len(hist))
400 405
401 406 def connect_request(self, ident, parent):
402 407 if self._recorded_ports is not None:
403 408 content = self._recorded_ports.copy()
404 409 else:
405 410 content = {}
406 411 msg = self.session.send(self.shell_socket, 'connect_reply',
407 412 content, parent, ident)
408 413 self.log.debug(msg)
409 414
410 415 def shutdown_request(self, ident, parent):
411 416 self.shell.exit_now = True
412 417 self._shutdown_message = self.session.msg(u'shutdown_reply',
413 418 parent['content'], parent)
414 419 sys.exit(0)
415 420
416 421 #---------------------------------------------------------------------------
417 422 # Protected interface
418 423 #---------------------------------------------------------------------------
419 424
420 425 def _abort_queue(self):
421 426 while True:
422 427 try:
423 428 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
424 429 except Exception:
425 430 self.log.warn("Invalid Message:", exc_info=True)
426 431 continue
427 432 if msg is None:
428 433 break
429 434 else:
430 435 assert ident is not None, \
431 436 "Unexpected missing message part."
432 437
433 438 self.log.debug("Aborting:\n"+str(Message(msg)))
434 439 msg_type = msg['header']['msg_type']
435 440 reply_type = msg_type.split('_')[0] + '_reply'
436 441 reply_msg = self.session.send(self.shell_socket, reply_type,
437 442 {'status' : 'aborted'}, msg, ident=ident)
438 443 self.log.debug(reply_msg)
439 444 # We need to wait a bit for requests to come in. This can probably
440 445 # be set shorter for true asynchronous clients.
441 446 time.sleep(0.1)
442 447
443 448 def _no_raw_input(self):
444 449 """Raise StdinNotImplentedError if active frontend doesn't support
445 450 stdin."""
446 451 raise StdinNotImplementedError("raw_input was called, but this "
447 452 "frontend does not support stdin.")
448 453
449 454 def _raw_input(self, prompt, ident, parent):
450 455 # Flush output before making the request.
451 456 sys.stderr.flush()
452 457 sys.stdout.flush()
453 458
454 459 # Send the input request.
455 460 content = json_clean(dict(prompt=prompt))
456 461 self.session.send(self.stdin_socket, u'input_request', content, parent,
457 462 ident=ident)
458 463
459 464 # Await a response.
460 465 while True:
461 466 try:
462 467 ident, reply = self.session.recv(self.stdin_socket, 0)
463 468 except Exception:
464 469 self.log.warn("Invalid Message:", exc_info=True)
465 470 else:
466 471 break
467 472 try:
468 473 value = reply['content']['value']
469 474 except:
470 475 self.log.error("Got bad raw_input reply: ")
471 476 self.log.error(str(Message(parent)))
472 477 value = ''
473 478 if value == '\x04':
474 479 # EOF
475 480 raise EOFError
476 481 return value
477 482
478 483 def _complete(self, msg):
479 484 c = msg['content']
480 485 try:
481 486 cpos = int(c['cursor_pos'])
482 487 except:
483 488 # If we don't get something that we can convert to an integer, at
484 489 # least attempt the completion guessing the cursor is at the end of
485 490 # the text, if there's any, and otherwise of the line
486 491 cpos = len(c['text'])
487 492 if cpos==0:
488 493 cpos = len(c['line'])
489 494 return self.shell.complete(c['text'], c['line'], cpos)
490 495
491 496 def _object_info(self, context):
492 497 symbol, leftover = self._symbol_from_context(context)
493 498 if symbol is not None and not leftover:
494 499 doc = getattr(symbol, '__doc__', '')
495 500 else:
496 501 doc = ''
497 502 object_info = dict(docstring = doc)
498 503 return object_info
499 504
500 505 def _symbol_from_context(self, context):
501 506 if not context:
502 507 return None, context
503 508
504 509 base_symbol_string = context[0]
505 510 symbol = self.shell.user_ns.get(base_symbol_string, None)
506 511 if symbol is None:
507 512 symbol = __builtin__.__dict__.get(base_symbol_string, None)
508 513 if symbol is None:
509 514 return None, context
510 515
511 516 context = context[1:]
512 517 for i, name in enumerate(context):
513 518 new_symbol = getattr(symbol, name, None)
514 519 if new_symbol is None:
515 520 return symbol, context[i:]
516 521 else:
517 522 symbol = new_symbol
518 523
519 524 return symbol, []
520 525
521 526 def _at_shutdown(self):
522 527 """Actions taken at shutdown by the kernel, called by python's atexit.
523 528 """
524 529 # io.rprint("Kernel at_shutdown") # dbg
525 530 if self._shutdown_message is not None:
526 531 self.session.send(self.shell_socket, self._shutdown_message)
527 532 self.session.send(self.iopub_socket, self._shutdown_message)
528 533 self.log.debug(str(self._shutdown_message))
529 534 # A very short sleep to give zmq time to flush its message buffers
530 535 # before Python truly shuts down.
531 536 time.sleep(0.01)
532 537
533 538 #-----------------------------------------------------------------------------
534 539 # Aliases and Flags for the IPKernelApp
535 540 #-----------------------------------------------------------------------------
536 541
537 542 flags = dict(kernel_flags)
538 543 flags.update(shell_flags)
539 544
540 545 addflag = lambda *args: flags.update(boolean_flag(*args))
541 546
542 547 flags['pylab'] = (
543 548 {'IPKernelApp' : {'pylab' : 'auto'}},
544 549 """Pre-load matplotlib and numpy for interactive use with
545 550 the default matplotlib backend."""
546 551 )
547 552
548 553 aliases = dict(kernel_aliases)
549 554 aliases.update(shell_aliases)
550 555
551 556 # it's possible we don't want short aliases for *all* of these:
552 557 aliases.update(dict(
553 558 pylab='IPKernelApp.pylab',
554 559 ))
555 560
556 561 #-----------------------------------------------------------------------------
557 562 # The IPKernelApp class
558 563 #-----------------------------------------------------------------------------
559 564
560 565 class IPKernelApp(KernelApp, InteractiveShellApp):
561 566 name = 'ipkernel'
562 567
563 568 aliases = Dict(aliases)
564 569 flags = Dict(flags)
565 570 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
566 571 # configurables
567 572 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
568 573 config=True,
569 574 help="""Pre-load matplotlib and numpy for interactive use,
570 575 selecting a particular matplotlib backend and loop integration.
571 576 """
572 577 )
573 578
574 579 @catch_config_error
575 580 def initialize(self, argv=None):
576 581 super(IPKernelApp, self).initialize(argv)
577 582 self.init_shell()
578 583 self.init_extensions()
579 584 self.init_code()
580 585
581 586 def init_kernel(self):
582 587
583 588 kernel = Kernel(config=self.config, session=self.session,
584 589 shell_socket=self.shell_socket,
585 590 iopub_socket=self.iopub_socket,
586 591 stdin_socket=self.stdin_socket,
587 592 log=self.log,
588 593 profile_dir=self.profile_dir,
589 594 )
590 595 self.kernel = kernel
591 596 kernel.record_ports(self.ports)
592 597 shell = kernel.shell
593 598 if self.pylab:
594 599 try:
595 600 gui, backend = pylabtools.find_gui_and_backend(self.pylab)
596 601 shell.enable_pylab(gui, import_all=self.pylab_import_all)
597 602 except Exception:
598 603 self.log.error("Pylab initialization failed", exc_info=True)
599 604 # print exception straight to stdout, because normally
600 605 # _showtraceback associates the reply with an execution,
601 606 # which means frontends will never draw it, as this exception
602 607 # is not associated with any execute request.
603 608
604 609 # replace pyerr-sending traceback with stdout
605 610 _showtraceback = shell._showtraceback
606 611 def print_tb(etype, evalue, stb):
607 612 print ("Error initializing pylab, pylab mode will not "
608 613 "be active", file=io.stderr)
609 614 print (shell.InteractiveTB.stb2text(stb), file=io.stdout)
610 615 shell._showtraceback = print_tb
611 616
612 617 # send the traceback over stdout
613 618 shell.showtraceback(tb_offset=0)
614 619
615 620 # restore proper _showtraceback method
616 621 shell._showtraceback = _showtraceback
617 622
618 623
619 624 def init_shell(self):
620 625 self.shell = self.kernel.shell
621 626 self.shell.configurables.append(self)
622 627
623 628
624 629 #-----------------------------------------------------------------------------
625 630 # Kernel main and launch functions
626 631 #-----------------------------------------------------------------------------
627 632
628 633 def launch_kernel(*args, **kwargs):
629 634 """Launches a localhost IPython kernel, binding to the specified ports.
630 635
631 636 This function simply calls entry_point.base_launch_kernel with the right
632 637 first command to start an ipkernel. See base_launch_kernel for arguments.
633 638
634 639 Returns
635 640 -------
636 641 A tuple of form:
637 642 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
638 643 where kernel_process is a Popen object and the ports are integers.
639 644 """
640 645 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
641 646 *args, **kwargs)
642 647
643 648
644 649 def main():
645 650 """Run an IPKernel as an application"""
646 651 app = IPKernelApp.instance()
647 652 app.initialize()
648 653 app.start()
649 654
650 655
651 656 if __name__ == '__main__':
652 657 main()
General Comments 0
You need to be logged in to leave comments. Login now