##// END OF EJS Templates
Merge pull request #7108 from takluyver/digging-channels...
Min RK -
r19452:fe5dd275 merge
parent child Browse files
Show More
@@ -1,85 +1,92 b''
1 """Blocking channels
1 """Blocking channels
2
2
3 Useful for test suites and blocking terminal interfaces.
3 Useful for test suites and blocking terminal interfaces.
4 """
4 """
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2013 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING.txt, distributed as part of this software.
10 #-----------------------------------------------------------------------------
11
5
12 #-----------------------------------------------------------------------------
6 # Copyright (c) IPython Development Team.
13 # Imports
7 # Distributed under the terms of the Modified BSD License.
14 #-----------------------------------------------------------------------------
15
8
16 try:
9 try:
17 from queue import Queue, Empty # Py 3
10 from queue import Queue, Empty # Py 3
18 except ImportError:
11 except ImportError:
19 from Queue import Queue, Empty # Py 2
12 from Queue import Queue, Empty # Py 2
20
13
21 from IPython.kernel.channels import IOPubChannel, HBChannel, \
22 ShellChannel, StdInChannel
23
14
24 #-----------------------------------------------------------------------------
15 class ZMQSocketChannel(object):
25 # Blocking kernel manager
16 """A ZMQ socket in a simple blocking API"""
26 #-----------------------------------------------------------------------------
17 session = None
27
18 socket = None
28
19 stream = None
29 class BlockingChannelMixin(object):
20 _exiting = False
21 proxy_methods = []
22
23 def __init__(self, socket, session, loop=None):
24 """Create a channel.
25
26 Parameters
27 ----------
28 socket : :class:`zmq.Socket`
29 The ZMQ socket to use.
30 session : :class:`session.Session`
31 The session to use.
32 loop
33 Unused here, for other implementations
34 """
35 super(ZMQSocketChannel, self).__init__()
30
36
31 def __init__(self, *args, **kwds):
37 self.socket = socket
32 super(BlockingChannelMixin, self).__init__(*args, **kwds)
38 self.session = session
33 self._in_queue = Queue()
34
39
35 def call_handlers(self, msg):
40 def _recv(self, **kwargs):
36 self._in_queue.put(msg)
41 msg = self.socket.recv_multipart(**kwargs)
42 ident,smsg = self.session.feed_identities(msg)
43 return self.session.deserialize(smsg)
37
44
38 def get_msg(self, block=True, timeout=None):
45 def get_msg(self, block=True, timeout=None):
39 """ Gets a message if there is one that is ready. """
46 """ Gets a message if there is one that is ready. """
40 if timeout is None:
47 if block:
41 # Queue.get(timeout=None) has stupid uninteruptible
48 if timeout is not None:
42 # behavior, so wait for a week instead
49 timeout *= 1000 # seconds to ms
43 timeout = 604800
50 ready = self.socket.poll(timeout)
44 return self._in_queue.get(block, timeout)
51 else:
52 ready = self.socket.poll(timeout=0)
53
54 if ready:
55 return self._recv()
56 else:
57 raise Empty
45
58
46 def get_msgs(self):
59 def get_msgs(self):
47 """ Get all messages that are currently ready. """
60 """ Get all messages that are currently ready. """
48 msgs = []
61 msgs = []
49 while True:
62 while True:
50 try:
63 try:
51 msgs.append(self.get_msg(block=False))
64 msgs.append(self.get_msg(block=False))
52 except Empty:
65 except Empty:
53 break
66 break
54 return msgs
67 return msgs
55
68
56 def msg_ready(self):
69 def msg_ready(self):
57 """ Is there a message that has been received? """
70 """ Is there a message that has been received? """
58 return not self._in_queue.empty()
71 return bool(self.socket.poll(timeout=0))
59
72
60
73 def close(self):
61 class BlockingIOPubChannel(BlockingChannelMixin, IOPubChannel):
74 if self.socket is not None:
75 try:
76 self.socket.close(linger=0)
77 except Exception:
62 pass
78 pass
79 self.socket = None
80 stop = close
63
81
82 def is_alive(self):
83 return (self.socket is not None)
64
84
65 class BlockingShellChannel(BlockingChannelMixin, ShellChannel):
85 def send(self, msg):
66 def call_handlers(self, msg):
86 """Pass a message to the ZMQ socket to send
67 if msg['msg_type'] == 'kernel_info_reply':
87 """
68 self._handle_kernel_info_reply(msg)
88 self.session.send(self.socket, msg)
69 return super(BlockingShellChannel, self).call_handlers(msg)
70
71
89
72 class BlockingStdInChannel(BlockingChannelMixin, StdInChannel):
90 def start(self):
73 pass
91 pass
74
92
75
76 class BlockingHBChannel(HBChannel):
77
78 # This kernel needs quicker monitoring, shorten to 1 sec.
79 # less than 0.5s is unreliable, and will get occasional
80 # false reports of missed beats.
81 time_to_dead = 1.
82
83 def call_handlers(self, since_last_heartbeat):
84 """ Pause beating on missed heartbeat. """
85 pass
@@ -1,33 +1,39 b''
1 """Implements a fully blocking kernel client.
1 """Implements a fully blocking kernel client.
2
2
3 Useful for test suites and blocking terminal interfaces.
3 Useful for test suites and blocking terminal interfaces.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 # Copyright (c) IPython Development Team.
6 # Copyright (C) 2013 The IPython Development Team
6 # Distributed under the terms of the Modified BSD License.
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING.txt, distributed as part of this software.
10 #-----------------------------------------------------------------------------
11
7
12 #-----------------------------------------------------------------------------
8 try:
13 # Imports
9 from queue import Empty # Python 3
14 #-----------------------------------------------------------------------------
10 except ImportError:
11 from Queue import Empty # Python 2
15
12
16 from IPython.utils.traitlets import Type
13 from IPython.utils.traitlets import Type
14 from IPython.kernel.channels import HBChannel
17 from IPython.kernel.client import KernelClient
15 from IPython.kernel.client import KernelClient
18 from .channels import (
16 from .channels import ZMQSocketChannel
19 BlockingIOPubChannel, BlockingHBChannel,
20 BlockingShellChannel, BlockingStdInChannel
21 )
22
23 #-----------------------------------------------------------------------------
24 # Blocking kernel manager
25 #-----------------------------------------------------------------------------
26
17
27 class BlockingKernelClient(KernelClient):
18 class BlockingKernelClient(KernelClient):
19 def wait_for_ready(self):
20 # Wait for kernel info reply on shell channel
21 while True:
22 msg = self.shell_channel.get_msg(block=True)
23 if msg['msg_type'] == 'kernel_info_reply':
24 self._handle_kernel_info_reply(msg)
25 break
26
27 # Flush IOPub channel
28 while True:
29 try:
30 msg = self.iopub_channel.get_msg(block=True, timeout=0.2)
31 print(msg['msg_type'])
32 except Empty:
33 break
28
34
29 # The classes to use for the various channels
35 # The classes to use for the various channels
30 shell_channel_class = Type(BlockingShellChannel)
36 shell_channel_class = Type(ZMQSocketChannel)
31 iopub_channel_class = Type(BlockingIOPubChannel)
37 iopub_channel_class = Type(ZMQSocketChannel)
32 stdin_channel_class = Type(BlockingStdInChannel)
38 stdin_channel_class = Type(ZMQSocketChannel)
33 hb_channel_class = Type(BlockingHBChannel)
39 hb_channel_class = Type(HBChannel)
This diff has been collapsed as it changes many lines, (503 lines changed) Show them Hide them
@@ -1,644 +1,203 b''
1 """Base classes to manage a Client's interaction with a running kernel"""
1 """Base classes to manage a Client's interaction with a running kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 import atexit
8 import atexit
9 import errno
9 import errno
10 from threading import Thread
10 from threading import Thread
11 import time
11 import time
12
12
13 import zmq
13 import zmq
14 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
14 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
15 # during garbage collection of threads at exit:
15 # during garbage collection of threads at exit:
16 from zmq import ZMQError
16 from zmq import ZMQError
17 from zmq.eventloop import ioloop, zmqstream
18
17
19 from IPython.core.release import kernel_protocol_version_info
18 from IPython.core.release import kernel_protocol_version_info
20
19
21 from .channelsabc import (
20 from .channelsabc import HBChannelABC
22 ShellChannelABC, IOPubChannelABC,
23 HBChannelABC, StdInChannelABC,
24 )
25 from IPython.utils.py3compat import string_types, iteritems
26
21
27 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
28 # Constants and exceptions
23 # Constants and exceptions
29 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
30
25
31 major_protocol_version = kernel_protocol_version_info[0]
26 major_protocol_version = kernel_protocol_version_info[0]
32
27
33 class InvalidPortNumber(Exception):
28 class InvalidPortNumber(Exception):
34 pass
29 pass
35
30
36 #-----------------------------------------------------------------------------
31 class HBChannel(Thread):
37 # Utility functions
32 """The heartbeat channel which monitors the kernel heartbeat.
38 #-----------------------------------------------------------------------------
39
40 # some utilities to validate message structure, these might get moved elsewhere
41 # if they prove to have more generic utility
42
43 def validate_string_list(lst):
44 """Validate that the input is a list of strings.
45
46 Raises ValueError if not."""
47 if not isinstance(lst, list):
48 raise ValueError('input %r must be a list' % lst)
49 for x in lst:
50 if not isinstance(x, string_types):
51 raise ValueError('element %r in list must be a string' % x)
52
53
54 def validate_string_dict(dct):
55 """Validate that the input is a dict with string keys and values.
56
57 Raises ValueError if not."""
58 for k,v in iteritems(dct):
59 if not isinstance(k, string_types):
60 raise ValueError('key %r in dict must be a string' % k)
61 if not isinstance(v, string_types):
62 raise ValueError('value %r in dict must be a string' % v)
63
64
65 #-----------------------------------------------------------------------------
66 # ZMQ Socket Channel classes
67 #-----------------------------------------------------------------------------
68
33
69 class ZMQSocketChannel(Thread):
34 Note that the heartbeat channel is paused by default. As long as you start
70 """The base class for the channels that use ZMQ sockets."""
35 this channel, the kernel manager will ensure that it is paused and un-paused
36 as appropriate.
37 """
71 context = None
38 context = None
72 session = None
39 session = None
73 socket = None
40 socket = None
74 ioloop = None
41 address = None
75 stream = None
76 _address = None
77 _exiting = False
42 _exiting = False
78 proxy_methods = []
43
44 time_to_dead = 1.
45 poller = None
46 _running = None
47 _pause = None
48 _beating = None
79
49
80 def __init__(self, context, session, address):
50 def __init__(self, context, session, address):
81 """Create a channel.
51 """Create the heartbeat monitor thread.
82
52
83 Parameters
53 Parameters
84 ----------
54 ----------
85 context : :class:`zmq.Context`
55 context : :class:`zmq.Context`
86 The ZMQ context to use.
56 The ZMQ context to use.
87 session : :class:`session.Session`
57 session : :class:`session.Session`
88 The session to use.
58 The session to use.
89 address : zmq url
59 address : zmq url
90 Standard (ip, port) tuple that the kernel is listening on.
60 Standard (ip, port) tuple that the kernel is listening on.
91 """
61 """
92 super(ZMQSocketChannel, self).__init__()
62 super(HBChannel, self).__init__()
93 self.daemon = True
63 self.daemon = True
94
64
95 self.context = context
65 self.context = context
96 self.session = session
66 self.session = session
97 if isinstance(address, tuple):
67 if isinstance(address, tuple):
98 if address[1] == 0:
68 if address[1] == 0:
99 message = 'The port number for a channel cannot be 0.'
69 message = 'The port number for a channel cannot be 0.'
100 raise InvalidPortNumber(message)
70 raise InvalidPortNumber(message)
101 address = "tcp://%s:%i" % address
71 address = "tcp://%s:%i" % address
102 self._address = address
72 self.address = address
103 atexit.register(self._notice_exit)
73 atexit.register(self._notice_exit)
104
74
105 def _notice_exit(self):
106 self._exiting = True
107
108 def _run_loop(self):
109 """Run my loop, ignoring EINTR events in the poller"""
110 while True:
111 try:
112 self.ioloop.start()
113 except ZMQError as e:
114 if e.errno == errno.EINTR:
115 continue
116 else:
117 raise
118 except Exception:
119 if self._exiting:
120 break
121 else:
122 raise
123 else:
124 break
125
126 def stop(self):
127 """Stop the channel's event loop and join its thread.
128
129 This calls :meth:`~threading.Thread.join` and returns when the thread
130 terminates. :class:`RuntimeError` will be raised if
131 :meth:`~threading.Thread.start` is called again.
132 """
133 if self.ioloop is not None:
134 self.ioloop.stop()
135 self.join()
136 self.close()
137
138 def close(self):
139 if self.ioloop is not None:
140 try:
141 self.ioloop.close(all_fds=True)
142 except Exception:
143 pass
144 if self.socket is not None:
145 try:
146 self.socket.close(linger=0)
147 except Exception:
148 pass
149 self.socket = None
150
151 @property
152 def address(self):
153 """Get the channel's address as a zmq url string.
154
155 These URLS have the form: 'tcp://127.0.0.1:5555'.
156 """
157 return self._address
158
159 def _queue_send(self, msg):
160 """Queue a message to be sent from the IOLoop's thread.
161
162 Parameters
163 ----------
164 msg : message to send
165
166 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
167 thread control of the action.
168 """
169 def thread_send():
170 self.session.send(self.stream, msg)
171 self.ioloop.add_callback(thread_send)
172
173 def _handle_recv(self, msg):
174 """Callback for stream.on_recv.
175
176 Unpacks message, and calls handlers with it.
177 """
178 ident,smsg = self.session.feed_identities(msg)
179 msg = self.session.deserialize(smsg)
180 self.call_handlers(msg)
181
182
183
184 class ShellChannel(ZMQSocketChannel):
185 """The shell channel for issuing request/replies to the kernel."""
186
187 command_queue = None
188 # flag for whether execute requests should be allowed to call raw_input:
189 allow_stdin = True
190 proxy_methods = [
191 'execute',
192 'complete',
193 'inspect',
194 'history',
195 'kernel_info',
196 'shutdown',
197 'is_complete',
198 ]
199
200 def __init__(self, context, session, address):
201 super(ShellChannel, self).__init__(context, session, address)
202 self.ioloop = ioloop.IOLoop()
203
204 def run(self):
205 """The thread's main activity. Call start() instead."""
206 self.socket = self.context.socket(zmq.DEALER)
207 self.socket.linger = 1000
208 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
209 self.socket.connect(self.address)
210 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
211 self.stream.on_recv(self._handle_recv)
212 self._run_loop()
213
214 def call_handlers(self, msg):
215 """This method is called in the ioloop thread when a message arrives.
216
217 Subclasses should override this method to handle incoming messages.
218 It is important to remember that this method is called in the thread
219 so that some logic must be done to ensure that the application level
220 handlers are called in the application thread.
221 """
222 raise NotImplementedError('call_handlers must be defined in a subclass.')
223
224 def execute(self, code, silent=False, store_history=True,
225 user_expressions=None, allow_stdin=None):
226 """Execute code in the kernel.
227
228 Parameters
229 ----------
230 code : str
231 A string of Python code.
232
233 silent : bool, optional (default False)
234 If set, the kernel will execute the code as quietly possible, and
235 will force store_history to be False.
236
237 store_history : bool, optional (default True)
238 If set, the kernel will store command history. This is forced
239 to be False if silent is True.
240
241 user_expressions : dict, optional
242 A dict mapping names to expressions to be evaluated in the user's
243 dict. The expression values are returned as strings formatted using
244 :func:`repr`.
245
246 allow_stdin : bool, optional (default self.allow_stdin)
247 Flag for whether the kernel can send stdin requests to frontends.
248
249 Some frontends (e.g. the Notebook) do not support stdin requests.
250 If raw_input is called from code executed from such a frontend, a
251 StdinNotImplementedError will be raised.
252
253 Returns
254 -------
255 The msg_id of the message sent.
256 """
257 if user_expressions is None:
258 user_expressions = {}
259 if allow_stdin is None:
260 allow_stdin = self.allow_stdin
261
262
263 # Don't waste network traffic if inputs are invalid
264 if not isinstance(code, string_types):
265 raise ValueError('code %r must be a string' % code)
266 validate_string_dict(user_expressions)
267
268 # Create class for content/msg creation. Related to, but possibly
269 # not in Session.
270 content = dict(code=code, silent=silent, store_history=store_history,
271 user_expressions=user_expressions,
272 allow_stdin=allow_stdin,
273 )
274 msg = self.session.msg('execute_request', content)
275 self._queue_send(msg)
276 return msg['header']['msg_id']
277
278 def complete(self, code, cursor_pos=None):
279 """Tab complete text in the kernel's namespace.
280
281 Parameters
282 ----------
283 code : str
284 The context in which completion is requested.
285 Can be anything between a variable name and an entire cell.
286 cursor_pos : int, optional
287 The position of the cursor in the block of code where the completion was requested.
288 Default: ``len(code)``
289
290 Returns
291 -------
292 The msg_id of the message sent.
293 """
294 if cursor_pos is None:
295 cursor_pos = len(code)
296 content = dict(code=code, cursor_pos=cursor_pos)
297 msg = self.session.msg('complete_request', content)
298 self._queue_send(msg)
299 return msg['header']['msg_id']
300
301 def inspect(self, code, cursor_pos=None, detail_level=0):
302 """Get metadata information about an object in the kernel's namespace.
303
304 It is up to the kernel to determine the appropriate object to inspect.
305
306 Parameters
307 ----------
308 code : str
309 The context in which info is requested.
310 Can be anything between a variable name and an entire cell.
311 cursor_pos : int, optional
312 The position of the cursor in the block of code where the info was requested.
313 Default: ``len(code)``
314 detail_level : int, optional
315 The level of detail for the introspection (0-2)
316
317 Returns
318 -------
319 The msg_id of the message sent.
320 """
321 if cursor_pos is None:
322 cursor_pos = len(code)
323 content = dict(code=code, cursor_pos=cursor_pos,
324 detail_level=detail_level,
325 )
326 msg = self.session.msg('inspect_request', content)
327 self._queue_send(msg)
328 return msg['header']['msg_id']
329
330 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
331 """Get entries from the kernel's history list.
332
333 Parameters
334 ----------
335 raw : bool
336 If True, return the raw input.
337 output : bool
338 If True, then return the output as well.
339 hist_access_type : str
340 'range' (fill in session, start and stop params), 'tail' (fill in n)
341 or 'search' (fill in pattern param).
342
343 session : int
344 For a range request, the session from which to get lines. Session
345 numbers are positive integers; negative ones count back from the
346 current session.
347 start : int
348 The first line number of a history range.
349 stop : int
350 The final (excluded) line number of a history range.
351
352 n : int
353 The number of lines of history to get for a tail request.
354
355 pattern : str
356 The glob-syntax pattern for a search request.
357
358 Returns
359 -------
360 The msg_id of the message sent.
361 """
362 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
363 **kwargs)
364 msg = self.session.msg('history_request', content)
365 self._queue_send(msg)
366 return msg['header']['msg_id']
367
368 def kernel_info(self):
369 """Request kernel info."""
370 msg = self.session.msg('kernel_info_request')
371 self._queue_send(msg)
372 return msg['header']['msg_id']
373
374 def _handle_kernel_info_reply(self, msg):
375 """handle kernel info reply
376
377 sets protocol adaptation version
378 """
379 adapt_version = int(msg['content']['protocol_version'].split('.')[0])
380 if adapt_version != major_protocol_version:
381 self.session.adapt_version = adapt_version
382
383 def shutdown(self, restart=False):
384 """Request an immediate kernel shutdown.
385
386 Upon receipt of the (empty) reply, client code can safely assume that
387 the kernel has shut down and it's safe to forcefully terminate it if
388 it's still alive.
389
390 The kernel will send the reply via a function registered with Python's
391 atexit module, ensuring it's truly done as the kernel is done with all
392 normal operation.
393 """
394 # Send quit message to kernel. Once we implement kernel-side setattr,
395 # this should probably be done that way, but for now this will do.
396 msg = self.session.msg('shutdown_request', {'restart':restart})
397 self._queue_send(msg)
398 return msg['header']['msg_id']
399
400 def is_complete(self, code):
401 msg = self.session.msg('is_complete_request', {'code': code})
402 self._queue_send(msg)
403 return msg['header']['msg_id']
404
405
406 class IOPubChannel(ZMQSocketChannel):
407 """The iopub channel which listens for messages that the kernel publishes.
408
409 This channel is where all output is published to frontends.
410 """
411
412 def __init__(self, context, session, address):
413 super(IOPubChannel, self).__init__(context, session, address)
414 self.ioloop = ioloop.IOLoop()
415
416 def run(self):
417 """The thread's main activity. Call start() instead."""
418 self.socket = self.context.socket(zmq.SUB)
419 self.socket.linger = 1000
420 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
421 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
422 self.socket.connect(self.address)
423 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
424 self.stream.on_recv(self._handle_recv)
425 self._run_loop()
426
427 def call_handlers(self, msg):
428 """This method is called in the ioloop thread when a message arrives.
429
430 Subclasses should override this method to handle incoming messages.
431 It is important to remember that this method is called in the thread
432 so that some logic must be done to ensure that the application leve
433 handlers are called in the application thread.
434 """
435 raise NotImplementedError('call_handlers must be defined in a subclass.')
436
437 def flush(self, timeout=1.0):
438 """Immediately processes all pending messages on the iopub channel.
439
440 Callers should use this method to ensure that :meth:`call_handlers`
441 has been called for all messages that have been received on the
442 0MQ SUB socket of this channel.
443
444 This method is thread safe.
445
446 Parameters
447 ----------
448 timeout : float, optional
449 The maximum amount of time to spend flushing, in seconds. The
450 default is one second.
451 """
452 # We do the IOLoop callback process twice to ensure that the IOLoop
453 # gets to perform at least one full poll.
454 stop_time = time.time() + timeout
455 for i in range(2):
456 self._flushed = False
457 self.ioloop.add_callback(self._flush)
458 while not self._flushed and time.time() < stop_time:
459 time.sleep(0.01)
460
461 def _flush(self):
462 """Callback for :method:`self.flush`."""
463 self.stream.flush()
464 self._flushed = True
465
466
467 class StdInChannel(ZMQSocketChannel):
468 """The stdin channel to handle raw_input requests that the kernel makes."""
469
470 msg_queue = None
471 proxy_methods = ['input']
472
473 def __init__(self, context, session, address):
474 super(StdInChannel, self).__init__(context, session, address)
475 self.ioloop = ioloop.IOLoop()
476
477 def run(self):
478 """The thread's main activity. Call start() instead."""
479 self.socket = self.context.socket(zmq.DEALER)
480 self.socket.linger = 1000
481 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
482 self.socket.connect(self.address)
483 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
484 self.stream.on_recv(self._handle_recv)
485 self._run_loop()
486
487 def call_handlers(self, msg):
488 """This method is called in the ioloop thread when a message arrives.
489
490 Subclasses should override this method to handle incoming messages.
491 It is important to remember that this method is called in the thread
492 so that some logic must be done to ensure that the application leve
493 handlers are called in the application thread.
494 """
495 raise NotImplementedError('call_handlers must be defined in a subclass.')
496
497 def input(self, string):
498 """Send a string of raw input to the kernel."""
499 content = dict(value=string)
500 msg = self.session.msg('input_reply', content)
501 self._queue_send(msg)
502
503
504 class HBChannel(ZMQSocketChannel):
505 """The heartbeat channel which monitors the kernel heartbeat.
506
507 Note that the heartbeat channel is paused by default. As long as you start
508 this channel, the kernel manager will ensure that it is paused and un-paused
509 as appropriate.
510 """
511
512 time_to_dead = 3.0
513 socket = None
514 poller = None
515 _running = None
516 _pause = None
517 _beating = None
518
519 def __init__(self, context, session, address):
520 super(HBChannel, self).__init__(context, session, address)
521 self._running = False
75 self._running = False
522 self._pause =True
76 self._pause = True
523 self.poller = zmq.Poller()
77 self.poller = zmq.Poller()
524
78
79 def _notice_exit(self):
80 self._exiting = True
81
525 def _create_socket(self):
82 def _create_socket(self):
526 if self.socket is not None:
83 if self.socket is not None:
527 # close previous socket, before opening a new one
84 # close previous socket, before opening a new one
528 self.poller.unregister(self.socket)
85 self.poller.unregister(self.socket)
529 self.socket.close()
86 self.socket.close()
530 self.socket = self.context.socket(zmq.REQ)
87 self.socket = self.context.socket(zmq.REQ)
531 self.socket.linger = 1000
88 self.socket.linger = 1000
532 self.socket.connect(self.address)
89 self.socket.connect(self.address)
533
90
534 self.poller.register(self.socket, zmq.POLLIN)
91 self.poller.register(self.socket, zmq.POLLIN)
535
92
536 def _poll(self, start_time):
93 def _poll(self, start_time):
537 """poll for heartbeat replies until we reach self.time_to_dead.
94 """poll for heartbeat replies until we reach self.time_to_dead.
538
95
539 Ignores interrupts, and returns the result of poll(), which
96 Ignores interrupts, and returns the result of poll(), which
540 will be an empty list if no messages arrived before the timeout,
97 will be an empty list if no messages arrived before the timeout,
541 or the event tuple if there is a message to receive.
98 or the event tuple if there is a message to receive.
542 """
99 """
543
100
544 until_dead = self.time_to_dead - (time.time() - start_time)
101 until_dead = self.time_to_dead - (time.time() - start_time)
545 # ensure poll at least once
102 # ensure poll at least once
546 until_dead = max(until_dead, 1e-3)
103 until_dead = max(until_dead, 1e-3)
547 events = []
104 events = []
548 while True:
105 while True:
549 try:
106 try:
550 events = self.poller.poll(1000 * until_dead)
107 events = self.poller.poll(1000 * until_dead)
551 except ZMQError as e:
108 except ZMQError as e:
552 if e.errno == errno.EINTR:
109 if e.errno == errno.EINTR:
553 # ignore interrupts during heartbeat
110 # ignore interrupts during heartbeat
554 # this may never actually happen
111 # this may never actually happen
555 until_dead = self.time_to_dead - (time.time() - start_time)
112 until_dead = self.time_to_dead - (time.time() - start_time)
556 until_dead = max(until_dead, 1e-3)
113 until_dead = max(until_dead, 1e-3)
557 pass
114 pass
558 else:
115 else:
559 raise
116 raise
560 except Exception:
117 except Exception:
561 if self._exiting:
118 if self._exiting:
562 break
119 break
563 else:
120 else:
564 raise
121 raise
565 else:
122 else:
566 break
123 break
567 return events
124 return events
568
125
569 def run(self):
126 def run(self):
570 """The thread's main activity. Call start() instead."""
127 """The thread's main activity. Call start() instead."""
571 self._create_socket()
128 self._create_socket()
572 self._running = True
129 self._running = True
573 self._beating = True
130 self._beating = True
574
131
575 while self._running:
132 while self._running:
576 if self._pause:
133 if self._pause:
577 # just sleep, and skip the rest of the loop
134 # just sleep, and skip the rest of the loop
578 time.sleep(self.time_to_dead)
135 time.sleep(self.time_to_dead)
579 continue
136 continue
580
137
581 since_last_heartbeat = 0.0
138 since_last_heartbeat = 0.0
582 # io.rprint('Ping from HB channel') # dbg
139 # io.rprint('Ping from HB channel') # dbg
583 # no need to catch EFSM here, because the previous event was
140 # no need to catch EFSM here, because the previous event was
584 # either a recv or connect, which cannot be followed by EFSM
141 # either a recv or connect, which cannot be followed by EFSM
585 self.socket.send(b'ping')
142 self.socket.send(b'ping')
586 request_time = time.time()
143 request_time = time.time()
587 ready = self._poll(request_time)
144 ready = self._poll(request_time)
588 if ready:
145 if ready:
589 self._beating = True
146 self._beating = True
590 # the poll above guarantees we have something to recv
147 # the poll above guarantees we have something to recv
591 self.socket.recv()
148 self.socket.recv()
592 # sleep the remainder of the cycle
149 # sleep the remainder of the cycle
593 remainder = self.time_to_dead - (time.time() - request_time)
150 remainder = self.time_to_dead - (time.time() - request_time)
594 if remainder > 0:
151 if remainder > 0:
595 time.sleep(remainder)
152 time.sleep(remainder)
596 continue
153 continue
597 else:
154 else:
598 # nothing was received within the time limit, signal heart failure
155 # nothing was received within the time limit, signal heart failure
599 self._beating = False
156 self._beating = False
600 since_last_heartbeat = time.time() - request_time
157 since_last_heartbeat = time.time() - request_time
601 self.call_handlers(since_last_heartbeat)
158 self.call_handlers(since_last_heartbeat)
602 # and close/reopen the socket, because the REQ/REP cycle has been broken
159 # and close/reopen the socket, because the REQ/REP cycle has been broken
603 self._create_socket()
160 self._create_socket()
604 continue
161 continue
605
162
606 def pause(self):
163 def pause(self):
607 """Pause the heartbeat."""
164 """Pause the heartbeat."""
608 self._pause = True
165 self._pause = True
609
166
610 def unpause(self):
167 def unpause(self):
611 """Unpause the heartbeat."""
168 """Unpause the heartbeat."""
612 self._pause = False
169 self._pause = False
613
170
614 def is_beating(self):
171 def is_beating(self):
615 """Is the heartbeat running and responsive (and not paused)."""
172 """Is the heartbeat running and responsive (and not paused)."""
616 if self.is_alive() and not self._pause and self._beating:
173 if self.is_alive() and not self._pause and self._beating:
617 return True
174 return True
618 else:
175 else:
619 return False
176 return False
620
177
621 def stop(self):
178 def stop(self):
622 """Stop the channel's event loop and join its thread."""
179 """Stop the channel's event loop and join its thread."""
623 self._running = False
180 self._running = False
624 super(HBChannel, self).stop()
181 self.join()
182 self.close()
183
184 def close(self):
185 if self.socket is not None:
186 try:
187 self.socket.close(linger=0)
188 except Exception:
189 pass
190 self.socket = None
625
191
626 def call_handlers(self, since_last_heartbeat):
192 def call_handlers(self, since_last_heartbeat):
627 """This method is called in the ioloop thread when a message arrives.
193 """This method is called in the ioloop thread when a message arrives.
628
194
629 Subclasses should override this method to handle incoming messages.
195 Subclasses should override this method to handle incoming messages.
630 It is important to remember that this method is called in the thread
196 It is important to remember that this method is called in the thread
631 so that some logic must be done to ensure that the application level
197 so that some logic must be done to ensure that the application level
632 handlers are called in the application thread.
198 handlers are called in the application thread.
633 """
199 """
634 raise NotImplementedError('call_handlers must be defined in a subclass.')
200 pass
635
636
201
637 #---------------------------------------------------------------------#-----------------------------------------------------------------------------
638 # ABC Registration
639 #-----------------------------------------------------------------------------
640
202
641 ShellChannelABC.register(ShellChannel)
642 IOPubChannelABC.register(IOPubChannel)
643 HBChannelABC.register(HBChannel)
203 HBChannelABC.register(HBChannel)
644 StdInChannelABC.register(StdInChannel)
@@ -1,113 +1,49 b''
1 """Abstract base classes for kernel client channels"""
1 """Abstract base classes for kernel client channels"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import abc
6 import abc
7
7
8 from IPython.utils.py3compat import with_metaclass
8 from IPython.utils.py3compat import with_metaclass
9
9
10
10
11 class ChannelABC(with_metaclass(abc.ABCMeta, object)):
11 class ChannelABC(with_metaclass(abc.ABCMeta, object)):
12 """A base class for all channel ABCs."""
12 """A base class for all channel ABCs."""
13
13
14 @abc.abstractmethod
14 @abc.abstractmethod
15 def start(self):
15 def start(self):
16 pass
16 pass
17
17
18 @abc.abstractmethod
18 @abc.abstractmethod
19 def stop(self):
19 def stop(self):
20 pass
20 pass
21
21
22 @abc.abstractmethod
22 @abc.abstractmethod
23 def is_alive(self):
23 def is_alive(self):
24 pass
24 pass
25
25
26
26
27 class ShellChannelABC(ChannelABC):
28 """ShellChannel ABC.
29
30 The docstrings for this class can be found in the base implementation:
31
32 `IPython.kernel.channels.ShellChannel`
33 """
34
35 @abc.abstractproperty
36 def allow_stdin(self):
37 pass
38
39 @abc.abstractmethod
40 def execute(self, code, silent=False, store_history=True,
41 user_expressions=None, allow_stdin=None):
42 pass
43
44 @abc.abstractmethod
45 def complete(self, text, line, cursor_pos, block=None):
46 pass
47
48 @abc.abstractmethod
49 def inspect(self, oname, detail_level=0):
50 pass
51
52 @abc.abstractmethod
53 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
54 pass
55
56 @abc.abstractmethod
57 def kernel_info(self):
58 pass
59
60 @abc.abstractmethod
61 def shutdown(self, restart=False):
62 pass
63
64
65 class IOPubChannelABC(ChannelABC):
66 """IOPubChannel ABC.
67
68 The docstrings for this class can be found in the base implementation:
69
70 `IPython.kernel.channels.IOPubChannel`
71 """
72
73 @abc.abstractmethod
74 def flush(self, timeout=1.0):
75 pass
76
77
78 class StdInChannelABC(ChannelABC):
79 """StdInChannel ABC.
80
81 The docstrings for this class can be found in the base implementation:
82
83 `IPython.kernel.channels.StdInChannel`
84 """
85
86 @abc.abstractmethod
87 def input(self, string):
88 pass
89
90
91 class HBChannelABC(ChannelABC):
27 class HBChannelABC(ChannelABC):
92 """HBChannel ABC.
28 """HBChannel ABC.
93
29
94 The docstrings for this class can be found in the base implementation:
30 The docstrings for this class can be found in the base implementation:
95
31
96 `IPython.kernel.channels.HBChannel`
32 `IPython.kernel.channels.HBChannel`
97 """
33 """
98
34
99 @abc.abstractproperty
35 @abc.abstractproperty
100 def time_to_dead(self):
36 def time_to_dead(self):
101 pass
37 pass
102
38
103 @abc.abstractmethod
39 @abc.abstractmethod
104 def pause(self):
40 def pause(self):
105 pass
41 pass
106
42
107 @abc.abstractmethod
43 @abc.abstractmethod
108 def unpause(self):
44 def unpause(self):
109 pass
45 pass
110
46
111 @abc.abstractmethod
47 @abc.abstractmethod
112 def is_beating(self):
48 def is_beating(self):
113 pass
49 pass
@@ -1,186 +1,386 b''
1 """Base class to manage the interaction with a running kernel"""
1 """Base class to manage the interaction with a running kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7 from IPython.kernel.channels import major_protocol_version
8 from IPython.utils.py3compat import string_types, iteritems
7
9
8 import zmq
10 import zmq
9
11
10 from IPython.utils.traitlets import (
12 from IPython.utils.traitlets import (
11 Any, Instance, Type,
13 Any, Instance, Type,
12 )
14 )
13
15
14 from .zmq.session import Session
16 from .channelsabc import (ChannelABC, HBChannelABC)
15 from .channels import (
16 ShellChannel, IOPubChannel,
17 HBChannel, StdInChannel,
18 )
19 from .clientabc import KernelClientABC
17 from .clientabc import KernelClientABC
20 from .connect import ConnectionFileMixin
18 from .connect import ConnectionFileMixin
21
19
22
20
21 # some utilities to validate message structure, these might get moved elsewhere
22 # if they prove to have more generic utility
23
24 def validate_string_dict(dct):
25 """Validate that the input is a dict with string keys and values.
26
27 Raises ValueError if not."""
28 for k,v in iteritems(dct):
29 if not isinstance(k, string_types):
30 raise ValueError('key %r in dict must be a string' % k)
31 if not isinstance(v, string_types):
32 raise ValueError('value %r in dict must be a string' % v)
33
34
23 class KernelClient(ConnectionFileMixin):
35 class KernelClient(ConnectionFileMixin):
24 """Communicates with a single kernel on any host via zmq channels.
36 """Communicates with a single kernel on any host via zmq channels.
25
37
26 There are four channels associated with each kernel:
38 There are four channels associated with each kernel:
27
39
28 * shell: for request/reply calls to the kernel.
40 * shell: for request/reply calls to the kernel.
29 * iopub: for the kernel to publish results to frontends.
41 * iopub: for the kernel to publish results to frontends.
30 * hb: for monitoring the kernel's heartbeat.
42 * hb: for monitoring the kernel's heartbeat.
31 * stdin: for frontends to reply to raw_input calls in the kernel.
43 * stdin: for frontends to reply to raw_input calls in the kernel.
32
44
33 The methods of the channels are exposed as methods of the client itself
45 The methods of the channels are exposed as methods of the client itself
34 (KernelClient.execute, complete, history, etc.).
46 (KernelClient.execute, complete, history, etc.).
35 See the channels themselves for documentation of these methods.
47 See the channels themselves for documentation of these methods.
36
48
37 """
49 """
38
50
39 # The PyZMQ Context to use for communication with the kernel.
51 # The PyZMQ Context to use for communication with the kernel.
40 context = Instance(zmq.Context)
52 context = Instance(zmq.Context)
41 def _context_default(self):
53 def _context_default(self):
42 return zmq.Context.instance()
54 return zmq.Context.instance()
43
55
44 # The classes to use for the various channels
56 # The classes to use for the various channels
45 shell_channel_class = Type(ShellChannel)
57 shell_channel_class = Type(ChannelABC)
46 iopub_channel_class = Type(IOPubChannel)
58 iopub_channel_class = Type(ChannelABC)
47 stdin_channel_class = Type(StdInChannel)
59 stdin_channel_class = Type(ChannelABC)
48 hb_channel_class = Type(HBChannel)
60 hb_channel_class = Type(HBChannelABC)
49
61
50 # Protected traits
62 # Protected traits
51 _shell_channel = Any
63 _shell_channel = Any
52 _iopub_channel = Any
64 _iopub_channel = Any
53 _stdin_channel = Any
65 _stdin_channel = Any
54 _hb_channel = Any
66 _hb_channel = Any
55
67
68 # flag for whether execute requests should be allowed to call raw_input:
69 allow_stdin = True
70
56 #--------------------------------------------------------------------------
71 #--------------------------------------------------------------------------
57 # Channel proxy methods
72 # Channel proxy methods
58 #--------------------------------------------------------------------------
73 #--------------------------------------------------------------------------
59
74
60 def _get_msg(channel, *args, **kwargs):
75 def _get_msg(channel, *args, **kwargs):
61 return channel.get_msg(*args, **kwargs)
76 return channel.get_msg(*args, **kwargs)
62
77
63 def get_shell_msg(self, *args, **kwargs):
78 def get_shell_msg(self, *args, **kwargs):
64 """Get a message from the shell channel"""
79 """Get a message from the shell channel"""
65 return self.shell_channel.get_msg(*args, **kwargs)
80 return self.shell_channel.get_msg(*args, **kwargs)
66
81
67 def get_iopub_msg(self, *args, **kwargs):
82 def get_iopub_msg(self, *args, **kwargs):
68 """Get a message from the iopub channel"""
83 """Get a message from the iopub channel"""
69 return self.iopub_channel.get_msg(*args, **kwargs)
84 return self.iopub_channel.get_msg(*args, **kwargs)
70
85
71 def get_stdin_msg(self, *args, **kwargs):
86 def get_stdin_msg(self, *args, **kwargs):
72 """Get a message from the stdin channel"""
87 """Get a message from the stdin channel"""
73 return self.stdin_channel.get_msg(*args, **kwargs)
88 return self.stdin_channel.get_msg(*args, **kwargs)
74
89
75 #--------------------------------------------------------------------------
90 #--------------------------------------------------------------------------
76 # Channel management methods
91 # Channel management methods
77 #--------------------------------------------------------------------------
92 #--------------------------------------------------------------------------
78
93
79 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
94 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
80 """Starts the channels for this kernel.
95 """Starts the channels for this kernel.
81
96
82 This will create the channels if they do not exist and then start
97 This will create the channels if they do not exist and then start
83 them (their activity runs in a thread). If port numbers of 0 are
98 them (their activity runs in a thread). If port numbers of 0 are
84 being used (random ports) then you must first call
99 being used (random ports) then you must first call
85 :meth:`start_kernel`. If the channels have been stopped and you
100 :meth:`start_kernel`. If the channels have been stopped and you
86 call this, :class:`RuntimeError` will be raised.
101 call this, :class:`RuntimeError` will be raised.
87 """
102 """
88 if shell:
103 if shell:
89 self.shell_channel.start()
104 self.shell_channel.start()
90 for method in self.shell_channel.proxy_methods:
105 self.kernel_info()
91 setattr(self, method, getattr(self.shell_channel, method))
92 if iopub:
106 if iopub:
93 self.iopub_channel.start()
107 self.iopub_channel.start()
94 for method in self.iopub_channel.proxy_methods:
95 setattr(self, method, getattr(self.iopub_channel, method))
96 if stdin:
108 if stdin:
97 self.stdin_channel.start()
109 self.stdin_channel.start()
98 for method in self.stdin_channel.proxy_methods:
110 self.allow_stdin = True
99 setattr(self, method, getattr(self.stdin_channel, method))
100 self.shell_channel.allow_stdin = True
101 else:
111 else:
102 self.shell_channel.allow_stdin = False
112 self.allow_stdin = False
103 if hb:
113 if hb:
104 self.hb_channel.start()
114 self.hb_channel.start()
105
115
106 def stop_channels(self):
116 def stop_channels(self):
107 """Stops all the running channels for this kernel.
117 """Stops all the running channels for this kernel.
108
118
109 This stops their event loops and joins their threads.
119 This stops their event loops and joins their threads.
110 """
120 """
111 if self.shell_channel.is_alive():
121 if self.shell_channel.is_alive():
112 self.shell_channel.stop()
122 self.shell_channel.stop()
113 if self.iopub_channel.is_alive():
123 if self.iopub_channel.is_alive():
114 self.iopub_channel.stop()
124 self.iopub_channel.stop()
115 if self.stdin_channel.is_alive():
125 if self.stdin_channel.is_alive():
116 self.stdin_channel.stop()
126 self.stdin_channel.stop()
117 if self.hb_channel.is_alive():
127 if self.hb_channel.is_alive():
118 self.hb_channel.stop()
128 self.hb_channel.stop()
119
129
120 @property
130 @property
121 def channels_running(self):
131 def channels_running(self):
122 """Are any of the channels created and running?"""
132 """Are any of the channels created and running?"""
123 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
133 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
124 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
134 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
125
135
136 ioloop = None # Overridden in subclasses that use pyzmq event loop
137
126 @property
138 @property
127 def shell_channel(self):
139 def shell_channel(self):
128 """Get the shell channel object for this kernel."""
140 """Get the shell channel object for this kernel."""
129 if self._shell_channel is None:
141 if self._shell_channel is None:
130 url = self._make_url('shell')
142 url = self._make_url('shell')
131 self.log.debug("connecting shell channel to %s", url)
143 self.log.debug("connecting shell channel to %s", url)
144 socket = self.connect_shell(identity=self.session.bsession)
132 self._shell_channel = self.shell_channel_class(
145 self._shell_channel = self.shell_channel_class(
133 self.context, self.session, url
146 socket, self.session, self.ioloop
134 )
147 )
135 return self._shell_channel
148 return self._shell_channel
136
149
137 @property
150 @property
138 def iopub_channel(self):
151 def iopub_channel(self):
139 """Get the iopub channel object for this kernel."""
152 """Get the iopub channel object for this kernel."""
140 if self._iopub_channel is None:
153 if self._iopub_channel is None:
141 url = self._make_url('iopub')
154 url = self._make_url('iopub')
142 self.log.debug("connecting iopub channel to %s", url)
155 self.log.debug("connecting iopub channel to %s", url)
156 socket = self.connect_iopub()
143 self._iopub_channel = self.iopub_channel_class(
157 self._iopub_channel = self.iopub_channel_class(
144 self.context, self.session, url
158 socket, self.session, self.ioloop
145 )
159 )
146 return self._iopub_channel
160 return self._iopub_channel
147
161
148 @property
162 @property
149 def stdin_channel(self):
163 def stdin_channel(self):
150 """Get the stdin channel object for this kernel."""
164 """Get the stdin channel object for this kernel."""
151 if self._stdin_channel is None:
165 if self._stdin_channel is None:
152 url = self._make_url('stdin')
166 url = self._make_url('stdin')
153 self.log.debug("connecting stdin channel to %s", url)
167 self.log.debug("connecting stdin channel to %s", url)
168 socket = self.connect_stdin(identity=self.session.bsession)
154 self._stdin_channel = self.stdin_channel_class(
169 self._stdin_channel = self.stdin_channel_class(
155 self.context, self.session, url
170 socket, self.session, self.ioloop
156 )
171 )
157 return self._stdin_channel
172 return self._stdin_channel
158
173
159 @property
174 @property
160 def hb_channel(self):
175 def hb_channel(self):
161 """Get the hb channel object for this kernel."""
176 """Get the hb channel object for this kernel."""
162 if self._hb_channel is None:
177 if self._hb_channel is None:
163 url = self._make_url('hb')
178 url = self._make_url('hb')
164 self.log.debug("connecting heartbeat channel to %s", url)
179 self.log.debug("connecting heartbeat channel to %s", url)
165 self._hb_channel = self.hb_channel_class(
180 self._hb_channel = self.hb_channel_class(
166 self.context, self.session, url
181 self.context, self.session, url
167 )
182 )
168 return self._hb_channel
183 return self._hb_channel
169
184
170 def is_alive(self):
185 def is_alive(self):
171 """Is the kernel process still running?"""
186 """Is the kernel process still running?"""
172 if self._hb_channel is not None:
187 if self._hb_channel is not None:
173 # We didn't start the kernel with this KernelManager so we
188 # We didn't start the kernel with this KernelManager so we
174 # use the heartbeat.
189 # use the heartbeat.
175 return self._hb_channel.is_beating()
190 return self._hb_channel.is_beating()
176 else:
191 else:
177 # no heartbeat and not local, we can't tell if it's running,
192 # no heartbeat and not local, we can't tell if it's running,
178 # so naively return True
193 # so naively return True
179 return True
194 return True
180
195
181
196
182 #-----------------------------------------------------------------------------
197 # Methods to send specific messages on channels
183 # ABC Registration
198 def execute(self, code, silent=False, store_history=True,
184 #-----------------------------------------------------------------------------
199 user_expressions=None, allow_stdin=None):
200 """Execute code in the kernel.
201
202 Parameters
203 ----------
204 code : str
205 A string of Python code.
206
207 silent : bool, optional (default False)
208 If set, the kernel will execute the code as quietly possible, and
209 will force store_history to be False.
210
211 store_history : bool, optional (default True)
212 If set, the kernel will store command history. This is forced
213 to be False if silent is True.
214
215 user_expressions : dict, optional
216 A dict mapping names to expressions to be evaluated in the user's
217 dict. The expression values are returned as strings formatted using
218 :func:`repr`.
219
220 allow_stdin : bool, optional (default self.allow_stdin)
221 Flag for whether the kernel can send stdin requests to frontends.
222
223 Some frontends (e.g. the Notebook) do not support stdin requests.
224 If raw_input is called from code executed from such a frontend, a
225 StdinNotImplementedError will be raised.
226
227 Returns
228 -------
229 The msg_id of the message sent.
230 """
231 if user_expressions is None:
232 user_expressions = {}
233 if allow_stdin is None:
234 allow_stdin = self.allow_stdin
235
236
237 # Don't waste network traffic if inputs are invalid
238 if not isinstance(code, string_types):
239 raise ValueError('code %r must be a string' % code)
240 validate_string_dict(user_expressions)
241
242 # Create class for content/msg creation. Related to, but possibly
243 # not in Session.
244 content = dict(code=code, silent=silent, store_history=store_history,
245 user_expressions=user_expressions,
246 allow_stdin=allow_stdin,
247 )
248 msg = self.session.msg('execute_request', content)
249 self.shell_channel.send(msg)
250 return msg['header']['msg_id']
251
252 def complete(self, code, cursor_pos=None):
253 """Tab complete text in the kernel's namespace.
254
255 Parameters
256 ----------
257 code : str
258 The context in which completion is requested.
259 Can be anything between a variable name and an entire cell.
260 cursor_pos : int, optional
261 The position of the cursor in the block of code where the completion was requested.
262 Default: ``len(code)``
263
264 Returns
265 -------
266 The msg_id of the message sent.
267 """
268 if cursor_pos is None:
269 cursor_pos = len(code)
270 content = dict(code=code, cursor_pos=cursor_pos)
271 msg = self.session.msg('complete_request', content)
272 self.shell_channel.send(msg)
273 return msg['header']['msg_id']
274
275 def inspect(self, code, cursor_pos=None, detail_level=0):
276 """Get metadata information about an object in the kernel's namespace.
277
278 It is up to the kernel to determine the appropriate object to inspect.
279
280 Parameters
281 ----------
282 code : str
283 The context in which info is requested.
284 Can be anything between a variable name and an entire cell.
285 cursor_pos : int, optional
286 The position of the cursor in the block of code where the info was requested.
287 Default: ``len(code)``
288 detail_level : int, optional
289 The level of detail for the introspection (0-2)
290
291 Returns
292 -------
293 The msg_id of the message sent.
294 """
295 if cursor_pos is None:
296 cursor_pos = len(code)
297 content = dict(code=code, cursor_pos=cursor_pos,
298 detail_level=detail_level,
299 )
300 msg = self.session.msg('inspect_request', content)
301 self.shell_channel.send(msg)
302 return msg['header']['msg_id']
303
304 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
305 """Get entries from the kernel's history list.
306
307 Parameters
308 ----------
309 raw : bool
310 If True, return the raw input.
311 output : bool
312 If True, then return the output as well.
313 hist_access_type : str
314 'range' (fill in session, start and stop params), 'tail' (fill in n)
315 or 'search' (fill in pattern param).
316
317 session : int
318 For a range request, the session from which to get lines. Session
319 numbers are positive integers; negative ones count back from the
320 current session.
321 start : int
322 The first line number of a history range.
323 stop : int
324 The final (excluded) line number of a history range.
325
326 n : int
327 The number of lines of history to get for a tail request.
328
329 pattern : str
330 The glob-syntax pattern for a search request.
331
332 Returns
333 -------
334 The msg_id of the message sent.
335 """
336 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
337 **kwargs)
338 msg = self.session.msg('history_request', content)
339 self.shell_channel.send(msg)
340 return msg['header']['msg_id']
341
342 def kernel_info(self):
343 """Request kernel info."""
344 msg = self.session.msg('kernel_info_request')
345 self.shell_channel.send(msg)
346 return msg['header']['msg_id']
347
348 def _handle_kernel_info_reply(self, msg):
349 """handle kernel info reply
350
351 sets protocol adaptation version
352 """
353 adapt_version = int(msg['content']['protocol_version'].split('.')[0])
354 if adapt_version != major_protocol_version:
355 self.session.adapt_version = adapt_version
356
357 def shutdown(self, restart=False):
358 """Request an immediate kernel shutdown.
359
360 Upon receipt of the (empty) reply, client code can safely assume that
361 the kernel has shut down and it's safe to forcefully terminate it if
362 it's still alive.
363
364 The kernel will send the reply via a function registered with Python's
365 atexit module, ensuring it's truly done as the kernel is done with all
366 normal operation.
367 """
368 # Send quit message to kernel. Once we implement kernel-side setattr,
369 # this should probably be done that way, but for now this will do.
370 msg = self.session.msg('shutdown_request', {'restart':restart})
371 self.shell_channel.send(msg)
372 return msg['header']['msg_id']
373
374 def is_complete(self, code):
375 msg = self.session.msg('is_complete_request', {'code': code})
376 self.shell_channel.send(msg)
377 return msg['header']['msg_id']
378
379 def input(self, string):
380 """Send a string of raw input to the kernel."""
381 content = dict(value=string)
382 msg = self.session.msg('input_reply', content)
383 self.stdin_channel.send(msg)
384
185
385
186 KernelClientABC.register(KernelClient)
386 KernelClientABC.register(KernelClient)
@@ -1,10 +1,8 b''
1 from .channels import (
1 from .channels import (
2 InProcessShellChannel,
2 InProcessChannel,
3 InProcessIOPubChannel,
4 InProcessStdInChannel,
5 InProcessHBChannel,
3 InProcessHBChannel,
6 )
4 )
7
5
8 from .client import InProcessKernelClient
6 from .client import InProcessKernelClient
9 from .manager import InProcessKernelManager
7 from .manager import InProcessKernelManager
10 from .blocking import BlockingInProcessKernelClient
8 from .blocking import BlockingInProcessKernelClient
@@ -1,58 +1,94 b''
1 """ Implements a fully blocking kernel client.
1 """ Implements a fully blocking kernel client.
2
2
3 Useful for test suites and blocking terminal interfaces.
3 Useful for test suites and blocking terminal interfaces.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2012 The IPython Development Team
6 # Copyright (C) 2012 The IPython Development Team
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING.txt, distributed as part of this software.
9 # the file COPYING.txt, distributed as part of this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 try:
13 # Imports
13 from queue import Queue, Empty # Py 3
14 #-----------------------------------------------------------------------------
14 except ImportError:
15 from Queue import Queue, Empty # Py 2
15
16
16 # IPython imports
17 # IPython imports
17 from IPython.utils.io import raw_print
18 from IPython.utils.io import raw_print
18 from IPython.utils.traitlets import Type
19 from IPython.utils.traitlets import Type
19 from IPython.kernel.blocking.channels import BlockingChannelMixin
20 #from IPython.kernel.blocking.channels import BlockingChannelMixin
20
21
21 # Local imports
22 # Local imports
22 from .channels import (
23 from .channels import (
23 InProcessShellChannel,
24 InProcessChannel,
24 InProcessIOPubChannel,
25 InProcessStdInChannel,
26 )
25 )
27 from .client import InProcessKernelClient
26 from .client import InProcessKernelClient
28
27
29 #-----------------------------------------------------------------------------
28 class BlockingInProcessChannel(InProcessChannel):
30 # Blocking kernel manager
29
31 #-----------------------------------------------------------------------------
30 def __init__(self, *args, **kwds):
31 super(BlockingInProcessChannel, self).__init__(*args, **kwds)
32 self._in_queue = Queue()
32
33
33 class BlockingInProcessShellChannel(BlockingChannelMixin, InProcessShellChannel):
34 def call_handlers(self, msg):
34 pass
35 self._in_queue.put(msg)
36
37 def get_msg(self, block=True, timeout=None):
38 """ Gets a message if there is one that is ready. """
39 if timeout is None:
40 # Queue.get(timeout=None) has stupid uninteruptible
41 # behavior, so wait for a week instead
42 timeout = 604800
43 return self._in_queue.get(block, timeout)
44
45 def get_msgs(self):
46 """ Get all messages that are currently ready. """
47 msgs = []
48 while True:
49 try:
50 msgs.append(self.get_msg(block=False))
51 except Empty:
52 break
53 return msgs
35
54
36 class BlockingInProcessIOPubChannel(BlockingChannelMixin, InProcessIOPubChannel):
55 def msg_ready(self):
37 pass
56 """ Is there a message that has been received? """
57 return not self._in_queue.empty()
38
58
39 class BlockingInProcessStdInChannel(BlockingChannelMixin, InProcessStdInChannel):
40
59
60 class BlockingInProcessStdInChannel(BlockingInProcessChannel):
41 def call_handlers(self, msg):
61 def call_handlers(self, msg):
42 """ Overridden for the in-process channel.
62 """ Overridden for the in-process channel.
43
63
44 This methods simply calls raw_input directly.
64 This methods simply calls raw_input directly.
45 """
65 """
46 msg_type = msg['header']['msg_type']
66 msg_type = msg['header']['msg_type']
47 if msg_type == 'input_request':
67 if msg_type == 'input_request':
48 _raw_input = self.client.kernel._sys_raw_input
68 _raw_input = self.client.kernel._sys_raw_input
49 prompt = msg['content']['prompt']
69 prompt = msg['content']['prompt']
50 raw_print(prompt, end='')
70 raw_print(prompt, end='')
51 self.input(_raw_input())
71 self.client.input(_raw_input())
52
72
53 class BlockingInProcessKernelClient(InProcessKernelClient):
73 class BlockingInProcessKernelClient(InProcessKernelClient):
54
74
55 # The classes to use for the various channels.
75 # The classes to use for the various channels.
56 shell_channel_class = Type(BlockingInProcessShellChannel)
76 shell_channel_class = Type(BlockingInProcessChannel)
57 iopub_channel_class = Type(BlockingInProcessIOPubChannel)
77 iopub_channel_class = Type(BlockingInProcessChannel)
58 stdin_channel_class = Type(BlockingInProcessStdInChannel)
78 stdin_channel_class = Type(BlockingInProcessStdInChannel)
79
80 def wait_for_ready(self):
81 # Wait for kernel info reply on shell channel
82 while True:
83 msg = self.shell_channel.get_msg(block=True)
84 if msg['msg_type'] == 'kernel_info_reply':
85 self._handle_kernel_info_reply(msg)
86 break
87
88 # Flush IOPub channel
89 while True:
90 try:
91 msg = self.iopub_channel.get_msg(block=True, timeout=0.2)
92 print(msg['msg_type'])
93 except Empty:
94 break
@@ -1,196 +1,97 b''
1 """A kernel client for in-process kernels."""
1 """A kernel client for in-process kernels."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from IPython.kernel.channelsabc import (
6 from IPython.kernel.channelsabc import HBChannelABC
7 ShellChannelABC, IOPubChannelABC,
8 HBChannelABC, StdInChannelABC,
9 )
10
7
11 from .socket import DummySocket
8 from .socket import DummySocket
12
9
13 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
14 # Channel classes
11 # Channel classes
15 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
16
13
17 class InProcessChannel(object):
14 class InProcessChannel(object):
18 """Base class for in-process channels."""
15 """Base class for in-process channels."""
19 proxy_methods = []
16 proxy_methods = []
20
17
21 def __init__(self, client=None):
18 def __init__(self, client=None):
22 super(InProcessChannel, self).__init__()
19 super(InProcessChannel, self).__init__()
23 self.client = client
20 self.client = client
24 self._is_alive = False
21 self._is_alive = False
25
22
26 #--------------------------------------------------------------------------
27 # Channel interface
28 #--------------------------------------------------------------------------
29
30 def is_alive(self):
23 def is_alive(self):
31 return self._is_alive
24 return self._is_alive
32
25
33 def start(self):
26 def start(self):
34 self._is_alive = True
27 self._is_alive = True
35
28
36 def stop(self):
29 def stop(self):
37 self._is_alive = False
30 self._is_alive = False
38
31
39 def call_handlers(self, msg):
32 def call_handlers(self, msg):
40 """ This method is called in the main thread when a message arrives.
33 """ This method is called in the main thread when a message arrives.
41
34
42 Subclasses should override this method to handle incoming messages.
35 Subclasses should override this method to handle incoming messages.
43 """
36 """
44 raise NotImplementedError('call_handlers must be defined in a subclass.')
37 raise NotImplementedError('call_handlers must be defined in a subclass.')
45
38
46 #--------------------------------------------------------------------------
39 def flush(self, timeout=1.0):
47 # InProcessChannel interface
40 pass
48 #--------------------------------------------------------------------------
41
49
42
50 def call_handlers_later(self, *args, **kwds):
43 def call_handlers_later(self, *args, **kwds):
51 """ Call the message handlers later.
44 """ Call the message handlers later.
52
45
53 The default implementation just calls the handlers immediately, but this
46 The default implementation just calls the handlers immediately, but this
54 method exists so that GUI toolkits can defer calling the handlers until
47 method exists so that GUI toolkits can defer calling the handlers until
55 after the event loop has run, as expected by GUI frontends.
48 after the event loop has run, as expected by GUI frontends.
56 """
49 """
57 self.call_handlers(*args, **kwds)
50 self.call_handlers(*args, **kwds)
58
51
59 def process_events(self):
52 def process_events(self):
60 """ Process any pending GUI events.
53 """ Process any pending GUI events.
61
54
62 This method will be never be called from a frontend without an event
55 This method will be never be called from a frontend without an event
63 loop (e.g., a terminal frontend).
56 loop (e.g., a terminal frontend).
64 """
57 """
65 raise NotImplementedError
58 raise NotImplementedError
66
59
67
60
68 class InProcessShellChannel(InProcessChannel):
69 """See `IPython.kernel.channels.ShellChannel` for docstrings."""
70
71 # flag for whether execute requests should be allowed to call raw_input
72 allow_stdin = True
73 proxy_methods = [
74 'execute',
75 'complete',
76 'inspect',
77 'history',
78 'shutdown',
79 'kernel_info',
80 ]
81
82 #--------------------------------------------------------------------------
83 # ShellChannel interface
84 #--------------------------------------------------------------------------
85
86 def execute(self, code, silent=False, store_history=True,
87 user_expressions={}, allow_stdin=None):
88 if allow_stdin is None:
89 allow_stdin = self.allow_stdin
90 content = dict(code=code, silent=silent, store_history=store_history,
91 user_expressions=user_expressions,
92 allow_stdin=allow_stdin)
93 msg = self.client.session.msg('execute_request', content)
94 self._dispatch_to_kernel(msg)
95 return msg['header']['msg_id']
96
97 def complete(self, code, cursor_pos=None):
98 if cursor_pos is None:
99 cursor_pos = len(code)
100 content = dict(code=code, cursor_pos=cursor_pos)
101 msg = self.client.session.msg('complete_request', content)
102 self._dispatch_to_kernel(msg)
103 return msg['header']['msg_id']
104
105 def inspect(self, code, cursor_pos=None, detail_level=0):
106 if cursor_pos is None:
107 cursor_pos = len(code)
108 content = dict(code=code, cursor_pos=cursor_pos,
109 detail_level=detail_level,
110 )
111 msg = self.client.session.msg('inspect_request', content)
112 self._dispatch_to_kernel(msg)
113 return msg['header']['msg_id']
114
115 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
116 content = dict(raw=raw, output=output,
117 hist_access_type=hist_access_type, **kwds)
118 msg = self.client.session.msg('history_request', content)
119 self._dispatch_to_kernel(msg)
120 return msg['header']['msg_id']
121
122 def shutdown(self, restart=False):
123 # FIXME: What to do here?
124 raise NotImplementedError('Cannot shutdown in-process kernel')
125
126 def kernel_info(self):
127 """Request kernel info."""
128 msg = self.client.session.msg('kernel_info_request')
129 self._dispatch_to_kernel(msg)
130 return msg['header']['msg_id']
131
132 #--------------------------------------------------------------------------
133 # Protected interface
134 #--------------------------------------------------------------------------
135
136 def _dispatch_to_kernel(self, msg):
137 """ Send a message to the kernel and handle a reply.
138 """
139 kernel = self.client.kernel
140 if kernel is None:
141 raise RuntimeError('Cannot send request. No kernel exists.')
142
143 stream = DummySocket()
144 self.client.session.send(stream, msg)
145 msg_parts = stream.recv_multipart()
146 kernel.dispatch_shell(stream, msg_parts)
147
148 idents, reply_msg = self.client.session.recv(stream, copy=False)
149 self.call_handlers_later(reply_msg)
150
151
61
152 class InProcessIOPubChannel(InProcessChannel):
62 class InProcessHBChannel(object):
153 """See `IPython.kernel.channels.IOPubChannel` for docstrings."""
63 """A dummy heartbeat channel interface for in-process kernels.
154
64
155 def flush(self, timeout=1.0):
65 Normally we use the heartbeat to check that the kernel process is alive.
156 pass
66 When the kernel is in-process, that doesn't make sense, but clients still
157
67 expect this interface.
158
68 """
159 class InProcessStdInChannel(InProcessChannel):
160 """See `IPython.kernel.channels.StdInChannel` for docstrings."""
161
162 proxy_methods = ['input']
163
69
164 def input(self, string):
70 time_to_dead = 3.0
165 kernel = self.client.kernel
166 if kernel is None:
167 raise RuntimeError('Cannot send input reply. No kernel exists.')
168 kernel.raw_input_str = string
169
71
72 def __init__(self, client=None):
73 super(InProcessHBChannel, self).__init__()
74 self.client = client
75 self._is_alive = False
76 self._pause = True
170
77
171 class InProcessHBChannel(InProcessChannel):
78 def is_alive(self):
172 """See `IPython.kernel.channels.HBChannel` for docstrings."""
79 return self._is_alive
173
80
174 time_to_dead = 3.0
81 def start(self):
82 self._is_alive = True
175
83
176 def __init__(self, *args, **kwds):
84 def stop(self):
177 super(InProcessHBChannel, self).__init__(*args, **kwds)
85 self._is_alive = False
178 self._pause = True
179
86
180 def pause(self):
87 def pause(self):
181 self._pause = True
88 self._pause = True
182
89
183 def unpause(self):
90 def unpause(self):
184 self._pause = False
91 self._pause = False
185
92
186 def is_beating(self):
93 def is_beating(self):
187 return not self._pause
94 return not self._pause
188
95
189 #-----------------------------------------------------------------------------
190 # ABC Registration
191 #-----------------------------------------------------------------------------
192
96
193 ShellChannelABC.register(InProcessShellChannel)
194 IOPubChannelABC.register(InProcessIOPubChannel)
195 HBChannelABC.register(InProcessHBChannel)
97 HBChannelABC.register(InProcessHBChannel)
196 StdInChannelABC.register(InProcessStdInChannel)
@@ -1,87 +1,156 b''
1 """A client for in-process kernels."""
1 """A client for in-process kernels."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2012 The IPython Development Team
4 # Copyright (C) 2012 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 # IPython imports
14 # IPython imports
15 from IPython.kernel.inprocess.socket import DummySocket
15 from IPython.utils.traitlets import Type, Instance
16 from IPython.utils.traitlets import Type, Instance
16 from IPython.kernel.clientabc import KernelClientABC
17 from IPython.kernel.clientabc import KernelClientABC
17 from IPython.kernel.client import KernelClient
18 from IPython.kernel.client import KernelClient
18
19
19 # Local imports
20 # Local imports
20 from .channels import (
21 from .channels import (
21 InProcessShellChannel,
22 InProcessChannel,
22 InProcessIOPubChannel,
23 InProcessHBChannel,
23 InProcessHBChannel,
24 InProcessStdInChannel,
25
24
26 )
25 )
27
26
28 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
29 # Main kernel Client class
28 # Main kernel Client class
30 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
31
30
32 class InProcessKernelClient(KernelClient):
31 class InProcessKernelClient(KernelClient):
33 """A client for an in-process kernel.
32 """A client for an in-process kernel.
34
33
35 This class implements the interface of
34 This class implements the interface of
36 `IPython.kernel.clientabc.KernelClientABC` and allows
35 `IPython.kernel.clientabc.KernelClientABC` and allows
37 (asynchronous) frontends to be used seamlessly with an in-process kernel.
36 (asynchronous) frontends to be used seamlessly with an in-process kernel.
38
37
39 See `IPython.kernel.client.KernelClient` for docstrings.
38 See `IPython.kernel.client.KernelClient` for docstrings.
40 """
39 """
41
40
42 # The classes to use for the various channels.
41 # The classes to use for the various channels.
43 shell_channel_class = Type(InProcessShellChannel)
42 shell_channel_class = Type(InProcessChannel)
44 iopub_channel_class = Type(InProcessIOPubChannel)
43 iopub_channel_class = Type(InProcessChannel)
45 stdin_channel_class = Type(InProcessStdInChannel)
44 stdin_channel_class = Type(InProcessChannel)
46 hb_channel_class = Type(InProcessHBChannel)
45 hb_channel_class = Type(InProcessHBChannel)
47
46
48 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel')
47 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel')
49
48
50 #--------------------------------------------------------------------------
49 #--------------------------------------------------------------------------
51 # Channel management methods
50 # Channel management methods
52 #--------------------------------------------------------------------------
51 #--------------------------------------------------------------------------
53
52
54 def start_channels(self, *args, **kwargs):
53 def start_channels(self, *args, **kwargs):
55 super(InProcessKernelClient, self).start_channels(self)
54 super(InProcessKernelClient, self).start_channels(self)
56 self.kernel.frontends.append(self)
55 self.kernel.frontends.append(self)
57
56
58 @property
57 @property
59 def shell_channel(self):
58 def shell_channel(self):
60 if self._shell_channel is None:
59 if self._shell_channel is None:
61 self._shell_channel = self.shell_channel_class(self)
60 self._shell_channel = self.shell_channel_class(self)
62 return self._shell_channel
61 return self._shell_channel
63
62
64 @property
63 @property
65 def iopub_channel(self):
64 def iopub_channel(self):
66 if self._iopub_channel is None:
65 if self._iopub_channel is None:
67 self._iopub_channel = self.iopub_channel_class(self)
66 self._iopub_channel = self.iopub_channel_class(self)
68 return self._iopub_channel
67 return self._iopub_channel
69
68
70 @property
69 @property
71 def stdin_channel(self):
70 def stdin_channel(self):
72 if self._stdin_channel is None:
71 if self._stdin_channel is None:
73 self._stdin_channel = self.stdin_channel_class(self)
72 self._stdin_channel = self.stdin_channel_class(self)
74 return self._stdin_channel
73 return self._stdin_channel
75
74
76 @property
75 @property
77 def hb_channel(self):
76 def hb_channel(self):
78 if self._hb_channel is None:
77 if self._hb_channel is None:
79 self._hb_channel = self.hb_channel_class(self)
78 self._hb_channel = self.hb_channel_class(self)
80 return self._hb_channel
79 return self._hb_channel
81
80
81 # Methods for sending specific messages
82 # -------------------------------------
83
84 def execute(self, code, silent=False, store_history=True,
85 user_expressions={}, allow_stdin=None):
86 if allow_stdin is None:
87 allow_stdin = self.allow_stdin
88 content = dict(code=code, silent=silent, store_history=store_history,
89 user_expressions=user_expressions,
90 allow_stdin=allow_stdin)
91 msg = self.session.msg('execute_request', content)
92 self._dispatch_to_kernel(msg)
93 return msg['header']['msg_id']
94
95 def complete(self, code, cursor_pos=None):
96 if cursor_pos is None:
97 cursor_pos = len(code)
98 content = dict(code=code, cursor_pos=cursor_pos)
99 msg = self.session.msg('complete_request', content)
100 self._dispatch_to_kernel(msg)
101 return msg['header']['msg_id']
102
103 def inspect(self, code, cursor_pos=None, detail_level=0):
104 if cursor_pos is None:
105 cursor_pos = len(code)
106 content = dict(code=code, cursor_pos=cursor_pos,
107 detail_level=detail_level,
108 )
109 msg = self.session.msg('inspect_request', content)
110 self._dispatch_to_kernel(msg)
111 return msg['header']['msg_id']
112
113 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
114 content = dict(raw=raw, output=output,
115 hist_access_type=hist_access_type, **kwds)
116 msg = self.session.msg('history_request', content)
117 self._dispatch_to_kernel(msg)
118 return msg['header']['msg_id']
119
120 def shutdown(self, restart=False):
121 # FIXME: What to do here?
122 raise NotImplementedError('Cannot shutdown in-process kernel')
123
124 def kernel_info(self):
125 """Request kernel info."""
126 msg = self.session.msg('kernel_info_request')
127 self._dispatch_to_kernel(msg)
128 return msg['header']['msg_id']
129
130 def input(self, string):
131 if self.kernel is None:
132 raise RuntimeError('Cannot send input reply. No kernel exists.')
133 self.kernel.raw_input_str = string
134
135
136 def _dispatch_to_kernel(self, msg):
137 """ Send a message to the kernel and handle a reply.
138 """
139 kernel = self.kernel
140 if kernel is None:
141 raise RuntimeError('Cannot send request. No kernel exists.')
142
143 stream = DummySocket()
144 self.session.send(stream, msg)
145 msg_parts = stream.recv_multipart()
146 kernel.dispatch_shell(stream, msg_parts)
147
148 idents, reply_msg = self.session.recv(stream, copy=False)
149 self.shell_channel.call_handlers_later(reply_msg)
150
82
151
83 #-----------------------------------------------------------------------------
152 #-----------------------------------------------------------------------------
84 # ABC Registration
153 # ABC Registration
85 #-----------------------------------------------------------------------------
154 #-----------------------------------------------------------------------------
86
155
87 KernelClientABC.register(InProcessKernelClient)
156 KernelClientABC.register(InProcessKernelClient)
@@ -1,82 +1,83 b''
1 # Copyright (c) IPython Development Team.
1 # Copyright (c) IPython Development Team.
2 # Distributed under the terms of the Modified BSD License.
2 # Distributed under the terms of the Modified BSD License.
3
3
4 from __future__ import print_function
4 from __future__ import print_function
5
5
6 import sys
6 import sys
7 import unittest
7 import unittest
8
8
9 from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient
9 from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient
10 from IPython.kernel.inprocess.manager import InProcessKernelManager
10 from IPython.kernel.inprocess.manager import InProcessKernelManager
11 from IPython.kernel.inprocess.ipkernel import InProcessKernel
11 from IPython.kernel.inprocess.ipkernel import InProcessKernel
12 from IPython.testing.decorators import skipif_not_matplotlib
12 from IPython.testing.decorators import skipif_not_matplotlib
13 from IPython.utils.io import capture_output
13 from IPython.utils.io import capture_output
14 from IPython.utils import py3compat
14 from IPython.utils import py3compat
15
15
16 if py3compat.PY3:
16 if py3compat.PY3:
17 from io import StringIO
17 from io import StringIO
18 else:
18 else:
19 from StringIO import StringIO
19 from StringIO import StringIO
20
20
21
21
22 class InProcessKernelTestCase(unittest.TestCase):
22 class InProcessKernelTestCase(unittest.TestCase):
23
23
24 def setUp(self):
24 def setUp(self):
25 self.km = InProcessKernelManager()
25 self.km = InProcessKernelManager()
26 self.km.start_kernel()
26 self.km.start_kernel()
27 self.kc = BlockingInProcessKernelClient(kernel=self.km.kernel)
27 self.kc = BlockingInProcessKernelClient(kernel=self.km.kernel)
28 self.kc.start_channels()
28 self.kc.start_channels()
29 self.kc.wait_for_ready()
29
30
30 @skipif_not_matplotlib
31 @skipif_not_matplotlib
31 def test_pylab(self):
32 def test_pylab(self):
32 """Does %pylab work in the in-process kernel?"""
33 """Does %pylab work in the in-process kernel?"""
33 kc = self.kc
34 kc = self.kc
34 kc.execute('%pylab')
35 kc.execute('%pylab')
35 msg = get_stream_message(kc)
36 msg = get_stream_message(kc)
36 self.assertIn('matplotlib', msg['content']['text'])
37 self.assertIn('matplotlib', msg['content']['text'])
37
38
38 def test_raw_input(self):
39 def test_raw_input(self):
39 """ Does the in-process kernel handle raw_input correctly?
40 """ Does the in-process kernel handle raw_input correctly?
40 """
41 """
41 io = StringIO('foobar\n')
42 io = StringIO('foobar\n')
42 sys_stdin = sys.stdin
43 sys_stdin = sys.stdin
43 sys.stdin = io
44 sys.stdin = io
44 try:
45 try:
45 if py3compat.PY3:
46 if py3compat.PY3:
46 self.kc.execute('x = input()')
47 self.kc.execute('x = input()')
47 else:
48 else:
48 self.kc.execute('x = raw_input()')
49 self.kc.execute('x = raw_input()')
49 finally:
50 finally:
50 sys.stdin = sys_stdin
51 sys.stdin = sys_stdin
51 self.assertEqual(self.km.kernel.shell.user_ns.get('x'), 'foobar')
52 self.assertEqual(self.km.kernel.shell.user_ns.get('x'), 'foobar')
52
53
53 def test_stdout(self):
54 def test_stdout(self):
54 """ Does the in-process kernel correctly capture IO?
55 """ Does the in-process kernel correctly capture IO?
55 """
56 """
56 kernel = InProcessKernel()
57 kernel = InProcessKernel()
57
58
58 with capture_output() as io:
59 with capture_output() as io:
59 kernel.shell.run_cell('print("foo")')
60 kernel.shell.run_cell('print("foo")')
60 self.assertEqual(io.stdout, 'foo\n')
61 self.assertEqual(io.stdout, 'foo\n')
61
62
62 kc = BlockingInProcessKernelClient(kernel=kernel)
63 kc = BlockingInProcessKernelClient(kernel=kernel)
63 kernel.frontends.append(kc)
64 kernel.frontends.append(kc)
64 kc.shell_channel.execute('print("bar")')
65 kc.execute('print("bar")')
65 msg = get_stream_message(kc)
66 msg = get_stream_message(kc)
66 self.assertEqual(msg['content']['text'], 'bar\n')
67 self.assertEqual(msg['content']['text'], 'bar\n')
67
68
68 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
69 # Utility functions
70 # Utility functions
70 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
71
72
72 def get_stream_message(kernel_client, timeout=5):
73 def get_stream_message(kernel_client, timeout=5):
73 """ Gets a single stream message synchronously from the sub channel.
74 """ Gets a single stream message synchronously from the sub channel.
74 """
75 """
75 while True:
76 while True:
76 msg = kernel_client.get_iopub_msg(timeout=timeout)
77 msg = kernel_client.get_iopub_msg(timeout=timeout)
77 if msg['header']['msg_type'] == 'stream':
78 if msg['header']['msg_type'] == 'stream':
78 return msg
79 return msg
79
80
80
81
81 if __name__ == '__main__':
82 if __name__ == '__main__':
82 unittest.main()
83 unittest.main()
@@ -1,104 +1,108 b''
1 # Copyright (c) IPython Development Team.
1 # Copyright (c) IPython Development Team.
2 # Distributed under the terms of the Modified BSD License.
2 # Distributed under the terms of the Modified BSD License.
3
3
4 from __future__ import print_function
4 from __future__ import print_function
5
5
6 import unittest
6 import unittest
7
7
8 from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient
8 from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient
9 from IPython.kernel.inprocess.manager import InProcessKernelManager
9 from IPython.kernel.inprocess.manager import InProcessKernelManager
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Test case
12 # Test case
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 class InProcessKernelManagerTestCase(unittest.TestCase):
15 class InProcessKernelManagerTestCase(unittest.TestCase):
16
16
17 def test_interface(self):
17 def test_interface(self):
18 """ Does the in-process kernel manager implement the basic KM interface?
18 """ Does the in-process kernel manager implement the basic KM interface?
19 """
19 """
20 km = InProcessKernelManager()
20 km = InProcessKernelManager()
21 self.assert_(not km.has_kernel)
21 self.assert_(not km.has_kernel)
22
22
23 km.start_kernel()
23 km.start_kernel()
24 self.assert_(km.has_kernel)
24 self.assert_(km.has_kernel)
25 self.assert_(km.kernel is not None)
25 self.assert_(km.kernel is not None)
26
26
27 kc = BlockingInProcessKernelClient(kernel=km.kernel)
27 kc = BlockingInProcessKernelClient(kernel=km.kernel)
28 self.assert_(not kc.channels_running)
28 self.assert_(not kc.channels_running)
29
29
30 kc.start_channels()
30 kc.start_channels()
31 self.assert_(kc.channels_running)
31 self.assert_(kc.channels_running)
32
32
33 old_kernel = km.kernel
33 old_kernel = km.kernel
34 km.restart_kernel()
34 km.restart_kernel()
35 self.assertIsNotNone(km.kernel)
35 self.assertIsNotNone(km.kernel)
36 self.assertNotEquals(km.kernel, old_kernel)
36 self.assertNotEquals(km.kernel, old_kernel)
37
37
38 km.shutdown_kernel()
38 km.shutdown_kernel()
39 self.assert_(not km.has_kernel)
39 self.assert_(not km.has_kernel)
40
40
41 self.assertRaises(NotImplementedError, km.interrupt_kernel)
41 self.assertRaises(NotImplementedError, km.interrupt_kernel)
42 self.assertRaises(NotImplementedError, km.signal_kernel, 9)
42 self.assertRaises(NotImplementedError, km.signal_kernel, 9)
43
43
44 kc.stop_channels()
44 kc.stop_channels()
45 self.assert_(not kc.channels_running)
45 self.assert_(not kc.channels_running)
46
46
47 def test_execute(self):
47 def test_execute(self):
48 """ Does executing code in an in-process kernel work?
48 """ Does executing code in an in-process kernel work?
49 """
49 """
50 km = InProcessKernelManager()
50 km = InProcessKernelManager()
51 km.start_kernel()
51 km.start_kernel()
52 kc = BlockingInProcessKernelClient(kernel=km.kernel)
52 kc = BlockingInProcessKernelClient(kernel=km.kernel)
53 kc.start_channels()
53 kc.start_channels()
54 kc.wait_for_ready()
54 kc.execute('foo = 1')
55 kc.execute('foo = 1')
55 self.assertEquals(km.kernel.shell.user_ns['foo'], 1)
56 self.assertEquals(km.kernel.shell.user_ns['foo'], 1)
56
57
57 def test_complete(self):
58 def test_complete(self):
58 """ Does requesting completion from an in-process kernel work?
59 """ Does requesting completion from an in-process kernel work?
59 """
60 """
60 km = InProcessKernelManager()
61 km = InProcessKernelManager()
61 km.start_kernel()
62 km.start_kernel()
62 kc = BlockingInProcessKernelClient(kernel=km.kernel)
63 kc = BlockingInProcessKernelClient(kernel=km.kernel)
63 kc.start_channels()
64 kc.start_channels()
65 kc.wait_for_ready()
64 km.kernel.shell.push({'my_bar': 0, 'my_baz': 1})
66 km.kernel.shell.push({'my_bar': 0, 'my_baz': 1})
65 kc.complete('my_ba', 5)
67 kc.complete('my_ba', 5)
66 msg = kc.get_shell_msg()
68 msg = kc.get_shell_msg()
67 self.assertEqual(msg['header']['msg_type'], 'complete_reply')
69 self.assertEqual(msg['header']['msg_type'], 'complete_reply')
68 self.assertEqual(sorted(msg['content']['matches']),
70 self.assertEqual(sorted(msg['content']['matches']),
69 ['my_bar', 'my_baz'])
71 ['my_bar', 'my_baz'])
70
72
71 def test_inspect(self):
73 def test_inspect(self):
72 """ Does requesting object information from an in-process kernel work?
74 """ Does requesting object information from an in-process kernel work?
73 """
75 """
74 km = InProcessKernelManager()
76 km = InProcessKernelManager()
75 km.start_kernel()
77 km.start_kernel()
76 kc = BlockingInProcessKernelClient(kernel=km.kernel)
78 kc = BlockingInProcessKernelClient(kernel=km.kernel)
77 kc.start_channels()
79 kc.start_channels()
80 kc.wait_for_ready()
78 km.kernel.shell.user_ns['foo'] = 1
81 km.kernel.shell.user_ns['foo'] = 1
79 kc.inspect('foo')
82 kc.inspect('foo')
80 msg = kc.get_shell_msg()
83 msg = kc.get_shell_msg()
81 self.assertEqual(msg['header']['msg_type'], 'inspect_reply')
84 self.assertEqual(msg['header']['msg_type'], 'inspect_reply')
82 content = msg['content']
85 content = msg['content']
83 assert content['found']
86 assert content['found']
84 text = content['data']['text/plain']
87 text = content['data']['text/plain']
85 self.assertIn('int', text)
88 self.assertIn('int', text)
86
89
87 def test_history(self):
90 def test_history(self):
88 """ Does requesting history from an in-process kernel work?
91 """ Does requesting history from an in-process kernel work?
89 """
92 """
90 km = InProcessKernelManager()
93 km = InProcessKernelManager()
91 km.start_kernel()
94 km.start_kernel()
92 kc = BlockingInProcessKernelClient(kernel=km.kernel)
95 kc = BlockingInProcessKernelClient(kernel=km.kernel)
93 kc.start_channels()
96 kc.start_channels()
97 kc.wait_for_ready()
94 kc.execute('%who')
98 kc.execute('%who')
95 kc.history(hist_access_type='tail', n=1)
99 kc.history(hist_access_type='tail', n=1)
96 msg = kc.shell_channel.get_msgs()[-1]
100 msg = kc.shell_channel.get_msgs()[-1]
97 self.assertEquals(msg['header']['msg_type'], 'history_reply')
101 self.assertEquals(msg['header']['msg_type'], 'history_reply')
98 history = msg['content']['history']
102 history = msg['content']['history']
99 self.assertEquals(len(history), 1)
103 self.assertEquals(len(history), 1)
100 self.assertEquals(history[0][2], '%who')
104 self.assertEquals(history[0][2], '%who')
101
105
102
106
103 if __name__ == '__main__':
107 if __name__ == '__main__':
104 unittest.main()
108 unittest.main()
@@ -1,451 +1,442 b''
1 """Base class to manage a running kernel"""
1 """Base class to manage a running kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 from contextlib import contextmanager
8 from contextlib import contextmanager
9 import os
9 import os
10 import re
10 import re
11 import signal
11 import signal
12 import sys
12 import sys
13 import time
13 import time
14 import warnings
14 import warnings
15 try:
15 try:
16 from queue import Empty # Py 3
16 from queue import Empty # Py 3
17 except ImportError:
17 except ImportError:
18 from Queue import Empty # Py 2
18 from Queue import Empty # Py 2
19
19
20 import zmq
20 import zmq
21
21
22 from IPython.utils.importstring import import_item
22 from IPython.utils.importstring import import_item
23 from IPython.utils.localinterfaces import is_local_ip, local_ips
23 from IPython.utils.localinterfaces import is_local_ip, local_ips
24 from IPython.utils.path import get_ipython_dir
24 from IPython.utils.path import get_ipython_dir
25 from IPython.utils.traitlets import (
25 from IPython.utils.traitlets import (
26 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
26 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
27 )
27 )
28 from IPython.kernel import (
28 from IPython.kernel import (
29 launch_kernel,
29 launch_kernel,
30 kernelspec,
30 kernelspec,
31 )
31 )
32 from .connect import ConnectionFileMixin
32 from .connect import ConnectionFileMixin
33 from .zmq.session import Session
33 from .zmq.session import Session
34 from .managerabc import (
34 from .managerabc import (
35 KernelManagerABC
35 KernelManagerABC
36 )
36 )
37
37
38
38
39 class KernelManager(ConnectionFileMixin):
39 class KernelManager(ConnectionFileMixin):
40 """Manages a single kernel in a subprocess on this host.
40 """Manages a single kernel in a subprocess on this host.
41
41
42 This version starts kernels with Popen.
42 This version starts kernels with Popen.
43 """
43 """
44
44
45 # The PyZMQ Context to use for communication with the kernel.
45 # The PyZMQ Context to use for communication with the kernel.
46 context = Instance(zmq.Context)
46 context = Instance(zmq.Context)
47 def _context_default(self):
47 def _context_default(self):
48 return zmq.Context.instance()
48 return zmq.Context.instance()
49
49
50 # the class to create with our `client` method
50 # the class to create with our `client` method
51 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
51 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
52 client_factory = Type()
52 client_factory = Type()
53 def _client_class_changed(self, name, old, new):
53 def _client_class_changed(self, name, old, new):
54 self.client_factory = import_item(str(new))
54 self.client_factory = import_item(str(new))
55
55
56 # The kernel process with which the KernelManager is communicating.
56 # The kernel process with which the KernelManager is communicating.
57 # generally a Popen instance
57 # generally a Popen instance
58 kernel = Any()
58 kernel = Any()
59
59
60 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
60 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
61
61
62 def _kernel_spec_manager_default(self):
62 def _kernel_spec_manager_default(self):
63 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
63 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
64
64
65 kernel_name = Unicode(kernelspec.NATIVE_KERNEL_NAME)
65 kernel_name = Unicode(kernelspec.NATIVE_KERNEL_NAME)
66
66
67 kernel_spec = Instance(kernelspec.KernelSpec)
67 kernel_spec = Instance(kernelspec.KernelSpec)
68
68
69 def _kernel_spec_default(self):
69 def _kernel_spec_default(self):
70 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
70 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
71
71
72 def _kernel_name_changed(self, name, old, new):
72 def _kernel_name_changed(self, name, old, new):
73 if new == 'python':
73 if new == 'python':
74 self.kernel_name = kernelspec.NATIVE_KERNEL_NAME
74 self.kernel_name = kernelspec.NATIVE_KERNEL_NAME
75 # This triggered another run of this function, so we can exit now
75 # This triggered another run of this function, so we can exit now
76 return
76 return
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
79
79
80 kernel_cmd = List(Unicode, config=True,
80 kernel_cmd = List(Unicode, config=True,
81 help="""DEPRECATED: Use kernel_name instead.
81 help="""DEPRECATED: Use kernel_name instead.
82
82
83 The Popen Command to launch the kernel.
83 The Popen Command to launch the kernel.
84 Override this if you have a custom kernel.
84 Override this if you have a custom kernel.
85 If kernel_cmd is specified in a configuration file,
85 If kernel_cmd is specified in a configuration file,
86 IPython does not pass any arguments to the kernel,
86 IPython does not pass any arguments to the kernel,
87 because it cannot make any assumptions about the
87 because it cannot make any assumptions about the
88 arguments that the kernel understands. In particular,
88 arguments that the kernel understands. In particular,
89 this means that the kernel does not receive the
89 this means that the kernel does not receive the
90 option --debug if it given on the IPython command line.
90 option --debug if it given on the IPython command line.
91 """
91 """
92 )
92 )
93
93
94 def _kernel_cmd_changed(self, name, old, new):
94 def _kernel_cmd_changed(self, name, old, new):
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
96 "start different kernels.")
96 "start different kernels.")
97 self.ipython_kernel = False
97 self.ipython_kernel = False
98
98
99 ipython_kernel = Bool(True)
99 ipython_kernel = Bool(True)
100
100
101 ipython_dir = Unicode()
101 ipython_dir = Unicode()
102 def _ipython_dir_default(self):
102 def _ipython_dir_default(self):
103 return get_ipython_dir()
103 return get_ipython_dir()
104
104
105 # Protected traits
105 # Protected traits
106 _launch_args = Any()
106 _launch_args = Any()
107 _control_socket = Any()
107 _control_socket = Any()
108
108
109 _restarter = Any()
109 _restarter = Any()
110
110
111 autorestart = Bool(False, config=True,
111 autorestart = Bool(False, config=True,
112 help="""Should we autorestart the kernel if it dies."""
112 help="""Should we autorestart the kernel if it dies."""
113 )
113 )
114
114
115 def __del__(self):
115 def __del__(self):
116 self._close_control_socket()
116 self._close_control_socket()
117 self.cleanup_connection_file()
117 self.cleanup_connection_file()
118
118
119 #--------------------------------------------------------------------------
119 #--------------------------------------------------------------------------
120 # Kernel restarter
120 # Kernel restarter
121 #--------------------------------------------------------------------------
121 #--------------------------------------------------------------------------
122
122
123 def start_restarter(self):
123 def start_restarter(self):
124 pass
124 pass
125
125
126 def stop_restarter(self):
126 def stop_restarter(self):
127 pass
127 pass
128
128
129 def add_restart_callback(self, callback, event='restart'):
129 def add_restart_callback(self, callback, event='restart'):
130 """register a callback to be called when a kernel is restarted"""
130 """register a callback to be called when a kernel is restarted"""
131 if self._restarter is None:
131 if self._restarter is None:
132 return
132 return
133 self._restarter.add_callback(callback, event)
133 self._restarter.add_callback(callback, event)
134
134
135 def remove_restart_callback(self, callback, event='restart'):
135 def remove_restart_callback(self, callback, event='restart'):
136 """unregister a callback to be called when a kernel is restarted"""
136 """unregister a callback to be called when a kernel is restarted"""
137 if self._restarter is None:
137 if self._restarter is None:
138 return
138 return
139 self._restarter.remove_callback(callback, event)
139 self._restarter.remove_callback(callback, event)
140
140
141 #--------------------------------------------------------------------------
141 #--------------------------------------------------------------------------
142 # create a Client connected to our Kernel
142 # create a Client connected to our Kernel
143 #--------------------------------------------------------------------------
143 #--------------------------------------------------------------------------
144
144
145 def client(self, **kwargs):
145 def client(self, **kwargs):
146 """Create a client configured to connect to our kernel"""
146 """Create a client configured to connect to our kernel"""
147 if self.client_factory is None:
147 if self.client_factory is None:
148 self.client_factory = import_item(self.client_class)
148 self.client_factory = import_item(self.client_class)
149
149
150 kw = {}
150 kw = {}
151 kw.update(self.get_connection_info())
151 kw.update(self.get_connection_info())
152 kw.update(dict(
152 kw.update(dict(
153 connection_file=self.connection_file,
153 connection_file=self.connection_file,
154 session=self.session,
154 session=self.session,
155 parent=self,
155 parent=self,
156 ))
156 ))
157
157
158 # add kwargs last, for manual overrides
158 # add kwargs last, for manual overrides
159 kw.update(kwargs)
159 kw.update(kwargs)
160 return self.client_factory(**kw)
160 return self.client_factory(**kw)
161
161
162 #--------------------------------------------------------------------------
162 #--------------------------------------------------------------------------
163 # Kernel management
163 # Kernel management
164 #--------------------------------------------------------------------------
164 #--------------------------------------------------------------------------
165
165
166 def format_kernel_cmd(self, extra_arguments=None):
166 def format_kernel_cmd(self, extra_arguments=None):
167 """replace templated args (e.g. {connection_file})"""
167 """replace templated args (e.g. {connection_file})"""
168 extra_arguments = extra_arguments or []
168 extra_arguments = extra_arguments or []
169 if self.kernel_cmd:
169 if self.kernel_cmd:
170 cmd = self.kernel_cmd + extra_arguments
170 cmd = self.kernel_cmd + extra_arguments
171 else:
171 else:
172 cmd = self.kernel_spec.argv + extra_arguments
172 cmd = self.kernel_spec.argv + extra_arguments
173
173
174 ns = dict(connection_file=self.connection_file)
174 ns = dict(connection_file=self.connection_file)
175 ns.update(self._launch_args)
175 ns.update(self._launch_args)
176
176
177 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
177 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
178 def from_ns(match):
178 def from_ns(match):
179 """Get the key out of ns if it's there, otherwise no change."""
179 """Get the key out of ns if it's there, otherwise no change."""
180 return ns.get(match.group(1), match.group())
180 return ns.get(match.group(1), match.group())
181
181
182 return [ pat.sub(from_ns, arg) for arg in cmd ]
182 return [ pat.sub(from_ns, arg) for arg in cmd ]
183
183
184 def _launch_kernel(self, kernel_cmd, **kw):
184 def _launch_kernel(self, kernel_cmd, **kw):
185 """actually launch the kernel
185 """actually launch the kernel
186
186
187 override in a subclass to launch kernel subprocesses differently
187 override in a subclass to launch kernel subprocesses differently
188 """
188 """
189 return launch_kernel(kernel_cmd, **kw)
189 return launch_kernel(kernel_cmd, **kw)
190
190
191 # Control socket used for polite kernel shutdown
191 # Control socket used for polite kernel shutdown
192
192
193 def _connect_control_socket(self):
193 def _connect_control_socket(self):
194 if self._control_socket is None:
194 if self._control_socket is None:
195 self._control_socket = self.connect_control()
195 self._control_socket = self.connect_control()
196 self._control_socket.linger = 100
196 self._control_socket.linger = 100
197
197
198 def _close_control_socket(self):
198 def _close_control_socket(self):
199 if self._control_socket is None:
199 if self._control_socket is None:
200 return
200 return
201 self._control_socket.close()
201 self._control_socket.close()
202 self._control_socket = None
202 self._control_socket = None
203
203
204 def start_kernel(self, **kw):
204 def start_kernel(self, **kw):
205 """Starts a kernel on this host in a separate process.
205 """Starts a kernel on this host in a separate process.
206
206
207 If random ports (port=0) are being used, this method must be called
207 If random ports (port=0) are being used, this method must be called
208 before the channels are created.
208 before the channels are created.
209
209
210 Parameters
210 Parameters
211 ----------
211 ----------
212 **kw : optional
212 **kw : optional
213 keyword arguments that are passed down to build the kernel_cmd
213 keyword arguments that are passed down to build the kernel_cmd
214 and launching the kernel (e.g. Popen kwargs).
214 and launching the kernel (e.g. Popen kwargs).
215 """
215 """
216 if self.transport == 'tcp' and not is_local_ip(self.ip):
216 if self.transport == 'tcp' and not is_local_ip(self.ip):
217 raise RuntimeError("Can only launch a kernel on a local interface. "
217 raise RuntimeError("Can only launch a kernel on a local interface. "
218 "Make sure that the '*_address' attributes are "
218 "Make sure that the '*_address' attributes are "
219 "configured properly. "
219 "configured properly. "
220 "Currently valid addresses are: %s" % local_ips()
220 "Currently valid addresses are: %s" % local_ips()
221 )
221 )
222
222
223 # write connection file / get default ports
223 # write connection file / get default ports
224 self.write_connection_file()
224 self.write_connection_file()
225
225
226 # save kwargs for use in restart
226 # save kwargs for use in restart
227 self._launch_args = kw.copy()
227 self._launch_args = kw.copy()
228 # build the Popen cmd
228 # build the Popen cmd
229 extra_arguments = kw.pop('extra_arguments', [])
229 extra_arguments = kw.pop('extra_arguments', [])
230 kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
230 kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
231 if self.kernel_cmd:
231 if self.kernel_cmd:
232 # If kernel_cmd has been set manually, don't refer to a kernel spec
232 # If kernel_cmd has been set manually, don't refer to a kernel spec
233 env = os.environ
233 env = os.environ
234 else:
234 else:
235 # Environment variables from kernel spec are added to os.environ
235 # Environment variables from kernel spec are added to os.environ
236 env = os.environ.copy()
236 env = os.environ.copy()
237 env.update(self.kernel_spec.env or {})
237 env.update(self.kernel_spec.env or {})
238 # launch the kernel subprocess
238 # launch the kernel subprocess
239 self.kernel = self._launch_kernel(kernel_cmd, env=env,
239 self.kernel = self._launch_kernel(kernel_cmd, env=env,
240 **kw)
240 **kw)
241 self.start_restarter()
241 self.start_restarter()
242 self._connect_control_socket()
242 self._connect_control_socket()
243
243
244 def request_shutdown(self, restart=False):
244 def request_shutdown(self, restart=False):
245 """Send a shutdown request via control channel
245 """Send a shutdown request via control channel
246
246
247 On Windows, this just kills kernels instead, because the shutdown
247 On Windows, this just kills kernels instead, because the shutdown
248 messages don't work.
248 messages don't work.
249 """
249 """
250 content = dict(restart=restart)
250 content = dict(restart=restart)
251 msg = self.session.msg("shutdown_request", content=content)
251 msg = self.session.msg("shutdown_request", content=content)
252 self.session.send(self._control_socket, msg)
252 self.session.send(self._control_socket, msg)
253
253
254 def finish_shutdown(self, waittime=1, pollinterval=0.1):
254 def finish_shutdown(self, waittime=1, pollinterval=0.1):
255 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
255 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
256
256
257 This does not send shutdown requests - use :meth:`request_shutdown`
257 This does not send shutdown requests - use :meth:`request_shutdown`
258 first.
258 first.
259 """
259 """
260 for i in range(int(waittime/pollinterval)):
260 for i in range(int(waittime/pollinterval)):
261 if self.is_alive():
261 if self.is_alive():
262 time.sleep(pollinterval)
262 time.sleep(pollinterval)
263 else:
263 else:
264 break
264 break
265 else:
265 else:
266 # OK, we've waited long enough.
266 # OK, we've waited long enough.
267 if self.has_kernel:
267 if self.has_kernel:
268 self._kill_kernel()
268 self._kill_kernel()
269
269
270 def cleanup(self, connection_file=True):
270 def cleanup(self, connection_file=True):
271 """Clean up resources when the kernel is shut down"""
271 """Clean up resources when the kernel is shut down"""
272 if connection_file:
272 if connection_file:
273 self.cleanup_connection_file()
273 self.cleanup_connection_file()
274
274
275 self.cleanup_ipc_files()
275 self.cleanup_ipc_files()
276 self._close_control_socket()
276 self._close_control_socket()
277
277
278 def shutdown_kernel(self, now=False, restart=False):
278 def shutdown_kernel(self, now=False, restart=False):
279 """Attempts to the stop the kernel process cleanly.
279 """Attempts to the stop the kernel process cleanly.
280
280
281 This attempts to shutdown the kernels cleanly by:
281 This attempts to shutdown the kernels cleanly by:
282
282
283 1. Sending it a shutdown message over the shell channel.
283 1. Sending it a shutdown message over the shell channel.
284 2. If that fails, the kernel is shutdown forcibly by sending it
284 2. If that fails, the kernel is shutdown forcibly by sending it
285 a signal.
285 a signal.
286
286
287 Parameters
287 Parameters
288 ----------
288 ----------
289 now : bool
289 now : bool
290 Should the kernel be forcible killed *now*. This skips the
290 Should the kernel be forcible killed *now*. This skips the
291 first, nice shutdown attempt.
291 first, nice shutdown attempt.
292 restart: bool
292 restart: bool
293 Will this kernel be restarted after it is shutdown. When this
293 Will this kernel be restarted after it is shutdown. When this
294 is True, connection files will not be cleaned up.
294 is True, connection files will not be cleaned up.
295 """
295 """
296 # Stop monitoring for restarting while we shutdown.
296 # Stop monitoring for restarting while we shutdown.
297 self.stop_restarter()
297 self.stop_restarter()
298
298
299 if now:
299 if now:
300 self._kill_kernel()
300 self._kill_kernel()
301 else:
301 else:
302 self.request_shutdown(restart=restart)
302 self.request_shutdown(restart=restart)
303 # Don't send any additional kernel kill messages immediately, to give
303 # Don't send any additional kernel kill messages immediately, to give
304 # the kernel a chance to properly execute shutdown actions. Wait for at
304 # the kernel a chance to properly execute shutdown actions. Wait for at
305 # most 1s, checking every 0.1s.
305 # most 1s, checking every 0.1s.
306 self.finish_shutdown()
306 self.finish_shutdown()
307
307
308 self.cleanup(connection_file=not restart)
308 self.cleanup(connection_file=not restart)
309
309
310 def restart_kernel(self, now=False, **kw):
310 def restart_kernel(self, now=False, **kw):
311 """Restarts a kernel with the arguments that were used to launch it.
311 """Restarts a kernel with the arguments that were used to launch it.
312
312
313 If the old kernel was launched with random ports, the same ports will be
313 If the old kernel was launched with random ports, the same ports will be
314 used for the new kernel. The same connection file is used again.
314 used for the new kernel. The same connection file is used again.
315
315
316 Parameters
316 Parameters
317 ----------
317 ----------
318 now : bool, optional
318 now : bool, optional
319 If True, the kernel is forcefully restarted *immediately*, without
319 If True, the kernel is forcefully restarted *immediately*, without
320 having a chance to do any cleanup action. Otherwise the kernel is
320 having a chance to do any cleanup action. Otherwise the kernel is
321 given 1s to clean up before a forceful restart is issued.
321 given 1s to clean up before a forceful restart is issued.
322
322
323 In all cases the kernel is restarted, the only difference is whether
323 In all cases the kernel is restarted, the only difference is whether
324 it is given a chance to perform a clean shutdown or not.
324 it is given a chance to perform a clean shutdown or not.
325
325
326 **kw : optional
326 **kw : optional
327 Any options specified here will overwrite those used to launch the
327 Any options specified here will overwrite those used to launch the
328 kernel.
328 kernel.
329 """
329 """
330 if self._launch_args is None:
330 if self._launch_args is None:
331 raise RuntimeError("Cannot restart the kernel. "
331 raise RuntimeError("Cannot restart the kernel. "
332 "No previous call to 'start_kernel'.")
332 "No previous call to 'start_kernel'.")
333 else:
333 else:
334 # Stop currently running kernel.
334 # Stop currently running kernel.
335 self.shutdown_kernel(now=now, restart=True)
335 self.shutdown_kernel(now=now, restart=True)
336
336
337 # Start new kernel.
337 # Start new kernel.
338 self._launch_args.update(kw)
338 self._launch_args.update(kw)
339 self.start_kernel(**self._launch_args)
339 self.start_kernel(**self._launch_args)
340
340
341 @property
341 @property
342 def has_kernel(self):
342 def has_kernel(self):
343 """Has a kernel been started that we are managing."""
343 """Has a kernel been started that we are managing."""
344 return self.kernel is not None
344 return self.kernel is not None
345
345
346 def _kill_kernel(self):
346 def _kill_kernel(self):
347 """Kill the running kernel.
347 """Kill the running kernel.
348
348
349 This is a private method, callers should use shutdown_kernel(now=True).
349 This is a private method, callers should use shutdown_kernel(now=True).
350 """
350 """
351 if self.has_kernel:
351 if self.has_kernel:
352
352
353 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
353 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
354 # TerminateProcess() on Win32).
354 # TerminateProcess() on Win32).
355 try:
355 try:
356 self.kernel.kill()
356 self.kernel.kill()
357 except OSError as e:
357 except OSError as e:
358 # In Windows, we will get an Access Denied error if the process
358 # In Windows, we will get an Access Denied error if the process
359 # has already terminated. Ignore it.
359 # has already terminated. Ignore it.
360 if sys.platform == 'win32':
360 if sys.platform == 'win32':
361 if e.winerror != 5:
361 if e.winerror != 5:
362 raise
362 raise
363 # On Unix, we may get an ESRCH error if the process has already
363 # On Unix, we may get an ESRCH error if the process has already
364 # terminated. Ignore it.
364 # terminated. Ignore it.
365 else:
365 else:
366 from errno import ESRCH
366 from errno import ESRCH
367 if e.errno != ESRCH:
367 if e.errno != ESRCH:
368 raise
368 raise
369
369
370 # Block until the kernel terminates.
370 # Block until the kernel terminates.
371 self.kernel.wait()
371 self.kernel.wait()
372 self.kernel = None
372 self.kernel = None
373 else:
373 else:
374 raise RuntimeError("Cannot kill kernel. No kernel is running!")
374 raise RuntimeError("Cannot kill kernel. No kernel is running!")
375
375
376 def interrupt_kernel(self):
376 def interrupt_kernel(self):
377 """Interrupts the kernel by sending it a signal.
377 """Interrupts the kernel by sending it a signal.
378
378
379 Unlike ``signal_kernel``, this operation is well supported on all
379 Unlike ``signal_kernel``, this operation is well supported on all
380 platforms.
380 platforms.
381 """
381 """
382 if self.has_kernel:
382 if self.has_kernel:
383 if sys.platform == 'win32':
383 if sys.platform == 'win32':
384 from .zmq.parentpoller import ParentPollerWindows as Poller
384 from .zmq.parentpoller import ParentPollerWindows as Poller
385 Poller.send_interrupt(self.kernel.win32_interrupt_event)
385 Poller.send_interrupt(self.kernel.win32_interrupt_event)
386 else:
386 else:
387 self.kernel.send_signal(signal.SIGINT)
387 self.kernel.send_signal(signal.SIGINT)
388 else:
388 else:
389 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
389 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
390
390
391 def signal_kernel(self, signum):
391 def signal_kernel(self, signum):
392 """Sends a signal to the kernel.
392 """Sends a signal to the kernel.
393
393
394 Note that since only SIGTERM is supported on Windows, this function is
394 Note that since only SIGTERM is supported on Windows, this function is
395 only useful on Unix systems.
395 only useful on Unix systems.
396 """
396 """
397 if self.has_kernel:
397 if self.has_kernel:
398 self.kernel.send_signal(signum)
398 self.kernel.send_signal(signum)
399 else:
399 else:
400 raise RuntimeError("Cannot signal kernel. No kernel is running!")
400 raise RuntimeError("Cannot signal kernel. No kernel is running!")
401
401
402 def is_alive(self):
402 def is_alive(self):
403 """Is the kernel process still running?"""
403 """Is the kernel process still running?"""
404 if self.has_kernel:
404 if self.has_kernel:
405 if self.kernel.poll() is None:
405 if self.kernel.poll() is None:
406 return True
406 return True
407 else:
407 else:
408 return False
408 return False
409 else:
409 else:
410 # we don't have a kernel
410 # we don't have a kernel
411 return False
411 return False
412
412
413
413
414 KernelManagerABC.register(KernelManager)
414 KernelManagerABC.register(KernelManager)
415
415
416
416
417 def start_new_kernel(startup_timeout=60, kernel_name='python', **kwargs):
417 def start_new_kernel(startup_timeout=60, kernel_name='python', **kwargs):
418 """Start a new kernel, and return its Manager and Client"""
418 """Start a new kernel, and return its Manager and Client"""
419 km = KernelManager(kernel_name=kernel_name)
419 km = KernelManager(kernel_name=kernel_name)
420 km.start_kernel(**kwargs)
420 km.start_kernel(**kwargs)
421 kc = km.client()
421 kc = km.client()
422 kc.start_channels()
422 kc.start_channels()
423 kc.wait_for_ready()
423
424
424 kc.kernel_info()
425 kc.get_shell_msg(block=True, timeout=startup_timeout)
426
427 # Flush channels
428 for channel in (kc.shell_channel, kc.iopub_channel):
429 while True:
430 try:
431 channel.get_msg(block=True, timeout=0.1)
432 except Empty:
433 break
434 return km, kc
425 return km, kc
435
426
436 @contextmanager
427 @contextmanager
437 def run_kernel(**kwargs):
428 def run_kernel(**kwargs):
438 """Context manager to create a kernel in a subprocess.
429 """Context manager to create a kernel in a subprocess.
439
430
440 The kernel is shut down when the context exits.
431 The kernel is shut down when the context exits.
441
432
442 Returns
433 Returns
443 -------
434 -------
444 kernel_client: connected KernelClient instance
435 kernel_client: connected KernelClient instance
445 """
436 """
446 km, kc = start_new_kernel(**kwargs)
437 km, kc = start_new_kernel(**kwargs)
447 try:
438 try:
448 yield kc
439 yield kc
449 finally:
440 finally:
450 kc.stop_channels()
441 kc.stop_channels()
451 km.shutdown_kernel(now=True)
442 km.shutdown_kernel(now=True)
@@ -1,198 +1,199 b''
1 """test IPython.embed_kernel()"""
1 """test IPython.embed_kernel()"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2012 The IPython Development Team
4 # Copyright (C) 2012 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 import os
14 import os
15 import shutil
15 import shutil
16 import sys
16 import sys
17 import tempfile
17 import tempfile
18 import time
18 import time
19
19
20 from contextlib import contextmanager
20 from contextlib import contextmanager
21 from subprocess import Popen, PIPE
21 from subprocess import Popen, PIPE
22
22
23 import nose.tools as nt
23 import nose.tools as nt
24
24
25 from IPython.kernel import BlockingKernelClient
25 from IPython.kernel import BlockingKernelClient
26 from IPython.utils import path, py3compat
26 from IPython.utils import path, py3compat
27 from IPython.utils.py3compat import unicode_type
27 from IPython.utils.py3compat import unicode_type
28
28
29 #-------------------------------------------------------------------------------
29 #-------------------------------------------------------------------------------
30 # Tests
30 # Tests
31 #-------------------------------------------------------------------------------
31 #-------------------------------------------------------------------------------
32
32
33 SETUP_TIMEOUT = 60
33 SETUP_TIMEOUT = 60
34 TIMEOUT = 15
34 TIMEOUT = 15
35
35
36 def setup():
36 def setup():
37 """setup temporary IPYTHONDIR for tests"""
37 """setup temporary IPYTHONDIR for tests"""
38 global IPYTHONDIR
38 global IPYTHONDIR
39 global env
39 global env
40 global save_get_ipython_dir
40 global save_get_ipython_dir
41
41
42 IPYTHONDIR = tempfile.mkdtemp()
42 IPYTHONDIR = tempfile.mkdtemp()
43
43
44 env = os.environ.copy()
44 env = os.environ.copy()
45 env["IPYTHONDIR"] = IPYTHONDIR
45 env["IPYTHONDIR"] = IPYTHONDIR
46
46
47 save_get_ipython_dir = path.get_ipython_dir
47 save_get_ipython_dir = path.get_ipython_dir
48 path.get_ipython_dir = lambda : IPYTHONDIR
48 path.get_ipython_dir = lambda : IPYTHONDIR
49
49
50
50
51 def teardown():
51 def teardown():
52 path.get_ipython_dir = save_get_ipython_dir
52 path.get_ipython_dir = save_get_ipython_dir
53
53
54 try:
54 try:
55 shutil.rmtree(IPYTHONDIR)
55 shutil.rmtree(IPYTHONDIR)
56 except (OSError, IOError):
56 except (OSError, IOError):
57 # no such file
57 # no such file
58 pass
58 pass
59
59
60
60
61 @contextmanager
61 @contextmanager
62 def setup_kernel(cmd):
62 def setup_kernel(cmd):
63 """start an embedded kernel in a subprocess, and wait for it to be ready
63 """start an embedded kernel in a subprocess, and wait for it to be ready
64
64
65 Returns
65 Returns
66 -------
66 -------
67 kernel_manager: connected KernelManager instance
67 kernel_manager: connected KernelManager instance
68 """
68 """
69 kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE, env=env)
69 kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE, env=env)
70 connection_file = os.path.join(IPYTHONDIR,
70 connection_file = os.path.join(IPYTHONDIR,
71 'profile_default',
71 'profile_default',
72 'security',
72 'security',
73 'kernel-%i.json' % kernel.pid
73 'kernel-%i.json' % kernel.pid
74 )
74 )
75 # wait for connection file to exist, timeout after 5s
75 # wait for connection file to exist, timeout after 5s
76 tic = time.time()
76 tic = time.time()
77 while not os.path.exists(connection_file) \
77 while not os.path.exists(connection_file) \
78 and kernel.poll() is None \
78 and kernel.poll() is None \
79 and time.time() < tic + SETUP_TIMEOUT:
79 and time.time() < tic + SETUP_TIMEOUT:
80 time.sleep(0.1)
80 time.sleep(0.1)
81
81
82 if kernel.poll() is not None:
82 if kernel.poll() is not None:
83 o,e = kernel.communicate()
83 o,e = kernel.communicate()
84 e = py3compat.cast_unicode(e)
84 e = py3compat.cast_unicode(e)
85 raise IOError("Kernel failed to start:\n%s" % e)
85 raise IOError("Kernel failed to start:\n%s" % e)
86
86
87 if not os.path.exists(connection_file):
87 if not os.path.exists(connection_file):
88 if kernel.poll() is None:
88 if kernel.poll() is None:
89 kernel.terminate()
89 kernel.terminate()
90 raise IOError("Connection file %r never arrived" % connection_file)
90 raise IOError("Connection file %r never arrived" % connection_file)
91
91
92 client = BlockingKernelClient(connection_file=connection_file)
92 client = BlockingKernelClient(connection_file=connection_file)
93 client.load_connection_file()
93 client.load_connection_file()
94 client.start_channels()
94 client.start_channels()
95 client.wait_for_ready()
95
96
96 try:
97 try:
97 yield client
98 yield client
98 finally:
99 finally:
99 client.stop_channels()
100 client.stop_channels()
100 kernel.terminate()
101 kernel.terminate()
101
102
102 def test_embed_kernel_basic():
103 def test_embed_kernel_basic():
103 """IPython.embed_kernel() is basically functional"""
104 """IPython.embed_kernel() is basically functional"""
104 cmd = '\n'.join([
105 cmd = '\n'.join([
105 'from IPython import embed_kernel',
106 'from IPython import embed_kernel',
106 'def go():',
107 'def go():',
107 ' a=5',
108 ' a=5',
108 ' b="hi there"',
109 ' b="hi there"',
109 ' embed_kernel()',
110 ' embed_kernel()',
110 'go()',
111 'go()',
111 '',
112 '',
112 ])
113 ])
113
114
114 with setup_kernel(cmd) as client:
115 with setup_kernel(cmd) as client:
115 # oinfo a (int)
116 # oinfo a (int)
116 msg_id = client.inspect('a')
117 msg_id = client.inspect('a')
117 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
118 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
118 content = msg['content']
119 content = msg['content']
119 nt.assert_true(content['found'])
120 nt.assert_true(content['found'])
120
121
121 msg_id = client.execute("c=a*2")
122 msg_id = client.execute("c=a*2")
122 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
123 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
123 content = msg['content']
124 content = msg['content']
124 nt.assert_equal(content['status'], u'ok')
125 nt.assert_equal(content['status'], u'ok')
125
126
126 # oinfo c (should be 10)
127 # oinfo c (should be 10)
127 msg_id = client.inspect('c')
128 msg_id = client.inspect('c')
128 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
129 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
129 content = msg['content']
130 content = msg['content']
130 nt.assert_true(content['found'])
131 nt.assert_true(content['found'])
131 text = content['data']['text/plain']
132 text = content['data']['text/plain']
132 nt.assert_in('10', text)
133 nt.assert_in('10', text)
133
134
134 def test_embed_kernel_namespace():
135 def test_embed_kernel_namespace():
135 """IPython.embed_kernel() inherits calling namespace"""
136 """IPython.embed_kernel() inherits calling namespace"""
136 cmd = '\n'.join([
137 cmd = '\n'.join([
137 'from IPython import embed_kernel',
138 'from IPython import embed_kernel',
138 'def go():',
139 'def go():',
139 ' a=5',
140 ' a=5',
140 ' b="hi there"',
141 ' b="hi there"',
141 ' embed_kernel()',
142 ' embed_kernel()',
142 'go()',
143 'go()',
143 '',
144 '',
144 ])
145 ])
145
146
146 with setup_kernel(cmd) as client:
147 with setup_kernel(cmd) as client:
147 # oinfo a (int)
148 # oinfo a (int)
148 msg_id = client.inspect('a')
149 msg_id = client.inspect('a')
149 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
150 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
150 content = msg['content']
151 content = msg['content']
151 nt.assert_true(content['found'])
152 nt.assert_true(content['found'])
152 text = content['data']['text/plain']
153 text = content['data']['text/plain']
153 nt.assert_in(u'5', text)
154 nt.assert_in(u'5', text)
154
155
155 # oinfo b (str)
156 # oinfo b (str)
156 msg_id = client.inspect('b')
157 msg_id = client.inspect('b')
157 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
158 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
158 content = msg['content']
159 content = msg['content']
159 nt.assert_true(content['found'])
160 nt.assert_true(content['found'])
160 text = content['data']['text/plain']
161 text = content['data']['text/plain']
161 nt.assert_in(u'hi there', text)
162 nt.assert_in(u'hi there', text)
162
163
163 # oinfo c (undefined)
164 # oinfo c (undefined)
164 msg_id = client.inspect('c')
165 msg_id = client.inspect('c')
165 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
166 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
166 content = msg['content']
167 content = msg['content']
167 nt.assert_false(content['found'])
168 nt.assert_false(content['found'])
168
169
169 def test_embed_kernel_reentrant():
170 def test_embed_kernel_reentrant():
170 """IPython.embed_kernel() can be called multiple times"""
171 """IPython.embed_kernel() can be called multiple times"""
171 cmd = '\n'.join([
172 cmd = '\n'.join([
172 'from IPython import embed_kernel',
173 'from IPython import embed_kernel',
173 'count = 0',
174 'count = 0',
174 'def go():',
175 'def go():',
175 ' global count',
176 ' global count',
176 ' embed_kernel()',
177 ' embed_kernel()',
177 ' count = count + 1',
178 ' count = count + 1',
178 '',
179 '',
179 'while True:'
180 'while True:'
180 ' go()',
181 ' go()',
181 '',
182 '',
182 ])
183 ])
183
184
184 with setup_kernel(cmd) as client:
185 with setup_kernel(cmd) as client:
185 for i in range(5):
186 for i in range(5):
186 msg_id = client.inspect('count')
187 msg_id = client.inspect('count')
187 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
188 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
188 content = msg['content']
189 content = msg['content']
189 nt.assert_true(content['found'])
190 nt.assert_true(content['found'])
190 text = content['data']['text/plain']
191 text = content['data']['text/plain']
191 nt.assert_in(unicode_type(i), text)
192 nt.assert_in(unicode_type(i), text)
192
193
193 # exit from embed_kernel
194 # exit from embed_kernel
194 client.execute("get_ipython().exit_now = True")
195 client.execute("get_ipython().exit_now = True")
195 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
196 msg = client.get_shell_msg(block=True, timeout=TIMEOUT)
196 time.sleep(0.2)
197 time.sleep(0.2)
197
198
198
199
@@ -1,111 +1,111 b''
1 """Module containing a preprocessor that removes the outputs from code cells"""
1 """Module containing a preprocessor that removes the outputs from code cells"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import os
6 import os
7
7
8 try:
8 try:
9 from queue import Empty # Py 3
9 from queue import Empty # Py 3
10 except ImportError:
10 except ImportError:
11 from Queue import Empty # Py 2
11 from Queue import Empty # Py 2
12
12
13 from IPython.utils.traitlets import List, Unicode
13 from IPython.utils.traitlets import List, Unicode
14
14
15 from IPython.nbformat.v4 import output_from_msg
15 from IPython.nbformat.v4 import output_from_msg
16 from .base import Preprocessor
16 from .base import Preprocessor
17 from IPython.utils.traitlets import Integer
17 from IPython.utils.traitlets import Integer
18
18
19
19
20 class ExecutePreprocessor(Preprocessor):
20 class ExecutePreprocessor(Preprocessor):
21 """
21 """
22 Executes all the cells in a notebook
22 Executes all the cells in a notebook
23 """
23 """
24
24
25 timeout = Integer(30, config=True,
25 timeout = Integer(30, config=True,
26 help="The time to wait (in seconds) for output from executions."
26 help="The time to wait (in seconds) for output from executions."
27 )
27 )
28
28
29 extra_arguments = List(Unicode)
29 extra_arguments = List(Unicode)
30
30
31 def preprocess(self, nb, resources):
31 def preprocess(self, nb, resources):
32 from IPython.kernel import run_kernel
32 from IPython.kernel import run_kernel
33 kernel_name = nb.metadata.get('kernelspec', {}).get('name', 'python')
33 kernel_name = nb.metadata.get('kernelspec', {}).get('name', 'python')
34 self.log.info("Executing notebook with kernel: %s" % kernel_name)
34 self.log.info("Executing notebook with kernel: %s" % kernel_name)
35 with run_kernel(kernel_name=kernel_name,
35 with run_kernel(kernel_name=kernel_name,
36 extra_arguments=self.extra_arguments,
36 extra_arguments=self.extra_arguments,
37 stderr=open(os.devnull, 'w')) as kc:
37 stderr=open(os.devnull, 'w')) as kc:
38 self.kc = kc
38 self.kc = kc
39 nb, resources = super(ExecutePreprocessor, self).preprocess(nb, resources)
39 nb, resources = super(ExecutePreprocessor, self).preprocess(nb, resources)
40 return nb, resources
40 return nb, resources
41
41
42 def preprocess_cell(self, cell, resources, cell_index):
42 def preprocess_cell(self, cell, resources, cell_index):
43 """
43 """
44 Apply a transformation on each code cell. See base.py for details.
44 Apply a transformation on each code cell. See base.py for details.
45 """
45 """
46 if cell.cell_type != 'code':
46 if cell.cell_type != 'code':
47 return cell, resources
47 return cell, resources
48 try:
48 try:
49 outputs = self.run_cell(self.kc.shell_channel, self.kc.iopub_channel, cell)
49 outputs = self.run_cell(cell)
50 except Exception as e:
50 except Exception as e:
51 self.log.error("failed to run cell: " + repr(e))
51 self.log.error("failed to run cell: " + repr(e))
52 self.log.error(str(cell.source))
52 self.log.error(str(cell.source))
53 raise
53 raise
54 cell.outputs = outputs
54 cell.outputs = outputs
55 return cell, resources
55 return cell, resources
56
56
57 def run_cell(self, shell, iopub, cell):
57 def run_cell(self, cell):
58 msg_id = shell.execute(cell.source)
58 msg_id = self.kc.execute(cell.source)
59 self.log.debug("Executing cell:\n%s", cell.source)
59 self.log.debug("Executing cell:\n%s", cell.source)
60 # wait for finish, with timeout
60 # wait for finish, with timeout
61 while True:
61 while True:
62 try:
62 try:
63 msg = shell.get_msg(timeout=self.timeout)
63 msg = self.kc.shell_channel.get_msg(timeout=self.timeout)
64 except Empty:
64 except Empty:
65 self.log.error("Timeout waiting for execute reply")
65 self.log.error("Timeout waiting for execute reply")
66 raise
66 raise
67 if msg['parent_header'].get('msg_id') == msg_id:
67 if msg['parent_header'].get('msg_id') == msg_id:
68 break
68 break
69 else:
69 else:
70 # not our reply
70 # not our reply
71 continue
71 continue
72
72
73 outs = []
73 outs = []
74
74
75 while True:
75 while True:
76 try:
76 try:
77 msg = iopub.get_msg(timeout=self.timeout)
77 msg = self.kc.iopub_channel.get_msg(timeout=self.timeout)
78 except Empty:
78 except Empty:
79 self.log.warn("Timeout waiting for IOPub output")
79 self.log.warn("Timeout waiting for IOPub output")
80 break
80 break
81 if msg['parent_header'].get('msg_id') != msg_id:
81 if msg['parent_header'].get('msg_id') != msg_id:
82 # not an output from our execution
82 # not an output from our execution
83 continue
83 continue
84
84
85 msg_type = msg['msg_type']
85 msg_type = msg['msg_type']
86 self.log.debug("output: %s", msg_type)
86 self.log.debug("output: %s", msg_type)
87 content = msg['content']
87 content = msg['content']
88
88
89 # set the prompt number for the input and the output
89 # set the prompt number for the input and the output
90 if 'execution_count' in content:
90 if 'execution_count' in content:
91 cell['execution_count'] = content['execution_count']
91 cell['execution_count'] = content['execution_count']
92
92
93 if msg_type == 'status':
93 if msg_type == 'status':
94 if content['execution_state'] == 'idle':
94 if content['execution_state'] == 'idle':
95 break
95 break
96 else:
96 else:
97 continue
97 continue
98 elif msg_type == 'execute_input':
98 elif msg_type == 'execute_input':
99 continue
99 continue
100 elif msg_type == 'clear_output':
100 elif msg_type == 'clear_output':
101 outs = []
101 outs = []
102 continue
102 continue
103
103
104 try:
104 try:
105 out = output_from_msg(msg)
105 out = output_from_msg(msg)
106 except ValueError:
106 except ValueError:
107 self.log.error("unhandled iopub msg: " + msg_type)
107 self.log.error("unhandled iopub msg: " + msg_type)
108 else:
108 else:
109 outs.append(out)
109 outs.append(out)
110
110
111 return outs
111 return outs
@@ -1,37 +1,249 b''
1 """ Defines a KernelClient that provides signals and slots.
1 """ Defines a KernelClient that provides signals and slots.
2 """
2 """
3 import atexit
4 import errno
5 from threading import Thread
6 import time
7
8 import zmq
9 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
10 # during garbage collection of threads at exit:
11 from zmq import ZMQError
12 from zmq.eventloop import ioloop, zmqstream
13
14 from IPython.external.qt import QtCore
3
15
4 # Local imports
16 # Local imports
5 from IPython.utils.traitlets import Type
17 from IPython.utils.traitlets import Type, Instance
6 from IPython.kernel.channels import (
18 from IPython.kernel.channels import HBChannel
7 ShellChannel, IOPubChannel, StdInChannel, HBChannel
8 )
9 from IPython.kernel import KernelClient
19 from IPython.kernel import KernelClient
10
20
11 from .kernel_mixins import (
21 from .kernel_mixins import QtKernelClientMixin
12 QtShellChannelMixin, QtIOPubChannelMixin,
22 from .util import SuperQObject
13 QtStdInChannelMixin, QtHBChannelMixin,
14 QtKernelClientMixin
15 )
16
23
17 class QtShellChannel(QtShellChannelMixin, ShellChannel):
24 class QtHBChannel(SuperQObject, HBChannel):
18 pass
25 # A longer timeout than the base class
26 time_to_dead = 3.0
27
28 # Emitted when the kernel has died.
29 kernel_died = QtCore.Signal(object)
30
31 def call_handlers(self, since_last_heartbeat):
32 """ Reimplemented to emit signals instead of making callbacks.
33 """
34 # Emit the generic signal.
35 self.kernel_died.emit(since_last_heartbeat)
36
37 from IPython.core.release import kernel_protocol_version_info
19
38
20 class QtIOPubChannel(QtIOPubChannelMixin, IOPubChannel):
39 major_protocol_version = kernel_protocol_version_info[0]
40
41 class InvalidPortNumber(Exception):
21 pass
42 pass
22
43
23 class QtStdInChannel(QtStdInChannelMixin, StdInChannel):
44
45 class QtZMQSocketChannel(SuperQObject):
46 """A ZMQ socket emitting a Qt signal when a message is received."""
47 session = None
48 socket = None
49 ioloop = None
50 stream = None
51
52 message_received = QtCore.Signal(object)
53
54 def process_events(self):
55 """ Process any pending GUI events.
56 """
57 QtCore.QCoreApplication.instance().processEvents()
58
59 def __init__(self, socket, session, loop):
60 """Create a channel.
61
62 Parameters
63 ----------
64 socket : :class:`zmq.Socket`
65 The ZMQ socket to use.
66 session : :class:`session.Session`
67 The session to use.
68 loop
69 A pyzmq ioloop to connect the socket to using a ZMQStream
70 """
71 super(QtZMQSocketChannel, self).__init__()
72
73 self.socket = socket
74 self.session = session
75 self.ioloop = loop
76
77 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
78 self.stream.on_recv(self._handle_recv)
79
80 _is_alive = False
81 def is_alive(self):
82 return self._is_alive
83
84 def start(self):
85 self._is_alive = True
86
87 def stop(self):
88 self._is_alive = False
89
90 def close(self):
91 if self.socket is not None:
92 try:
93 self.socket.close(linger=0)
94 except Exception:
24 pass
95 pass
96 self.socket = None
25
97
26 class QtHBChannel(QtHBChannelMixin, HBChannel):
98 def send(self, msg):
99 """Queue a message to be sent from the IOLoop's thread.
100
101 Parameters
102 ----------
103 msg : message to send
104
105 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
106 thread control of the action.
107 """
108 def thread_send():
109 self.session.send(self.stream, msg)
110 self.ioloop.add_callback(thread_send)
111
112 def _handle_recv(self, msg):
113 """Callback for stream.on_recv.
114
115 Unpacks message, and calls handlers with it.
116 """
117 ident,smsg = self.session.feed_identities(msg)
118 msg = self.session.deserialize(smsg)
119 self.call_handlers(msg)
120
121 def call_handlers(self, msg):
122 """This method is called in the ioloop thread when a message arrives.
123
124 Subclasses should override this method to handle incoming messages.
125 It is important to remember that this method is called in the thread
126 so that some logic must be done to ensure that the application level
127 handlers are called in the application thread.
128 """
129 # Emit the generic signal.
130 self.message_received.emit(msg)
131
132 def flush(self, timeout=1.0):
133 """Immediately processes all pending messages on this channel.
134
135 This is only used for the IOPub channel.
136
137 Callers should use this method to ensure that :meth:`call_handlers`
138 has been called for all messages that have been received on the
139 0MQ SUB socket of this channel.
140
141 This method is thread safe.
142
143 Parameters
144 ----------
145 timeout : float, optional
146 The maximum amount of time to spend flushing, in seconds. The
147 default is one second.
148 """
149 # We do the IOLoop callback process twice to ensure that the IOLoop
150 # gets to perform at least one full poll.
151 stop_time = time.time() + timeout
152 for i in range(2):
153 self._flushed = False
154 self.ioloop.add_callback(self._flush)
155 while not self._flushed and time.time() < stop_time:
156 time.sleep(0.01)
157
158 def _flush(self):
159 """Callback for :method:`self.flush`."""
160 self.stream.flush()
161 self._flushed = True
162
163
164 class IOLoopThread(Thread):
165 """Run a pyzmq ioloop in a thread to send and receive messages
166 """
167 def __init__(self, loop):
168 super(IOLoopThread, self).__init__()
169 self.daemon = True
170 atexit.register(self._notice_exit)
171 self.ioloop = loop or ioloop.IOLoop()
172
173 def _notice_exit(self):
174 self._exiting = True
175
176 def run(self):
177 """Run my loop, ignoring EINTR events in the poller"""
178 while True:
179 try:
180 self.ioloop.start()
181 except ZMQError as e:
182 if e.errno == errno.EINTR:
183 continue
184 else:
185 raise
186 except Exception:
187 if self._exiting:
188 break
189 else:
190 raise
191 else:
192 break
193
194 def stop(self):
195 """Stop the channel's event loop and join its thread.
196
197 This calls :meth:`~threading.Thread.join` and returns when the thread
198 terminates. :class:`RuntimeError` will be raised if
199 :meth:`~threading.Thread.start` is called again.
200 """
201 if self.ioloop is not None:
202 self.ioloop.stop()
203 self.join()
204 self.close()
205
206 def close(self):
207 if self.ioloop is not None:
208 try:
209 self.ioloop.close(all_fds=True)
210 except Exception:
27 pass
211 pass
28
212
29
213
30 class QtKernelClient(QtKernelClientMixin, KernelClient):
214 class QtKernelClient(QtKernelClientMixin, KernelClient):
31 """ A KernelClient that provides signals and slots.
215 """ A KernelClient that provides signals and slots.
32 """
216 """
33
217
34 iopub_channel_class = Type(QtIOPubChannel)
218 _ioloop = None
35 shell_channel_class = Type(QtShellChannel)
219 @property
36 stdin_channel_class = Type(QtStdInChannel)
220 def ioloop(self):
221 if self._ioloop is None:
222 self._ioloop = ioloop.IOLoop()
223 return self._ioloop
224
225 ioloop_thread = Instance(IOLoopThread)
226
227 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
228 if shell:
229 self.shell_channel.message_received.connect(self._check_kernel_info_reply)
230
231 self.ioloop_thread = IOLoopThread(self.ioloop)
232 self.ioloop_thread.start()
233
234 super(QtKernelClient, self).start_channels(shell, iopub, stdin, hb)
235
236 def _check_kernel_info_reply(self, msg):
237 if msg['msg_type'] == 'kernel_info_reply':
238 self._handle_kernel_info_reply(msg)
239 self.shell_channel.message_received.disconnect(self._check_kernel_info_reply)
240
241 def stop_channels(self):
242 super(QtKernelClient, self).stop_channels()
243 if self.ioloop_thread.is_alive():
244 self.ioloop_thread.stop()
245
246 iopub_channel_class = Type(QtZMQSocketChannel)
247 shell_channel_class = Type(QtZMQSocketChannel)
248 stdin_channel_class = Type(QtZMQSocketChannel)
37 hb_channel_class = Type(QtHBChannel)
249 hb_channel_class = Type(QtHBChannel)
@@ -1,809 +1,811 b''
1 """Frontend widget for the Qt Console"""
1 """Frontend widget for the Qt Console"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 from collections import namedtuple
8 from collections import namedtuple
9 import sys
9 import sys
10 import uuid
10 import uuid
11
11
12 from IPython.external import qt
12 from IPython.external import qt
13 from IPython.external.qt import QtCore, QtGui
13 from IPython.external.qt import QtCore, QtGui
14 from IPython.utils import py3compat
14 from IPython.utils import py3compat
15 from IPython.utils.importstring import import_item
15 from IPython.utils.importstring import import_item
16
16
17 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
17 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
18 from IPython.core.inputtransformer import classic_prompt
18 from IPython.core.inputtransformer import classic_prompt
19 from IPython.core.oinspect import call_tip
19 from IPython.core.oinspect import call_tip
20 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
20 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
21 from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName
21 from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName
22 from .bracket_matcher import BracketMatcher
22 from .bracket_matcher import BracketMatcher
23 from .call_tip_widget import CallTipWidget
23 from .call_tip_widget import CallTipWidget
24 from .history_console_widget import HistoryConsoleWidget
24 from .history_console_widget import HistoryConsoleWidget
25 from .pygments_highlighter import PygmentsHighlighter
25 from .pygments_highlighter import PygmentsHighlighter
26
26
27
27
28 class FrontendHighlighter(PygmentsHighlighter):
28 class FrontendHighlighter(PygmentsHighlighter):
29 """ A PygmentsHighlighter that understands and ignores prompts.
29 """ A PygmentsHighlighter that understands and ignores prompts.
30 """
30 """
31
31
32 def __init__(self, frontend, lexer=None):
32 def __init__(self, frontend, lexer=None):
33 super(FrontendHighlighter, self).__init__(frontend._control.document(), lexer=lexer)
33 super(FrontendHighlighter, self).__init__(frontend._control.document(), lexer=lexer)
34 self._current_offset = 0
34 self._current_offset = 0
35 self._frontend = frontend
35 self._frontend = frontend
36 self.highlighting_on = False
36 self.highlighting_on = False
37
37
38 def highlightBlock(self, string):
38 def highlightBlock(self, string):
39 """ Highlight a block of text. Reimplemented to highlight selectively.
39 """ Highlight a block of text. Reimplemented to highlight selectively.
40 """
40 """
41 if not self.highlighting_on:
41 if not self.highlighting_on:
42 return
42 return
43
43
44 # The input to this function is a unicode string that may contain
44 # The input to this function is a unicode string that may contain
45 # paragraph break characters, non-breaking spaces, etc. Here we acquire
45 # paragraph break characters, non-breaking spaces, etc. Here we acquire
46 # the string as plain text so we can compare it.
46 # the string as plain text so we can compare it.
47 current_block = self.currentBlock()
47 current_block = self.currentBlock()
48 string = self._frontend._get_block_plain_text(current_block)
48 string = self._frontend._get_block_plain_text(current_block)
49
49
50 # Decide whether to check for the regular or continuation prompt.
50 # Decide whether to check for the regular or continuation prompt.
51 if current_block.contains(self._frontend._prompt_pos):
51 if current_block.contains(self._frontend._prompt_pos):
52 prompt = self._frontend._prompt
52 prompt = self._frontend._prompt
53 else:
53 else:
54 prompt = self._frontend._continuation_prompt
54 prompt = self._frontend._continuation_prompt
55
55
56 # Only highlight if we can identify a prompt, but make sure not to
56 # Only highlight if we can identify a prompt, but make sure not to
57 # highlight the prompt.
57 # highlight the prompt.
58 if string.startswith(prompt):
58 if string.startswith(prompt):
59 self._current_offset = len(prompt)
59 self._current_offset = len(prompt)
60 string = string[len(prompt):]
60 string = string[len(prompt):]
61 super(FrontendHighlighter, self).highlightBlock(string)
61 super(FrontendHighlighter, self).highlightBlock(string)
62
62
63 def rehighlightBlock(self, block):
63 def rehighlightBlock(self, block):
64 """ Reimplemented to temporarily enable highlighting if disabled.
64 """ Reimplemented to temporarily enable highlighting if disabled.
65 """
65 """
66 old = self.highlighting_on
66 old = self.highlighting_on
67 self.highlighting_on = True
67 self.highlighting_on = True
68 super(FrontendHighlighter, self).rehighlightBlock(block)
68 super(FrontendHighlighter, self).rehighlightBlock(block)
69 self.highlighting_on = old
69 self.highlighting_on = old
70
70
71 def setFormat(self, start, count, format):
71 def setFormat(self, start, count, format):
72 """ Reimplemented to highlight selectively.
72 """ Reimplemented to highlight selectively.
73 """
73 """
74 start += self._current_offset
74 start += self._current_offset
75 super(FrontendHighlighter, self).setFormat(start, count, format)
75 super(FrontendHighlighter, self).setFormat(start, count, format)
76
76
77
77
78 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
79 """ A Qt frontend for a generic Python kernel.
79 """ A Qt frontend for a generic Python kernel.
80 """
80 """
81
81
82 # The text to show when the kernel is (re)started.
82 # The text to show when the kernel is (re)started.
83 banner = Unicode(config=True)
83 banner = Unicode(config=True)
84 kernel_banner = Unicode()
84 kernel_banner = Unicode()
85 # Whether to show the banner
86 _display_banner = Bool(False)
85
87
86 # An option and corresponding signal for overriding the default kernel
88 # An option and corresponding signal for overriding the default kernel
87 # interrupt behavior.
89 # interrupt behavior.
88 custom_interrupt = Bool(False)
90 custom_interrupt = Bool(False)
89 custom_interrupt_requested = QtCore.Signal()
91 custom_interrupt_requested = QtCore.Signal()
90
92
91 # An option and corresponding signals for overriding the default kernel
93 # An option and corresponding signals for overriding the default kernel
92 # restart behavior.
94 # restart behavior.
93 custom_restart = Bool(False)
95 custom_restart = Bool(False)
94 custom_restart_kernel_died = QtCore.Signal(float)
96 custom_restart_kernel_died = QtCore.Signal(float)
95 custom_restart_requested = QtCore.Signal()
97 custom_restart_requested = QtCore.Signal()
96
98
97 # Whether to automatically show calltips on open-parentheses.
99 # Whether to automatically show calltips on open-parentheses.
98 enable_calltips = Bool(True, config=True,
100 enable_calltips = Bool(True, config=True,
99 help="Whether to draw information calltips on open-parentheses.")
101 help="Whether to draw information calltips on open-parentheses.")
100
102
101 clear_on_kernel_restart = Bool(True, config=True,
103 clear_on_kernel_restart = Bool(True, config=True,
102 help="Whether to clear the console when the kernel is restarted")
104 help="Whether to clear the console when the kernel is restarted")
103
105
104 confirm_restart = Bool(True, config=True,
106 confirm_restart = Bool(True, config=True,
105 help="Whether to ask for user confirmation when restarting kernel")
107 help="Whether to ask for user confirmation when restarting kernel")
106
108
107 lexer_class = DottedObjectName(config=True,
109 lexer_class = DottedObjectName(config=True,
108 help="The pygments lexer class to use."
110 help="The pygments lexer class to use."
109 )
111 )
110 def _lexer_class_changed(self, name, old, new):
112 def _lexer_class_changed(self, name, old, new):
111 lexer_class = import_item(new)
113 lexer_class = import_item(new)
112 self.lexer = lexer_class()
114 self.lexer = lexer_class()
113
115
114 def _lexer_class_default(self):
116 def _lexer_class_default(self):
115 if py3compat.PY3:
117 if py3compat.PY3:
116 return 'pygments.lexers.Python3Lexer'
118 return 'pygments.lexers.Python3Lexer'
117 else:
119 else:
118 return 'pygments.lexers.PythonLexer'
120 return 'pygments.lexers.PythonLexer'
119
121
120 lexer = Any()
122 lexer = Any()
121 def _lexer_default(self):
123 def _lexer_default(self):
122 lexer_class = import_item(self.lexer_class)
124 lexer_class = import_item(self.lexer_class)
123 return lexer_class()
125 return lexer_class()
124
126
125 # Emitted when a user visible 'execute_request' has been submitted to the
127 # Emitted when a user visible 'execute_request' has been submitted to the
126 # kernel from the FrontendWidget. Contains the code to be executed.
128 # kernel from the FrontendWidget. Contains the code to be executed.
127 executing = QtCore.Signal(object)
129 executing = QtCore.Signal(object)
128
130
129 # Emitted when a user-visible 'execute_reply' has been received from the
131 # Emitted when a user-visible 'execute_reply' has been received from the
130 # kernel and processed by the FrontendWidget. Contains the response message.
132 # kernel and processed by the FrontendWidget. Contains the response message.
131 executed = QtCore.Signal(object)
133 executed = QtCore.Signal(object)
132
134
133 # Emitted when an exit request has been received from the kernel.
135 # Emitted when an exit request has been received from the kernel.
134 exit_requested = QtCore.Signal(object)
136 exit_requested = QtCore.Signal(object)
135
137
136 # Protected class variables.
138 # Protected class variables.
137 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
139 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
138 logical_line_transforms=[],
140 logical_line_transforms=[],
139 python_line_transforms=[],
141 python_line_transforms=[],
140 )
142 )
141 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
143 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
142 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
144 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
143 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
145 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
144 _input_splitter_class = InputSplitter
146 _input_splitter_class = InputSplitter
145 _local_kernel = False
147 _local_kernel = False
146 _highlighter = Instance(FrontendHighlighter)
148 _highlighter = Instance(FrontendHighlighter)
147
149
148 #---------------------------------------------------------------------------
150 #---------------------------------------------------------------------------
149 # 'object' interface
151 # 'object' interface
150 #---------------------------------------------------------------------------
152 #---------------------------------------------------------------------------
151
153
152 def __init__(self, *args, **kw):
154 def __init__(self, *args, **kw):
153 super(FrontendWidget, self).__init__(*args, **kw)
155 super(FrontendWidget, self).__init__(*args, **kw)
154 # FIXME: remove this when PySide min version is updated past 1.0.7
156 # FIXME: remove this when PySide min version is updated past 1.0.7
155 # forcefully disable calltips if PySide is < 1.0.7, because they crash
157 # forcefully disable calltips if PySide is < 1.0.7, because they crash
156 if qt.QT_API == qt.QT_API_PYSIDE:
158 if qt.QT_API == qt.QT_API_PYSIDE:
157 import PySide
159 import PySide
158 if PySide.__version_info__ < (1,0,7):
160 if PySide.__version_info__ < (1,0,7):
159 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
161 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
160 self.enable_calltips = False
162 self.enable_calltips = False
161
163
162 # FrontendWidget protected variables.
164 # FrontendWidget protected variables.
163 self._bracket_matcher = BracketMatcher(self._control)
165 self._bracket_matcher = BracketMatcher(self._control)
164 self._call_tip_widget = CallTipWidget(self._control)
166 self._call_tip_widget = CallTipWidget(self._control)
165 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
167 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
166 self._hidden = False
168 self._hidden = False
167 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
169 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
168 self._input_splitter = self._input_splitter_class()
170 self._input_splitter = self._input_splitter_class()
169 self._kernel_manager = None
171 self._kernel_manager = None
170 self._kernel_client = None
172 self._kernel_client = None
171 self._request_info = {}
173 self._request_info = {}
172 self._request_info['execute'] = {};
174 self._request_info['execute'] = {};
173 self._callback_dict = {}
175 self._callback_dict = {}
174 self._display_banner = True
176 self._display_banner = True
175
177
176 # Configure the ConsoleWidget.
178 # Configure the ConsoleWidget.
177 self.tab_width = 4
179 self.tab_width = 4
178 self._set_continuation_prompt('... ')
180 self._set_continuation_prompt('... ')
179
181
180 # Configure the CallTipWidget.
182 # Configure the CallTipWidget.
181 self._call_tip_widget.setFont(self.font)
183 self._call_tip_widget.setFont(self.font)
182 self.font_changed.connect(self._call_tip_widget.setFont)
184 self.font_changed.connect(self._call_tip_widget.setFont)
183
185
184 # Configure actions.
186 # Configure actions.
185 action = self._copy_raw_action
187 action = self._copy_raw_action
186 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
188 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
187 action.setEnabled(False)
189 action.setEnabled(False)
188 action.setShortcut(QtGui.QKeySequence(key))
190 action.setShortcut(QtGui.QKeySequence(key))
189 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
191 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
190 action.triggered.connect(self.copy_raw)
192 action.triggered.connect(self.copy_raw)
191 self.copy_available.connect(action.setEnabled)
193 self.copy_available.connect(action.setEnabled)
192 self.addAction(action)
194 self.addAction(action)
193
195
194 # Connect signal handlers.
196 # Connect signal handlers.
195 document = self._control.document()
197 document = self._control.document()
196 document.contentsChange.connect(self._document_contents_change)
198 document.contentsChange.connect(self._document_contents_change)
197
199
198 # Set flag for whether we are connected via localhost.
200 # Set flag for whether we are connected via localhost.
199 self._local_kernel = kw.get('local_kernel',
201 self._local_kernel = kw.get('local_kernel',
200 FrontendWidget._local_kernel)
202 FrontendWidget._local_kernel)
201
203
202 # Whether or not a clear_output call is pending new output.
204 # Whether or not a clear_output call is pending new output.
203 self._pending_clearoutput = False
205 self._pending_clearoutput = False
204
206
205 #---------------------------------------------------------------------------
207 #---------------------------------------------------------------------------
206 # 'ConsoleWidget' public interface
208 # 'ConsoleWidget' public interface
207 #---------------------------------------------------------------------------
209 #---------------------------------------------------------------------------
208
210
209 def copy(self):
211 def copy(self):
210 """ Copy the currently selected text to the clipboard, removing prompts.
212 """ Copy the currently selected text to the clipboard, removing prompts.
211 """
213 """
212 if self._page_control is not None and self._page_control.hasFocus():
214 if self._page_control is not None and self._page_control.hasFocus():
213 self._page_control.copy()
215 self._page_control.copy()
214 elif self._control.hasFocus():
216 elif self._control.hasFocus():
215 text = self._control.textCursor().selection().toPlainText()
217 text = self._control.textCursor().selection().toPlainText()
216 if text:
218 if text:
217 was_newline = text[-1] == '\n'
219 was_newline = text[-1] == '\n'
218 text = self._prompt_transformer.transform_cell(text)
220 text = self._prompt_transformer.transform_cell(text)
219 if not was_newline: # user doesn't need newline
221 if not was_newline: # user doesn't need newline
220 text = text[:-1]
222 text = text[:-1]
221 QtGui.QApplication.clipboard().setText(text)
223 QtGui.QApplication.clipboard().setText(text)
222 else:
224 else:
223 self.log.debug("frontend widget : unknown copy target")
225 self.log.debug("frontend widget : unknown copy target")
224
226
225 #---------------------------------------------------------------------------
227 #---------------------------------------------------------------------------
226 # 'ConsoleWidget' abstract interface
228 # 'ConsoleWidget' abstract interface
227 #---------------------------------------------------------------------------
229 #---------------------------------------------------------------------------
228
230
229 def _is_complete(self, source, interactive):
231 def _is_complete(self, source, interactive):
230 """ Returns whether 'source' can be completely processed and a new
232 """ Returns whether 'source' can be completely processed and a new
231 prompt created. When triggered by an Enter/Return key press,
233 prompt created. When triggered by an Enter/Return key press,
232 'interactive' is True; otherwise, it is False.
234 'interactive' is True; otherwise, it is False.
233 """
235 """
234 self._input_splitter.reset()
236 self._input_splitter.reset()
235 try:
237 try:
236 complete = self._input_splitter.push(source)
238 complete = self._input_splitter.push(source)
237 except SyntaxError:
239 except SyntaxError:
238 return True
240 return True
239 if interactive:
241 if interactive:
240 complete = not self._input_splitter.push_accepts_more()
242 complete = not self._input_splitter.push_accepts_more()
241 return complete
243 return complete
242
244
243 def _execute(self, source, hidden):
245 def _execute(self, source, hidden):
244 """ Execute 'source'. If 'hidden', do not show any output.
246 """ Execute 'source'. If 'hidden', do not show any output.
245
247
246 See parent class :meth:`execute` docstring for full details.
248 See parent class :meth:`execute` docstring for full details.
247 """
249 """
248 msg_id = self.kernel_client.execute(source, hidden)
250 msg_id = self.kernel_client.execute(source, hidden)
249 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
251 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
250 self._hidden = hidden
252 self._hidden = hidden
251 if not hidden:
253 if not hidden:
252 self.executing.emit(source)
254 self.executing.emit(source)
253
255
254 def _prompt_started_hook(self):
256 def _prompt_started_hook(self):
255 """ Called immediately after a new prompt is displayed.
257 """ Called immediately after a new prompt is displayed.
256 """
258 """
257 if not self._reading:
259 if not self._reading:
258 self._highlighter.highlighting_on = True
260 self._highlighter.highlighting_on = True
259
261
260 def _prompt_finished_hook(self):
262 def _prompt_finished_hook(self):
261 """ Called immediately after a prompt is finished, i.e. when some input
263 """ Called immediately after a prompt is finished, i.e. when some input
262 will be processed and a new prompt displayed.
264 will be processed and a new prompt displayed.
263 """
265 """
264 # Flush all state from the input splitter so the next round of
266 # Flush all state from the input splitter so the next round of
265 # reading input starts with a clean buffer.
267 # reading input starts with a clean buffer.
266 self._input_splitter.reset()
268 self._input_splitter.reset()
267
269
268 if not self._reading:
270 if not self._reading:
269 self._highlighter.highlighting_on = False
271 self._highlighter.highlighting_on = False
270
272
271 def _tab_pressed(self):
273 def _tab_pressed(self):
272 """ Called when the tab key is pressed. Returns whether to continue
274 """ Called when the tab key is pressed. Returns whether to continue
273 processing the event.
275 processing the event.
274 """
276 """
275 # Perform tab completion if:
277 # Perform tab completion if:
276 # 1) The cursor is in the input buffer.
278 # 1) The cursor is in the input buffer.
277 # 2) There is a non-whitespace character before the cursor.
279 # 2) There is a non-whitespace character before the cursor.
278 text = self._get_input_buffer_cursor_line()
280 text = self._get_input_buffer_cursor_line()
279 if text is None:
281 if text is None:
280 return False
282 return False
281 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
283 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
282 if complete:
284 if complete:
283 self._complete()
285 self._complete()
284 return not complete
286 return not complete
285
287
286 #---------------------------------------------------------------------------
288 #---------------------------------------------------------------------------
287 # 'ConsoleWidget' protected interface
289 # 'ConsoleWidget' protected interface
288 #---------------------------------------------------------------------------
290 #---------------------------------------------------------------------------
289
291
290 def _context_menu_make(self, pos):
292 def _context_menu_make(self, pos):
291 """ Reimplemented to add an action for raw copy.
293 """ Reimplemented to add an action for raw copy.
292 """
294 """
293 menu = super(FrontendWidget, self)._context_menu_make(pos)
295 menu = super(FrontendWidget, self)._context_menu_make(pos)
294 for before_action in menu.actions():
296 for before_action in menu.actions():
295 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
297 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
296 QtGui.QKeySequence.ExactMatch:
298 QtGui.QKeySequence.ExactMatch:
297 menu.insertAction(before_action, self._copy_raw_action)
299 menu.insertAction(before_action, self._copy_raw_action)
298 break
300 break
299 return menu
301 return menu
300
302
301 def request_interrupt_kernel(self):
303 def request_interrupt_kernel(self):
302 if self._executing:
304 if self._executing:
303 self.interrupt_kernel()
305 self.interrupt_kernel()
304
306
305 def request_restart_kernel(self):
307 def request_restart_kernel(self):
306 message = 'Are you sure you want to restart the kernel?'
308 message = 'Are you sure you want to restart the kernel?'
307 self.restart_kernel(message, now=False)
309 self.restart_kernel(message, now=False)
308
310
309 def _event_filter_console_keypress(self, event):
311 def _event_filter_console_keypress(self, event):
310 """ Reimplemented for execution interruption and smart backspace.
312 """ Reimplemented for execution interruption and smart backspace.
311 """
313 """
312 key = event.key()
314 key = event.key()
313 if self._control_key_down(event.modifiers(), include_command=False):
315 if self._control_key_down(event.modifiers(), include_command=False):
314
316
315 if key == QtCore.Qt.Key_C and self._executing:
317 if key == QtCore.Qt.Key_C and self._executing:
316 self.request_interrupt_kernel()
318 self.request_interrupt_kernel()
317 return True
319 return True
318
320
319 elif key == QtCore.Qt.Key_Period:
321 elif key == QtCore.Qt.Key_Period:
320 self.request_restart_kernel()
322 self.request_restart_kernel()
321 return True
323 return True
322
324
323 elif not event.modifiers() & QtCore.Qt.AltModifier:
325 elif not event.modifiers() & QtCore.Qt.AltModifier:
324
326
325 # Smart backspace: remove four characters in one backspace if:
327 # Smart backspace: remove four characters in one backspace if:
326 # 1) everything left of the cursor is whitespace
328 # 1) everything left of the cursor is whitespace
327 # 2) the four characters immediately left of the cursor are spaces
329 # 2) the four characters immediately left of the cursor are spaces
328 if key == QtCore.Qt.Key_Backspace:
330 if key == QtCore.Qt.Key_Backspace:
329 col = self._get_input_buffer_cursor_column()
331 col = self._get_input_buffer_cursor_column()
330 cursor = self._control.textCursor()
332 cursor = self._control.textCursor()
331 if col > 3 and not cursor.hasSelection():
333 if col > 3 and not cursor.hasSelection():
332 text = self._get_input_buffer_cursor_line()[:col]
334 text = self._get_input_buffer_cursor_line()[:col]
333 if text.endswith(' ') and not text.strip():
335 if text.endswith(' ') and not text.strip():
334 cursor.movePosition(QtGui.QTextCursor.Left,
336 cursor.movePosition(QtGui.QTextCursor.Left,
335 QtGui.QTextCursor.KeepAnchor, 4)
337 QtGui.QTextCursor.KeepAnchor, 4)
336 cursor.removeSelectedText()
338 cursor.removeSelectedText()
337 return True
339 return True
338
340
339 return super(FrontendWidget, self)._event_filter_console_keypress(event)
341 return super(FrontendWidget, self)._event_filter_console_keypress(event)
340
342
341 def _insert_continuation_prompt(self, cursor):
343 def _insert_continuation_prompt(self, cursor):
342 """ Reimplemented for auto-indentation.
344 """ Reimplemented for auto-indentation.
343 """
345 """
344 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
346 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
345 cursor.insertText(' ' * self._input_splitter.indent_spaces)
347 cursor.insertText(' ' * self._input_splitter.indent_spaces)
346
348
347 #---------------------------------------------------------------------------
349 #---------------------------------------------------------------------------
348 # 'BaseFrontendMixin' abstract interface
350 # 'BaseFrontendMixin' abstract interface
349 #---------------------------------------------------------------------------
351 #---------------------------------------------------------------------------
350 def _handle_clear_output(self, msg):
352 def _handle_clear_output(self, msg):
351 """Handle clear output messages."""
353 """Handle clear output messages."""
352 if include_output(msg):
354 if include_output(msg):
353 wait = msg['content'].get('wait', True)
355 wait = msg['content'].get('wait', True)
354 if wait:
356 if wait:
355 self._pending_clearoutput = True
357 self._pending_clearoutput = True
356 else:
358 else:
357 self.clear_output()
359 self.clear_output()
358
360
359 def _silent_exec_callback(self, expr, callback):
361 def _silent_exec_callback(self, expr, callback):
360 """Silently execute `expr` in the kernel and call `callback` with reply
362 """Silently execute `expr` in the kernel and call `callback` with reply
361
363
362 the `expr` is evaluated silently in the kernel (without) output in
364 the `expr` is evaluated silently in the kernel (without) output in
363 the frontend. Call `callback` with the
365 the frontend. Call `callback` with the
364 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
366 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
365
367
366 Parameters
368 Parameters
367 ----------
369 ----------
368 expr : string
370 expr : string
369 valid string to be executed by the kernel.
371 valid string to be executed by the kernel.
370 callback : function
372 callback : function
371 function accepting one argument, as a string. The string will be
373 function accepting one argument, as a string. The string will be
372 the `repr` of the result of evaluating `expr`
374 the `repr` of the result of evaluating `expr`
373
375
374 The `callback` is called with the `repr()` of the result of `expr` as
376 The `callback` is called with the `repr()` of the result of `expr` as
375 first argument. To get the object, do `eval()` on the passed value.
377 first argument. To get the object, do `eval()` on the passed value.
376
378
377 See Also
379 See Also
378 --------
380 --------
379 _handle_exec_callback : private method, deal with calling callback with reply
381 _handle_exec_callback : private method, deal with calling callback with reply
380
382
381 """
383 """
382
384
383 # generate uuid, which would be used as an indication of whether or
385 # generate uuid, which would be used as an indication of whether or
384 # not the unique request originated from here (can use msg id ?)
386 # not the unique request originated from here (can use msg id ?)
385 local_uuid = str(uuid.uuid1())
387 local_uuid = str(uuid.uuid1())
386 msg_id = self.kernel_client.execute('',
388 msg_id = self.kernel_client.execute('',
387 silent=True, user_expressions={ local_uuid:expr })
389 silent=True, user_expressions={ local_uuid:expr })
388 self._callback_dict[local_uuid] = callback
390 self._callback_dict[local_uuid] = callback
389 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
391 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
390
392
391 def _handle_exec_callback(self, msg):
393 def _handle_exec_callback(self, msg):
392 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
394 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
393
395
394 Parameters
396 Parameters
395 ----------
397 ----------
396 msg : raw message send by the kernel containing an `user_expressions`
398 msg : raw message send by the kernel containing an `user_expressions`
397 and having a 'silent_exec_callback' kind.
399 and having a 'silent_exec_callback' kind.
398
400
399 Notes
401 Notes
400 -----
402 -----
401 This function will look for a `callback` associated with the
403 This function will look for a `callback` associated with the
402 corresponding message id. Association has been made by
404 corresponding message id. Association has been made by
403 `_silent_exec_callback`. `callback` is then called with the `repr()`
405 `_silent_exec_callback`. `callback` is then called with the `repr()`
404 of the value of corresponding `user_expressions` as argument.
406 of the value of corresponding `user_expressions` as argument.
405 `callback` is then removed from the known list so that any message
407 `callback` is then removed from the known list so that any message
406 coming again with the same id won't trigger it.
408 coming again with the same id won't trigger it.
407
409
408 """
410 """
409
411
410 user_exp = msg['content'].get('user_expressions')
412 user_exp = msg['content'].get('user_expressions')
411 if not user_exp:
413 if not user_exp:
412 return
414 return
413 for expression in user_exp:
415 for expression in user_exp:
414 if expression in self._callback_dict:
416 if expression in self._callback_dict:
415 self._callback_dict.pop(expression)(user_exp[expression])
417 self._callback_dict.pop(expression)(user_exp[expression])
416
418
417 def _handle_execute_reply(self, msg):
419 def _handle_execute_reply(self, msg):
418 """ Handles replies for code execution.
420 """ Handles replies for code execution.
419 """
421 """
420 self.log.debug("execute: %s", msg.get('content', ''))
422 self.log.debug("execute: %s", msg.get('content', ''))
421 msg_id = msg['parent_header']['msg_id']
423 msg_id = msg['parent_header']['msg_id']
422 info = self._request_info['execute'].get(msg_id)
424 info = self._request_info['execute'].get(msg_id)
423 # unset reading flag, because if execute finished, raw_input can't
425 # unset reading flag, because if execute finished, raw_input can't
424 # still be pending.
426 # still be pending.
425 self._reading = False
427 self._reading = False
426 if info and info.kind == 'user' and not self._hidden:
428 if info and info.kind == 'user' and not self._hidden:
427 # Make sure that all output from the SUB channel has been processed
429 # Make sure that all output from the SUB channel has been processed
428 # before writing a new prompt.
430 # before writing a new prompt.
429 self.kernel_client.iopub_channel.flush()
431 self.kernel_client.iopub_channel.flush()
430
432
431 # Reset the ANSI style information to prevent bad text in stdout
433 # Reset the ANSI style information to prevent bad text in stdout
432 # from messing up our colors. We're not a true terminal so we're
434 # from messing up our colors. We're not a true terminal so we're
433 # allowed to do this.
435 # allowed to do this.
434 if self.ansi_codes:
436 if self.ansi_codes:
435 self._ansi_processor.reset_sgr()
437 self._ansi_processor.reset_sgr()
436
438
437 content = msg['content']
439 content = msg['content']
438 status = content['status']
440 status = content['status']
439 if status == 'ok':
441 if status == 'ok':
440 self._process_execute_ok(msg)
442 self._process_execute_ok(msg)
441 elif status == 'error':
443 elif status == 'error':
442 self._process_execute_error(msg)
444 self._process_execute_error(msg)
443 elif status == 'aborted':
445 elif status == 'aborted':
444 self._process_execute_abort(msg)
446 self._process_execute_abort(msg)
445
447
446 self._show_interpreter_prompt_for_reply(msg)
448 self._show_interpreter_prompt_for_reply(msg)
447 self.executed.emit(msg)
449 self.executed.emit(msg)
448 self._request_info['execute'].pop(msg_id)
450 self._request_info['execute'].pop(msg_id)
449 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
451 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
450 self._handle_exec_callback(msg)
452 self._handle_exec_callback(msg)
451 self._request_info['execute'].pop(msg_id)
453 self._request_info['execute'].pop(msg_id)
452 else:
454 else:
453 super(FrontendWidget, self)._handle_execute_reply(msg)
455 super(FrontendWidget, self)._handle_execute_reply(msg)
454
456
455 def _handle_input_request(self, msg):
457 def _handle_input_request(self, msg):
456 """ Handle requests for raw_input.
458 """ Handle requests for raw_input.
457 """
459 """
458 self.log.debug("input: %s", msg.get('content', ''))
460 self.log.debug("input: %s", msg.get('content', ''))
459 if self._hidden:
461 if self._hidden:
460 raise RuntimeError('Request for raw input during hidden execution.')
462 raise RuntimeError('Request for raw input during hidden execution.')
461
463
462 # Make sure that all output from the SUB channel has been processed
464 # Make sure that all output from the SUB channel has been processed
463 # before entering readline mode.
465 # before entering readline mode.
464 self.kernel_client.iopub_channel.flush()
466 self.kernel_client.iopub_channel.flush()
465
467
466 def callback(line):
468 def callback(line):
467 self.kernel_client.stdin_channel.input(line)
469 self.kernel_client.input(line)
468 if self._reading:
470 if self._reading:
469 self.log.debug("Got second input request, assuming first was interrupted.")
471 self.log.debug("Got second input request, assuming first was interrupted.")
470 self._reading = False
472 self._reading = False
471 self._readline(msg['content']['prompt'], callback=callback)
473 self._readline(msg['content']['prompt'], callback=callback)
472
474
473 def _kernel_restarted_message(self, died=True):
475 def _kernel_restarted_message(self, died=True):
474 msg = "Kernel died, restarting" if died else "Kernel restarting"
476 msg = "Kernel died, restarting" if died else "Kernel restarting"
475 self._append_html("<br>%s<hr><br>" % msg,
477 self._append_html("<br>%s<hr><br>" % msg,
476 before_prompt=False
478 before_prompt=False
477 )
479 )
478
480
479 def _handle_kernel_died(self, since_last_heartbeat):
481 def _handle_kernel_died(self, since_last_heartbeat):
480 """Handle the kernel's death (if we do not own the kernel).
482 """Handle the kernel's death (if we do not own the kernel).
481 """
483 """
482 self.log.warn("kernel died: %s", since_last_heartbeat)
484 self.log.warn("kernel died: %s", since_last_heartbeat)
483 if self.custom_restart:
485 if self.custom_restart:
484 self.custom_restart_kernel_died.emit(since_last_heartbeat)
486 self.custom_restart_kernel_died.emit(since_last_heartbeat)
485 else:
487 else:
486 self._kernel_restarted_message(died=True)
488 self._kernel_restarted_message(died=True)
487 self.reset()
489 self.reset()
488
490
489 def _handle_kernel_restarted(self, died=True):
491 def _handle_kernel_restarted(self, died=True):
490 """Notice that the autorestarter restarted the kernel.
492 """Notice that the autorestarter restarted the kernel.
491
493
492 There's nothing to do but show a message.
494 There's nothing to do but show a message.
493 """
495 """
494 self.log.warn("kernel restarted")
496 self.log.warn("kernel restarted")
495 self._kernel_restarted_message(died=died)
497 self._kernel_restarted_message(died=died)
496 self.reset()
498 self.reset()
497
499
498 def _handle_inspect_reply(self, rep):
500 def _handle_inspect_reply(self, rep):
499 """Handle replies for call tips."""
501 """Handle replies for call tips."""
500 self.log.debug("oinfo: %s", rep.get('content', ''))
502 self.log.debug("oinfo: %s", rep.get('content', ''))
501 cursor = self._get_cursor()
503 cursor = self._get_cursor()
502 info = self._request_info.get('call_tip')
504 info = self._request_info.get('call_tip')
503 if info and info.id == rep['parent_header']['msg_id'] and \
505 if info and info.id == rep['parent_header']['msg_id'] and \
504 info.pos == cursor.position():
506 info.pos == cursor.position():
505 content = rep['content']
507 content = rep['content']
506 if content.get('status') == 'ok' and content.get('found', False):
508 if content.get('status') == 'ok' and content.get('found', False):
507 self._call_tip_widget.show_inspect_data(content)
509 self._call_tip_widget.show_inspect_data(content)
508
510
509 def _handle_execute_result(self, msg):
511 def _handle_execute_result(self, msg):
510 """ Handle display hook output.
512 """ Handle display hook output.
511 """
513 """
512 self.log.debug("execute_result: %s", msg.get('content', ''))
514 self.log.debug("execute_result: %s", msg.get('content', ''))
513 if self.include_output(msg):
515 if self.include_output(msg):
514 self.flush_clearoutput()
516 self.flush_clearoutput()
515 text = msg['content']['data']
517 text = msg['content']['data']
516 self._append_plain_text(text + '\n', before_prompt=True)
518 self._append_plain_text(text + '\n', before_prompt=True)
517
519
518 def _handle_stream(self, msg):
520 def _handle_stream(self, msg):
519 """ Handle stdout, stderr, and stdin.
521 """ Handle stdout, stderr, and stdin.
520 """
522 """
521 self.log.debug("stream: %s", msg.get('content', ''))
523 self.log.debug("stream: %s", msg.get('content', ''))
522 if self.include_output(msg):
524 if self.include_output(msg):
523 self.flush_clearoutput()
525 self.flush_clearoutput()
524 self.append_stream(msg['content']['text'])
526 self.append_stream(msg['content']['text'])
525
527
526 def _handle_shutdown_reply(self, msg):
528 def _handle_shutdown_reply(self, msg):
527 """ Handle shutdown signal, only if from other console.
529 """ Handle shutdown signal, only if from other console.
528 """
530 """
529 self.log.info("shutdown: %s", msg.get('content', ''))
531 self.log.info("shutdown: %s", msg.get('content', ''))
530 restart = msg.get('content', {}).get('restart', False)
532 restart = msg.get('content', {}).get('restart', False)
531 if not self._hidden and not self.from_here(msg):
533 if not self._hidden and not self.from_here(msg):
532 # got shutdown reply, request came from session other than ours
534 # got shutdown reply, request came from session other than ours
533 if restart:
535 if restart:
534 # someone restarted the kernel, handle it
536 # someone restarted the kernel, handle it
535 self._handle_kernel_restarted(died=False)
537 self._handle_kernel_restarted(died=False)
536 else:
538 else:
537 # kernel was shutdown permanently
539 # kernel was shutdown permanently
538 # this triggers exit_requested if the kernel was local,
540 # this triggers exit_requested if the kernel was local,
539 # and a dialog if the kernel was remote,
541 # and a dialog if the kernel was remote,
540 # so we don't suddenly clear the qtconsole without asking.
542 # so we don't suddenly clear the qtconsole without asking.
541 if self._local_kernel:
543 if self._local_kernel:
542 self.exit_requested.emit(self)
544 self.exit_requested.emit(self)
543 else:
545 else:
544 title = self.window().windowTitle()
546 title = self.window().windowTitle()
545 reply = QtGui.QMessageBox.question(self, title,
547 reply = QtGui.QMessageBox.question(self, title,
546 "Kernel has been shutdown permanently. "
548 "Kernel has been shutdown permanently. "
547 "Close the Console?",
549 "Close the Console?",
548 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
550 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
549 if reply == QtGui.QMessageBox.Yes:
551 if reply == QtGui.QMessageBox.Yes:
550 self.exit_requested.emit(self)
552 self.exit_requested.emit(self)
551
553
552 def _handle_status(self, msg):
554 def _handle_status(self, msg):
553 """Handle status message"""
555 """Handle status message"""
554 # This is where a busy/idle indicator would be triggered,
556 # This is where a busy/idle indicator would be triggered,
555 # when we make one.
557 # when we make one.
556 state = msg['content'].get('execution_state', '')
558 state = msg['content'].get('execution_state', '')
557 if state == 'starting':
559 if state == 'starting':
558 # kernel started while we were running
560 # kernel started while we were running
559 if self._executing:
561 if self._executing:
560 self._handle_kernel_restarted(died=True)
562 self._handle_kernel_restarted(died=True)
561 elif state == 'idle':
563 elif state == 'idle':
562 pass
564 pass
563 elif state == 'busy':
565 elif state == 'busy':
564 pass
566 pass
565
567
566 def _started_channels(self):
568 def _started_channels(self):
567 """ Called when the KernelManager channels have started listening or
569 """ Called when the KernelManager channels have started listening or
568 when the frontend is assigned an already listening KernelManager.
570 when the frontend is assigned an already listening KernelManager.
569 """
571 """
570 self.reset(clear=True)
572 self.reset(clear=True)
571
573
572 #---------------------------------------------------------------------------
574 #---------------------------------------------------------------------------
573 # 'FrontendWidget' public interface
575 # 'FrontendWidget' public interface
574 #---------------------------------------------------------------------------
576 #---------------------------------------------------------------------------
575
577
576 def copy_raw(self):
578 def copy_raw(self):
577 """ Copy the currently selected text to the clipboard without attempting
579 """ Copy the currently selected text to the clipboard without attempting
578 to remove prompts or otherwise alter the text.
580 to remove prompts or otherwise alter the text.
579 """
581 """
580 self._control.copy()
582 self._control.copy()
581
583
582 def execute_file(self, path, hidden=False):
584 def execute_file(self, path, hidden=False):
583 """ Attempts to execute file with 'path'. If 'hidden', no output is
585 """ Attempts to execute file with 'path'. If 'hidden', no output is
584 shown.
586 shown.
585 """
587 """
586 self.execute('execfile(%r)' % path, hidden=hidden)
588 self.execute('execfile(%r)' % path, hidden=hidden)
587
589
588 def interrupt_kernel(self):
590 def interrupt_kernel(self):
589 """ Attempts to interrupt the running kernel.
591 """ Attempts to interrupt the running kernel.
590
592
591 Also unsets _reading flag, to avoid runtime errors
593 Also unsets _reading flag, to avoid runtime errors
592 if raw_input is called again.
594 if raw_input is called again.
593 """
595 """
594 if self.custom_interrupt:
596 if self.custom_interrupt:
595 self._reading = False
597 self._reading = False
596 self.custom_interrupt_requested.emit()
598 self.custom_interrupt_requested.emit()
597 elif self.kernel_manager:
599 elif self.kernel_manager:
598 self._reading = False
600 self._reading = False
599 self.kernel_manager.interrupt_kernel()
601 self.kernel_manager.interrupt_kernel()
600 else:
602 else:
601 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
603 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
602
604
603 def reset(self, clear=False):
605 def reset(self, clear=False):
604 """ Resets the widget to its initial state if ``clear`` parameter
606 """ Resets the widget to its initial state if ``clear`` parameter
605 is True, otherwise
607 is True, otherwise
606 prints a visual indication of the fact that the kernel restarted, but
608 prints a visual indication of the fact that the kernel restarted, but
607 does not clear the traces from previous usage of the kernel before it
609 does not clear the traces from previous usage of the kernel before it
608 was restarted. With ``clear=True``, it is similar to ``%clear``, but
610 was restarted. With ``clear=True``, it is similar to ``%clear``, but
609 also re-writes the banner and aborts execution if necessary.
611 also re-writes the banner and aborts execution if necessary.
610 """
612 """
611 if self._executing:
613 if self._executing:
612 self._executing = False
614 self._executing = False
613 self._request_info['execute'] = {}
615 self._request_info['execute'] = {}
614 self._reading = False
616 self._reading = False
615 self._highlighter.highlighting_on = False
617 self._highlighter.highlighting_on = False
616
618
617 if clear:
619 if clear:
618 self._control.clear()
620 self._control.clear()
619 if self._display_banner:
621 if self._display_banner:
620 self._append_plain_text(self.banner)
622 self._append_plain_text(self.banner)
621 if self.kernel_banner:
623 if self.kernel_banner:
622 self._append_plain_text(self.kernel_banner)
624 self._append_plain_text(self.kernel_banner)
623
625
624 # update output marker for stdout/stderr, so that startup
626 # update output marker for stdout/stderr, so that startup
625 # messages appear after banner:
627 # messages appear after banner:
626 self._append_before_prompt_pos = self._get_cursor().position()
628 self._append_before_prompt_pos = self._get_cursor().position()
627 self._show_interpreter_prompt()
629 self._show_interpreter_prompt()
628
630
629 def restart_kernel(self, message, now=False):
631 def restart_kernel(self, message, now=False):
630 """ Attempts to restart the running kernel.
632 """ Attempts to restart the running kernel.
631 """
633 """
632 # FIXME: now should be configurable via a checkbox in the dialog. Right
634 # FIXME: now should be configurable via a checkbox in the dialog. Right
633 # now at least the heartbeat path sets it to True and the manual restart
635 # now at least the heartbeat path sets it to True and the manual restart
634 # to False. But those should just be the pre-selected states of a
636 # to False. But those should just be the pre-selected states of a
635 # checkbox that the user could override if so desired. But I don't know
637 # checkbox that the user could override if so desired. But I don't know
636 # enough Qt to go implementing the checkbox now.
638 # enough Qt to go implementing the checkbox now.
637
639
638 if self.custom_restart:
640 if self.custom_restart:
639 self.custom_restart_requested.emit()
641 self.custom_restart_requested.emit()
640 return
642 return
641
643
642 if self.kernel_manager:
644 if self.kernel_manager:
643 # Pause the heart beat channel to prevent further warnings.
645 # Pause the heart beat channel to prevent further warnings.
644 self.kernel_client.hb_channel.pause()
646 self.kernel_client.hb_channel.pause()
645
647
646 # Prompt the user to restart the kernel. Un-pause the heartbeat if
648 # Prompt the user to restart the kernel. Un-pause the heartbeat if
647 # they decline. (If they accept, the heartbeat will be un-paused
649 # they decline. (If they accept, the heartbeat will be un-paused
648 # automatically when the kernel is restarted.)
650 # automatically when the kernel is restarted.)
649 if self.confirm_restart:
651 if self.confirm_restart:
650 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
652 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
651 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
653 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
652 message, buttons)
654 message, buttons)
653 do_restart = result == QtGui.QMessageBox.Yes
655 do_restart = result == QtGui.QMessageBox.Yes
654 else:
656 else:
655 # confirm_restart is False, so we don't need to ask user
657 # confirm_restart is False, so we don't need to ask user
656 # anything, just do the restart
658 # anything, just do the restart
657 do_restart = True
659 do_restart = True
658 if do_restart:
660 if do_restart:
659 try:
661 try:
660 self.kernel_manager.restart_kernel(now=now)
662 self.kernel_manager.restart_kernel(now=now)
661 except RuntimeError as e:
663 except RuntimeError as e:
662 self._append_plain_text(
664 self._append_plain_text(
663 'Error restarting kernel: %s\n' % e,
665 'Error restarting kernel: %s\n' % e,
664 before_prompt=True
666 before_prompt=True
665 )
667 )
666 else:
668 else:
667 self._append_html("<br>Restarting kernel...\n<hr><br>",
669 self._append_html("<br>Restarting kernel...\n<hr><br>",
668 before_prompt=True,
670 before_prompt=True,
669 )
671 )
670 else:
672 else:
671 self.kernel_client.hb_channel.unpause()
673 self.kernel_client.hb_channel.unpause()
672
674
673 else:
675 else:
674 self._append_plain_text(
676 self._append_plain_text(
675 'Cannot restart a Kernel I did not start\n',
677 'Cannot restart a Kernel I did not start\n',
676 before_prompt=True
678 before_prompt=True
677 )
679 )
678
680
679 def append_stream(self, text):
681 def append_stream(self, text):
680 """Appends text to the output stream."""
682 """Appends text to the output stream."""
681 # Most consoles treat tabs as being 8 space characters. Convert tabs
683 # Most consoles treat tabs as being 8 space characters. Convert tabs
682 # to spaces so that output looks as expected regardless of this
684 # to spaces so that output looks as expected regardless of this
683 # widget's tab width.
685 # widget's tab width.
684 text = text.expandtabs(8)
686 text = text.expandtabs(8)
685 self._append_plain_text(text, before_prompt=True)
687 self._append_plain_text(text, before_prompt=True)
686 self._control.moveCursor(QtGui.QTextCursor.End)
688 self._control.moveCursor(QtGui.QTextCursor.End)
687
689
688 def flush_clearoutput(self):
690 def flush_clearoutput(self):
689 """If a clearoutput is pending, execute it."""
691 """If a clearoutput is pending, execute it."""
690 if self._pending_clearoutput:
692 if self._pending_clearoutput:
691 self._pending_clearoutput = False
693 self._pending_clearoutput = False
692 self.clear_output()
694 self.clear_output()
693
695
694 def clear_output(self):
696 def clear_output(self):
695 """Clears the current line of output."""
697 """Clears the current line of output."""
696 cursor = self._control.textCursor()
698 cursor = self._control.textCursor()
697 cursor.beginEditBlock()
699 cursor.beginEditBlock()
698 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
700 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
699 cursor.insertText('')
701 cursor.insertText('')
700 cursor.endEditBlock()
702 cursor.endEditBlock()
701
703
702 #---------------------------------------------------------------------------
704 #---------------------------------------------------------------------------
703 # 'FrontendWidget' protected interface
705 # 'FrontendWidget' protected interface
704 #---------------------------------------------------------------------------
706 #---------------------------------------------------------------------------
705
707
706 def _auto_call_tip(self):
708 def _auto_call_tip(self):
707 """Trigger call tip automatically on open parenthesis
709 """Trigger call tip automatically on open parenthesis
708
710
709 Call tips can be requested explcitly with `_call_tip`.
711 Call tips can be requested explcitly with `_call_tip`.
710 """
712 """
711 cursor = self._get_cursor()
713 cursor = self._get_cursor()
712 cursor.movePosition(QtGui.QTextCursor.Left)
714 cursor.movePosition(QtGui.QTextCursor.Left)
713 if cursor.document().characterAt(cursor.position()) == '(':
715 if cursor.document().characterAt(cursor.position()) == '(':
714 # trigger auto call tip on open paren
716 # trigger auto call tip on open paren
715 self._call_tip()
717 self._call_tip()
716
718
717 def _call_tip(self):
719 def _call_tip(self):
718 """Shows a call tip, if appropriate, at the current cursor location."""
720 """Shows a call tip, if appropriate, at the current cursor location."""
719 # Decide if it makes sense to show a call tip
721 # Decide if it makes sense to show a call tip
720 if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
722 if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
721 return False
723 return False
722 cursor_pos = self._get_input_buffer_cursor_pos()
724 cursor_pos = self._get_input_buffer_cursor_pos()
723 code = self.input_buffer
725 code = self.input_buffer
724 # Send the metadata request to the kernel
726 # Send the metadata request to the kernel
725 msg_id = self.kernel_client.inspect(code, cursor_pos)
727 msg_id = self.kernel_client.inspect(code, cursor_pos)
726 pos = self._get_cursor().position()
728 pos = self._get_cursor().position()
727 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
729 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
728 return True
730 return True
729
731
730 def _complete(self):
732 def _complete(self):
731 """ Performs completion at the current cursor location.
733 """ Performs completion at the current cursor location.
732 """
734 """
733 # Send the completion request to the kernel
735 # Send the completion request to the kernel
734 msg_id = self.kernel_client.complete(
736 msg_id = self.kernel_client.complete(
735 code=self.input_buffer,
737 code=self.input_buffer,
736 cursor_pos=self._get_input_buffer_cursor_pos(),
738 cursor_pos=self._get_input_buffer_cursor_pos(),
737 )
739 )
738 pos = self._get_cursor().position()
740 pos = self._get_cursor().position()
739 info = self._CompletionRequest(msg_id, pos)
741 info = self._CompletionRequest(msg_id, pos)
740 self._request_info['complete'] = info
742 self._request_info['complete'] = info
741
743
742 def _process_execute_abort(self, msg):
744 def _process_execute_abort(self, msg):
743 """ Process a reply for an aborted execution request.
745 """ Process a reply for an aborted execution request.
744 """
746 """
745 self._append_plain_text("ERROR: execution aborted\n")
747 self._append_plain_text("ERROR: execution aborted\n")
746
748
747 def _process_execute_error(self, msg):
749 def _process_execute_error(self, msg):
748 """ Process a reply for an execution request that resulted in an error.
750 """ Process a reply for an execution request that resulted in an error.
749 """
751 """
750 content = msg['content']
752 content = msg['content']
751 # If a SystemExit is passed along, this means exit() was called - also
753 # If a SystemExit is passed along, this means exit() was called - also
752 # all the ipython %exit magic syntax of '-k' to be used to keep
754 # all the ipython %exit magic syntax of '-k' to be used to keep
753 # the kernel running
755 # the kernel running
754 if content['ename']=='SystemExit':
756 if content['ename']=='SystemExit':
755 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
757 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
756 self._keep_kernel_on_exit = keepkernel
758 self._keep_kernel_on_exit = keepkernel
757 self.exit_requested.emit(self)
759 self.exit_requested.emit(self)
758 else:
760 else:
759 traceback = ''.join(content['traceback'])
761 traceback = ''.join(content['traceback'])
760 self._append_plain_text(traceback)
762 self._append_plain_text(traceback)
761
763
762 def _process_execute_ok(self, msg):
764 def _process_execute_ok(self, msg):
763 """ Process a reply for a successful execution request.
765 """ Process a reply for a successful execution request.
764 """
766 """
765 payload = msg['content']['payload']
767 payload = msg['content']['payload']
766 for item in payload:
768 for item in payload:
767 if not self._process_execute_payload(item):
769 if not self._process_execute_payload(item):
768 warning = 'Warning: received unknown payload of type %s'
770 warning = 'Warning: received unknown payload of type %s'
769 print(warning % repr(item['source']))
771 print(warning % repr(item['source']))
770
772
771 def _process_execute_payload(self, item):
773 def _process_execute_payload(self, item):
772 """ Process a single payload item from the list of payload items in an
774 """ Process a single payload item from the list of payload items in an
773 execution reply. Returns whether the payload was handled.
775 execution reply. Returns whether the payload was handled.
774 """
776 """
775 # The basic FrontendWidget doesn't handle payloads, as they are a
777 # The basic FrontendWidget doesn't handle payloads, as they are a
776 # mechanism for going beyond the standard Python interpreter model.
778 # mechanism for going beyond the standard Python interpreter model.
777 return False
779 return False
778
780
779 def _show_interpreter_prompt(self):
781 def _show_interpreter_prompt(self):
780 """ Shows a prompt for the interpreter.
782 """ Shows a prompt for the interpreter.
781 """
783 """
782 self._show_prompt('>>> ')
784 self._show_prompt('>>> ')
783
785
784 def _show_interpreter_prompt_for_reply(self, msg):
786 def _show_interpreter_prompt_for_reply(self, msg):
785 """ Shows a prompt for the interpreter given an 'execute_reply' message.
787 """ Shows a prompt for the interpreter given an 'execute_reply' message.
786 """
788 """
787 self._show_interpreter_prompt()
789 self._show_interpreter_prompt()
788
790
789 #------ Signal handlers ----------------------------------------------------
791 #------ Signal handlers ----------------------------------------------------
790
792
791 def _document_contents_change(self, position, removed, added):
793 def _document_contents_change(self, position, removed, added):
792 """ Called whenever the document's content changes. Display a call tip
794 """ Called whenever the document's content changes. Display a call tip
793 if appropriate.
795 if appropriate.
794 """
796 """
795 # Calculate where the cursor should be *after* the change:
797 # Calculate where the cursor should be *after* the change:
796 position += added
798 position += added
797
799
798 document = self._control.document()
800 document = self._control.document()
799 if position == self._get_cursor().position():
801 if position == self._get_cursor().position():
800 self._auto_call_tip()
802 self._auto_call_tip()
801
803
802 #------ Trait default initializers -----------------------------------------
804 #------ Trait default initializers -----------------------------------------
803
805
804 def _banner_default(self):
806 def _banner_default(self):
805 """ Returns the standard Python banner.
807 """ Returns the standard Python banner.
806 """
808 """
807 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
809 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
808 '"license" for more information.'
810 '"license" for more information.'
809 return banner % (sys.version, sys.platform)
811 return banner % (sys.version, sys.platform)
@@ -1,305 +1,305 b''
1 # System library imports
1 # System library imports
2 from IPython.external.qt import QtGui
2 from IPython.external.qt import QtGui
3
3
4 # Local imports
4 # Local imports
5 from IPython.utils.py3compat import unicode_type
5 from IPython.utils.py3compat import unicode_type
6 from IPython.utils.traitlets import Bool
6 from IPython.utils.traitlets import Bool
7 from .console_widget import ConsoleWidget
7 from .console_widget import ConsoleWidget
8
8
9
9
10 class HistoryConsoleWidget(ConsoleWidget):
10 class HistoryConsoleWidget(ConsoleWidget):
11 """ A ConsoleWidget that keeps a history of the commands that have been
11 """ A ConsoleWidget that keeps a history of the commands that have been
12 executed and provides a readline-esque interface to this history.
12 executed and provides a readline-esque interface to this history.
13 """
13 """
14
14
15 #------ Configuration ------------------------------------------------------
15 #------ Configuration ------------------------------------------------------
16
16
17 # If enabled, the input buffer will become "locked" to history movement when
17 # If enabled, the input buffer will become "locked" to history movement when
18 # an edit is made to a multi-line input buffer. To override the lock, use
18 # an edit is made to a multi-line input buffer. To override the lock, use
19 # Shift in conjunction with the standard history cycling keys.
19 # Shift in conjunction with the standard history cycling keys.
20 history_lock = Bool(False, config=True)
20 history_lock = Bool(False, config=True)
21
21
22 #---------------------------------------------------------------------------
22 #---------------------------------------------------------------------------
23 # 'object' interface
23 # 'object' interface
24 #---------------------------------------------------------------------------
24 #---------------------------------------------------------------------------
25
25
26 def __init__(self, *args, **kw):
26 def __init__(self, *args, **kw):
27 super(HistoryConsoleWidget, self).__init__(*args, **kw)
27 super(HistoryConsoleWidget, self).__init__(*args, **kw)
28
28
29 # HistoryConsoleWidget protected variables.
29 # HistoryConsoleWidget protected variables.
30 self._history = []
30 self._history = []
31 self._history_edits = {}
31 self._history_edits = {}
32 self._history_index = 0
32 self._history_index = 0
33 self._history_prefix = ''
33 self._history_prefix = ''
34
34
35 #---------------------------------------------------------------------------
35 #---------------------------------------------------------------------------
36 # 'ConsoleWidget' public interface
36 # 'ConsoleWidget' public interface
37 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
38
38
39 def execute(self, source=None, hidden=False, interactive=False):
39 def execute(self, source=None, hidden=False, interactive=False):
40 """ Reimplemented to the store history.
40 """ Reimplemented to the store history.
41 """
41 """
42 if not hidden:
42 if not hidden:
43 history = self.input_buffer if source is None else source
43 history = self.input_buffer if source is None else source
44
44
45 executed = super(HistoryConsoleWidget, self).execute(
45 executed = super(HistoryConsoleWidget, self).execute(
46 source, hidden, interactive)
46 source, hidden, interactive)
47
47
48 if executed and not hidden:
48 if executed and not hidden:
49 # Save the command unless it was an empty string or was identical
49 # Save the command unless it was an empty string or was identical
50 # to the previous command.
50 # to the previous command.
51 history = history.rstrip()
51 history = history.rstrip()
52 if history and (not self._history or self._history[-1] != history):
52 if history and (not self._history or self._history[-1] != history):
53 self._history.append(history)
53 self._history.append(history)
54
54
55 # Emulate readline: reset all history edits.
55 # Emulate readline: reset all history edits.
56 self._history_edits = {}
56 self._history_edits = {}
57
57
58 # Move the history index to the most recent item.
58 # Move the history index to the most recent item.
59 self._history_index = len(self._history)
59 self._history_index = len(self._history)
60
60
61 return executed
61 return executed
62
62
63 #---------------------------------------------------------------------------
63 #---------------------------------------------------------------------------
64 # 'ConsoleWidget' abstract interface
64 # 'ConsoleWidget' abstract interface
65 #---------------------------------------------------------------------------
65 #---------------------------------------------------------------------------
66
66
67 def _up_pressed(self, shift_modifier):
67 def _up_pressed(self, shift_modifier):
68 """ Called when the up key is pressed. Returns whether to continue
68 """ Called when the up key is pressed. Returns whether to continue
69 processing the event.
69 processing the event.
70 """
70 """
71 prompt_cursor = self._get_prompt_cursor()
71 prompt_cursor = self._get_prompt_cursor()
72 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
72 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
73 # Bail out if we're locked.
73 # Bail out if we're locked.
74 if self._history_locked() and not shift_modifier:
74 if self._history_locked() and not shift_modifier:
75 return False
75 return False
76
76
77 # Set a search prefix based on the cursor position.
77 # Set a search prefix based on the cursor position.
78 col = self._get_input_buffer_cursor_column()
78 col = self._get_input_buffer_cursor_column()
79 input_buffer = self.input_buffer
79 input_buffer = self.input_buffer
80 # use the *shortest* of the cursor column and the history prefix
80 # use the *shortest* of the cursor column and the history prefix
81 # to determine if the prefix has changed
81 # to determine if the prefix has changed
82 n = min(col, len(self._history_prefix))
82 n = min(col, len(self._history_prefix))
83
83
84 # prefix changed, restart search from the beginning
84 # prefix changed, restart search from the beginning
85 if (self._history_prefix[:n] != input_buffer[:n]):
85 if (self._history_prefix[:n] != input_buffer[:n]):
86 self._history_index = len(self._history)
86 self._history_index = len(self._history)
87
87
88 # the only time we shouldn't set the history prefix
88 # the only time we shouldn't set the history prefix
89 # to the line up to the cursor is if we are already
89 # to the line up to the cursor is if we are already
90 # in a simple scroll (no prefix),
90 # in a simple scroll (no prefix),
91 # and the cursor is at the end of the first line
91 # and the cursor is at the end of the first line
92
92
93 # check if we are at the end of the first line
93 # check if we are at the end of the first line
94 c = self._get_cursor()
94 c = self._get_cursor()
95 current_pos = c.position()
95 current_pos = c.position()
96 c.movePosition(QtGui.QTextCursor.EndOfLine)
96 c.movePosition(QtGui.QTextCursor.EndOfLine)
97 at_eol = (c.position() == current_pos)
97 at_eol = (c.position() == current_pos)
98
98
99 if self._history_index == len(self._history) or \
99 if self._history_index == len(self._history) or \
100 not (self._history_prefix == '' and at_eol) or \
100 not (self._history_prefix == '' and at_eol) or \
101 not (self._get_edited_history(self._history_index)[:col] == input_buffer[:col]):
101 not (self._get_edited_history(self._history_index)[:col] == input_buffer[:col]):
102 self._history_prefix = input_buffer[:col]
102 self._history_prefix = input_buffer[:col]
103
103
104 # Perform the search.
104 # Perform the search.
105 self.history_previous(self._history_prefix,
105 self.history_previous(self._history_prefix,
106 as_prefix=not shift_modifier)
106 as_prefix=not shift_modifier)
107
107
108 # Go to the first line of the prompt for seemless history scrolling.
108 # Go to the first line of the prompt for seemless history scrolling.
109 # Emulate readline: keep the cursor position fixed for a prefix
109 # Emulate readline: keep the cursor position fixed for a prefix
110 # search.
110 # search.
111 cursor = self._get_prompt_cursor()
111 cursor = self._get_prompt_cursor()
112 if self._history_prefix:
112 if self._history_prefix:
113 cursor.movePosition(QtGui.QTextCursor.Right,
113 cursor.movePosition(QtGui.QTextCursor.Right,
114 n=len(self._history_prefix))
114 n=len(self._history_prefix))
115 else:
115 else:
116 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
116 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
117 self._set_cursor(cursor)
117 self._set_cursor(cursor)
118
118
119 return False
119 return False
120
120
121 return True
121 return True
122
122
123 def _down_pressed(self, shift_modifier):
123 def _down_pressed(self, shift_modifier):
124 """ Called when the down key is pressed. Returns whether to continue
124 """ Called when the down key is pressed. Returns whether to continue
125 processing the event.
125 processing the event.
126 """
126 """
127 end_cursor = self._get_end_cursor()
127 end_cursor = self._get_end_cursor()
128 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
128 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
129 # Bail out if we're locked.
129 # Bail out if we're locked.
130 if self._history_locked() and not shift_modifier:
130 if self._history_locked() and not shift_modifier:
131 return False
131 return False
132
132
133 # Perform the search.
133 # Perform the search.
134 replaced = self.history_next(self._history_prefix,
134 replaced = self.history_next(self._history_prefix,
135 as_prefix=not shift_modifier)
135 as_prefix=not shift_modifier)
136
136
137 # Emulate readline: keep the cursor position fixed for a prefix
137 # Emulate readline: keep the cursor position fixed for a prefix
138 # search. (We don't need to move the cursor to the end of the buffer
138 # search. (We don't need to move the cursor to the end of the buffer
139 # in the other case because this happens automatically when the
139 # in the other case because this happens automatically when the
140 # input buffer is set.)
140 # input buffer is set.)
141 if self._history_prefix and replaced:
141 if self._history_prefix and replaced:
142 cursor = self._get_prompt_cursor()
142 cursor = self._get_prompt_cursor()
143 cursor.movePosition(QtGui.QTextCursor.Right,
143 cursor.movePosition(QtGui.QTextCursor.Right,
144 n=len(self._history_prefix))
144 n=len(self._history_prefix))
145 self._set_cursor(cursor)
145 self._set_cursor(cursor)
146
146
147 return False
147 return False
148
148
149 return True
149 return True
150
150
151 #---------------------------------------------------------------------------
151 #---------------------------------------------------------------------------
152 # 'HistoryConsoleWidget' public interface
152 # 'HistoryConsoleWidget' public interface
153 #---------------------------------------------------------------------------
153 #---------------------------------------------------------------------------
154
154
155 def history_previous(self, substring='', as_prefix=True):
155 def history_previous(self, substring='', as_prefix=True):
156 """ If possible, set the input buffer to a previous history item.
156 """ If possible, set the input buffer to a previous history item.
157
157
158 Parameters
158 Parameters
159 ----------
159 ----------
160 substring : str, optional
160 substring : str, optional
161 If specified, search for an item with this substring.
161 If specified, search for an item with this substring.
162 as_prefix : bool, optional
162 as_prefix : bool, optional
163 If True, the substring must match at the beginning (default).
163 If True, the substring must match at the beginning (default).
164
164
165 Returns
165 Returns
166 -------
166 -------
167 Whether the input buffer was changed.
167 Whether the input buffer was changed.
168 """
168 """
169 index = self._history_index
169 index = self._history_index
170 replace = False
170 replace = False
171 while index > 0:
171 while index > 0:
172 index -= 1
172 index -= 1
173 history = self._get_edited_history(index)
173 history = self._get_edited_history(index)
174 if (as_prefix and history.startswith(substring)) \
174 if (as_prefix and history.startswith(substring)) \
175 or (not as_prefix and substring in history):
175 or (not as_prefix and substring in history):
176 replace = True
176 replace = True
177 break
177 break
178
178
179 if replace:
179 if replace:
180 self._store_edits()
180 self._store_edits()
181 self._history_index = index
181 self._history_index = index
182 self.input_buffer = history
182 self.input_buffer = history
183
183
184 return replace
184 return replace
185
185
186 def history_next(self, substring='', as_prefix=True):
186 def history_next(self, substring='', as_prefix=True):
187 """ If possible, set the input buffer to a subsequent history item.
187 """ If possible, set the input buffer to a subsequent history item.
188
188
189 Parameters
189 Parameters
190 ----------
190 ----------
191 substring : str, optional
191 substring : str, optional
192 If specified, search for an item with this substring.
192 If specified, search for an item with this substring.
193 as_prefix : bool, optional
193 as_prefix : bool, optional
194 If True, the substring must match at the beginning (default).
194 If True, the substring must match at the beginning (default).
195
195
196 Returns
196 Returns
197 -------
197 -------
198 Whether the input buffer was changed.
198 Whether the input buffer was changed.
199 """
199 """
200 index = self._history_index
200 index = self._history_index
201 replace = False
201 replace = False
202 while index < len(self._history):
202 while index < len(self._history):
203 index += 1
203 index += 1
204 history = self._get_edited_history(index)
204 history = self._get_edited_history(index)
205 if (as_prefix and history.startswith(substring)) \
205 if (as_prefix and history.startswith(substring)) \
206 or (not as_prefix and substring in history):
206 or (not as_prefix and substring in history):
207 replace = True
207 replace = True
208 break
208 break
209
209
210 if replace:
210 if replace:
211 self._store_edits()
211 self._store_edits()
212 self._history_index = index
212 self._history_index = index
213 self.input_buffer = history
213 self.input_buffer = history
214
214
215 return replace
215 return replace
216
216
217 def history_tail(self, n=10):
217 def history_tail(self, n=10):
218 """ Get the local history list.
218 """ Get the local history list.
219
219
220 Parameters
220 Parameters
221 ----------
221 ----------
222 n : int
222 n : int
223 The (maximum) number of history items to get.
223 The (maximum) number of history items to get.
224 """
224 """
225 return self._history[-n:]
225 return self._history[-n:]
226
226
227 def _request_update_session_history_length(self):
227 def _request_update_session_history_length(self):
228 msg_id = self.kernel_client.shell_channel.execute('',
228 msg_id = self.kernel_client.execute('',
229 silent=True,
229 silent=True,
230 user_expressions={
230 user_expressions={
231 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
231 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
232 }
232 }
233 )
233 )
234 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')
234 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')
235
235
236 def _handle_execute_reply(self, msg):
236 def _handle_execute_reply(self, msg):
237 """ Handles replies for code execution, here only session history length
237 """ Handles replies for code execution, here only session history length
238 """
238 """
239 msg_id = msg['parent_header']['msg_id']
239 msg_id = msg['parent_header']['msg_id']
240 info = self._request_info['execute'].pop(msg_id,None)
240 info = self._request_info['execute'].pop(msg_id,None)
241 if info and info.kind == 'save_magic' and not self._hidden:
241 if info and info.kind == 'save_magic' and not self._hidden:
242 content = msg['content']
242 content = msg['content']
243 status = content['status']
243 status = content['status']
244 if status == 'ok':
244 if status == 'ok':
245 self._max_session_history = int(
245 self._max_session_history = int(
246 content['user_expressions']['hlen']['data']['text/plain']
246 content['user_expressions']['hlen']['data']['text/plain']
247 )
247 )
248
248
249 def save_magic(self):
249 def save_magic(self):
250 # update the session history length
250 # update the session history length
251 self._request_update_session_history_length()
251 self._request_update_session_history_length()
252
252
253 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
253 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
254 "Enter A filename",
254 "Enter A filename",
255 filter='Python File (*.py);; All files (*.*)'
255 filter='Python File (*.py);; All files (*.*)'
256 )
256 )
257
257
258 # let's the user search/type for a file name, while the history length
258 # let's the user search/type for a file name, while the history length
259 # is fetched
259 # is fetched
260
260
261 if file_name:
261 if file_name:
262 hist_range, ok = QtGui.QInputDialog.getText(self,
262 hist_range, ok = QtGui.QInputDialog.getText(self,
263 'Please enter an interval of command to save',
263 'Please enter an interval of command to save',
264 'Saving commands:',
264 'Saving commands:',
265 text=str('1-'+str(self._max_session_history))
265 text=str('1-'+str(self._max_session_history))
266 )
266 )
267 if ok:
267 if ok:
268 self.execute("%save"+" "+file_name+" "+str(hist_range))
268 self.execute("%save"+" "+file_name+" "+str(hist_range))
269
269
270 #---------------------------------------------------------------------------
270 #---------------------------------------------------------------------------
271 # 'HistoryConsoleWidget' protected interface
271 # 'HistoryConsoleWidget' protected interface
272 #---------------------------------------------------------------------------
272 #---------------------------------------------------------------------------
273
273
274 def _history_locked(self):
274 def _history_locked(self):
275 """ Returns whether history movement is locked.
275 """ Returns whether history movement is locked.
276 """
276 """
277 return (self.history_lock and
277 return (self.history_lock and
278 (self._get_edited_history(self._history_index) !=
278 (self._get_edited_history(self._history_index) !=
279 self.input_buffer) and
279 self.input_buffer) and
280 (self._get_prompt_cursor().blockNumber() !=
280 (self._get_prompt_cursor().blockNumber() !=
281 self._get_end_cursor().blockNumber()))
281 self._get_end_cursor().blockNumber()))
282
282
283 def _get_edited_history(self, index):
283 def _get_edited_history(self, index):
284 """ Retrieves a history item, possibly with temporary edits.
284 """ Retrieves a history item, possibly with temporary edits.
285 """
285 """
286 if index in self._history_edits:
286 if index in self._history_edits:
287 return self._history_edits[index]
287 return self._history_edits[index]
288 elif index == len(self._history):
288 elif index == len(self._history):
289 return unicode_type()
289 return unicode_type()
290 return self._history[index]
290 return self._history[index]
291
291
292 def _set_history(self, history):
292 def _set_history(self, history):
293 """ Replace the current history with a sequence of history items.
293 """ Replace the current history with a sequence of history items.
294 """
294 """
295 self._history = list(history)
295 self._history = list(history)
296 self._history_edits = {}
296 self._history_edits = {}
297 self._history_index = len(self._history)
297 self._history_index = len(self._history)
298
298
299 def _store_edits(self):
299 def _store_edits(self):
300 """ If there are edits to the current input buffer, store them.
300 """ If there are edits to the current input buffer, store them.
301 """
301 """
302 current = self.input_buffer
302 current = self.input_buffer
303 if self._history_index == len(self._history) or \
303 if self._history_index == len(self._history) or \
304 self._history[self._history_index] != current:
304 self._history[self._history_index] != current:
305 self._history_edits[self._history_index] = current
305 self._history_edits[self._history_index] = current
@@ -1,599 +1,598 b''
1 """A FrontendWidget that emulates the interface of the console IPython.
1 """A FrontendWidget that emulates the interface of the console IPython.
2
2
3 This supports the additional functionality provided by the IPython kernel.
3 This supports the additional functionality provided by the IPython kernel.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 from collections import namedtuple
9 from collections import namedtuple
10 import os.path
10 import os.path
11 import re
11 import re
12 from subprocess import Popen
12 from subprocess import Popen
13 import sys
13 import sys
14 import time
14 import time
15 from textwrap import dedent
15 from textwrap import dedent
16
16
17 from IPython.external.qt import QtCore, QtGui
17 from IPython.external.qt import QtCore, QtGui
18
18
19 from IPython.core.inputsplitter import IPythonInputSplitter
19 from IPython.core.inputsplitter import IPythonInputSplitter
20 from IPython.core.release import version
20 from IPython.core.release import version
21 from IPython.core.inputtransformer import ipy_prompt
21 from IPython.core.inputtransformer import ipy_prompt
22 from IPython.utils.traitlets import Bool, Unicode
22 from IPython.utils.traitlets import Bool, Unicode
23 from .frontend_widget import FrontendWidget
23 from .frontend_widget import FrontendWidget
24 from . import styles
24 from . import styles
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Constants
27 # Constants
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 # Default strings to build and display input and output prompts (and separators
30 # Default strings to build and display input and output prompts (and separators
31 # in between)
31 # in between)
32 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
32 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
33 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
33 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
34 default_input_sep = '\n'
34 default_input_sep = '\n'
35 default_output_sep = ''
35 default_output_sep = ''
36 default_output_sep2 = ''
36 default_output_sep2 = ''
37
37
38 # Base path for most payload sources.
38 # Base path for most payload sources.
39 zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
39 zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
40
40
41 if sys.platform.startswith('win'):
41 if sys.platform.startswith('win'):
42 default_editor = 'notepad'
42 default_editor = 'notepad'
43 else:
43 else:
44 default_editor = ''
44 default_editor = ''
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # IPythonWidget class
47 # IPythonWidget class
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50 class IPythonWidget(FrontendWidget):
50 class IPythonWidget(FrontendWidget):
51 """ A FrontendWidget for an IPython kernel.
51 """ A FrontendWidget for an IPython kernel.
52 """
52 """
53
53
54 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
54 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
55 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
55 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
56 # settings.
56 # settings.
57 custom_edit = Bool(False)
57 custom_edit = Bool(False)
58 custom_edit_requested = QtCore.Signal(object, object)
58 custom_edit_requested = QtCore.Signal(object, object)
59
59
60 editor = Unicode(default_editor, config=True,
60 editor = Unicode(default_editor, config=True,
61 help="""
61 help="""
62 A command for invoking a system text editor. If the string contains a
62 A command for invoking a system text editor. If the string contains a
63 {filename} format specifier, it will be used. Otherwise, the filename
63 {filename} format specifier, it will be used. Otherwise, the filename
64 will be appended to the end the command.
64 will be appended to the end the command.
65 """)
65 """)
66
66
67 editor_line = Unicode(config=True,
67 editor_line = Unicode(config=True,
68 help="""
68 help="""
69 The editor command to use when a specific line number is requested. The
69 The editor command to use when a specific line number is requested. The
70 string should contain two format specifiers: {line} and {filename}. If
70 string should contain two format specifiers: {line} and {filename}. If
71 this parameter is not specified, the line number option to the %edit
71 this parameter is not specified, the line number option to the %edit
72 magic will be ignored.
72 magic will be ignored.
73 """)
73 """)
74
74
75 style_sheet = Unicode(config=True,
75 style_sheet = Unicode(config=True,
76 help="""
76 help="""
77 A CSS stylesheet. The stylesheet can contain classes for:
77 A CSS stylesheet. The stylesheet can contain classes for:
78 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
78 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
79 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
79 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
80 3. IPython: .error, .in-prompt, .out-prompt, etc
80 3. IPython: .error, .in-prompt, .out-prompt, etc
81 """)
81 """)
82
82
83 syntax_style = Unicode(config=True,
83 syntax_style = Unicode(config=True,
84 help="""
84 help="""
85 If not empty, use this Pygments style for syntax highlighting.
85 If not empty, use this Pygments style for syntax highlighting.
86 Otherwise, the style sheet is queried for Pygments style
86 Otherwise, the style sheet is queried for Pygments style
87 information.
87 information.
88 """)
88 """)
89
89
90 # Prompts.
90 # Prompts.
91 in_prompt = Unicode(default_in_prompt, config=True)
91 in_prompt = Unicode(default_in_prompt, config=True)
92 out_prompt = Unicode(default_out_prompt, config=True)
92 out_prompt = Unicode(default_out_prompt, config=True)
93 input_sep = Unicode(default_input_sep, config=True)
93 input_sep = Unicode(default_input_sep, config=True)
94 output_sep = Unicode(default_output_sep, config=True)
94 output_sep = Unicode(default_output_sep, config=True)
95 output_sep2 = Unicode(default_output_sep2, config=True)
95 output_sep2 = Unicode(default_output_sep2, config=True)
96
96
97 # FrontendWidget protected class variables.
97 # FrontendWidget protected class variables.
98 _input_splitter_class = IPythonInputSplitter
98 _input_splitter_class = IPythonInputSplitter
99 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
99 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
100 logical_line_transforms=[],
100 logical_line_transforms=[],
101 python_line_transforms=[],
101 python_line_transforms=[],
102 )
102 )
103
103
104 # IPythonWidget protected class variables.
104 # IPythonWidget protected class variables.
105 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
105 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
106 _payload_source_edit = 'edit'
106 _payload_source_edit = 'edit'
107 _payload_source_exit = 'ask_exit'
107 _payload_source_exit = 'ask_exit'
108 _payload_source_next_input = 'set_next_input'
108 _payload_source_next_input = 'set_next_input'
109 _payload_source_page = 'page'
109 _payload_source_page = 'page'
110 _retrying_history_request = False
110 _retrying_history_request = False
111 _starting = False
111 _starting = False
112
112
113 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
114 # 'object' interface
114 # 'object' interface
115 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
116
116
117 def __init__(self, *args, **kw):
117 def __init__(self, *args, **kw):
118 super(IPythonWidget, self).__init__(*args, **kw)
118 super(IPythonWidget, self).__init__(*args, **kw)
119
119
120 # IPythonWidget protected variables.
120 # IPythonWidget protected variables.
121 self._payload_handlers = {
121 self._payload_handlers = {
122 self._payload_source_edit : self._handle_payload_edit,
122 self._payload_source_edit : self._handle_payload_edit,
123 self._payload_source_exit : self._handle_payload_exit,
123 self._payload_source_exit : self._handle_payload_exit,
124 self._payload_source_page : self._handle_payload_page,
124 self._payload_source_page : self._handle_payload_page,
125 self._payload_source_next_input : self._handle_payload_next_input }
125 self._payload_source_next_input : self._handle_payload_next_input }
126 self._previous_prompt_obj = None
126 self._previous_prompt_obj = None
127 self._keep_kernel_on_exit = None
127 self._keep_kernel_on_exit = None
128
128
129 # Initialize widget styling.
129 # Initialize widget styling.
130 if self.style_sheet:
130 if self.style_sheet:
131 self._style_sheet_changed()
131 self._style_sheet_changed()
132 self._syntax_style_changed()
132 self._syntax_style_changed()
133 else:
133 else:
134 self.set_default_style()
134 self.set_default_style()
135
135
136 self._guiref_loaded = False
136 self._guiref_loaded = False
137
137
138 #---------------------------------------------------------------------------
138 #---------------------------------------------------------------------------
139 # 'BaseFrontendMixin' abstract interface
139 # 'BaseFrontendMixin' abstract interface
140 #---------------------------------------------------------------------------
140 #---------------------------------------------------------------------------
141 def _handle_complete_reply(self, rep):
141 def _handle_complete_reply(self, rep):
142 """ Reimplemented to support IPython's improved completion machinery.
142 """ Reimplemented to support IPython's improved completion machinery.
143 """
143 """
144 self.log.debug("complete: %s", rep.get('content', ''))
144 self.log.debug("complete: %s", rep.get('content', ''))
145 cursor = self._get_cursor()
145 cursor = self._get_cursor()
146 info = self._request_info.get('complete')
146 info = self._request_info.get('complete')
147 if info and info.id == rep['parent_header']['msg_id'] and \
147 if info and info.id == rep['parent_header']['msg_id'] and \
148 info.pos == cursor.position():
148 info.pos == cursor.position():
149 content = rep['content']
149 content = rep['content']
150 matches = content['matches']
150 matches = content['matches']
151 start = content['cursor_start']
151 start = content['cursor_start']
152 end = content['cursor_end']
152 end = content['cursor_end']
153
153
154 start = max(start, 0)
154 start = max(start, 0)
155 end = max(end, start)
155 end = max(end, start)
156
156
157 # Move the control's cursor to the desired end point
157 # Move the control's cursor to the desired end point
158 cursor_pos = self._get_input_buffer_cursor_pos()
158 cursor_pos = self._get_input_buffer_cursor_pos()
159 if end < cursor_pos:
159 if end < cursor_pos:
160 cursor.movePosition(QtGui.QTextCursor.Left,
160 cursor.movePosition(QtGui.QTextCursor.Left,
161 n=(cursor_pos - end))
161 n=(cursor_pos - end))
162 elif end > cursor_pos:
162 elif end > cursor_pos:
163 cursor.movePosition(QtGui.QTextCursor.Right,
163 cursor.movePosition(QtGui.QTextCursor.Right,
164 n=(end - cursor_pos))
164 n=(end - cursor_pos))
165 # This line actually applies the move to control's cursor
165 # This line actually applies the move to control's cursor
166 self._control.setTextCursor(cursor)
166 self._control.setTextCursor(cursor)
167
167
168 offset = end - start
168 offset = end - start
169 # Move the local cursor object to the start of the match and
169 # Move the local cursor object to the start of the match and
170 # complete.
170 # complete.
171 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
171 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
172 self._complete_with_items(cursor, matches)
172 self._complete_with_items(cursor, matches)
173
173
174 def _handle_execute_reply(self, msg):
174 def _handle_execute_reply(self, msg):
175 """ Reimplemented to support prompt requests.
175 """ Reimplemented to support prompt requests.
176 """
176 """
177 msg_id = msg['parent_header'].get('msg_id')
177 msg_id = msg['parent_header'].get('msg_id')
178 info = self._request_info['execute'].get(msg_id)
178 info = self._request_info['execute'].get(msg_id)
179 if info and info.kind == 'prompt':
179 if info and info.kind == 'prompt':
180 content = msg['content']
180 content = msg['content']
181 if content['status'] == 'aborted':
181 if content['status'] == 'aborted':
182 self._show_interpreter_prompt()
182 self._show_interpreter_prompt()
183 else:
183 else:
184 number = content['execution_count'] + 1
184 number = content['execution_count'] + 1
185 self._show_interpreter_prompt(number)
185 self._show_interpreter_prompt(number)
186 self._request_info['execute'].pop(msg_id)
186 self._request_info['execute'].pop(msg_id)
187 else:
187 else:
188 super(IPythonWidget, self)._handle_execute_reply(msg)
188 super(IPythonWidget, self)._handle_execute_reply(msg)
189
189
190 def _handle_history_reply(self, msg):
190 def _handle_history_reply(self, msg):
191 """ Implemented to handle history tail replies, which are only supported
191 """ Implemented to handle history tail replies, which are only supported
192 by the IPython kernel.
192 by the IPython kernel.
193 """
193 """
194 content = msg['content']
194 content = msg['content']
195 if 'history' not in content:
195 if 'history' not in content:
196 self.log.error("History request failed: %r"%content)
196 self.log.error("History request failed: %r"%content)
197 if content.get('status', '') == 'aborted' and \
197 if content.get('status', '') == 'aborted' and \
198 not self._retrying_history_request:
198 not self._retrying_history_request:
199 # a *different* action caused this request to be aborted, so
199 # a *different* action caused this request to be aborted, so
200 # we should try again.
200 # we should try again.
201 self.log.error("Retrying aborted history request")
201 self.log.error("Retrying aborted history request")
202 # prevent multiple retries of aborted requests:
202 # prevent multiple retries of aborted requests:
203 self._retrying_history_request = True
203 self._retrying_history_request = True
204 # wait out the kernel's queue flush, which is currently timed at 0.1s
204 # wait out the kernel's queue flush, which is currently timed at 0.1s
205 time.sleep(0.25)
205 time.sleep(0.25)
206 self.kernel_client.shell_channel.history(hist_access_type='tail',n=1000)
206 self.kernel_client.history(hist_access_type='tail',n=1000)
207 else:
207 else:
208 self._retrying_history_request = False
208 self._retrying_history_request = False
209 return
209 return
210 # reset retry flag
210 # reset retry flag
211 self._retrying_history_request = False
211 self._retrying_history_request = False
212 history_items = content['history']
212 history_items = content['history']
213 self.log.debug("Received history reply with %i entries", len(history_items))
213 self.log.debug("Received history reply with %i entries", len(history_items))
214 items = []
214 items = []
215 last_cell = u""
215 last_cell = u""
216 for _, _, cell in history_items:
216 for _, _, cell in history_items:
217 cell = cell.rstrip()
217 cell = cell.rstrip()
218 if cell != last_cell:
218 if cell != last_cell:
219 items.append(cell)
219 items.append(cell)
220 last_cell = cell
220 last_cell = cell
221 self._set_history(items)
221 self._set_history(items)
222
222
223 def _insert_other_input(self, cursor, content):
223 def _insert_other_input(self, cursor, content):
224 """Insert function for input from other frontends"""
224 """Insert function for input from other frontends"""
225 cursor.beginEditBlock()
225 cursor.beginEditBlock()
226 start = cursor.position()
226 start = cursor.position()
227 n = content.get('execution_count', 0)
227 n = content.get('execution_count', 0)
228 cursor.insertText('\n')
228 cursor.insertText('\n')
229 self._insert_html(cursor, self._make_in_prompt(n))
229 self._insert_html(cursor, self._make_in_prompt(n))
230 cursor.insertText(content['code'])
230 cursor.insertText(content['code'])
231 self._highlighter.rehighlightBlock(cursor.block())
231 self._highlighter.rehighlightBlock(cursor.block())
232 cursor.endEditBlock()
232 cursor.endEditBlock()
233
233
234 def _handle_execute_input(self, msg):
234 def _handle_execute_input(self, msg):
235 """Handle an execute_input message"""
235 """Handle an execute_input message"""
236 self.log.debug("execute_input: %s", msg.get('content', ''))
236 self.log.debug("execute_input: %s", msg.get('content', ''))
237 if self.include_output(msg):
237 if self.include_output(msg):
238 self._append_custom(self._insert_other_input, msg['content'], before_prompt=True)
238 self._append_custom(self._insert_other_input, msg['content'], before_prompt=True)
239
239
240
240
241 def _handle_execute_result(self, msg):
241 def _handle_execute_result(self, msg):
242 """ Reimplemented for IPython-style "display hook".
242 """ Reimplemented for IPython-style "display hook".
243 """
243 """
244 self.log.debug("execute_result: %s", msg.get('content', ''))
244 self.log.debug("execute_result: %s", msg.get('content', ''))
245 if self.include_output(msg):
245 if self.include_output(msg):
246 self.flush_clearoutput()
246 self.flush_clearoutput()
247 content = msg['content']
247 content = msg['content']
248 prompt_number = content.get('execution_count', 0)
248 prompt_number = content.get('execution_count', 0)
249 data = content['data']
249 data = content['data']
250 if 'text/plain' in data:
250 if 'text/plain' in data:
251 self._append_plain_text(self.output_sep, True)
251 self._append_plain_text(self.output_sep, True)
252 self._append_html(self._make_out_prompt(prompt_number), True)
252 self._append_html(self._make_out_prompt(prompt_number), True)
253 text = data['text/plain']
253 text = data['text/plain']
254 # If the repr is multiline, make sure we start on a new line,
254 # If the repr is multiline, make sure we start on a new line,
255 # so that its lines are aligned.
255 # so that its lines are aligned.
256 if "\n" in text and not self.output_sep.endswith("\n"):
256 if "\n" in text and not self.output_sep.endswith("\n"):
257 self._append_plain_text('\n', True)
257 self._append_plain_text('\n', True)
258 self._append_plain_text(text + self.output_sep2, True)
258 self._append_plain_text(text + self.output_sep2, True)
259
259
260 def _handle_display_data(self, msg):
260 def _handle_display_data(self, msg):
261 """ The base handler for the ``display_data`` message.
261 """ The base handler for the ``display_data`` message.
262 """
262 """
263 self.log.debug("display: %s", msg.get('content', ''))
263 self.log.debug("display: %s", msg.get('content', ''))
264 # For now, we don't display data from other frontends, but we
264 # For now, we don't display data from other frontends, but we
265 # eventually will as this allows all frontends to monitor the display
265 # eventually will as this allows all frontends to monitor the display
266 # data. But we need to figure out how to handle this in the GUI.
266 # data. But we need to figure out how to handle this in the GUI.
267 if self.include_output(msg):
267 if self.include_output(msg):
268 self.flush_clearoutput()
268 self.flush_clearoutput()
269 data = msg['content']['data']
269 data = msg['content']['data']
270 metadata = msg['content']['metadata']
270 metadata = msg['content']['metadata']
271 # In the regular IPythonWidget, we simply print the plain text
271 # In the regular IPythonWidget, we simply print the plain text
272 # representation.
272 # representation.
273 if 'text/plain' in data:
273 if 'text/plain' in data:
274 text = data['text/plain']
274 text = data['text/plain']
275 self._append_plain_text(text, True)
275 self._append_plain_text(text, True)
276 # This newline seems to be needed for text and html output.
276 # This newline seems to be needed for text and html output.
277 self._append_plain_text(u'\n', True)
277 self._append_plain_text(u'\n', True)
278
278
279 def _handle_kernel_info_reply(self, rep):
279 def _handle_kernel_info_reply(self, rep):
280 """Handle kernel info replies."""
280 """Handle kernel info replies."""
281 content = rep['content']
281 content = rep['content']
282 if not self._guiref_loaded:
282 if not self._guiref_loaded:
283 if content.get('language') == 'python':
283 if content.get('language') == 'python':
284 self._load_guiref_magic()
284 self._load_guiref_magic()
285 self._guiref_loaded = True
285 self._guiref_loaded = True
286
286
287 self.kernel_banner = content.get('banner', '')
287 self.kernel_banner = content.get('banner', '')
288 if self._starting:
288 if self._starting:
289 # finish handling started channels
289 # finish handling started channels
290 self._starting = False
290 self._starting = False
291 super(IPythonWidget, self)._started_channels()
291 super(IPythonWidget, self)._started_channels()
292
292
293 def _started_channels(self):
293 def _started_channels(self):
294 """Reimplemented to make a history request and load %guiref."""
294 """Reimplemented to make a history request and load %guiref."""
295 self._starting = True
295 self._starting = True
296 # The reply will trigger %guiref load provided language=='python'
296 # The reply will trigger %guiref load provided language=='python'
297 self.kernel_client.kernel_info()
297 self.kernel_client.kernel_info()
298
298
299 self.kernel_client.shell_channel.history(hist_access_type='tail',
299 self.kernel_client.history(hist_access_type='tail', n=1000)
300 n=1000)
301
300
302 def _load_guiref_magic(self):
301 def _load_guiref_magic(self):
303 """Load %guiref magic."""
302 """Load %guiref magic."""
304 self.kernel_client.shell_channel.execute('\n'.join([
303 self.kernel_client.execute('\n'.join([
305 "try:",
304 "try:",
306 " _usage",
305 " _usage",
307 "except:",
306 "except:",
308 " from IPython.core import usage as _usage",
307 " from IPython.core import usage as _usage",
309 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
308 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
310 " del _usage",
309 " del _usage",
311 ]), silent=True)
310 ]), silent=True)
312
311
313 #---------------------------------------------------------------------------
312 #---------------------------------------------------------------------------
314 # 'ConsoleWidget' public interface
313 # 'ConsoleWidget' public interface
315 #---------------------------------------------------------------------------
314 #---------------------------------------------------------------------------
316
315
317 #---------------------------------------------------------------------------
316 #---------------------------------------------------------------------------
318 # 'FrontendWidget' public interface
317 # 'FrontendWidget' public interface
319 #---------------------------------------------------------------------------
318 #---------------------------------------------------------------------------
320
319
321 def execute_file(self, path, hidden=False):
320 def execute_file(self, path, hidden=False):
322 """ Reimplemented to use the 'run' magic.
321 """ Reimplemented to use the 'run' magic.
323 """
322 """
324 # Use forward slashes on Windows to avoid escaping each separator.
323 # Use forward slashes on Windows to avoid escaping each separator.
325 if sys.platform == 'win32':
324 if sys.platform == 'win32':
326 path = os.path.normpath(path).replace('\\', '/')
325 path = os.path.normpath(path).replace('\\', '/')
327
326
328 # Perhaps we should not be using %run directly, but while we
327 # Perhaps we should not be using %run directly, but while we
329 # are, it is necessary to quote or escape filenames containing spaces
328 # are, it is necessary to quote or escape filenames containing spaces
330 # or quotes.
329 # or quotes.
331
330
332 # In earlier code here, to minimize escaping, we sometimes quoted the
331 # In earlier code here, to minimize escaping, we sometimes quoted the
333 # filename with single quotes. But to do this, this code must be
332 # filename with single quotes. But to do this, this code must be
334 # platform-aware, because run uses shlex rather than python string
333 # platform-aware, because run uses shlex rather than python string
335 # parsing, so that:
334 # parsing, so that:
336 # * In Win: single quotes can be used in the filename without quoting,
335 # * In Win: single quotes can be used in the filename without quoting,
337 # and we cannot use single quotes to quote the filename.
336 # and we cannot use single quotes to quote the filename.
338 # * In *nix: we can escape double quotes in a double quoted filename,
337 # * In *nix: we can escape double quotes in a double quoted filename,
339 # but can't escape single quotes in a single quoted filename.
338 # but can't escape single quotes in a single quoted filename.
340
339
341 # So to keep this code non-platform-specific and simple, we now only
340 # So to keep this code non-platform-specific and simple, we now only
342 # use double quotes to quote filenames, and escape when needed:
341 # use double quotes to quote filenames, and escape when needed:
343 if ' ' in path or "'" in path or '"' in path:
342 if ' ' in path or "'" in path or '"' in path:
344 path = '"%s"' % path.replace('"', '\\"')
343 path = '"%s"' % path.replace('"', '\\"')
345 self.execute('%%run %s' % path, hidden=hidden)
344 self.execute('%%run %s' % path, hidden=hidden)
346
345
347 #---------------------------------------------------------------------------
346 #---------------------------------------------------------------------------
348 # 'FrontendWidget' protected interface
347 # 'FrontendWidget' protected interface
349 #---------------------------------------------------------------------------
348 #---------------------------------------------------------------------------
350
349
351 def _process_execute_error(self, msg):
350 def _process_execute_error(self, msg):
352 """ Reimplemented for IPython-style traceback formatting.
351 """ Reimplemented for IPython-style traceback formatting.
353 """
352 """
354 content = msg['content']
353 content = msg['content']
355 traceback = '\n'.join(content['traceback']) + '\n'
354 traceback = '\n'.join(content['traceback']) + '\n'
356 if False:
355 if False:
357 # FIXME: For now, tracebacks come as plain text, so we can't use
356 # FIXME: For now, tracebacks come as plain text, so we can't use
358 # the html renderer yet. Once we refactor ultratb to produce
357 # the html renderer yet. Once we refactor ultratb to produce
359 # properly styled tracebacks, this branch should be the default
358 # properly styled tracebacks, this branch should be the default
360 traceback = traceback.replace(' ', '&nbsp;')
359 traceback = traceback.replace(' ', '&nbsp;')
361 traceback = traceback.replace('\n', '<br/>')
360 traceback = traceback.replace('\n', '<br/>')
362
361
363 ename = content['ename']
362 ename = content['ename']
364 ename_styled = '<span class="error">%s</span>' % ename
363 ename_styled = '<span class="error">%s</span>' % ename
365 traceback = traceback.replace(ename, ename_styled)
364 traceback = traceback.replace(ename, ename_styled)
366
365
367 self._append_html(traceback)
366 self._append_html(traceback)
368 else:
367 else:
369 # This is the fallback for now, using plain text with ansi escapes
368 # This is the fallback for now, using plain text with ansi escapes
370 self._append_plain_text(traceback)
369 self._append_plain_text(traceback)
371
370
372 def _process_execute_payload(self, item):
371 def _process_execute_payload(self, item):
373 """ Reimplemented to dispatch payloads to handler methods.
372 """ Reimplemented to dispatch payloads to handler methods.
374 """
373 """
375 handler = self._payload_handlers.get(item['source'])
374 handler = self._payload_handlers.get(item['source'])
376 if handler is None:
375 if handler is None:
377 # We have no handler for this type of payload, simply ignore it
376 # We have no handler for this type of payload, simply ignore it
378 return False
377 return False
379 else:
378 else:
380 handler(item)
379 handler(item)
381 return True
380 return True
382
381
383 def _show_interpreter_prompt(self, number=None):
382 def _show_interpreter_prompt(self, number=None):
384 """ Reimplemented for IPython-style prompts.
383 """ Reimplemented for IPython-style prompts.
385 """
384 """
386 # If a number was not specified, make a prompt number request.
385 # If a number was not specified, make a prompt number request.
387 if number is None:
386 if number is None:
388 msg_id = self.kernel_client.shell_channel.execute('', silent=True)
387 msg_id = self.kernel_client.execute('', silent=True)
389 info = self._ExecutionRequest(msg_id, 'prompt')
388 info = self._ExecutionRequest(msg_id, 'prompt')
390 self._request_info['execute'][msg_id] = info
389 self._request_info['execute'][msg_id] = info
391 return
390 return
392
391
393 # Show a new prompt and save information about it so that it can be
392 # Show a new prompt and save information about it so that it can be
394 # updated later if the prompt number turns out to be wrong.
393 # updated later if the prompt number turns out to be wrong.
395 self._prompt_sep = self.input_sep
394 self._prompt_sep = self.input_sep
396 self._show_prompt(self._make_in_prompt(number), html=True)
395 self._show_prompt(self._make_in_prompt(number), html=True)
397 block = self._control.document().lastBlock()
396 block = self._control.document().lastBlock()
398 length = len(self._prompt)
397 length = len(self._prompt)
399 self._previous_prompt_obj = self._PromptBlock(block, length, number)
398 self._previous_prompt_obj = self._PromptBlock(block, length, number)
400
399
401 # Update continuation prompt to reflect (possibly) new prompt length.
400 # Update continuation prompt to reflect (possibly) new prompt length.
402 self._set_continuation_prompt(
401 self._set_continuation_prompt(
403 self._make_continuation_prompt(self._prompt), html=True)
402 self._make_continuation_prompt(self._prompt), html=True)
404
403
405 def _show_interpreter_prompt_for_reply(self, msg):
404 def _show_interpreter_prompt_for_reply(self, msg):
406 """ Reimplemented for IPython-style prompts.
405 """ Reimplemented for IPython-style prompts.
407 """
406 """
408 # Update the old prompt number if necessary.
407 # Update the old prompt number if necessary.
409 content = msg['content']
408 content = msg['content']
410 # abort replies do not have any keys:
409 # abort replies do not have any keys:
411 if content['status'] == 'aborted':
410 if content['status'] == 'aborted':
412 if self._previous_prompt_obj:
411 if self._previous_prompt_obj:
413 previous_prompt_number = self._previous_prompt_obj.number
412 previous_prompt_number = self._previous_prompt_obj.number
414 else:
413 else:
415 previous_prompt_number = 0
414 previous_prompt_number = 0
416 else:
415 else:
417 previous_prompt_number = content['execution_count']
416 previous_prompt_number = content['execution_count']
418 if self._previous_prompt_obj and \
417 if self._previous_prompt_obj and \
419 self._previous_prompt_obj.number != previous_prompt_number:
418 self._previous_prompt_obj.number != previous_prompt_number:
420 block = self._previous_prompt_obj.block
419 block = self._previous_prompt_obj.block
421
420
422 # Make sure the prompt block has not been erased.
421 # Make sure the prompt block has not been erased.
423 if block.isValid() and block.text():
422 if block.isValid() and block.text():
424
423
425 # Remove the old prompt and insert a new prompt.
424 # Remove the old prompt and insert a new prompt.
426 cursor = QtGui.QTextCursor(block)
425 cursor = QtGui.QTextCursor(block)
427 cursor.movePosition(QtGui.QTextCursor.Right,
426 cursor.movePosition(QtGui.QTextCursor.Right,
428 QtGui.QTextCursor.KeepAnchor,
427 QtGui.QTextCursor.KeepAnchor,
429 self._previous_prompt_obj.length)
428 self._previous_prompt_obj.length)
430 prompt = self._make_in_prompt(previous_prompt_number)
429 prompt = self._make_in_prompt(previous_prompt_number)
431 self._prompt = self._insert_html_fetching_plain_text(
430 self._prompt = self._insert_html_fetching_plain_text(
432 cursor, prompt)
431 cursor, prompt)
433
432
434 # When the HTML is inserted, Qt blows away the syntax
433 # When the HTML is inserted, Qt blows away the syntax
435 # highlighting for the line, so we need to rehighlight it.
434 # highlighting for the line, so we need to rehighlight it.
436 self._highlighter.rehighlightBlock(cursor.block())
435 self._highlighter.rehighlightBlock(cursor.block())
437
436
438 self._previous_prompt_obj = None
437 self._previous_prompt_obj = None
439
438
440 # Show a new prompt with the kernel's estimated prompt number.
439 # Show a new prompt with the kernel's estimated prompt number.
441 self._show_interpreter_prompt(previous_prompt_number + 1)
440 self._show_interpreter_prompt(previous_prompt_number + 1)
442
441
443 #---------------------------------------------------------------------------
442 #---------------------------------------------------------------------------
444 # 'IPythonWidget' interface
443 # 'IPythonWidget' interface
445 #---------------------------------------------------------------------------
444 #---------------------------------------------------------------------------
446
445
447 def set_default_style(self, colors='lightbg'):
446 def set_default_style(self, colors='lightbg'):
448 """ Sets the widget style to the class defaults.
447 """ Sets the widget style to the class defaults.
449
448
450 Parameters
449 Parameters
451 ----------
450 ----------
452 colors : str, optional (default lightbg)
451 colors : str, optional (default lightbg)
453 Whether to use the default IPython light background or dark
452 Whether to use the default IPython light background or dark
454 background or B&W style.
453 background or B&W style.
455 """
454 """
456 colors = colors.lower()
455 colors = colors.lower()
457 if colors=='lightbg':
456 if colors=='lightbg':
458 self.style_sheet = styles.default_light_style_sheet
457 self.style_sheet = styles.default_light_style_sheet
459 self.syntax_style = styles.default_light_syntax_style
458 self.syntax_style = styles.default_light_syntax_style
460 elif colors=='linux':
459 elif colors=='linux':
461 self.style_sheet = styles.default_dark_style_sheet
460 self.style_sheet = styles.default_dark_style_sheet
462 self.syntax_style = styles.default_dark_syntax_style
461 self.syntax_style = styles.default_dark_syntax_style
463 elif colors=='nocolor':
462 elif colors=='nocolor':
464 self.style_sheet = styles.default_bw_style_sheet
463 self.style_sheet = styles.default_bw_style_sheet
465 self.syntax_style = styles.default_bw_syntax_style
464 self.syntax_style = styles.default_bw_syntax_style
466 else:
465 else:
467 raise KeyError("No such color scheme: %s"%colors)
466 raise KeyError("No such color scheme: %s"%colors)
468
467
469 #---------------------------------------------------------------------------
468 #---------------------------------------------------------------------------
470 # 'IPythonWidget' protected interface
469 # 'IPythonWidget' protected interface
471 #---------------------------------------------------------------------------
470 #---------------------------------------------------------------------------
472
471
473 def _edit(self, filename, line=None):
472 def _edit(self, filename, line=None):
474 """ Opens a Python script for editing.
473 """ Opens a Python script for editing.
475
474
476 Parameters
475 Parameters
477 ----------
476 ----------
478 filename : str
477 filename : str
479 A path to a local system file.
478 A path to a local system file.
480
479
481 line : int, optional
480 line : int, optional
482 A line of interest in the file.
481 A line of interest in the file.
483 """
482 """
484 if self.custom_edit:
483 if self.custom_edit:
485 self.custom_edit_requested.emit(filename, line)
484 self.custom_edit_requested.emit(filename, line)
486 elif not self.editor:
485 elif not self.editor:
487 self._append_plain_text('No default editor available.\n'
486 self._append_plain_text('No default editor available.\n'
488 'Specify a GUI text editor in the `IPythonWidget.editor` '
487 'Specify a GUI text editor in the `IPythonWidget.editor` '
489 'configurable to enable the %edit magic')
488 'configurable to enable the %edit magic')
490 else:
489 else:
491 try:
490 try:
492 filename = '"%s"' % filename
491 filename = '"%s"' % filename
493 if line and self.editor_line:
492 if line and self.editor_line:
494 command = self.editor_line.format(filename=filename,
493 command = self.editor_line.format(filename=filename,
495 line=line)
494 line=line)
496 else:
495 else:
497 try:
496 try:
498 command = self.editor.format()
497 command = self.editor.format()
499 except KeyError:
498 except KeyError:
500 command = self.editor.format(filename=filename)
499 command = self.editor.format(filename=filename)
501 else:
500 else:
502 command += ' ' + filename
501 command += ' ' + filename
503 except KeyError:
502 except KeyError:
504 self._append_plain_text('Invalid editor command.\n')
503 self._append_plain_text('Invalid editor command.\n')
505 else:
504 else:
506 try:
505 try:
507 Popen(command, shell=True)
506 Popen(command, shell=True)
508 except OSError:
507 except OSError:
509 msg = 'Opening editor with command "%s" failed.\n'
508 msg = 'Opening editor with command "%s" failed.\n'
510 self._append_plain_text(msg % command)
509 self._append_plain_text(msg % command)
511
510
512 def _make_in_prompt(self, number):
511 def _make_in_prompt(self, number):
513 """ Given a prompt number, returns an HTML In prompt.
512 """ Given a prompt number, returns an HTML In prompt.
514 """
513 """
515 try:
514 try:
516 body = self.in_prompt % number
515 body = self.in_prompt % number
517 except TypeError:
516 except TypeError:
518 # allow in_prompt to leave out number, e.g. '>>> '
517 # allow in_prompt to leave out number, e.g. '>>> '
519 from xml.sax.saxutils import escape
518 from xml.sax.saxutils import escape
520 body = escape(self.in_prompt)
519 body = escape(self.in_prompt)
521 return '<span class="in-prompt">%s</span>' % body
520 return '<span class="in-prompt">%s</span>' % body
522
521
523 def _make_continuation_prompt(self, prompt):
522 def _make_continuation_prompt(self, prompt):
524 """ Given a plain text version of an In prompt, returns an HTML
523 """ Given a plain text version of an In prompt, returns an HTML
525 continuation prompt.
524 continuation prompt.
526 """
525 """
527 end_chars = '...: '
526 end_chars = '...: '
528 space_count = len(prompt.lstrip('\n')) - len(end_chars)
527 space_count = len(prompt.lstrip('\n')) - len(end_chars)
529 body = '&nbsp;' * space_count + end_chars
528 body = '&nbsp;' * space_count + end_chars
530 return '<span class="in-prompt">%s</span>' % body
529 return '<span class="in-prompt">%s</span>' % body
531
530
532 def _make_out_prompt(self, number):
531 def _make_out_prompt(self, number):
533 """ Given a prompt number, returns an HTML Out prompt.
532 """ Given a prompt number, returns an HTML Out prompt.
534 """
533 """
535 try:
534 try:
536 body = self.out_prompt % number
535 body = self.out_prompt % number
537 except TypeError:
536 except TypeError:
538 # allow out_prompt to leave out number, e.g. '<<< '
537 # allow out_prompt to leave out number, e.g. '<<< '
539 from xml.sax.saxutils import escape
538 from xml.sax.saxutils import escape
540 body = escape(self.out_prompt)
539 body = escape(self.out_prompt)
541 return '<span class="out-prompt">%s</span>' % body
540 return '<span class="out-prompt">%s</span>' % body
542
541
543 #------ Payload handlers --------------------------------------------------
542 #------ Payload handlers --------------------------------------------------
544
543
545 # Payload handlers with a generic interface: each takes the opaque payload
544 # Payload handlers with a generic interface: each takes the opaque payload
546 # dict, unpacks it and calls the underlying functions with the necessary
545 # dict, unpacks it and calls the underlying functions with the necessary
547 # arguments.
546 # arguments.
548
547
549 def _handle_payload_edit(self, item):
548 def _handle_payload_edit(self, item):
550 self._edit(item['filename'], item['line_number'])
549 self._edit(item['filename'], item['line_number'])
551
550
552 def _handle_payload_exit(self, item):
551 def _handle_payload_exit(self, item):
553 self._keep_kernel_on_exit = item['keepkernel']
552 self._keep_kernel_on_exit = item['keepkernel']
554 self.exit_requested.emit(self)
553 self.exit_requested.emit(self)
555
554
556 def _handle_payload_next_input(self, item):
555 def _handle_payload_next_input(self, item):
557 self.input_buffer = item['text']
556 self.input_buffer = item['text']
558
557
559 def _handle_payload_page(self, item):
558 def _handle_payload_page(self, item):
560 # Since the plain text widget supports only a very small subset of HTML
559 # Since the plain text widget supports only a very small subset of HTML
561 # and we have no control over the HTML source, we only page HTML
560 # and we have no control over the HTML source, we only page HTML
562 # payloads in the rich text widget.
561 # payloads in the rich text widget.
563 data = item['data']
562 data = item['data']
564 if 'text/html' in data and self.kind == 'rich':
563 if 'text/html' in data and self.kind == 'rich':
565 self._page(data['text/html'], html=True)
564 self._page(data['text/html'], html=True)
566 else:
565 else:
567 self._page(data['text/plain'], html=False)
566 self._page(data['text/plain'], html=False)
568
567
569 #------ Trait change handlers --------------------------------------------
568 #------ Trait change handlers --------------------------------------------
570
569
571 def _style_sheet_changed(self):
570 def _style_sheet_changed(self):
572 """ Set the style sheets of the underlying widgets.
571 """ Set the style sheets of the underlying widgets.
573 """
572 """
574 self.setStyleSheet(self.style_sheet)
573 self.setStyleSheet(self.style_sheet)
575 if self._control is not None:
574 if self._control is not None:
576 self._control.document().setDefaultStyleSheet(self.style_sheet)
575 self._control.document().setDefaultStyleSheet(self.style_sheet)
577 bg_color = self._control.palette().window().color()
576 bg_color = self._control.palette().window().color()
578 self._ansi_processor.set_background_color(bg_color)
577 self._ansi_processor.set_background_color(bg_color)
579
578
580 if self._page_control is not None:
579 if self._page_control is not None:
581 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
580 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
582
581
583
582
584
583
585 def _syntax_style_changed(self):
584 def _syntax_style_changed(self):
586 """ Set the style for the syntax highlighter.
585 """ Set the style for the syntax highlighter.
587 """
586 """
588 if self._highlighter is None:
587 if self._highlighter is None:
589 # ignore premature calls
588 # ignore premature calls
590 return
589 return
591 if self.syntax_style:
590 if self.syntax_style:
592 self._highlighter.set_style(self.syntax_style)
591 self._highlighter.set_style(self.syntax_style)
593 else:
592 else:
594 self._highlighter.set_style_sheet(self.style_sheet)
593 self._highlighter.set_style_sheet(self.style_sheet)
595
594
596 #------ Trait default initializers -----------------------------------------
595 #------ Trait default initializers -----------------------------------------
597
596
598 def _banner_default(self):
597 def _banner_default(self):
599 return "IPython QtConsole {version}\n".format(version=version)
598 return "IPython QtConsole {version}\n".format(version=version)
@@ -1,40 +1,75 b''
1 """ Defines an in-process KernelManager with signals and slots.
1 """ Defines an in-process KernelManager with signals and slots.
2 """
2 """
3
3
4 # Local imports.
4 # Local imports.
5 from IPython.external.qt import QtCore
5 from IPython.kernel.inprocess import (
6 from IPython.kernel.inprocess import (
6 InProcessShellChannel, InProcessIOPubChannel, InProcessStdInChannel,
7 InProcessHBChannel, InProcessKernelClient, InProcessKernelManager,
7 InProcessHBChannel, InProcessKernelClient, InProcessKernelManager,
8 )
8 )
9 from IPython.kernel.inprocess.channels import InProcessChannel
9
10
10 from IPython.utils.traitlets import Type
11 from IPython.utils.traitlets import Type
12 from .util import SuperQObject
11 from .kernel_mixins import (
13 from .kernel_mixins import (
12 QtShellChannelMixin, QtIOPubChannelMixin,
14 QtKernelClientMixin, QtKernelManagerMixin,
13 QtStdInChannelMixin, QtHBChannelMixin, QtKernelClientMixin,
14 QtKernelManagerMixin,
15 )
15 )
16
16
17 class QtInProcessChannel(SuperQObject, InProcessChannel):
18 # Emitted when the channel is started.
19 started = QtCore.Signal()
17
20
18 class QtInProcessShellChannel(QtShellChannelMixin, InProcessShellChannel):
21 # Emitted when the channel is stopped.
19 pass
22 stopped = QtCore.Signal()
20
23
21 class QtInProcessIOPubChannel(QtIOPubChannelMixin, InProcessIOPubChannel):
24 # Emitted when any message is received.
22 pass
25 message_received = QtCore.Signal(object)
23
26
24 class QtInProcessStdInChannel(QtStdInChannelMixin, InProcessStdInChannel):
27 def start(self):
25 pass
28 """ Reimplemented to emit signal.
29 """
30 super(QtInProcessChannel, self).start()
31 self.started.emit()
32
33 def stop(self):
34 """ Reimplemented to emit signal.
35 """
36 super(QtInProcessChannel, self).stop()
37 self.stopped.emit()
38
39 def call_handlers_later(self, *args, **kwds):
40 """ Call the message handlers later.
41 """
42 do_later = lambda: self.call_handlers(*args, **kwds)
43 QtCore.QTimer.singleShot(0, do_later)
44
45 def call_handlers(self, msg):
46 self.message_received.emit(msg)
47
48 def process_events(self):
49 """ Process any pending GUI events.
50 """
51 QtCore.QCoreApplication.instance().processEvents()
52
53 def flush(self, timeout=1.0):
54 """ Reimplemented to ensure that signals are dispatched immediately.
55 """
56 super(QtInProcessChannel, self).flush()
57 self.process_events()
58
59
60 class QtInProcessHBChannel(SuperQObject, InProcessHBChannel):
61 # This signal will never be fired, but it needs to exist
62 kernel_died = QtCore.Signal()
26
63
27 class QtInProcessHBChannel(QtHBChannelMixin, InProcessHBChannel):
28 pass
29
64
30 class QtInProcessKernelClient(QtKernelClientMixin, InProcessKernelClient):
65 class QtInProcessKernelClient(QtKernelClientMixin, InProcessKernelClient):
31 """ An in-process KernelManager with signals and slots.
66 """ An in-process KernelManager with signals and slots.
32 """
67 """
33
68
34 iopub_channel_class = Type(QtInProcessIOPubChannel)
69 iopub_channel_class = Type(QtInProcessChannel)
35 shell_channel_class = Type(QtInProcessShellChannel)
70 shell_channel_class = Type(QtInProcessChannel)
36 stdin_channel_class = Type(QtInProcessStdInChannel)
71 stdin_channel_class = Type(QtInProcessChannel)
37 hb_channel_class = Type(QtInProcessHBChannel)
72 hb_channel_class = Type(QtInProcessHBChannel)
38
73
39 class QtInProcessKernelManager(QtKernelManagerMixin, InProcessKernelManager):
74 class QtInProcessKernelManager(QtKernelManagerMixin, InProcessKernelManager):
40 client_class = __module__ + '.QtInProcessKernelClient'
75 client_class = __module__ + '.QtInProcessKernelClient'
@@ -1,196 +1,50 b''
1 """Defines a KernelManager that provides signals and slots."""
1 """Defines a KernelManager that provides signals and slots."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from IPython.external.qt import QtCore
6 from IPython.external.qt import QtCore
7
7
8 from IPython.utils.traitlets import HasTraits, Type
8 from IPython.utils.traitlets import HasTraits, Type
9 from .util import MetaQObjectHasTraits, SuperQObject
9 from .util import MetaQObjectHasTraits, SuperQObject
10
10
11
11
12 class ChannelQObject(SuperQObject):
13
14 # Emitted when the channel is started.
15 started = QtCore.Signal()
16
17 # Emitted when the channel is stopped.
18 stopped = QtCore.Signal()
19
20 def start(self):
21 """ Reimplemented to emit signal.
22 """
23 super(ChannelQObject, self).start()
24 self.started.emit()
25
26 def stop(self):
27 """ Reimplemented to emit signal.
28 """
29 super(ChannelQObject, self).stop()
30 self.stopped.emit()
31
32 #---------------------------------------------------------------------------
33 # InProcessChannel interface
34 #---------------------------------------------------------------------------
35
36 def call_handlers_later(self, *args, **kwds):
37 """ Call the message handlers later.
38 """
39 do_later = lambda: self.call_handlers(*args, **kwds)
40 QtCore.QTimer.singleShot(0, do_later)
41
42 def process_events(self):
43 """ Process any pending GUI events.
44 """
45 QtCore.QCoreApplication.instance().processEvents()
46
47
48 class QtShellChannelMixin(ChannelQObject):
49
50 # Emitted when any message is received.
51 message_received = QtCore.Signal(object)
52
53 # Emitted when a reply has been received for the corresponding request type.
54 execute_reply = QtCore.Signal(object)
55 complete_reply = QtCore.Signal(object)
56 inspect_reply = QtCore.Signal(object)
57 history_reply = QtCore.Signal(object)
58 kernel_info_reply = QtCore.Signal(object)
59
60 def call_handlers(self, msg):
61 """ Reimplemented to emit signals instead of making callbacks.
62 """
63 # Emit the generic signal.
64 self.message_received.emit(msg)
65
66 # Emit signals for specialized message types.
67 msg_type = msg['header']['msg_type']
68 if msg_type == 'kernel_info_reply':
69 self._handle_kernel_info_reply(msg)
70
71 signal = getattr(self, msg_type, None)
72 if signal:
73 signal.emit(msg)
74
75
76 class QtIOPubChannelMixin(ChannelQObject):
77
78 # Emitted when any message is received.
79 message_received = QtCore.Signal(object)
80
81 # Emitted when a message of type 'stream' is received.
82 stream_received = QtCore.Signal(object)
83
84 # Emitted when a message of type 'execute_input' is received.
85 execute_input_received = QtCore.Signal(object)
86
87 # Emitted when a message of type 'execute_result' is received.
88 execute_result_received = QtCore.Signal(object)
89
90 # Emitted when a message of type 'error' is received.
91 error_received = QtCore.Signal(object)
92
93 # Emitted when a message of type 'display_data' is received
94 display_data_received = QtCore.Signal(object)
95
96 # Emitted when a crash report message is received from the kernel's
97 # last-resort sys.excepthook.
98 crash_received = QtCore.Signal(object)
99
100 # Emitted when a shutdown is noticed.
101 shutdown_reply_received = QtCore.Signal(object)
102
103 def call_handlers(self, msg):
104 """ Reimplemented to emit signals instead of making callbacks.
105 """
106 # Emit the generic signal.
107 self.message_received.emit(msg)
108 # Emit signals for specialized message types.
109 msg_type = msg['header']['msg_type']
110 signal = getattr(self, msg_type + '_received', None)
111 if signal:
112 signal.emit(msg)
113
114 def flush(self):
115 """ Reimplemented to ensure that signals are dispatched immediately.
116 """
117 super(QtIOPubChannelMixin, self).flush()
118 QtCore.QCoreApplication.instance().processEvents()
119
120
121 class QtStdInChannelMixin(ChannelQObject):
122
123 # Emitted when any message is received.
124 message_received = QtCore.Signal(object)
125
126 # Emitted when an input request is received.
127 input_requested = QtCore.Signal(object)
128
129 def call_handlers(self, msg):
130 """ Reimplemented to emit signals instead of making callbacks.
131 """
132 # Emit the generic signal.
133 self.message_received.emit(msg)
134
135 # Emit signals for specialized message types.
136 msg_type = msg['header']['msg_type']
137 if msg_type == 'input_request':
138 self.input_requested.emit(msg)
139
140
141 class QtHBChannelMixin(ChannelQObject):
142
143 # Emitted when the kernel has died.
144 kernel_died = QtCore.Signal(object)
145
146 def call_handlers(self, since_last_heartbeat):
147 """ Reimplemented to emit signals instead of making callbacks.
148 """
149 self.kernel_died.emit(since_last_heartbeat)
150
151
152 class QtKernelRestarterMixin(MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})):
12 class QtKernelRestarterMixin(MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})):
153
13
154 _timer = None
14 _timer = None
155
15
156
16
157 class QtKernelManagerMixin(MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})):
17 class QtKernelManagerMixin(MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})):
158 """ A KernelClient that provides signals and slots.
18 """ A KernelClient that provides signals and slots.
159 """
19 """
160
20
161 kernel_restarted = QtCore.Signal()
21 kernel_restarted = QtCore.Signal()
162
22
163
23
164 class QtKernelClientMixin(MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})):
24 class QtKernelClientMixin(MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})):
165 """ A KernelClient that provides signals and slots.
25 """ A KernelClient that provides signals and slots.
166 """
26 """
167
27
168 # Emitted when the kernel client has started listening.
28 # Emitted when the kernel client has started listening.
169 started_channels = QtCore.Signal()
29 started_channels = QtCore.Signal()
170
30
171 # Emitted when the kernel client has stopped listening.
31 # Emitted when the kernel client has stopped listening.
172 stopped_channels = QtCore.Signal()
32 stopped_channels = QtCore.Signal()
173
33
174 # Use Qt-specific channel classes that emit signals.
175 iopub_channel_class = Type(QtIOPubChannelMixin)
176 shell_channel_class = Type(QtShellChannelMixin)
177 stdin_channel_class = Type(QtStdInChannelMixin)
178 hb_channel_class = Type(QtHBChannelMixin)
179
180 #---------------------------------------------------------------------------
34 #---------------------------------------------------------------------------
181 # 'KernelClient' interface
35 # 'KernelClient' interface
182 #---------------------------------------------------------------------------
36 #---------------------------------------------------------------------------
183
37
184 #------ Channel management -------------------------------------------------
38 #------ Channel management -------------------------------------------------
185
39
186 def start_channels(self, *args, **kw):
40 def start_channels(self, *args, **kw):
187 """ Reimplemented to emit signal.
41 """ Reimplemented to emit signal.
188 """
42 """
189 super(QtKernelClientMixin, self).start_channels(*args, **kw)
43 super(QtKernelClientMixin, self).start_channels(*args, **kw)
190 self.started_channels.emit()
44 self.started_channels.emit()
191
45
192 def stop_channels(self):
46 def stop_channels(self):
193 """ Reimplemented to emit signal.
47 """ Reimplemented to emit signal.
194 """
48 """
195 super(QtKernelClientMixin, self).stop_channels()
49 super(QtKernelClientMixin, self).stop_channels()
196 self.stopped_channels.emit()
50 self.stopped_channels.emit()
@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Adapt readline completer interface to make ZMQ request."""
2 """Adapt readline completer interface to make ZMQ request."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 try:
7 try:
8 from queue import Empty # Py 3
8 from queue import Empty # Py 3
9 except ImportError:
9 except ImportError:
10 from Queue import Empty # Py 2
10 from Queue import Empty # Py 2
11
11
12 from IPython.config import Configurable
12 from IPython.config import Configurable
13 from IPython.core.completer import IPCompleter
13 from IPython.core.completer import IPCompleter
14 from IPython.utils.traitlets import Float
14 from IPython.utils.traitlets import Float
15 import IPython.utils.rlineimpl as readline
15 import IPython.utils.rlineimpl as readline
16
16
17 class ZMQCompleter(IPCompleter):
17 class ZMQCompleter(IPCompleter):
18 """Client-side completion machinery.
18 """Client-side completion machinery.
19
19
20 How it works: self.complete will be called multiple times, with
20 How it works: self.complete will be called multiple times, with
21 state=0,1,2,... When state=0 it should compute ALL the completion matches,
21 state=0,1,2,... When state=0 it should compute ALL the completion matches,
22 and then return them for each value of state."""
22 and then return them for each value of state."""
23
23
24 timeout = Float(5.0, config=True, help='timeout before completion abort')
24 timeout = Float(5.0, config=True, help='timeout before completion abort')
25
25
26 def __init__(self, shell, client, config=None):
26 def __init__(self, shell, client, config=None):
27 super(ZMQCompleter,self).__init__(config=config)
27 super(ZMQCompleter,self).__init__(config=config)
28
28
29 self.shell = shell
29 self.shell = shell
30 self.client = client
30 self.client = client
31 self.matches = []
31 self.matches = []
32
32
33 def complete_request(self, text):
33 def complete_request(self, text):
34 line = readline.get_line_buffer()
34 line = readline.get_line_buffer()
35 cursor_pos = readline.get_endidx()
35 cursor_pos = readline.get_endidx()
36
36
37 # send completion request to kernel
37 # send completion request to kernel
38 # Give the kernel up to 0.5s to respond
38 # Give the kernel up to 0.5s to respond
39 msg_id = self.client.shell_channel.complete(
39 msg_id = self.client.complete(
40 code=line,
40 code=line,
41 cursor_pos=cursor_pos,
41 cursor_pos=cursor_pos,
42 )
42 )
43
43
44 msg = self.client.shell_channel.get_msg(timeout=self.timeout)
44 msg = self.client.shell_channel.get_msg(timeout=self.timeout)
45 if msg['parent_header']['msg_id'] == msg_id:
45 if msg['parent_header']['msg_id'] == msg_id:
46 return msg["content"]["matches"]
46 return msg["content"]["matches"]
47 return []
47 return []
48
48
49 def rlcomplete(self, text, state):
49 def rlcomplete(self, text, state):
50 if state == 0:
50 if state == 0:
51 try:
51 try:
52 self.matches = self.complete_request(text)
52 self.matches = self.complete_request(text)
53 except Empty:
53 except Empty:
54 #print('WARNING: Kernel timeout on tab completion.')
54 #print('WARNING: Kernel timeout on tab completion.')
55 pass
55 pass
56
56
57 try:
57 try:
58 return self.matches[state]
58 return self.matches[state]
59 except IndexError:
59 except IndexError:
60 return None
60 return None
61
61
62 def complete(self, text, line, cursor_pos=None):
62 def complete(self, text, line, cursor_pos=None):
63 return self.rlcomplete(text, 0)
63 return self.rlcomplete(text, 0)
@@ -1,578 +1,578 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """terminal client to the IPython kernel"""
2 """terminal client to the IPython kernel"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import base64
10 import bdb
10 import bdb
11 import signal
11 import signal
12 import os
12 import os
13 import sys
13 import sys
14 import time
14 import time
15 import subprocess
15 import subprocess
16 from getpass import getpass
16 from getpass import getpass
17 from io import BytesIO
17 from io import BytesIO
18
18
19 try:
19 try:
20 from queue import Empty # Py 3
20 from queue import Empty # Py 3
21 except ImportError:
21 except ImportError:
22 from Queue import Empty # Py 2
22 from Queue import Empty # Py 2
23
23
24 from IPython.core import page
24 from IPython.core import page
25 from IPython.core import release
25 from IPython.core import release
26 from IPython.terminal.console.zmqhistory import ZMQHistoryManager
26 from IPython.terminal.console.zmqhistory import ZMQHistoryManager
27 from IPython.utils.warn import warn, error
27 from IPython.utils.warn import warn, error
28 from IPython.utils import io
28 from IPython.utils import io
29 from IPython.utils.py3compat import string_types, input
29 from IPython.utils.py3compat import string_types, input
30 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float, Bool
30 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float, Bool
31 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
31 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
32
32
33 from IPython.terminal.interactiveshell import TerminalInteractiveShell
33 from IPython.terminal.interactiveshell import TerminalInteractiveShell
34 from IPython.terminal.console.completer import ZMQCompleter
34 from IPython.terminal.console.completer import ZMQCompleter
35
35
36 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
36 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
37 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
37 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
38 _executing = False
38 _executing = False
39 _execution_state = Unicode('')
39 _execution_state = Unicode('')
40 _pending_clearoutput = False
40 _pending_clearoutput = False
41 kernel_banner = Unicode('')
41 kernel_banner = Unicode('')
42 kernel_timeout = Float(60, config=True,
42 kernel_timeout = Float(60, config=True,
43 help="""Timeout for giving up on a kernel (in seconds).
43 help="""Timeout for giving up on a kernel (in seconds).
44
44
45 On first connect and restart, the console tests whether the
45 On first connect and restart, the console tests whether the
46 kernel is running and responsive by sending kernel_info_requests.
46 kernel is running and responsive by sending kernel_info_requests.
47 This sets the timeout in seconds for how long the kernel can take
47 This sets the timeout in seconds for how long the kernel can take
48 before being presumed dead.
48 before being presumed dead.
49 """
49 """
50 )
50 )
51
51
52 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
52 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
53 config=True, help=
53 config=True, help=
54 """
54 """
55 Handler for image type output. This is useful, for example,
55 Handler for image type output. This is useful, for example,
56 when connecting to the kernel in which pylab inline backend is
56 when connecting to the kernel in which pylab inline backend is
57 activated. There are four handlers defined. 'PIL': Use
57 activated. There are four handlers defined. 'PIL': Use
58 Python Imaging Library to popup image; 'stream': Use an
58 Python Imaging Library to popup image; 'stream': Use an
59 external program to show the image. Image will be fed into
59 external program to show the image. Image will be fed into
60 the STDIN of the program. You will need to configure
60 the STDIN of the program. You will need to configure
61 `stream_image_handler`; 'tempfile': Use an external program to
61 `stream_image_handler`; 'tempfile': Use an external program to
62 show the image. Image will be saved in a temporally file and
62 show the image. Image will be saved in a temporally file and
63 the program is called with the temporally file. You will need
63 the program is called with the temporally file. You will need
64 to configure `tempfile_image_handler`; 'callable': You can set
64 to configure `tempfile_image_handler`; 'callable': You can set
65 any Python callable which is called with the image data. You
65 any Python callable which is called with the image data. You
66 will need to configure `callable_image_handler`.
66 will need to configure `callable_image_handler`.
67 """
67 """
68 )
68 )
69
69
70 stream_image_handler = List(config=True, help=
70 stream_image_handler = List(config=True, help=
71 """
71 """
72 Command to invoke an image viewer program when you are using
72 Command to invoke an image viewer program when you are using
73 'stream' image handler. This option is a list of string where
73 'stream' image handler. This option is a list of string where
74 the first element is the command itself and reminders are the
74 the first element is the command itself and reminders are the
75 options for the command. Raw image data is given as STDIN to
75 options for the command. Raw image data is given as STDIN to
76 the program.
76 the program.
77 """
77 """
78 )
78 )
79
79
80 tempfile_image_handler = List(config=True, help=
80 tempfile_image_handler = List(config=True, help=
81 """
81 """
82 Command to invoke an image viewer program when you are using
82 Command to invoke an image viewer program when you are using
83 'tempfile' image handler. This option is a list of string
83 'tempfile' image handler. This option is a list of string
84 where the first element is the command itself and reminders
84 where the first element is the command itself and reminders
85 are the options for the command. You can use {file} and
85 are the options for the command. You can use {file} and
86 {format} in the string to represent the location of the
86 {format} in the string to represent the location of the
87 generated image file and image format.
87 generated image file and image format.
88 """
88 """
89 )
89 )
90
90
91 callable_image_handler = Any(config=True, help=
91 callable_image_handler = Any(config=True, help=
92 """
92 """
93 Callable object called via 'callable' image handler with one
93 Callable object called via 'callable' image handler with one
94 argument, `data`, which is `msg["content"]["data"]` where
94 argument, `data`, which is `msg["content"]["data"]` where
95 `msg` is the message from iopub channel. For exmaple, you can
95 `msg` is the message from iopub channel. For exmaple, you can
96 find base64 encoded PNG data as `data['image/png']`.
96 find base64 encoded PNG data as `data['image/png']`.
97 """
97 """
98 )
98 )
99
99
100 mime_preference = List(
100 mime_preference = List(
101 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
101 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
102 config=True, allow_none=False, help=
102 config=True, allow_none=False, help=
103 """
103 """
104 Preferred object representation MIME type in order. First
104 Preferred object representation MIME type in order. First
105 matched MIME type will be used.
105 matched MIME type will be used.
106 """
106 """
107 )
107 )
108
108
109 manager = Instance('IPython.kernel.KernelManager')
109 manager = Instance('IPython.kernel.KernelManager')
110 client = Instance('IPython.kernel.KernelClient')
110 client = Instance('IPython.kernel.KernelClient')
111 def _client_changed(self, name, old, new):
111 def _client_changed(self, name, old, new):
112 self.session_id = new.session.session
112 self.session_id = new.session.session
113 session_id = Unicode()
113 session_id = Unicode()
114
114
115 def init_completer(self):
115 def init_completer(self):
116 """Initialize the completion machinery.
116 """Initialize the completion machinery.
117
117
118 This creates completion machinery that can be used by client code,
118 This creates completion machinery that can be used by client code,
119 either interactively in-process (typically triggered by the readline
119 either interactively in-process (typically triggered by the readline
120 library), programmatically (such as in test suites) or out-of-process
120 library), programmatically (such as in test suites) or out-of-process
121 (typically over the network by remote frontends).
121 (typically over the network by remote frontends).
122 """
122 """
123 from IPython.core.completerlib import (module_completer,
123 from IPython.core.completerlib import (module_completer,
124 magic_run_completer, cd_completer)
124 magic_run_completer, cd_completer)
125
125
126 self.Completer = ZMQCompleter(self, self.client, config=self.config)
126 self.Completer = ZMQCompleter(self, self.client, config=self.config)
127
127
128
128
129 self.set_hook('complete_command', module_completer, str_key = 'import')
129 self.set_hook('complete_command', module_completer, str_key = 'import')
130 self.set_hook('complete_command', module_completer, str_key = 'from')
130 self.set_hook('complete_command', module_completer, str_key = 'from')
131 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
131 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
132 self.set_hook('complete_command', cd_completer, str_key = '%cd')
132 self.set_hook('complete_command', cd_completer, str_key = '%cd')
133
133
134 # Only configure readline if we truly are using readline. IPython can
134 # Only configure readline if we truly are using readline. IPython can
135 # do tab-completion over the network, in GUIs, etc, where readline
135 # do tab-completion over the network, in GUIs, etc, where readline
136 # itself may be absent
136 # itself may be absent
137 if self.has_readline:
137 if self.has_readline:
138 self.set_readline_completer()
138 self.set_readline_completer()
139
139
140 def run_cell(self, cell, store_history=True):
140 def run_cell(self, cell, store_history=True):
141 """Run a complete IPython cell.
141 """Run a complete IPython cell.
142
142
143 Parameters
143 Parameters
144 ----------
144 ----------
145 cell : str
145 cell : str
146 The code (including IPython code such as %magic functions) to run.
146 The code (including IPython code such as %magic functions) to run.
147 store_history : bool
147 store_history : bool
148 If True, the raw and translated cell will be stored in IPython's
148 If True, the raw and translated cell will be stored in IPython's
149 history. For user code calling back into IPython's machinery, this
149 history. For user code calling back into IPython's machinery, this
150 should be set to False.
150 should be set to False.
151 """
151 """
152 if (not cell) or cell.isspace():
152 if (not cell) or cell.isspace():
153 # pressing enter flushes any pending display
153 # pressing enter flushes any pending display
154 self.handle_iopub()
154 self.handle_iopub()
155 return
155 return
156
156
157 # flush stale replies, which could have been ignored, due to missed heartbeats
157 # flush stale replies, which could have been ignored, due to missed heartbeats
158 while self.client.shell_channel.msg_ready():
158 while self.client.shell_channel.msg_ready():
159 self.client.shell_channel.get_msg()
159 self.client.shell_channel.get_msg()
160 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
160 # execute takes 'hidden', which is the inverse of store_hist
161 msg_id = self.client.shell_channel.execute(cell, not store_history)
161 msg_id = self.client.execute(cell, not store_history)
162
162
163 # first thing is wait for any side effects (output, stdin, etc.)
163 # first thing is wait for any side effects (output, stdin, etc.)
164 self._executing = True
164 self._executing = True
165 self._execution_state = "busy"
165 self._execution_state = "busy"
166 while self._execution_state != 'idle' and self.client.is_alive():
166 while self._execution_state != 'idle' and self.client.is_alive():
167 try:
167 try:
168 self.handle_input_request(msg_id, timeout=0.05)
168 self.handle_input_request(msg_id, timeout=0.05)
169 except Empty:
169 except Empty:
170 # display intermediate print statements, etc.
170 # display intermediate print statements, etc.
171 self.handle_iopub(msg_id)
171 self.handle_iopub(msg_id)
172
172
173 # after all of that is done, wait for the execute reply
173 # after all of that is done, wait for the execute reply
174 while self.client.is_alive():
174 while self.client.is_alive():
175 try:
175 try:
176 self.handle_execute_reply(msg_id, timeout=0.05)
176 self.handle_execute_reply(msg_id, timeout=0.05)
177 except Empty:
177 except Empty:
178 pass
178 pass
179 else:
179 else:
180 break
180 break
181 self._executing = False
181 self._executing = False
182
182
183 #-----------------
183 #-----------------
184 # message handlers
184 # message handlers
185 #-----------------
185 #-----------------
186
186
187 def handle_execute_reply(self, msg_id, timeout=None):
187 def handle_execute_reply(self, msg_id, timeout=None):
188 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
188 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
189 if msg["parent_header"].get("msg_id", None) == msg_id:
189 if msg["parent_header"].get("msg_id", None) == msg_id:
190
190
191 self.handle_iopub(msg_id)
191 self.handle_iopub(msg_id)
192
192
193 content = msg["content"]
193 content = msg["content"]
194 status = content['status']
194 status = content['status']
195
195
196 if status == 'aborted':
196 if status == 'aborted':
197 self.write('Aborted\n')
197 self.write('Aborted\n')
198 return
198 return
199 elif status == 'ok':
199 elif status == 'ok':
200 # handle payloads
200 # handle payloads
201 for item in content["payload"]:
201 for item in content["payload"]:
202 source = item['source']
202 source = item['source']
203 if source == 'page':
203 if source == 'page':
204 page.page(item['data']['text/plain'])
204 page.page(item['data']['text/plain'])
205 elif source == 'set_next_input':
205 elif source == 'set_next_input':
206 self.set_next_input(item['text'])
206 self.set_next_input(item['text'])
207 elif source == 'ask_exit':
207 elif source == 'ask_exit':
208 self.ask_exit()
208 self.ask_exit()
209
209
210 elif status == 'error':
210 elif status == 'error':
211 for frame in content["traceback"]:
211 for frame in content["traceback"]:
212 print(frame, file=io.stderr)
212 print(frame, file=io.stderr)
213
213
214 self.execution_count = int(content["execution_count"] + 1)
214 self.execution_count = int(content["execution_count"] + 1)
215
215
216 include_other_output = Bool(False, config=True,
216 include_other_output = Bool(False, config=True,
217 help="""Whether to include output from clients
217 help="""Whether to include output from clients
218 other than this one sharing the same kernel.
218 other than this one sharing the same kernel.
219
219
220 Outputs are not displayed until enter is pressed.
220 Outputs are not displayed until enter is pressed.
221 """
221 """
222 )
222 )
223 other_output_prefix = Unicode("[remote] ", config=True,
223 other_output_prefix = Unicode("[remote] ", config=True,
224 help="""Prefix to add to outputs coming from clients other than this one.
224 help="""Prefix to add to outputs coming from clients other than this one.
225
225
226 Only relevant if include_other_output is True.
226 Only relevant if include_other_output is True.
227 """
227 """
228 )
228 )
229
229
230 def from_here(self, msg):
230 def from_here(self, msg):
231 """Return whether a message is from this session"""
231 """Return whether a message is from this session"""
232 return msg['parent_header'].get("session", self.session_id) == self.session_id
232 return msg['parent_header'].get("session", self.session_id) == self.session_id
233
233
234 def include_output(self, msg):
234 def include_output(self, msg):
235 """Return whether we should include a given output message"""
235 """Return whether we should include a given output message"""
236 from_here = self.from_here(msg)
236 from_here = self.from_here(msg)
237 if msg['msg_type'] == 'execute_input':
237 if msg['msg_type'] == 'execute_input':
238 # only echo inputs not from here
238 # only echo inputs not from here
239 return self.include_other_output and not from_here
239 return self.include_other_output and not from_here
240
240
241 if self.include_other_output:
241 if self.include_other_output:
242 return True
242 return True
243 else:
243 else:
244 return from_here
244 return from_here
245
245
246 def handle_iopub(self, msg_id=''):
246 def handle_iopub(self, msg_id=''):
247 """Process messages on the IOPub channel
247 """Process messages on the IOPub channel
248
248
249 This method consumes and processes messages on the IOPub channel,
249 This method consumes and processes messages on the IOPub channel,
250 such as stdout, stderr, execute_result and status.
250 such as stdout, stderr, execute_result and status.
251
251
252 It only displays output that is caused by this session.
252 It only displays output that is caused by this session.
253 """
253 """
254 while self.client.iopub_channel.msg_ready():
254 while self.client.iopub_channel.msg_ready():
255 sub_msg = self.client.iopub_channel.get_msg()
255 sub_msg = self.client.iopub_channel.get_msg()
256 msg_type = sub_msg['header']['msg_type']
256 msg_type = sub_msg['header']['msg_type']
257 parent = sub_msg["parent_header"]
257 parent = sub_msg["parent_header"]
258
258
259 if self.include_output(sub_msg):
259 if self.include_output(sub_msg):
260 if msg_type == 'status':
260 if msg_type == 'status':
261 self._execution_state = sub_msg["content"]["execution_state"]
261 self._execution_state = sub_msg["content"]["execution_state"]
262 elif msg_type == 'stream':
262 elif msg_type == 'stream':
263 if sub_msg["content"]["name"] == "stdout":
263 if sub_msg["content"]["name"] == "stdout":
264 if self._pending_clearoutput:
264 if self._pending_clearoutput:
265 print("\r", file=io.stdout, end="")
265 print("\r", file=io.stdout, end="")
266 self._pending_clearoutput = False
266 self._pending_clearoutput = False
267 print(sub_msg["content"]["text"], file=io.stdout, end="")
267 print(sub_msg["content"]["text"], file=io.stdout, end="")
268 io.stdout.flush()
268 io.stdout.flush()
269 elif sub_msg["content"]["name"] == "stderr":
269 elif sub_msg["content"]["name"] == "stderr":
270 if self._pending_clearoutput:
270 if self._pending_clearoutput:
271 print("\r", file=io.stderr, end="")
271 print("\r", file=io.stderr, end="")
272 self._pending_clearoutput = False
272 self._pending_clearoutput = False
273 print(sub_msg["content"]["text"], file=io.stderr, end="")
273 print(sub_msg["content"]["text"], file=io.stderr, end="")
274 io.stderr.flush()
274 io.stderr.flush()
275
275
276 elif msg_type == 'execute_result':
276 elif msg_type == 'execute_result':
277 if self._pending_clearoutput:
277 if self._pending_clearoutput:
278 print("\r", file=io.stdout, end="")
278 print("\r", file=io.stdout, end="")
279 self._pending_clearoutput = False
279 self._pending_clearoutput = False
280 self.execution_count = int(sub_msg["content"]["execution_count"])
280 self.execution_count = int(sub_msg["content"]["execution_count"])
281 if not self.from_here(sub_msg):
281 if not self.from_here(sub_msg):
282 sys.stdout.write(self.other_output_prefix)
282 sys.stdout.write(self.other_output_prefix)
283 format_dict = sub_msg["content"]["data"]
283 format_dict = sub_msg["content"]["data"]
284 self.handle_rich_data(format_dict)
284 self.handle_rich_data(format_dict)
285
285
286 # taken from DisplayHook.__call__:
286 # taken from DisplayHook.__call__:
287 hook = self.displayhook
287 hook = self.displayhook
288 hook.start_displayhook()
288 hook.start_displayhook()
289 hook.write_output_prompt()
289 hook.write_output_prompt()
290 hook.write_format_data(format_dict)
290 hook.write_format_data(format_dict)
291 hook.log_output(format_dict)
291 hook.log_output(format_dict)
292 hook.finish_displayhook()
292 hook.finish_displayhook()
293
293
294 elif msg_type == 'display_data':
294 elif msg_type == 'display_data':
295 data = sub_msg["content"]["data"]
295 data = sub_msg["content"]["data"]
296 handled = self.handle_rich_data(data)
296 handled = self.handle_rich_data(data)
297 if not handled:
297 if not handled:
298 if not self.from_here(sub_msg):
298 if not self.from_here(sub_msg):
299 sys.stdout.write(self.other_output_prefix)
299 sys.stdout.write(self.other_output_prefix)
300 # if it was an image, we handled it by now
300 # if it was an image, we handled it by now
301 if 'text/plain' in data:
301 if 'text/plain' in data:
302 print(data['text/plain'])
302 print(data['text/plain'])
303
303
304 elif msg_type == 'execute_input':
304 elif msg_type == 'execute_input':
305 content = sub_msg['content']
305 content = sub_msg['content']
306 self.execution_count = content['execution_count']
306 self.execution_count = content['execution_count']
307 if not self.from_here(sub_msg):
307 if not self.from_here(sub_msg):
308 sys.stdout.write(self.other_output_prefix)
308 sys.stdout.write(self.other_output_prefix)
309 sys.stdout.write(self.prompt_manager.render('in'))
309 sys.stdout.write(self.prompt_manager.render('in'))
310 sys.stdout.write(content['code'])
310 sys.stdout.write(content['code'])
311
311
312 elif msg_type == 'clear_output':
312 elif msg_type == 'clear_output':
313 if sub_msg["content"]["wait"]:
313 if sub_msg["content"]["wait"]:
314 self._pending_clearoutput = True
314 self._pending_clearoutput = True
315 else:
315 else:
316 print("\r", file=io.stdout, end="")
316 print("\r", file=io.stdout, end="")
317
317
318 _imagemime = {
318 _imagemime = {
319 'image/png': 'png',
319 'image/png': 'png',
320 'image/jpeg': 'jpeg',
320 'image/jpeg': 'jpeg',
321 'image/svg+xml': 'svg',
321 'image/svg+xml': 'svg',
322 }
322 }
323
323
324 def handle_rich_data(self, data):
324 def handle_rich_data(self, data):
325 for mime in self.mime_preference:
325 for mime in self.mime_preference:
326 if mime in data and mime in self._imagemime:
326 if mime in data and mime in self._imagemime:
327 self.handle_image(data, mime)
327 self.handle_image(data, mime)
328 return True
328 return True
329
329
330 def handle_image(self, data, mime):
330 def handle_image(self, data, mime):
331 handler = getattr(
331 handler = getattr(
332 self, 'handle_image_{0}'.format(self.image_handler), None)
332 self, 'handle_image_{0}'.format(self.image_handler), None)
333 if handler:
333 if handler:
334 handler(data, mime)
334 handler(data, mime)
335
335
336 def handle_image_PIL(self, data, mime):
336 def handle_image_PIL(self, data, mime):
337 if mime not in ('image/png', 'image/jpeg'):
337 if mime not in ('image/png', 'image/jpeg'):
338 return
338 return
339 import PIL.Image
339 import PIL.Image
340 raw = base64.decodestring(data[mime].encode('ascii'))
340 raw = base64.decodestring(data[mime].encode('ascii'))
341 img = PIL.Image.open(BytesIO(raw))
341 img = PIL.Image.open(BytesIO(raw))
342 img.show()
342 img.show()
343
343
344 def handle_image_stream(self, data, mime):
344 def handle_image_stream(self, data, mime):
345 raw = base64.decodestring(data[mime].encode('ascii'))
345 raw = base64.decodestring(data[mime].encode('ascii'))
346 imageformat = self._imagemime[mime]
346 imageformat = self._imagemime[mime]
347 fmt = dict(format=imageformat)
347 fmt = dict(format=imageformat)
348 args = [s.format(**fmt) for s in self.stream_image_handler]
348 args = [s.format(**fmt) for s in self.stream_image_handler]
349 with open(os.devnull, 'w') as devnull:
349 with open(os.devnull, 'w') as devnull:
350 proc = subprocess.Popen(
350 proc = subprocess.Popen(
351 args, stdin=subprocess.PIPE,
351 args, stdin=subprocess.PIPE,
352 stdout=devnull, stderr=devnull)
352 stdout=devnull, stderr=devnull)
353 proc.communicate(raw)
353 proc.communicate(raw)
354
354
355 def handle_image_tempfile(self, data, mime):
355 def handle_image_tempfile(self, data, mime):
356 raw = base64.decodestring(data[mime].encode('ascii'))
356 raw = base64.decodestring(data[mime].encode('ascii'))
357 imageformat = self._imagemime[mime]
357 imageformat = self._imagemime[mime]
358 filename = 'tmp.{0}'.format(imageformat)
358 filename = 'tmp.{0}'.format(imageformat)
359 with NamedFileInTemporaryDirectory(filename) as f, \
359 with NamedFileInTemporaryDirectory(filename) as f, \
360 open(os.devnull, 'w') as devnull:
360 open(os.devnull, 'w') as devnull:
361 f.write(raw)
361 f.write(raw)
362 f.flush()
362 f.flush()
363 fmt = dict(file=f.name, format=imageformat)
363 fmt = dict(file=f.name, format=imageformat)
364 args = [s.format(**fmt) for s in self.tempfile_image_handler]
364 args = [s.format(**fmt) for s in self.tempfile_image_handler]
365 subprocess.call(args, stdout=devnull, stderr=devnull)
365 subprocess.call(args, stdout=devnull, stderr=devnull)
366
366
367 def handle_image_callable(self, data, mime):
367 def handle_image_callable(self, data, mime):
368 self.callable_image_handler(data)
368 self.callable_image_handler(data)
369
369
370 def handle_input_request(self, msg_id, timeout=0.1):
370 def handle_input_request(self, msg_id, timeout=0.1):
371 """ Method to capture raw_input
371 """ Method to capture raw_input
372 """
372 """
373 req = self.client.stdin_channel.get_msg(timeout=timeout)
373 req = self.client.stdin_channel.get_msg(timeout=timeout)
374 # in case any iopub came while we were waiting:
374 # in case any iopub came while we were waiting:
375 self.handle_iopub(msg_id)
375 self.handle_iopub(msg_id)
376 if msg_id == req["parent_header"].get("msg_id"):
376 if msg_id == req["parent_header"].get("msg_id"):
377 # wrap SIGINT handler
377 # wrap SIGINT handler
378 real_handler = signal.getsignal(signal.SIGINT)
378 real_handler = signal.getsignal(signal.SIGINT)
379 def double_int(sig,frame):
379 def double_int(sig,frame):
380 # call real handler (forwards sigint to kernel),
380 # call real handler (forwards sigint to kernel),
381 # then raise local interrupt, stopping local raw_input
381 # then raise local interrupt, stopping local raw_input
382 real_handler(sig,frame)
382 real_handler(sig,frame)
383 raise KeyboardInterrupt
383 raise KeyboardInterrupt
384 signal.signal(signal.SIGINT, double_int)
384 signal.signal(signal.SIGINT, double_int)
385 content = req['content']
385 content = req['content']
386 read = getpass if content.get('password', False) else input
386 read = getpass if content.get('password', False) else input
387 try:
387 try:
388 raw_data = read(content["prompt"])
388 raw_data = read(content["prompt"])
389 except EOFError:
389 except EOFError:
390 # turn EOFError into EOF character
390 # turn EOFError into EOF character
391 raw_data = '\x04'
391 raw_data = '\x04'
392 except KeyboardInterrupt:
392 except KeyboardInterrupt:
393 sys.stdout.write('\n')
393 sys.stdout.write('\n')
394 return
394 return
395 finally:
395 finally:
396 # restore SIGINT handler
396 # restore SIGINT handler
397 signal.signal(signal.SIGINT, real_handler)
397 signal.signal(signal.SIGINT, real_handler)
398
398
399 # only send stdin reply if there *was not* another request
399 # only send stdin reply if there *was not* another request
400 # or execution finished while we were reading.
400 # or execution finished while we were reading.
401 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
401 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
402 self.client.stdin_channel.input(raw_data)
402 self.client.input(raw_data)
403
403
404 def mainloop(self, display_banner=False):
404 def mainloop(self, display_banner=False):
405 while True:
405 while True:
406 try:
406 try:
407 self.interact(display_banner=display_banner)
407 self.interact(display_banner=display_banner)
408 #self.interact_with_readline()
408 #self.interact_with_readline()
409 # XXX for testing of a readline-decoupled repl loop, call
409 # XXX for testing of a readline-decoupled repl loop, call
410 # interact_with_readline above
410 # interact_with_readline above
411 break
411 break
412 except KeyboardInterrupt:
412 except KeyboardInterrupt:
413 # this should not be necessary, but KeyboardInterrupt
413 # this should not be necessary, but KeyboardInterrupt
414 # handling seems rather unpredictable...
414 # handling seems rather unpredictable...
415 self.write("\nKeyboardInterrupt in interact()\n")
415 self.write("\nKeyboardInterrupt in interact()\n")
416
416
417 self.client.shell_channel.shutdown()
417 self.client.shutdown()
418
418
419 def _banner1_default(self):
419 def _banner1_default(self):
420 return "IPython Console {version}\n".format(version=release.version)
420 return "IPython Console {version}\n".format(version=release.version)
421
421
422 def compute_banner(self):
422 def compute_banner(self):
423 super(ZMQTerminalInteractiveShell, self).compute_banner()
423 super(ZMQTerminalInteractiveShell, self).compute_banner()
424 if self.client and not self.kernel_banner:
424 if self.client and not self.kernel_banner:
425 msg_id = self.client.kernel_info()
425 msg_id = self.client.kernel_info()
426 while True:
426 while True:
427 try:
427 try:
428 reply = self.client.get_shell_msg(timeout=1)
428 reply = self.client.get_shell_msg(timeout=1)
429 except Empty:
429 except Empty:
430 break
430 break
431 else:
431 else:
432 if reply['parent_header'].get('msg_id') == msg_id:
432 if reply['parent_header'].get('msg_id') == msg_id:
433 self.kernel_banner = reply['content'].get('banner', '')
433 self.kernel_banner = reply['content'].get('banner', '')
434 break
434 break
435 self.banner += self.kernel_banner
435 self.banner += self.kernel_banner
436
436
437 def wait_for_kernel(self, timeout=None):
437 def wait_for_kernel(self, timeout=None):
438 """method to wait for a kernel to be ready"""
438 """method to wait for a kernel to be ready"""
439 tic = time.time()
439 tic = time.time()
440 self.client.hb_channel.unpause()
440 self.client.hb_channel.unpause()
441 while True:
441 while True:
442 msg_id = self.client.kernel_info()
442 msg_id = self.client.kernel_info()
443 reply = None
443 reply = None
444 while True:
444 while True:
445 try:
445 try:
446 reply = self.client.get_shell_msg(timeout=1)
446 reply = self.client.get_shell_msg(timeout=1)
447 except Empty:
447 except Empty:
448 break
448 break
449 else:
449 else:
450 if reply['parent_header'].get('msg_id') == msg_id:
450 if reply['parent_header'].get('msg_id') == msg_id:
451 return True
451 return True
452 if timeout is not None \
452 if timeout is not None \
453 and (time.time() - tic) > timeout \
453 and (time.time() - tic) > timeout \
454 and not self.client.hb_channel.is_beating():
454 and not self.client.hb_channel.is_beating():
455 # heart failed
455 # heart failed
456 return False
456 return False
457 return True
457 return True
458
458
459 def interact(self, display_banner=None):
459 def interact(self, display_banner=None):
460 """Closely emulate the interactive Python console."""
460 """Closely emulate the interactive Python console."""
461
461
462 # batch run -> do not interact
462 # batch run -> do not interact
463 if self.exit_now:
463 if self.exit_now:
464 return
464 return
465
465
466 if display_banner is None:
466 if display_banner is None:
467 display_banner = self.display_banner
467 display_banner = self.display_banner
468
468
469 if isinstance(display_banner, string_types):
469 if isinstance(display_banner, string_types):
470 self.show_banner(display_banner)
470 self.show_banner(display_banner)
471 elif display_banner:
471 elif display_banner:
472 self.show_banner()
472 self.show_banner()
473
473
474 more = False
474 more = False
475
475
476 # run a non-empty no-op, so that we don't get a prompt until
476 # run a non-empty no-op, so that we don't get a prompt until
477 # we know the kernel is ready. This keeps the connection
477 # we know the kernel is ready. This keeps the connection
478 # message above the first prompt.
478 # message above the first prompt.
479 if not self.wait_for_kernel(self.kernel_timeout):
479 if not self.wait_for_kernel(self.kernel_timeout):
480 error("Kernel did not respond\n")
480 error("Kernel did not respond\n")
481 return
481 return
482
482
483 if self.has_readline:
483 if self.has_readline:
484 self.readline_startup_hook(self.pre_readline)
484 self.readline_startup_hook(self.pre_readline)
485 hlen_b4_cell = self.readline.get_current_history_length()
485 hlen_b4_cell = self.readline.get_current_history_length()
486 else:
486 else:
487 hlen_b4_cell = 0
487 hlen_b4_cell = 0
488 # exit_now is set by a call to %Exit or %Quit, through the
488 # exit_now is set by a call to %Exit or %Quit, through the
489 # ask_exit callback.
489 # ask_exit callback.
490
490
491 while not self.exit_now:
491 while not self.exit_now:
492 if not self.client.is_alive():
492 if not self.client.is_alive():
493 # kernel died, prompt for action or exit
493 # kernel died, prompt for action or exit
494
494
495 action = "restart" if self.manager else "wait for restart"
495 action = "restart" if self.manager else "wait for restart"
496 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
496 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
497 if ans:
497 if ans:
498 if self.manager:
498 if self.manager:
499 self.manager.restart_kernel(True)
499 self.manager.restart_kernel(True)
500 self.wait_for_kernel(self.kernel_timeout)
500 self.wait_for_kernel(self.kernel_timeout)
501 else:
501 else:
502 self.exit_now = True
502 self.exit_now = True
503 continue
503 continue
504 try:
504 try:
505 # protect prompt block from KeyboardInterrupt
505 # protect prompt block from KeyboardInterrupt
506 # when sitting on ctrl-C
506 # when sitting on ctrl-C
507 self.hooks.pre_prompt_hook()
507 self.hooks.pre_prompt_hook()
508 if more:
508 if more:
509 try:
509 try:
510 prompt = self.prompt_manager.render('in2')
510 prompt = self.prompt_manager.render('in2')
511 except Exception:
511 except Exception:
512 self.showtraceback()
512 self.showtraceback()
513 if self.autoindent:
513 if self.autoindent:
514 self.rl_do_indent = True
514 self.rl_do_indent = True
515
515
516 else:
516 else:
517 try:
517 try:
518 prompt = self.separate_in + self.prompt_manager.render('in')
518 prompt = self.separate_in + self.prompt_manager.render('in')
519 except Exception:
519 except Exception:
520 self.showtraceback()
520 self.showtraceback()
521
521
522 line = self.raw_input(prompt)
522 line = self.raw_input(prompt)
523 if self.exit_now:
523 if self.exit_now:
524 # quick exit on sys.std[in|out] close
524 # quick exit on sys.std[in|out] close
525 break
525 break
526 if self.autoindent:
526 if self.autoindent:
527 self.rl_do_indent = False
527 self.rl_do_indent = False
528
528
529 except KeyboardInterrupt:
529 except KeyboardInterrupt:
530 #double-guard against keyboardinterrupts during kbdint handling
530 #double-guard against keyboardinterrupts during kbdint handling
531 try:
531 try:
532 self.write('\n' + self.get_exception_only())
532 self.write('\n' + self.get_exception_only())
533 source_raw = self.input_splitter.raw_reset()
533 source_raw = self.input_splitter.raw_reset()
534 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
534 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
535 more = False
535 more = False
536 except KeyboardInterrupt:
536 except KeyboardInterrupt:
537 pass
537 pass
538 except EOFError:
538 except EOFError:
539 if self.autoindent:
539 if self.autoindent:
540 self.rl_do_indent = False
540 self.rl_do_indent = False
541 if self.has_readline:
541 if self.has_readline:
542 self.readline_startup_hook(None)
542 self.readline_startup_hook(None)
543 self.write('\n')
543 self.write('\n')
544 self.exit()
544 self.exit()
545 except bdb.BdbQuit:
545 except bdb.BdbQuit:
546 warn('The Python debugger has exited with a BdbQuit exception.\n'
546 warn('The Python debugger has exited with a BdbQuit exception.\n'
547 'Because of how pdb handles the stack, it is impossible\n'
547 'Because of how pdb handles the stack, it is impossible\n'
548 'for IPython to properly format this particular exception.\n'
548 'for IPython to properly format this particular exception.\n'
549 'IPython will resume normal operation.')
549 'IPython will resume normal operation.')
550 except:
550 except:
551 # exceptions here are VERY RARE, but they can be triggered
551 # exceptions here are VERY RARE, but they can be triggered
552 # asynchronously by signal handlers, for example.
552 # asynchronously by signal handlers, for example.
553 self.showtraceback()
553 self.showtraceback()
554 else:
554 else:
555 try:
555 try:
556 self.input_splitter.push(line)
556 self.input_splitter.push(line)
557 more = self.input_splitter.push_accepts_more()
557 more = self.input_splitter.push_accepts_more()
558 except SyntaxError:
558 except SyntaxError:
559 # Run the code directly - run_cell takes care of displaying
559 # Run the code directly - run_cell takes care of displaying
560 # the exception.
560 # the exception.
561 more = False
561 more = False
562 if (self.SyntaxTB.last_syntax_error and
562 if (self.SyntaxTB.last_syntax_error and
563 self.autoedit_syntax):
563 self.autoedit_syntax):
564 self.edit_syntax_error()
564 self.edit_syntax_error()
565 if not more:
565 if not more:
566 source_raw = self.input_splitter.raw_reset()
566 source_raw = self.input_splitter.raw_reset()
567 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
567 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
568 self.run_cell(source_raw)
568 self.run_cell(source_raw)
569
569
570
570
571 # Turn off the exit flag, so the mainloop can be restarted if desired
571 # Turn off the exit flag, so the mainloop can be restarted if desired
572 self.exit_now = False
572 self.exit_now = False
573
573
574 def init_history(self):
574 def init_history(self):
575 """Sets up the command history. """
575 """Sets up the command history. """
576 self.history_manager = ZMQHistoryManager(client=self.client)
576 self.history_manager = ZMQHistoryManager(client=self.client)
577 self.configurables.append(self.history_manager)
577 self.configurables.append(self.history_manager)
578
578
General Comments 0
You need to be logged in to leave comments. Login now