##// END OF EJS Templates
Adjustment to console signal-handling...
MinRK -
Show More
@@ -1,137 +1,149 b''
1 1 """ A minimal application using the ZMQ-based terminal IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Min RK
9 9 * Paul Ivanov
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 import signal
17 17 import sys
18 18 import time
19 19
20 20 from IPython.frontend.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
21 21
22 22 from IPython.utils.traitlets import (
23 23 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
24 24 )
25 from IPython.utils.warn import warn,error
26
25 27 from IPython.zmq.ipkernel import IPKernelApp
26 28 from IPython.zmq.session import Session, default_secure
27 29 from IPython.zmq.zmqshell import ZMQInteractiveShell
28 30 from IPython.frontend.kernelmixinapp import (
29 31 IPythonMixinConsoleApp, app_aliases, app_flags, aliases, app_aliases, flags
30 32 )
31 33
32 34 from IPython.frontend.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
33 35
34 36 #-----------------------------------------------------------------------------
35 37 # Globals
36 38 #-----------------------------------------------------------------------------
37 39
38 40 _examples = """
39 41 ipython console # start the ZMQ-based console
40 42 ipython console --existing # connect to an existing ipython session
41 43 """
42 44
43 45 #-----------------------------------------------------------------------------
44 46 # Flags and Aliases
45 47 #-----------------------------------------------------------------------------
46 48
47 49 # copy flags from mixin:
48 50 flags = dict(flags)
49 51 # start with mixin frontend flags:
50 52 frontend_flags = dict(app_flags)
51 53 # add TerminalIPApp flags:
52 54 frontend_flags.update(term_flags)
53 55 # pylab is not frontend-specific in two-process IPython
54 56 frontend_flags.pop('pylab')
55 57 # disable quick startup, as it won't propagate to the kernel anyway
56 58 frontend_flags.pop('quick')
57 59 # update full dict with frontend flags:
58 60 flags.update(frontend_flags)
59 61
60 62 # copy flags from mixin
61 63 aliases = dict(aliases)
62 64 # start with mixin frontend flags
63 65 frontend_aliases = dict(app_aliases)
64 66 # load updated frontend flags into full dict
65 67 aliases.update(frontend_aliases)
66 68
67 69 # get flags&aliases into sets, and remove a couple that
68 70 # shouldn't be scrubbed from backend flags:
69 71 frontend_aliases = set(frontend_aliases.keys())
70 72 frontend_flags = set(frontend_flags.keys())
71 73
72 74
73 75 #-----------------------------------------------------------------------------
74 76 # Classes
75 77 #-----------------------------------------------------------------------------
76 78
77 79
78 80 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonMixinConsoleApp):
79 81 name = "ipython-console"
80 82 """Start a terminal frontend to the IPython zmq kernel."""
81 83
82 84 description = """
83 85 The IPython terminal-based Console.
84 86
85 87 This launches a Console application inside a terminal.
86 88
87 89 The Console supports various extra features beyond the traditional
88 90 single-process Terminal IPython shell, such as connecting to an
89 91 existing ipython session, via:
90 92
91 93 ipython console --existing
92 94
93 95 where the previous session could have been created by another ipython
94 96 console, an ipython qtconsole, or by opening an ipython notebook.
95 97
96 98 """
97 99 examples = _examples
98 100
99 101 classes = List([IPKernelApp, ZMQTerminalInteractiveShell, Session])
100 102 flags = Dict(flags)
101 103 aliases = Dict(aliases)
102 104 subcommands = Dict()
103 105
104 106 def parse_command_line(self, argv=None):
105 107 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
106 108 self.swallow_args(frontend_aliases,frontend_flags,argv=argv)
107 109
108 110 def init_shell(self):
109 111 IPythonMixinConsoleApp.initialize(self)
110 112 # relay sigint to kernel
111 113 signal.signal(signal.SIGINT, self.handle_sigint)
112 114 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
113 115 display_banner=False, profile_dir=self.profile_dir,
114 116 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
115 117
116 118 def handle_sigint(self, *args):
117 self.shell.write('KeyboardInterrupt\n')
118 if self.kernel_manager.has_kernel:
119 self.kernel_manager.interrupt_kernel()
119 if self.shell._executing:
120 if self.kernel_manager.has_kernel:
121 # interrupt already gets passed to subprocess by signal handler.
122 # Only if we prevent that should we need to explicitly call
123 # interrupt_kernel, until which time, this would result in a
124 # double-interrupt:
125 # self.kernel_manager.interrupt_kernel()
126 pass
127 else:
128 self.shell.write_err('\n')
129 error("Cannot interrupt kernels we didn't start.\n")
120 130 else:
121 print 'Kernel process is either remote or unspecified.',
122 print 'Cannot interrupt.'
131 # raise the KeyboardInterrupt if we aren't waiting for execution,
132 # so that the interact loop advances, and prompt is redrawn, etc.
133 raise KeyboardInterrupt
134
123 135
124 136 def init_code(self):
125 137 # no-op in the frontend, code gets run in the backend
126 138 pass
127 139
128 140 def launch_new_instance():
129 141 """Create and run a full blown IPython instance"""
130 142 app = ZMQTerminalIPythonApp.instance()
131 143 app.initialize()
132 144 app.start()
133 145
134 146
135 147 if __name__ == '__main__':
136 148 launch_new_instance()
137 149
@@ -1,645 +1,648 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 # stop ignoring sigint, now that we are out of our own loop,
202 # we don't want to prevent future code from handling it
203 signal(SIGINT, default_int_handler)
201 204 if self.eventloop is not None:
202 205 try:
203 206 self.eventloop(self)
204 207 except KeyboardInterrupt:
205 208 # Ctrl-C shouldn't crash the kernel
206 209 io.raw_print("KeyboardInterrupt caught in kernel")
207 210
208 211
209 212 def record_ports(self, ports):
210 213 """Record the ports that this kernel is using.
211 214
212 215 The creator of the Kernel instance must call this methods if they
213 216 want the :meth:`connect_request` method to return the port numbers.
214 217 """
215 218 self._recorded_ports = ports
216 219
217 220 #---------------------------------------------------------------------------
218 221 # Kernel request handlers
219 222 #---------------------------------------------------------------------------
220 223
221 224 def _publish_pyin(self, code, parent):
222 225 """Publish the code request on the pyin stream."""
223 226
224 227 self.session.send(self.iopub_socket, u'pyin', {u'code':code},
225 228 parent=parent)
226 229
227 230 def execute_request(self, ident, parent):
228 231
229 232 self.session.send(self.iopub_socket,
230 233 u'status',
231 234 {u'execution_state':u'busy'},
232 235 parent=parent )
233 236
234 237 try:
235 238 content = parent[u'content']
236 239 code = content[u'code']
237 240 silent = content[u'silent']
238 241 except:
239 242 self.log.error("Got bad msg: ")
240 243 self.log.error(str(Message(parent)))
241 244 return
242 245
243 246 shell = self.shell # we'll need this a lot here
244 247
245 248 # Replace raw_input. Note that is not sufficient to replace
246 249 # raw_input in the user namespace.
247 250 if content.get('allow_stdin', False):
248 251 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
249 252 else:
250 253 raw_input = lambda prompt='' : self._no_raw_input()
251 254
252 255 if py3compat.PY3:
253 256 __builtin__.input = raw_input
254 257 else:
255 258 __builtin__.raw_input = raw_input
256 259
257 260 # Set the parent message of the display hook and out streams.
258 261 shell.displayhook.set_parent(parent)
259 262 shell.display_pub.set_parent(parent)
260 263 sys.stdout.set_parent(parent)
261 264 sys.stderr.set_parent(parent)
262 265
263 266 # Re-broadcast our input for the benefit of listening clients, and
264 267 # start computing output
265 268 if not silent:
266 269 self._publish_pyin(code, parent)
267 270
268 271 reply_content = {}
269 272 try:
270 273 if silent:
271 274 # run_code uses 'exec' mode, so no displayhook will fire, and it
272 275 # doesn't call logging or history manipulations. Print
273 276 # statements in that code will obviously still execute.
274 277 shell.run_code(code)
275 278 else:
276 279 # FIXME: the shell calls the exception handler itself.
277 280 shell.run_cell(code, store_history=True)
278 281 except:
279 282 status = u'error'
280 283 # FIXME: this code right now isn't being used yet by default,
281 284 # because the run_cell() call above directly fires off exception
282 285 # reporting. This code, therefore, is only active in the scenario
283 286 # where runlines itself has an unhandled exception. We need to
284 287 # uniformize this, for all exception construction to come from a
285 288 # single location in the codbase.
286 289 etype, evalue, tb = sys.exc_info()
287 290 tb_list = traceback.format_exception(etype, evalue, tb)
288 291 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
289 292 else:
290 293 status = u'ok'
291 294
292 295 reply_content[u'status'] = status
293 296
294 297 # Return the execution counter so clients can display prompts
295 298 reply_content['execution_count'] = shell.execution_count -1
296 299
297 300 # FIXME - fish exception info out of shell, possibly left there by
298 301 # runlines. We'll need to clean up this logic later.
299 302 if shell._reply_content is not None:
300 303 reply_content.update(shell._reply_content)
301 304 # reset after use
302 305 shell._reply_content = None
303 306
304 307 # At this point, we can tell whether the main code execution succeeded
305 308 # or not. If it did, we proceed to evaluate user_variables/expressions
306 309 if reply_content['status'] == 'ok':
307 310 reply_content[u'user_variables'] = \
308 311 shell.user_variables(content[u'user_variables'])
309 312 reply_content[u'user_expressions'] = \
310 313 shell.user_expressions(content[u'user_expressions'])
311 314 else:
312 315 # If there was an error, don't even try to compute variables or
313 316 # expressions
314 317 reply_content[u'user_variables'] = {}
315 318 reply_content[u'user_expressions'] = {}
316 319
317 320 # Payloads should be retrieved regardless of outcome, so we can both
318 321 # recover partial output (that could have been generated early in a
319 322 # block, before an error) and clear the payload system always.
320 323 reply_content[u'payload'] = shell.payload_manager.read_payload()
321 324 # Be agressive about clearing the payload because we don't want
322 325 # it to sit in memory until the next execute_request comes in.
323 326 shell.payload_manager.clear_payload()
324 327
325 328 # Flush output before sending the reply.
326 329 sys.stdout.flush()
327 330 sys.stderr.flush()
328 331 # FIXME: on rare occasions, the flush doesn't seem to make it to the
329 332 # clients... This seems to mitigate the problem, but we definitely need
330 333 # to better understand what's going on.
331 334 if self._execute_sleep:
332 335 time.sleep(self._execute_sleep)
333 336
334 337 # Send the reply.
335 338 reply_content = json_clean(reply_content)
336 339 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
337 340 reply_content, parent, ident=ident)
338 341 self.log.debug(str(reply_msg))
339 342
340 343 if reply_msg['content']['status'] == u'error':
341 344 self._abort_queue()
342 345
343 346 self.session.send(self.iopub_socket,
344 347 u'status',
345 348 {u'execution_state':u'idle'},
346 349 parent=parent )
347 350
348 351 def complete_request(self, ident, parent):
349 352 txt, matches = self._complete(parent)
350 353 matches = {'matches' : matches,
351 354 'matched_text' : txt,
352 355 'status' : 'ok'}
353 356 matches = json_clean(matches)
354 357 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
355 358 matches, parent, ident)
356 359 self.log.debug(str(completion_msg))
357 360
358 361 def object_info_request(self, ident, parent):
359 362 object_info = self.shell.object_inspect(parent['content']['oname'])
360 363 # Before we send this object over, we scrub it for JSON usage
361 364 oinfo = json_clean(object_info)
362 365 msg = self.session.send(self.shell_socket, 'object_info_reply',
363 366 oinfo, parent, ident)
364 367 self.log.debug(msg)
365 368
366 369 def history_request(self, ident, parent):
367 370 # We need to pull these out, as passing **kwargs doesn't work with
368 371 # unicode keys before Python 2.6.5.
369 372 hist_access_type = parent['content']['hist_access_type']
370 373 raw = parent['content']['raw']
371 374 output = parent['content']['output']
372 375 if hist_access_type == 'tail':
373 376 n = parent['content']['n']
374 377 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
375 378 include_latest=True)
376 379
377 380 elif hist_access_type == 'range':
378 381 session = parent['content']['session']
379 382 start = parent['content']['start']
380 383 stop = parent['content']['stop']
381 384 hist = self.shell.history_manager.get_range(session, start, stop,
382 385 raw=raw, output=output)
383 386
384 387 elif hist_access_type == 'search':
385 388 pattern = parent['content']['pattern']
386 389 hist = self.shell.history_manager.search(pattern, raw=raw,
387 390 output=output)
388 391
389 392 else:
390 393 hist = []
391 394 content = {'history' : list(hist)}
392 395 content = json_clean(content)
393 396 msg = self.session.send(self.shell_socket, 'history_reply',
394 397 content, parent, ident)
395 398 self.log.debug(str(msg))
396 399
397 400 def connect_request(self, ident, parent):
398 401 if self._recorded_ports is not None:
399 402 content = self._recorded_ports.copy()
400 403 else:
401 404 content = {}
402 405 msg = self.session.send(self.shell_socket, 'connect_reply',
403 406 content, parent, ident)
404 407 self.log.debug(msg)
405 408
406 409 def shutdown_request(self, ident, parent):
407 410 self.shell.exit_now = True
408 411 self._shutdown_message = self.session.msg(u'shutdown_reply',
409 412 parent['content'], parent)
410 413 sys.exit(0)
411 414
412 415 #---------------------------------------------------------------------------
413 416 # Protected interface
414 417 #---------------------------------------------------------------------------
415 418
416 419 def _abort_queue(self):
417 420 while True:
418 421 try:
419 422 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
420 423 except Exception:
421 424 self.log.warn("Invalid Message:", exc_info=True)
422 425 continue
423 426 if msg is None:
424 427 break
425 428 else:
426 429 assert ident is not None, \
427 430 "Unexpected missing message part."
428 431
429 432 self.log.debug("Aborting:\n"+str(Message(msg)))
430 433 msg_type = msg['header']['msg_type']
431 434 reply_type = msg_type.split('_')[0] + '_reply'
432 435 reply_msg = self.session.send(self.shell_socket, reply_type,
433 436 {'status' : 'aborted'}, msg, ident=ident)
434 437 self.log.debug(reply_msg)
435 438 # We need to wait a bit for requests to come in. This can probably
436 439 # be set shorter for true asynchronous clients.
437 440 time.sleep(0.1)
438 441
439 442 def _no_raw_input(self):
440 443 """Raise StdinNotImplentedError if active frontend doesn't support
441 444 stdin."""
442 445 raise StdinNotImplementedError("raw_input was called, but this "
443 446 "frontend does not support stdin.")
444 447
445 448 def _raw_input(self, prompt, ident, parent):
446 449 # Flush output before making the request.
447 450 sys.stderr.flush()
448 451 sys.stdout.flush()
449 452
450 453 # Send the input request.
451 454 content = json_clean(dict(prompt=prompt))
452 455 self.session.send(self.stdin_socket, u'input_request', content, parent,
453 456 ident=ident)
454 457
455 458 # Await a response.
456 459 while True:
457 460 try:
458 461 ident, reply = self.session.recv(self.stdin_socket, 0)
459 462 except Exception:
460 463 self.log.warn("Invalid Message:", exc_info=True)
461 464 else:
462 465 break
463 466 try:
464 467 value = reply['content']['value']
465 468 except:
466 469 self.log.error("Got bad raw_input reply: ")
467 470 self.log.error(str(Message(parent)))
468 471 value = ''
469 472 return value
470 473
471 474 def _complete(self, msg):
472 475 c = msg['content']
473 476 try:
474 477 cpos = int(c['cursor_pos'])
475 478 except:
476 479 # If we don't get something that we can convert to an integer, at
477 480 # least attempt the completion guessing the cursor is at the end of
478 481 # the text, if there's any, and otherwise of the line
479 482 cpos = len(c['text'])
480 483 if cpos==0:
481 484 cpos = len(c['line'])
482 485 return self.shell.complete(c['text'], c['line'], cpos)
483 486
484 487 def _object_info(self, context):
485 488 symbol, leftover = self._symbol_from_context(context)
486 489 if symbol is not None and not leftover:
487 490 doc = getattr(symbol, '__doc__', '')
488 491 else:
489 492 doc = ''
490 493 object_info = dict(docstring = doc)
491 494 return object_info
492 495
493 496 def _symbol_from_context(self, context):
494 497 if not context:
495 498 return None, context
496 499
497 500 base_symbol_string = context[0]
498 501 symbol = self.shell.user_ns.get(base_symbol_string, None)
499 502 if symbol is None:
500 503 symbol = __builtin__.__dict__.get(base_symbol_string, None)
501 504 if symbol is None:
502 505 return None, context
503 506
504 507 context = context[1:]
505 508 for i, name in enumerate(context):
506 509 new_symbol = getattr(symbol, name, None)
507 510 if new_symbol is None:
508 511 return symbol, context[i:]
509 512 else:
510 513 symbol = new_symbol
511 514
512 515 return symbol, []
513 516
514 517 def _at_shutdown(self):
515 518 """Actions taken at shutdown by the kernel, called by python's atexit.
516 519 """
517 520 # io.rprint("Kernel at_shutdown") # dbg
518 521 if self._shutdown_message is not None:
519 522 self.session.send(self.shell_socket, self._shutdown_message)
520 523 self.session.send(self.iopub_socket, self._shutdown_message)
521 524 self.log.debug(str(self._shutdown_message))
522 525 # A very short sleep to give zmq time to flush its message buffers
523 526 # before Python truly shuts down.
524 527 time.sleep(0.01)
525 528
526 529 #-----------------------------------------------------------------------------
527 530 # Aliases and Flags for the IPKernelApp
528 531 #-----------------------------------------------------------------------------
529 532
530 533 flags = dict(kernel_flags)
531 534 flags.update(shell_flags)
532 535
533 536 addflag = lambda *args: flags.update(boolean_flag(*args))
534 537
535 538 flags['pylab'] = (
536 539 {'IPKernelApp' : {'pylab' : 'auto'}},
537 540 """Pre-load matplotlib and numpy for interactive use with
538 541 the default matplotlib backend."""
539 542 )
540 543
541 544 aliases = dict(kernel_aliases)
542 545 aliases.update(shell_aliases)
543 546
544 547 # it's possible we don't want short aliases for *all* of these:
545 548 aliases.update(dict(
546 549 pylab='IPKernelApp.pylab',
547 550 ))
548 551
549 552 #-----------------------------------------------------------------------------
550 553 # The IPKernelApp class
551 554 #-----------------------------------------------------------------------------
552 555
553 556 class IPKernelApp(KernelApp, InteractiveShellApp):
554 557 name = 'ipkernel'
555 558
556 559 aliases = Dict(aliases)
557 560 flags = Dict(flags)
558 561 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
559 562 # configurables
560 563 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
561 564 config=True,
562 565 help="""Pre-load matplotlib and numpy for interactive use,
563 566 selecting a particular matplotlib backend and loop integration.
564 567 """
565 568 )
566 569
567 570 @catch_config_error
568 571 def initialize(self, argv=None):
569 572 super(IPKernelApp, self).initialize(argv)
570 573 self.init_shell()
571 574 self.init_extensions()
572 575 self.init_code()
573 576
574 577 def init_kernel(self):
575 578
576 579 kernel = Kernel(config=self.config, session=self.session,
577 580 shell_socket=self.shell_socket,
578 581 iopub_socket=self.iopub_socket,
579 582 stdin_socket=self.stdin_socket,
580 583 log=self.log,
581 584 profile_dir=self.profile_dir,
582 585 )
583 586 self.kernel = kernel
584 587 kernel.record_ports(self.ports)
585 588 shell = kernel.shell
586 589 if self.pylab:
587 590 try:
588 591 gui, backend = pylabtools.find_gui_and_backend(self.pylab)
589 592 shell.enable_pylab(gui, import_all=self.pylab_import_all)
590 593 except Exception:
591 594 self.log.error("Pylab initialization failed", exc_info=True)
592 595 # print exception straight to stdout, because normally
593 596 # _showtraceback associates the reply with an execution,
594 597 # which means frontends will never draw it, as this exception
595 598 # is not associated with any execute request.
596 599
597 600 # replace pyerr-sending traceback with stdout
598 601 _showtraceback = shell._showtraceback
599 602 def print_tb(etype, evalue, stb):
600 603 print ("Error initializing pylab, pylab mode will not "
601 604 "be active", file=io.stderr)
602 605 print (shell.InteractiveTB.stb2text(stb), file=io.stdout)
603 606 shell._showtraceback = print_tb
604 607
605 608 # send the traceback over stdout
606 609 shell.showtraceback(tb_offset=0)
607 610
608 611 # restore proper _showtraceback method
609 612 shell._showtraceback = _showtraceback
610 613
611 614
612 615 def init_shell(self):
613 616 self.shell = self.kernel.shell
614 617 self.shell.configurables.append(self)
615 618
616 619
617 620 #-----------------------------------------------------------------------------
618 621 # Kernel main and launch functions
619 622 #-----------------------------------------------------------------------------
620 623
621 624 def launch_kernel(*args, **kwargs):
622 625 """Launches a localhost IPython kernel, binding to the specified ports.
623 626
624 627 This function simply calls entry_point.base_launch_kernel with the right
625 628 first command to start an ipkernel. See base_launch_kernel for arguments.
626 629
627 630 Returns
628 631 -------
629 632 A tuple of form:
630 633 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
631 634 where kernel_process is a Popen object and the ports are integers.
632 635 """
633 636 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
634 637 *args, **kwargs)
635 638
636 639
637 640 def main():
638 641 """Run an IPKernel as an application"""
639 642 app = IPKernelApp.instance()
640 643 app.initialize()
641 644 app.start()
642 645
643 646
644 647 if __name__ == '__main__':
645 648 main()
General Comments 0
You need to be logged in to leave comments. Login now