##// END OF EJS Templates
BUG: GUI integration broken in the in-process kernel.
epatters -
Show More
@@ -1,131 +1,176
1 1 """ An in-process kernel. """
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2012 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 # Standard library imports
15 15 from contextlib import contextmanager
16 16 import logging
17 17 import sys
18 18
19 19 # Local imports.
20 from IPython.core.interactiveshell import InteractiveShellABC
20 21 from IPython.inprocess.socket import DummySocket
21 22 from IPython.utils.jsonutil import json_clean
22 from IPython.utils.traitlets import Any, Instance, List
23 from IPython.utils.traitlets import Any, Enum, Instance, List, Type
23 24 from IPython.zmq.ipkernel import Kernel
25 from IPython.zmq.zmqshell import ZMQInteractiveShell
24 26
25 27 #-----------------------------------------------------------------------------
26 28 # Main kernel class
27 29 #-----------------------------------------------------------------------------
28 30
29 31 class InProcessKernel(Kernel):
30 32
31 33 #-------------------------------------------------------------------------
32 34 # InProcessKernel interface
33 35 #-------------------------------------------------------------------------
34 36
37 # The frontends connected to this kernel.
35 38 frontends = List(
36 39 Instance('IPython.inprocess.kernelmanager.InProcessKernelManager'))
37 40
41 # The GUI environment that the kernel is running under. This need not be
42 # specified for the normal operation for the kernel, but is required for
43 # IPython's GUI support (including pylab). The default is 'inline' because
44 # it is safe under all GUI toolkits.
45 gui = Enum(('tk', 'gtk', 'wx', 'qt', 'qt4', 'inline'),
46 default_value='inline')
47
38 48 raw_input_str = Any()
39 49 stdout = Any()
40 50 stderr = Any()
41 51
42 52 #-------------------------------------------------------------------------
43 53 # Kernel interface
44 54 #-------------------------------------------------------------------------
45 55
56 shell_class = Type()
46 57 shell_streams = List()
47 58 control_stream = Any()
48 59 iopub_socket = Instance(DummySocket, ())
49 60 stdin_socket = Instance(DummySocket, ())
50 61
51 62 def __init__(self, **traits):
52 63 # When an InteractiveShell is instantiated by our base class, it binds
53 64 # the current values of sys.stdout and sys.stderr.
54 65 with self._redirected_io():
55 66 super(InProcessKernel, self).__init__(**traits)
56 67
57 68 self.iopub_socket.on_trait_change(self._io_dispatch, 'message_sent')
69 self.shell.kernel = self
58 70
59 71 def execute_request(self, stream, ident, parent):
60 72 """ Override for temporary IO redirection. """
61 73 with self._redirected_io():
62 74 super(InProcessKernel, self).execute_request(stream, ident, parent)
63 75
64 76 def start(self):
65 77 """ Override registration of dispatchers for streams. """
66 78 self.shell.exit_now = False
67 79
68 80 def _abort_queue(self, stream):
69 81 """ The in-process kernel doesn't abort requests. """
70 82 pass
71 83
72 84 def _raw_input(self, prompt, ident, parent):
73 85 # Flush output before making the request.
74 86 self.raw_input_str = None
75 87 sys.stderr.flush()
76 88 sys.stdout.flush()
77 89
78 90 # Send the input request.
79 91 content = json_clean(dict(prompt=prompt))
80 92 msg = self.session.msg(u'input_request', content, parent)
81 93 for frontend in self.frontends:
82 94 if frontend.session.session == parent['header']['session']:
83 95 frontend.stdin_channel.call_handlers(msg)
84 96 break
85 97 else:
86 98 log.error('No frontend found for raw_input request')
87 99 return str()
88 100
89 101 # Await a response.
90 102 while self.raw_input_str is None:
91 103 frontend.stdin_channel.process_events()
92 104 return self.raw_input_str
93 105
94 106 #-------------------------------------------------------------------------
95 107 # Protected interface
96 108 #-------------------------------------------------------------------------
97 109
98 110 @contextmanager
99 111 def _redirected_io(self):
100 112 """ Temporarily redirect IO to the kernel.
101 113 """
102 114 sys_stdout, sys_stderr = sys.stdout, sys.stderr
103 115 sys.stdout, sys.stderr = self.stdout, self.stderr
104 116 yield
105 117 sys.stdout, sys.stderr = sys_stdout, sys_stderr
106 118
107 119 #------ Trait change handlers --------------------------------------------
108 120
109 121 def _io_dispatch(self):
110 122 """ Called when a message is sent to the IO socket.
111 123 """
112 124 ident, msg = self.session.recv(self.iopub_socket, copy=False)
113 125 for frontend in self.frontends:
114 126 frontend.sub_channel.call_handlers(msg)
115 127
116 128 #------ Trait initializers -----------------------------------------------
117 129
118 130 def _log_default(self):
119 131 return logging.getLogger(__name__)
120 132
121 133 def _session_default(self):
122 134 from IPython.zmq.session import Session
123 135 return Session(config=self.config)
124 136
137 def _shell_class_default(self):
138 return InProcessInteractiveShell
139
125 140 def _stdout_default(self):
126 141 from IPython.zmq.iostream import OutStream
127 142 return OutStream(self.session, self.iopub_socket, u'stdout')
128 143
129 144 def _stderr_default(self):
130 145 from IPython.zmq.iostream import OutStream
131 146 return OutStream(self.session, self.iopub_socket, u'stderr')
147
148 #-----------------------------------------------------------------------------
149 # Interactive shell subclass
150 #-----------------------------------------------------------------------------
151
152 class InProcessInteractiveShell(ZMQInteractiveShell):
153
154 kernel = Instance('IPython.inprocess.ipkernel.InProcessKernel')
155
156 #-------------------------------------------------------------------------
157 # InteractiveShell interface
158 #-------------------------------------------------------------------------
159
160 def enable_gui(self, gui=None):
161 """ Enable GUI integration for the kernel.
162 """
163 from IPython.zmq.eventloops import enable_gui
164 if not gui:
165 gui = self.kernel.gui
166 enable_gui(gui, kernel=self.kernel)
167
168 def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
169 """ Activate pylab support at runtime.
170 """
171 if not gui:
172 gui = self.kernel.gui
173 super(InProcessInteractiveShell, self).enable_pylab(gui, import_all,
174 welcome_message)
175
176 InteractiveShellABC.register(InProcessInteractiveShell)
@@ -1,900 +1,903
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 import uuid
26 26
27 27 from datetime import datetime
28 28 from signal import (
29 29 signal, getsignal, default_int_handler, SIGINT, SIG_IGN
30 30 )
31 31
32 32 # System library imports
33 33 import zmq
34 34 from zmq.eventloop import ioloop
35 35 from zmq.eventloop.zmqstream import ZMQStream
36 36
37 37 # Local imports
38 38 from IPython.config.configurable import Configurable
39 39 from IPython.config.application import boolean_flag, catch_config_error
40 40 from IPython.core.application import ProfileDir
41 41 from IPython.core.error import StdinNotImplementedError
42 42 from IPython.core.shellapp import (
43 43 InteractiveShellApp, shell_flags, shell_aliases
44 44 )
45 45 from IPython.utils import io
46 46 from IPython.utils import py3compat
47 47 from IPython.utils.frame import extract_module_locals
48 48 from IPython.utils.jsonutil import json_clean
49 49 from IPython.utils.traitlets import (
50 Any, Instance, Float, Dict, CaselessStrEnum, List, Set, Integer, Unicode
50 Any, Instance, Float, Dict, CaselessStrEnum, List, Set, Integer, Unicode,
51 Type
51 52 )
52 53
53 54 from entry_point import base_launch_kernel
54 55 from kernelapp import KernelApp, kernel_flags, kernel_aliases
55 56 from serialize import serialize_object, unpack_apply_message
56 57 from session import Session, Message
57 58 from zmqshell import ZMQInteractiveShell
58 59
59 60
60 61 #-----------------------------------------------------------------------------
61 62 # Main kernel class
62 63 #-----------------------------------------------------------------------------
63 64
64 65 class Kernel(Configurable):
65 66
66 67 #---------------------------------------------------------------------------
67 68 # Kernel interface
68 69 #---------------------------------------------------------------------------
69 70
70 71 # attribute to override with a GUI
71 72 eventloop = Any(None)
72 73 def _eventloop_changed(self, name, old, new):
73 74 """schedule call to eventloop from IOLoop"""
74 75 loop = ioloop.IOLoop.instance()
75 76 loop.add_timeout(time.time()+0.1, self.enter_eventloop)
76 77
77 78 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
79 shell_class = Type(ZMQInteractiveShell)
80
78 81 session = Instance(Session)
79 82 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
80 83 shell_streams = List()
81 84 control_stream = Instance(ZMQStream)
82 85 iopub_socket = Instance(zmq.Socket)
83 86 stdin_socket = Instance(zmq.Socket)
84 87 log = Instance(logging.Logger)
85 88
86 89 user_module = Any()
87 90 def _user_module_changed(self, name, old, new):
88 91 if self.shell is not None:
89 92 self.shell.user_module = new
90 93
91 94 user_ns = Dict(default_value=None)
92 95 def _user_ns_changed(self, name, old, new):
93 96 if self.shell is not None:
94 97 self.shell.user_ns = new
95 98 self.shell.init_user_ns()
96 99
97 100 # identities:
98 101 int_id = Integer(-1)
99 102 ident = Unicode()
100 103
101 104 def _ident_default(self):
102 105 return unicode(uuid.uuid4())
103 106
104 107
105 108 # Private interface
106 109
107 110 # Time to sleep after flushing the stdout/err buffers in each execute
108 111 # cycle. While this introduces a hard limit on the minimal latency of the
109 112 # execute cycle, it helps prevent output synchronization problems for
110 113 # clients.
111 114 # Units are in seconds. The minimum zmq latency on local host is probably
112 115 # ~150 microseconds, set this to 500us for now. We may need to increase it
113 116 # a little if it's not enough after more interactive testing.
114 117 _execute_sleep = Float(0.0005, config=True)
115 118
116 119 # Frequency of the kernel's event loop.
117 120 # Units are in seconds, kernel subclasses for GUI toolkits may need to
118 121 # adapt to milliseconds.
119 122 _poll_interval = Float(0.05, config=True)
120 123
121 124 # If the shutdown was requested over the network, we leave here the
122 125 # necessary reply message so it can be sent by our registered atexit
123 126 # handler. This ensures that the reply is only sent to clients truly at
124 127 # the end of our shutdown process (which happens after the underlying
125 128 # IPython shell's own shutdown).
126 129 _shutdown_message = None
127 130
128 131 # This is a dict of port number that the kernel is listening on. It is set
129 132 # by record_ports and used by connect_request.
130 133 _recorded_ports = Dict()
131 134
132 135 # set of aborted msg_ids
133 136 aborted = Set()
134 137
135 138
136 139 def __init__(self, **kwargs):
137 140 super(Kernel, self).__init__(**kwargs)
138 141
139 142 # Initialize the InteractiveShell subclass
140 self.shell = ZMQInteractiveShell.instance(config=self.config,
143 self.shell = self.shell_class.instance(config=self.config,
141 144 profile_dir = self.profile_dir,
142 145 user_module = self.user_module,
143 146 user_ns = self.user_ns,
144 147 )
145 148 self.shell.displayhook.session = self.session
146 149 self.shell.displayhook.pub_socket = self.iopub_socket
147 150 self.shell.displayhook.topic = self._topic('pyout')
148 151 self.shell.display_pub.session = self.session
149 152 self.shell.display_pub.pub_socket = self.iopub_socket
150 153 self.shell.data_pub.session = self.session
151 154 self.shell.data_pub.pub_socket = self.iopub_socket
152 155
153 156 # TMP - hack while developing
154 157 self.shell._reply_content = None
155 158
156 159 # Build dict of handlers for message types
157 160 msg_types = [ 'execute_request', 'complete_request',
158 161 'object_info_request', 'history_request',
159 162 'connect_request', 'shutdown_request',
160 163 'apply_request',
161 164 ]
162 165 self.shell_handlers = {}
163 166 for msg_type in msg_types:
164 167 self.shell_handlers[msg_type] = getattr(self, msg_type)
165 168
166 169 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
167 170 self.control_handlers = {}
168 171 for msg_type in control_msg_types:
169 172 self.control_handlers[msg_type] = getattr(self, msg_type)
170 173
171 174 def dispatch_control(self, msg):
172 175 """dispatch control requests"""
173 176 idents,msg = self.session.feed_identities(msg, copy=False)
174 177 try:
175 178 msg = self.session.unserialize(msg, content=True, copy=False)
176 179 except:
177 180 self.log.error("Invalid Control Message", exc_info=True)
178 181 return
179 182
180 183 self.log.debug("Control received: %s", msg)
181 184
182 185 header = msg['header']
183 186 msg_id = header['msg_id']
184 187 msg_type = header['msg_type']
185 188
186 189 handler = self.control_handlers.get(msg_type, None)
187 190 if handler is None:
188 191 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
189 192 else:
190 193 try:
191 194 handler(self.control_stream, idents, msg)
192 195 except Exception:
193 196 self.log.error("Exception in control handler:", exc_info=True)
194 197
195 198 def dispatch_shell(self, stream, msg):
196 199 """dispatch shell requests"""
197 200 # flush control requests first
198 201 if self.control_stream:
199 202 self.control_stream.flush()
200 203
201 204 idents,msg = self.session.feed_identities(msg, copy=False)
202 205 try:
203 206 msg = self.session.unserialize(msg, content=True, copy=False)
204 207 except:
205 208 self.log.error("Invalid Message", exc_info=True)
206 209 return
207 210
208 211 header = msg['header']
209 212 msg_id = header['msg_id']
210 213 msg_type = msg['header']['msg_type']
211 214
212 215 # Print some info about this message and leave a '--->' marker, so it's
213 216 # easier to trace visually the message chain when debugging. Each
214 217 # handler prints its message at the end.
215 218 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
216 219 self.log.debug(' Content: %s\n --->\n ', msg['content'])
217 220
218 221 if msg_id in self.aborted:
219 222 self.aborted.remove(msg_id)
220 223 # is it safe to assume a msg_id will not be resubmitted?
221 224 reply_type = msg_type.split('_')[0] + '_reply'
222 225 status = {'status' : 'aborted'}
223 226 md = {'engine' : self.ident}
224 227 md.update(status)
225 228 reply_msg = self.session.send(stream, reply_type, metadata=md,
226 229 content=status, parent=msg, ident=idents)
227 230 return
228 231
229 232 handler = self.shell_handlers.get(msg_type, None)
230 233 if handler is None:
231 234 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
232 235 else:
233 236 # ensure default_int_handler during handler call
234 237 sig = signal(SIGINT, default_int_handler)
235 238 try:
236 239 handler(stream, idents, msg)
237 240 except Exception:
238 241 self.log.error("Exception in message handler:", exc_info=True)
239 242 finally:
240 243 signal(SIGINT, sig)
241 244
242 245 def enter_eventloop(self):
243 246 """enter eventloop"""
244 247 self.log.info("entering eventloop")
245 248 # restore default_int_handler
246 249 signal(SIGINT, default_int_handler)
247 250 while self.eventloop is not None:
248 251 try:
249 252 self.eventloop(self)
250 253 except KeyboardInterrupt:
251 254 # Ctrl-C shouldn't crash the kernel
252 255 self.log.error("KeyboardInterrupt caught in kernel")
253 256 continue
254 257 else:
255 258 # eventloop exited cleanly, this means we should stop (right?)
256 259 self.eventloop = None
257 260 break
258 261 self.log.info("exiting eventloop")
259 262
260 263 def start(self):
261 264 """register dispatchers for streams"""
262 265 self.shell.exit_now = False
263 266 if self.control_stream:
264 267 self.control_stream.on_recv(self.dispatch_control, copy=False)
265 268
266 269 def make_dispatcher(stream):
267 270 def dispatcher(msg):
268 271 return self.dispatch_shell(stream, msg)
269 272 return dispatcher
270 273
271 274 for s in self.shell_streams:
272 275 s.on_recv(make_dispatcher(s), copy=False)
273 276
274 277 def do_one_iteration(self):
275 278 """step eventloop just once"""
276 279 if self.control_stream:
277 280 self.control_stream.flush()
278 281 for stream in self.shell_streams:
279 282 # handle at most one request per iteration
280 283 stream.flush(zmq.POLLIN, 1)
281 284 stream.flush(zmq.POLLOUT)
282 285
283 286
284 287 def record_ports(self, ports):
285 288 """Record the ports that this kernel is using.
286 289
287 290 The creator of the Kernel instance must call this methods if they
288 291 want the :meth:`connect_request` method to return the port numbers.
289 292 """
290 293 self._recorded_ports = ports
291 294
292 295 #---------------------------------------------------------------------------
293 296 # Kernel request handlers
294 297 #---------------------------------------------------------------------------
295 298
296 299 def _make_metadata(self, other=None):
297 300 """init metadata dict, for execute/apply_reply"""
298 301 new_md = {
299 302 'dependencies_met' : True,
300 303 'engine' : self.ident,
301 304 'started': datetime.now(),
302 305 }
303 306 if other:
304 307 new_md.update(other)
305 308 return new_md
306 309
307 310 def _publish_pyin(self, code, parent, execution_count):
308 311 """Publish the code request on the pyin stream."""
309 312
310 313 self.session.send(self.iopub_socket, u'pyin',
311 314 {u'code':code, u'execution_count': execution_count},
312 315 parent=parent, ident=self._topic('pyin')
313 316 )
314 317
315 318 def _publish_status(self, status, parent=None):
316 319 """send status (busy/idle) on IOPub"""
317 320 self.session.send(self.iopub_socket,
318 321 u'status',
319 322 {u'execution_state': status},
320 323 parent=parent,
321 324 ident=self._topic('status'),
322 325 )
323 326
324 327
325 328 def execute_request(self, stream, ident, parent):
326 329 """handle an execute_request"""
327 330
328 331 self._publish_status(u'busy', parent)
329 332
330 333 try:
331 334 content = parent[u'content']
332 335 code = content[u'code']
333 336 silent = content[u'silent']
334 337 store_history = content.get(u'store_history', not silent)
335 338 except:
336 339 self.log.error("Got bad msg: ")
337 340 self.log.error("%s", parent)
338 341 return
339 342
340 343 md = self._make_metadata(parent['metadata'])
341 344
342 345 shell = self.shell # we'll need this a lot here
343 346
344 347 # Replace raw_input. Note that is not sufficient to replace
345 348 # raw_input in the user namespace.
346 349 if content.get('allow_stdin', False):
347 350 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
348 351 else:
349 352 raw_input = lambda prompt='' : self._no_raw_input()
350 353
351 354 if py3compat.PY3:
352 355 __builtin__.input = raw_input
353 356 else:
354 357 __builtin__.raw_input = raw_input
355 358
356 359 # Set the parent message of the display hook and out streams.
357 360 shell.displayhook.set_parent(parent)
358 361 shell.display_pub.set_parent(parent)
359 362 shell.data_pub.set_parent(parent)
360 363 sys.stdout.set_parent(parent)
361 364 sys.stderr.set_parent(parent)
362 365
363 366 # Re-broadcast our input for the benefit of listening clients, and
364 367 # start computing output
365 368 if not silent:
366 369 self._publish_pyin(code, parent, shell.execution_count)
367 370
368 371 reply_content = {}
369 372 try:
370 373 # FIXME: the shell calls the exception handler itself.
371 374 shell.run_cell(code, store_history=store_history, silent=silent)
372 375 except:
373 376 status = u'error'
374 377 # FIXME: this code right now isn't being used yet by default,
375 378 # because the run_cell() call above directly fires off exception
376 379 # reporting. This code, therefore, is only active in the scenario
377 380 # where runlines itself has an unhandled exception. We need to
378 381 # uniformize this, for all exception construction to come from a
379 382 # single location in the codbase.
380 383 etype, evalue, tb = sys.exc_info()
381 384 tb_list = traceback.format_exception(etype, evalue, tb)
382 385 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
383 386 else:
384 387 status = u'ok'
385 388
386 389 reply_content[u'status'] = status
387 390
388 391 # Return the execution counter so clients can display prompts
389 392 reply_content['execution_count'] = shell.execution_count - 1
390 393
391 394 # FIXME - fish exception info out of shell, possibly left there by
392 395 # runlines. We'll need to clean up this logic later.
393 396 if shell._reply_content is not None:
394 397 reply_content.update(shell._reply_content)
395 398 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
396 399 reply_content['engine_info'] = e_info
397 400 # reset after use
398 401 shell._reply_content = None
399 402
400 403 # At this point, we can tell whether the main code execution succeeded
401 404 # or not. If it did, we proceed to evaluate user_variables/expressions
402 405 if reply_content['status'] == 'ok':
403 406 reply_content[u'user_variables'] = \
404 407 shell.user_variables(content.get(u'user_variables', []))
405 408 reply_content[u'user_expressions'] = \
406 409 shell.user_expressions(content.get(u'user_expressions', {}))
407 410 else:
408 411 # If there was an error, don't even try to compute variables or
409 412 # expressions
410 413 reply_content[u'user_variables'] = {}
411 414 reply_content[u'user_expressions'] = {}
412 415
413 416 # Payloads should be retrieved regardless of outcome, so we can both
414 417 # recover partial output (that could have been generated early in a
415 418 # block, before an error) and clear the payload system always.
416 419 reply_content[u'payload'] = shell.payload_manager.read_payload()
417 420 # Be agressive about clearing the payload because we don't want
418 421 # it to sit in memory until the next execute_request comes in.
419 422 shell.payload_manager.clear_payload()
420 423
421 424 # Flush output before sending the reply.
422 425 sys.stdout.flush()
423 426 sys.stderr.flush()
424 427 # FIXME: on rare occasions, the flush doesn't seem to make it to the
425 428 # clients... This seems to mitigate the problem, but we definitely need
426 429 # to better understand what's going on.
427 430 if self._execute_sleep:
428 431 time.sleep(self._execute_sleep)
429 432
430 433 # Send the reply.
431 434 reply_content = json_clean(reply_content)
432 435
433 436 md['status'] = reply_content['status']
434 437 if reply_content['status'] == 'error' and \
435 438 reply_content['ename'] == 'UnmetDependency':
436 439 md['dependencies_met'] = False
437 440
438 441 reply_msg = self.session.send(stream, u'execute_reply',
439 442 reply_content, parent, metadata=md,
440 443 ident=ident)
441 444
442 445 self.log.debug("%s", reply_msg)
443 446
444 447 if not silent and reply_msg['content']['status'] == u'error':
445 448 self._abort_queues()
446 449
447 450 self._publish_status(u'idle', parent)
448 451
449 452 def complete_request(self, stream, ident, parent):
450 453 txt, matches = self._complete(parent)
451 454 matches = {'matches' : matches,
452 455 'matched_text' : txt,
453 456 'status' : 'ok'}
454 457 matches = json_clean(matches)
455 458 completion_msg = self.session.send(stream, 'complete_reply',
456 459 matches, parent, ident)
457 460 self.log.debug("%s", completion_msg)
458 461
459 462 def object_info_request(self, stream, ident, parent):
460 463 content = parent['content']
461 464 object_info = self.shell.object_inspect(content['oname'],
462 465 detail_level = content.get('detail_level', 0)
463 466 )
464 467 # Before we send this object over, we scrub it for JSON usage
465 468 oinfo = json_clean(object_info)
466 469 msg = self.session.send(stream, 'object_info_reply',
467 470 oinfo, parent, ident)
468 471 self.log.debug("%s", msg)
469 472
470 473 def history_request(self, stream, ident, parent):
471 474 # We need to pull these out, as passing **kwargs doesn't work with
472 475 # unicode keys before Python 2.6.5.
473 476 hist_access_type = parent['content']['hist_access_type']
474 477 raw = parent['content']['raw']
475 478 output = parent['content']['output']
476 479 if hist_access_type == 'tail':
477 480 n = parent['content']['n']
478 481 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
479 482 include_latest=True)
480 483
481 484 elif hist_access_type == 'range':
482 485 session = parent['content']['session']
483 486 start = parent['content']['start']
484 487 stop = parent['content']['stop']
485 488 hist = self.shell.history_manager.get_range(session, start, stop,
486 489 raw=raw, output=output)
487 490
488 491 elif hist_access_type == 'search':
489 492 pattern = parent['content']['pattern']
490 493 hist = self.shell.history_manager.search(pattern, raw=raw,
491 494 output=output)
492 495
493 496 else:
494 497 hist = []
495 498 hist = list(hist)
496 499 content = {'history' : hist}
497 500 content = json_clean(content)
498 501 msg = self.session.send(stream, 'history_reply',
499 502 content, parent, ident)
500 503 self.log.debug("Sending history reply with %i entries", len(hist))
501 504
502 505 def connect_request(self, stream, ident, parent):
503 506 if self._recorded_ports is not None:
504 507 content = self._recorded_ports.copy()
505 508 else:
506 509 content = {}
507 510 msg = self.session.send(stream, 'connect_reply',
508 511 content, parent, ident)
509 512 self.log.debug("%s", msg)
510 513
511 514 def shutdown_request(self, stream, ident, parent):
512 515 self.shell.exit_now = True
513 516 content = dict(status='ok')
514 517 content.update(parent['content'])
515 518 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
516 519 # same content, but different msg_id for broadcasting on IOPub
517 520 self._shutdown_message = self.session.msg(u'shutdown_reply',
518 521 content, parent
519 522 )
520 523
521 524 self._at_shutdown()
522 525 # call sys.exit after a short delay
523 526 loop = ioloop.IOLoop.instance()
524 527 loop.add_timeout(time.time()+0.1, loop.stop)
525 528
526 529 #---------------------------------------------------------------------------
527 530 # Engine methods
528 531 #---------------------------------------------------------------------------
529 532
530 533 def apply_request(self, stream, ident, parent):
531 534 try:
532 535 content = parent[u'content']
533 536 bufs = parent[u'buffers']
534 537 msg_id = parent['header']['msg_id']
535 538 except:
536 539 self.log.error("Got bad msg: %s", parent, exc_info=True)
537 540 return
538 541
539 542 self._publish_status(u'busy', parent)
540 543
541 544 # Set the parent message of the display hook and out streams.
542 545 shell = self.shell
543 546 shell.displayhook.set_parent(parent)
544 547 shell.display_pub.set_parent(parent)
545 548 shell.data_pub.set_parent(parent)
546 549 sys.stdout.set_parent(parent)
547 550 sys.stderr.set_parent(parent)
548 551
549 552 # pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
550 553 # self.iopub_socket.send(pyin_msg)
551 554 # self.session.send(self.iopub_socket, u'pyin', {u'code':code},parent=parent)
552 555 md = self._make_metadata(parent['metadata'])
553 556 try:
554 557 working = shell.user_ns
555 558
556 559 prefix = "_"+str(msg_id).replace("-","")+"_"
557 560
558 561 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
559 562
560 563 fname = getattr(f, '__name__', 'f')
561 564
562 565 fname = prefix+"f"
563 566 argname = prefix+"args"
564 567 kwargname = prefix+"kwargs"
565 568 resultname = prefix+"result"
566 569
567 570 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
568 571 # print ns
569 572 working.update(ns)
570 573 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
571 574 try:
572 575 exec code in shell.user_global_ns, shell.user_ns
573 576 result = working.get(resultname)
574 577 finally:
575 578 for key in ns.iterkeys():
576 579 working.pop(key)
577 580
578 581 result_buf = serialize_object(result,
579 582 buffer_threshold=self.session.buffer_threshold,
580 583 item_threshold=self.session.item_threshold,
581 584 )
582 585
583 586 except:
584 587 # invoke IPython traceback formatting
585 588 shell.showtraceback()
586 589 # FIXME - fish exception info out of shell, possibly left there by
587 590 # run_code. We'll need to clean up this logic later.
588 591 reply_content = {}
589 592 if shell._reply_content is not None:
590 593 reply_content.update(shell._reply_content)
591 594 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
592 595 reply_content['engine_info'] = e_info
593 596 # reset after use
594 597 shell._reply_content = None
595 598
596 599 self.session.send(self.iopub_socket, u'pyerr', reply_content, parent=parent,
597 600 ident=self._topic('pyerr'))
598 601 result_buf = []
599 602
600 603 if reply_content['ename'] == 'UnmetDependency':
601 604 md['dependencies_met'] = False
602 605 else:
603 606 reply_content = {'status' : 'ok'}
604 607
605 608 # put 'ok'/'error' status in header, for scheduler introspection:
606 609 md['status'] = reply_content['status']
607 610
608 611 # flush i/o
609 612 sys.stdout.flush()
610 613 sys.stderr.flush()
611 614
612 615 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
613 616 parent=parent, ident=ident,buffers=result_buf, metadata=md)
614 617
615 618 self._publish_status(u'idle', parent)
616 619
617 620 #---------------------------------------------------------------------------
618 621 # Control messages
619 622 #---------------------------------------------------------------------------
620 623
621 624 def abort_request(self, stream, ident, parent):
622 625 """abort a specifig msg by id"""
623 626 msg_ids = parent['content'].get('msg_ids', None)
624 627 if isinstance(msg_ids, basestring):
625 628 msg_ids = [msg_ids]
626 629 if not msg_ids:
627 630 self.abort_queues()
628 631 for mid in msg_ids:
629 632 self.aborted.add(str(mid))
630 633
631 634 content = dict(status='ok')
632 635 reply_msg = self.session.send(stream, 'abort_reply', content=content,
633 636 parent=parent, ident=ident)
634 637 self.log.debug("%s", reply_msg)
635 638
636 639 def clear_request(self, stream, idents, parent):
637 640 """Clear our namespace."""
638 641 self.shell.reset(False)
639 642 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
640 643 content = dict(status='ok'))
641 644
642 645
643 646 #---------------------------------------------------------------------------
644 647 # Protected interface
645 648 #---------------------------------------------------------------------------
646 649
647 650 def _wrap_exception(self, method=None):
648 651 # import here, because _wrap_exception is only used in parallel,
649 652 # and parallel has higher min pyzmq version
650 653 from IPython.parallel.error import wrap_exception
651 654 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
652 655 content = wrap_exception(e_info)
653 656 return content
654 657
655 658 def _topic(self, topic):
656 659 """prefixed topic for IOPub messages"""
657 660 if self.int_id >= 0:
658 661 base = "engine.%i" % self.int_id
659 662 else:
660 663 base = "kernel.%s" % self.ident
661 664
662 665 return py3compat.cast_bytes("%s.%s" % (base, topic))
663 666
664 667 def _abort_queues(self):
665 668 for stream in self.shell_streams:
666 669 if stream:
667 670 self._abort_queue(stream)
668 671
669 672 def _abort_queue(self, stream):
670 673 poller = zmq.Poller()
671 674 poller.register(stream.socket, zmq.POLLIN)
672 675 while True:
673 676 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
674 677 if msg is None:
675 678 return
676 679
677 680 self.log.info("Aborting:")
678 681 self.log.info("%s", msg)
679 682 msg_type = msg['header']['msg_type']
680 683 reply_type = msg_type.split('_')[0] + '_reply'
681 684
682 685 status = {'status' : 'aborted'}
683 686 md = {'engine' : self.ident}
684 687 md.update(status)
685 688 reply_msg = self.session.send(stream, reply_type, metadata=md,
686 689 content=status, parent=msg, ident=idents)
687 690 self.log.debug("%s", reply_msg)
688 691 # We need to wait a bit for requests to come in. This can probably
689 692 # be set shorter for true asynchronous clients.
690 693 poller.poll(50)
691 694
692 695
693 696 def _no_raw_input(self):
694 697 """Raise StdinNotImplentedError if active frontend doesn't support
695 698 stdin."""
696 699 raise StdinNotImplementedError("raw_input was called, but this "
697 700 "frontend does not support stdin.")
698 701
699 702 def _raw_input(self, prompt, ident, parent):
700 703 # Flush output before making the request.
701 704 sys.stderr.flush()
702 705 sys.stdout.flush()
703 706
704 707 # Send the input request.
705 708 content = json_clean(dict(prompt=prompt))
706 709 self.session.send(self.stdin_socket, u'input_request', content, parent,
707 710 ident=ident)
708 711
709 712 # Await a response.
710 713 while True:
711 714 try:
712 715 ident, reply = self.session.recv(self.stdin_socket, 0)
713 716 except Exception:
714 717 self.log.warn("Invalid Message:", exc_info=True)
715 718 else:
716 719 break
717 720 try:
718 721 value = reply['content']['value']
719 722 except:
720 723 self.log.error("Got bad raw_input reply: ")
721 724 self.log.error("%s", parent)
722 725 value = ''
723 726 if value == '\x04':
724 727 # EOF
725 728 raise EOFError
726 729 return value
727 730
728 731 def _complete(self, msg):
729 732 c = msg['content']
730 733 try:
731 734 cpos = int(c['cursor_pos'])
732 735 except:
733 736 # If we don't get something that we can convert to an integer, at
734 737 # least attempt the completion guessing the cursor is at the end of
735 738 # the text, if there's any, and otherwise of the line
736 739 cpos = len(c['text'])
737 740 if cpos==0:
738 741 cpos = len(c['line'])
739 742 return self.shell.complete(c['text'], c['line'], cpos)
740 743
741 744 def _at_shutdown(self):
742 745 """Actions taken at shutdown by the kernel, called by python's atexit.
743 746 """
744 747 # io.rprint("Kernel at_shutdown") # dbg
745 748 if self._shutdown_message is not None:
746 749 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
747 750 self.log.debug("%s", self._shutdown_message)
748 751 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
749 752
750 753 #-----------------------------------------------------------------------------
751 754 # Aliases and Flags for the IPKernelApp
752 755 #-----------------------------------------------------------------------------
753 756
754 757 flags = dict(kernel_flags)
755 758 flags.update(shell_flags)
756 759
757 760 addflag = lambda *args: flags.update(boolean_flag(*args))
758 761
759 762 flags['pylab'] = (
760 763 {'IPKernelApp' : {'pylab' : 'auto'}},
761 764 """Pre-load matplotlib and numpy for interactive use with
762 765 the default matplotlib backend."""
763 766 )
764 767
765 768 aliases = dict(kernel_aliases)
766 769 aliases.update(shell_aliases)
767 770
768 771 #-----------------------------------------------------------------------------
769 772 # The IPKernelApp class
770 773 #-----------------------------------------------------------------------------
771 774
772 775 class IPKernelApp(KernelApp, InteractiveShellApp):
773 776 name = 'ipkernel'
774 777
775 778 aliases = Dict(aliases)
776 779 flags = Dict(flags)
777 780 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
778 781
779 782 @catch_config_error
780 783 def initialize(self, argv=None):
781 784 super(IPKernelApp, self).initialize(argv)
782 785 self.init_path()
783 786 self.init_shell()
784 787 self.init_gui_pylab()
785 788 self.init_extensions()
786 789 self.init_code()
787 790
788 791 def init_kernel(self):
789 792
790 793 shell_stream = ZMQStream(self.shell_socket)
791 794
792 795 kernel = Kernel(config=self.config, session=self.session,
793 796 shell_streams=[shell_stream],
794 797 iopub_socket=self.iopub_socket,
795 798 stdin_socket=self.stdin_socket,
796 799 log=self.log,
797 800 profile_dir=self.profile_dir,
798 801 )
799 802 self.kernel = kernel
800 803 kernel.record_ports(self.ports)
801 804 shell = kernel.shell
802 805
803 806 def init_gui_pylab(self):
804 807 """Enable GUI event loop integration, taking pylab into account."""
805 808
806 809 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
807 810 # to ensure that any exception is printed straight to stderr.
808 811 # Normally _showtraceback associates the reply with an execution,
809 812 # which means frontends will never draw it, as this exception
810 813 # is not associated with any execute request.
811 814
812 815 shell = self.shell
813 816 _showtraceback = shell._showtraceback
814 817 try:
815 818 # replace pyerr-sending traceback with stderr
816 819 def print_tb(etype, evalue, stb):
817 820 print ("GUI event loop or pylab initialization failed",
818 821 file=io.stderr)
819 822 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
820 823 shell._showtraceback = print_tb
821 824 InteractiveShellApp.init_gui_pylab(self)
822 825 finally:
823 826 shell._showtraceback = _showtraceback
824 827
825 828 def init_shell(self):
826 829 self.shell = self.kernel.shell
827 830 self.shell.configurables.append(self)
828 831
829 832
830 833 #-----------------------------------------------------------------------------
831 834 # Kernel main and launch functions
832 835 #-----------------------------------------------------------------------------
833 836
834 837 def launch_kernel(*args, **kwargs):
835 838 """Launches a localhost IPython kernel, binding to the specified ports.
836 839
837 840 This function simply calls entry_point.base_launch_kernel with the right
838 841 first command to start an ipkernel. See base_launch_kernel for arguments.
839 842
840 843 Returns
841 844 -------
842 845 A tuple of form:
843 846 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
844 847 where kernel_process is a Popen object and the ports are integers.
845 848 """
846 849 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
847 850 *args, **kwargs)
848 851
849 852
850 853 def embed_kernel(module=None, local_ns=None, **kwargs):
851 854 """Embed and start an IPython kernel in a given scope.
852 855
853 856 Parameters
854 857 ----------
855 858 module : ModuleType, optional
856 859 The module to load into IPython globals (default: caller)
857 860 local_ns : dict, optional
858 861 The namespace to load into IPython user namespace (default: caller)
859 862
860 863 kwargs : various, optional
861 864 Further keyword args are relayed to the KernelApp constructor,
862 865 allowing configuration of the Kernel. Will only have an effect
863 866 on the first embed_kernel call for a given process.
864 867
865 868 """
866 869 # get the app if it exists, or set it up if it doesn't
867 870 if IPKernelApp.initialized():
868 871 app = IPKernelApp.instance()
869 872 else:
870 873 app = IPKernelApp.instance(**kwargs)
871 874 app.initialize([])
872 875 # Undo unnecessary sys module mangling from init_sys_modules.
873 876 # This would not be necessary if we could prevent it
874 877 # in the first place by using a different InteractiveShell
875 878 # subclass, as in the regular embed case.
876 879 main = app.kernel.shell._orig_sys_modules_main_mod
877 880 if main is not None:
878 881 sys.modules[app.kernel.shell._orig_sys_modules_main_name] = main
879 882
880 883 # load the calling scope if not given
881 884 (caller_module, caller_locals) = extract_module_locals(1)
882 885 if module is None:
883 886 module = caller_module
884 887 if local_ns is None:
885 888 local_ns = caller_locals
886 889
887 890 app.kernel.user_module = module
888 891 app.kernel.user_ns = local_ns
889 892 app.shell.set_completer_frame()
890 893 app.start()
891 894
892 895 def main():
893 896 """Run an IPKernel as an application"""
894 897 app = IPKernelApp.instance()
895 898 app.initialize()
896 899 app.start()
897 900
898 901
899 902 if __name__ == '__main__':
900 903 main()
@@ -1,48 +1,58
1 1 """ A simple example of using the Qt console with an in-process kernel.
2 2
3 3 We shall see how to create the frontend widget, create an in-process kernel,
4 4 push Python objects into the kernel's namespace, and execute code in the
5 5 kernel, both directly and via the frontend widget.
6 6 """
7 7
8 8 from IPython.inprocess.ipkernel import InProcessKernel
9 9 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
10 10 from IPython.frontend.qt.inprocess_kernelmanager import QtInProcessKernelManager
11 11 from IPython.lib import guisupport
12 12
13 13
14 14 def main():
15 15 app = guisupport.get_app_qt4()
16 16
17 # Create a kernel and populate the namespace.
18 kernel = InProcessKernel()
17 # Create a kernel.
18 #
19 # Setting the GUI is not necessary for the normal operation of the kernel,
20 # but it is used for IPython GUI's integration, particularly in pylab. By
21 # default, the inline backend is used, which is safe under all toolkits.
22 #
23 # WARNING: Under no circumstances should another GUI toolkit, like wx, be
24 # used when running a Qt application. This will lead to unexpected behavior,
25 # including segfaults.
26 kernel = InProcessKernel(gui='qt4')
27
28 # Populate the kernel's namespace.
19 29 kernel.shell.push({'x': 0, 'y': 1, 'z': 2})
20 30
21 31 # Create a kernel manager for the frontend and register it with the kernel.
22 32 km = QtInProcessKernelManager(kernel=kernel)
23 33 km.start_channels()
24 34 kernel.frontends.append(km)
25 35
26 36 # Create the Qt console frontend.
27 37 control = RichIPythonWidget()
28 38 control.exit_requested.connect(app.quit)
29 39 control.kernel_manager = km
30 40 control.show()
31 41
32 42 # Execute some code directly. Note where the output appears.
33 43 kernel.shell.run_cell('print "x=%r, y=%r, z=%r" % (x,y,z)')
34 44
35 45 # Execute some code through the frontend (once the event loop is
36 46 # running). Again, note where the output appears.
37 47 do_later(control.execute, '%who')
38 48
39 49 guisupport.start_event_loop_qt4(app)
40 50
41 51
42 52 def do_later(func, *args, **kwds):
43 53 from IPython.external.qt import QtCore
44 54 QtCore.QTimer.singleShot(0, lambda: func(*args, **kwds))
45 55
46 56
47 57 if __name__ == '__main__':
48 58 main()
General Comments 0
You need to be logged in to leave comments. Login now