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