##// END OF EJS Templates
Merge pull request #5951 from takluyver/kernelbase...
Min RK -
r17098:2fddb199 merge
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (675 lines changed) Show them Hide them
@@ -0,0 +1,675 b''
1 """Base class for a kernel that talks to frontends over 0MQ."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 from __future__ import print_function
7
8 import sys
9 import time
10 import logging
11 import uuid
12
13 from datetime import datetime
14 from signal import (
15 signal, default_int_handler, SIGINT
16 )
17
18 import zmq
19 from zmq.eventloop import ioloop
20 from zmq.eventloop.zmqstream import ZMQStream
21
22 from IPython.config.configurable import Configurable
23 from IPython.core.error import StdinNotImplementedError
24 from IPython.core import release
25 from IPython.utils import py3compat
26 from IPython.utils.py3compat import unicode_type, string_types
27 from IPython.utils.jsonutil import json_clean
28 from IPython.utils.traitlets import (
29 Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool,
30 )
31
32 from .session import Session
33
34
35 class Kernel(Configurable):
36
37 #---------------------------------------------------------------------------
38 # Kernel interface
39 #---------------------------------------------------------------------------
40
41 # attribute to override with a GUI
42 eventloop = Any(None)
43 def _eventloop_changed(self, name, old, new):
44 """schedule call to eventloop from IOLoop"""
45 loop = ioloop.IOLoop.instance()
46 loop.add_callback(self.enter_eventloop)
47
48 session = Instance(Session)
49 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
50 shell_streams = List()
51 control_stream = Instance(ZMQStream)
52 iopub_socket = Instance(zmq.Socket)
53 stdin_socket = Instance(zmq.Socket)
54 log = Instance(logging.Logger)
55
56 # identities:
57 int_id = Integer(-1)
58 ident = Unicode()
59
60 def _ident_default(self):
61 return unicode_type(uuid.uuid4())
62
63 # Private interface
64
65 _darwin_app_nap = Bool(True, config=True,
66 help="""Whether to use appnope for compatiblity with OS X App Nap.
67
68 Only affects OS X >= 10.9.
69 """
70 )
71
72 # track associations with current request
73 _allow_stdin = Bool(False)
74 _parent_header = Dict()
75 _parent_ident = Any(b'')
76 # Time to sleep after flushing the stdout/err buffers in each execute
77 # cycle. While this introduces a hard limit on the minimal latency of the
78 # execute cycle, it helps prevent output synchronization problems for
79 # clients.
80 # Units are in seconds. The minimum zmq latency on local host is probably
81 # ~150 microseconds, set this to 500us for now. We may need to increase it
82 # a little if it's not enough after more interactive testing.
83 _execute_sleep = Float(0.0005, config=True)
84
85 # Frequency of the kernel's event loop.
86 # Units are in seconds, kernel subclasses for GUI toolkits may need to
87 # adapt to milliseconds.
88 _poll_interval = Float(0.05, config=True)
89
90 # If the shutdown was requested over the network, we leave here the
91 # necessary reply message so it can be sent by our registered atexit
92 # handler. This ensures that the reply is only sent to clients truly at
93 # the end of our shutdown process (which happens after the underlying
94 # IPython shell's own shutdown).
95 _shutdown_message = None
96
97 # This is a dict of port number that the kernel is listening on. It is set
98 # by record_ports and used by connect_request.
99 _recorded_ports = Dict()
100
101 # set of aborted msg_ids
102 aborted = Set()
103
104 # Track execution count here. For IPython, we override this to use the
105 # execution count we store in the shell.
106 execution_count = 0
107
108
109 def __init__(self, **kwargs):
110 super(Kernel, self).__init__(**kwargs)
111
112 # Build dict of handlers for message types
113 msg_types = [ 'execute_request', 'complete_request',
114 'inspect_request', 'history_request',
115 'kernel_info_request',
116 'connect_request', 'shutdown_request',
117 'apply_request',
118 ]
119 self.shell_handlers = {}
120 for msg_type in msg_types:
121 self.shell_handlers[msg_type] = getattr(self, msg_type)
122
123 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
124 self.control_handlers = {}
125 for msg_type in control_msg_types:
126 self.control_handlers[msg_type] = getattr(self, msg_type)
127
128
129 def dispatch_control(self, msg):
130 """dispatch control requests"""
131 idents,msg = self.session.feed_identities(msg, copy=False)
132 try:
133 msg = self.session.unserialize(msg, content=True, copy=False)
134 except:
135 self.log.error("Invalid Control Message", exc_info=True)
136 return
137
138 self.log.debug("Control received: %s", msg)
139
140 header = msg['header']
141 msg_type = header['msg_type']
142
143 handler = self.control_handlers.get(msg_type, None)
144 if handler is None:
145 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
146 else:
147 try:
148 handler(self.control_stream, idents, msg)
149 except Exception:
150 self.log.error("Exception in control handler:", exc_info=True)
151
152 def dispatch_shell(self, stream, msg):
153 """dispatch shell requests"""
154 # flush control requests first
155 if self.control_stream:
156 self.control_stream.flush()
157
158 idents,msg = self.session.feed_identities(msg, copy=False)
159 try:
160 msg = self.session.unserialize(msg, content=True, copy=False)
161 except:
162 self.log.error("Invalid Message", exc_info=True)
163 return
164
165 header = msg['header']
166 msg_id = header['msg_id']
167 msg_type = msg['header']['msg_type']
168
169 # Print some info about this message and leave a '--->' marker, so it's
170 # easier to trace visually the message chain when debugging. Each
171 # handler prints its message at the end.
172 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
173 self.log.debug(' Content: %s\n --->\n ', msg['content'])
174
175 if msg_id in self.aborted:
176 self.aborted.remove(msg_id)
177 # is it safe to assume a msg_id will not be resubmitted?
178 reply_type = msg_type.split('_')[0] + '_reply'
179 status = {'status' : 'aborted'}
180 md = {'engine' : self.ident}
181 md.update(status)
182 self.session.send(stream, reply_type, metadata=md,
183 content=status, parent=msg, ident=idents)
184 return
185
186 handler = self.shell_handlers.get(msg_type, None)
187 if handler is None:
188 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
189 else:
190 # ensure default_int_handler during handler call
191 sig = signal(SIGINT, default_int_handler)
192 self.log.debug("%s: %s", msg_type, msg)
193 try:
194 handler(stream, idents, msg)
195 except Exception:
196 self.log.error("Exception in message handler:", exc_info=True)
197 finally:
198 signal(SIGINT, sig)
199
200 def enter_eventloop(self):
201 """enter eventloop"""
202 self.log.info("entering eventloop %s", self.eventloop)
203 for stream in self.shell_streams:
204 # flush any pending replies,
205 # which may be skipped by entering the eventloop
206 stream.flush(zmq.POLLOUT)
207 # restore default_int_handler
208 signal(SIGINT, default_int_handler)
209 while self.eventloop is not None:
210 try:
211 self.eventloop(self)
212 except KeyboardInterrupt:
213 # Ctrl-C shouldn't crash the kernel
214 self.log.error("KeyboardInterrupt caught in kernel")
215 continue
216 else:
217 # eventloop exited cleanly, this means we should stop (right?)
218 self.eventloop = None
219 break
220 self.log.info("exiting eventloop")
221
222 def start(self):
223 """register dispatchers for streams"""
224 if self.control_stream:
225 self.control_stream.on_recv(self.dispatch_control, copy=False)
226
227 def make_dispatcher(stream):
228 def dispatcher(msg):
229 return self.dispatch_shell(stream, msg)
230 return dispatcher
231
232 for s in self.shell_streams:
233 s.on_recv(make_dispatcher(s), copy=False)
234
235 # publish idle status
236 self._publish_status('starting')
237
238 def do_one_iteration(self):
239 """step eventloop just once"""
240 if self.control_stream:
241 self.control_stream.flush()
242 for stream in self.shell_streams:
243 # handle at most one request per iteration
244 stream.flush(zmq.POLLIN, 1)
245 stream.flush(zmq.POLLOUT)
246
247
248 def record_ports(self, ports):
249 """Record the ports that this kernel is using.
250
251 The creator of the Kernel instance must call this methods if they
252 want the :meth:`connect_request` method to return the port numbers.
253 """
254 self._recorded_ports = ports
255
256 #---------------------------------------------------------------------------
257 # Kernel request handlers
258 #---------------------------------------------------------------------------
259
260 def _make_metadata(self, other=None):
261 """init metadata dict, for execute/apply_reply"""
262 new_md = {
263 'dependencies_met' : True,
264 'engine' : self.ident,
265 'started': datetime.now(),
266 }
267 if other:
268 new_md.update(other)
269 return new_md
270
271 def _publish_execute_input(self, code, parent, execution_count):
272 """Publish the code request on the iopub stream."""
273
274 self.session.send(self.iopub_socket, u'execute_input',
275 {u'code':code, u'execution_count': execution_count},
276 parent=parent, ident=self._topic('execute_input')
277 )
278
279 def _publish_status(self, status, parent=None):
280 """send status (busy/idle) on IOPub"""
281 self.session.send(self.iopub_socket,
282 u'status',
283 {u'execution_state': status},
284 parent=parent,
285 ident=self._topic('status'),
286 )
287
288 def set_parent(self, ident, parent):
289 """Set the current parent_header
290
291 Side effects (IOPub messages) and replies are associated with
292 the request that caused them via the parent_header.
293
294 The parent identity is used to route input_request messages
295 on the stdin channel.
296 """
297 self._parent_ident = ident
298 self._parent_header = parent
299
300 def send_response(self, stream, msg_or_type, content=None, ident=None,
301 buffers=None, track=False, header=None, metadata=None):
302 """Send a response to the message we're currently processing.
303
304 This accepts all the parameters of :meth:`IPython.kernel.zmq.session.Session.send`
305 except ``parent``.
306
307 This relies on :meth:`set_parent` having been called for the current
308 message.
309 """
310 return self.session.send(stream, msg_or_type, content, self._parent_header,
311 ident, buffers, track, header, metadata)
312
313 def execute_request(self, stream, ident, parent):
314 """handle an execute_request"""
315
316 self._publish_status(u'busy', parent)
317
318 try:
319 content = parent[u'content']
320 code = py3compat.cast_unicode_py2(content[u'code'])
321 silent = content[u'silent']
322 store_history = content.get(u'store_history', not silent)
323 user_expressions = content.get('user_expressions', {})
324 allow_stdin = content.get('allow_stdin', False)
325 except:
326 self.log.error("Got bad msg: ")
327 self.log.error("%s", parent)
328 return
329
330 md = self._make_metadata(parent['metadata'])
331
332 # Set the parent message of the display hook and out streams.
333 self.set_parent(ident, parent)
334
335 # Re-broadcast our input for the benefit of listening clients, and
336 # start computing output
337 if not silent:
338 self.execution_count += 1
339 self._publish_execute_input(code, parent, self.execution_count)
340
341 reply_content = self.do_execute(code, silent, store_history,
342 user_expressions, allow_stdin)
343
344 # Flush output before sending the reply.
345 sys.stdout.flush()
346 sys.stderr.flush()
347 # FIXME: on rare occasions, the flush doesn't seem to make it to the
348 # clients... This seems to mitigate the problem, but we definitely need
349 # to better understand what's going on.
350 if self._execute_sleep:
351 time.sleep(self._execute_sleep)
352
353 # Send the reply.
354 reply_content = json_clean(reply_content)
355
356 md['status'] = reply_content['status']
357 if reply_content['status'] == 'error' and \
358 reply_content['ename'] == 'UnmetDependency':
359 md['dependencies_met'] = False
360
361 reply_msg = self.session.send(stream, u'execute_reply',
362 reply_content, parent, metadata=md,
363 ident=ident)
364
365 self.log.debug("%s", reply_msg)
366
367 if not silent and reply_msg['content']['status'] == u'error':
368 self._abort_queues()
369
370 self._publish_status(u'idle', parent)
371
372 def do_execute(self, code, silent, store_history=True,
373 user_experssions=None, allow_stdin=False):
374 """Execute user code. Must be overridden by subclasses.
375 """
376 raise NotImplementedError
377
378 def complete_request(self, stream, ident, parent):
379 content = parent['content']
380 code = content['code']
381 cursor_pos = content['cursor_pos']
382
383 matches = self.do_complete(code, cursor_pos)
384 matches = json_clean(matches)
385 completion_msg = self.session.send(stream, 'complete_reply',
386 matches, parent, ident)
387 self.log.debug("%s", completion_msg)
388
389 def do_complete(self, code, cursor_pos):
390 """Override in subclasses to find completions.
391 """
392 return {'matches' : [],
393 'cursor_end' : cursor_pos,
394 'cursor_start' : cursor_pos,
395 'metadata' : {},
396 'status' : 'ok'}
397
398 def inspect_request(self, stream, ident, parent):
399 content = parent['content']
400
401 reply_content = self.do_inspect(content['code'], content['cursor_pos'],
402 content.get('detail_level', 0))
403 # Before we send this object over, we scrub it for JSON usage
404 reply_content = json_clean(reply_content)
405 msg = self.session.send(stream, 'inspect_reply',
406 reply_content, parent, ident)
407 self.log.debug("%s", msg)
408
409 def do_inspect(self, code, cursor_pos, detail_level=0):
410 """Override in subclasses to allow introspection.
411 """
412 return {'status': 'ok', 'data':{}, 'metadata':{}, 'found':False}
413
414 def history_request(self, stream, ident, parent):
415 content = parent['content']
416
417 reply_content = self.do_history(**content)
418
419 reply_content = json_clean(reply_content)
420 msg = self.session.send(stream, 'history_reply',
421 reply_content, parent, ident)
422 self.log.debug("%s", msg)
423
424 def do_history(self, hist_access_type, output, raw, session=None, start=None,
425 stop=None, n=None, pattern=None, unique=False):
426 """Override in subclasses to access history.
427 """
428 return {'history': []}
429
430 def connect_request(self, stream, ident, parent):
431 if self._recorded_ports is not None:
432 content = self._recorded_ports.copy()
433 else:
434 content = {}
435 msg = self.session.send(stream, 'connect_reply',
436 content, parent, ident)
437 self.log.debug("%s", msg)
438
439 @property
440 def kernel_info(self):
441 return {
442 'protocol_version': release.kernel_protocol_version,
443 'implementation': self.implementation,
444 'implementation_version': self.implementation_version,
445 'language': self.language,
446 'language_version': self.language_version,
447 'banner': self.banner,
448 }
449
450 def kernel_info_request(self, stream, ident, parent):
451 msg = self.session.send(stream, 'kernel_info_reply',
452 self.kernel_info, parent, ident)
453 self.log.debug("%s", msg)
454
455 def shutdown_request(self, stream, ident, parent):
456 content = self.do_shutdown(parent['content']['restart'])
457 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
458 # same content, but different msg_id for broadcasting on IOPub
459 self._shutdown_message = self.session.msg(u'shutdown_reply',
460 content, parent
461 )
462
463 self._at_shutdown()
464 # call sys.exit after a short delay
465 loop = ioloop.IOLoop.instance()
466 loop.add_timeout(time.time()+0.1, loop.stop)
467
468 def do_shutdown(self, restart):
469 """Override in subclasses to do things when the frontend shuts down the
470 kernel.
471 """
472 return {'status': 'ok', 'restart': restart}
473
474 #---------------------------------------------------------------------------
475 # Engine methods
476 #---------------------------------------------------------------------------
477
478 def apply_request(self, stream, ident, parent):
479 try:
480 content = parent[u'content']
481 bufs = parent[u'buffers']
482 msg_id = parent['header']['msg_id']
483 except:
484 self.log.error("Got bad msg: %s", parent, exc_info=True)
485 return
486
487 self._publish_status(u'busy', parent)
488
489 # Set the parent message of the display hook and out streams.
490 self.set_parent(ident, parent)
491
492 md = self._make_metadata(parent['metadata'])
493
494 reply_content, result_buf = self.do_apply(content, bufs, msg_id, md)
495
496 # put 'ok'/'error' status in header, for scheduler introspection:
497 md['status'] = reply_content['status']
498
499 # flush i/o
500 sys.stdout.flush()
501 sys.stderr.flush()
502
503 self.session.send(stream, u'apply_reply', reply_content,
504 parent=parent, ident=ident,buffers=result_buf, metadata=md)
505
506 self._publish_status(u'idle', parent)
507
508 def do_apply(self, content, bufs, msg_id, reply_metadata):
509 """Override in subclasses to support the IPython parallel framework.
510 """
511 raise NotImplementedError
512
513 #---------------------------------------------------------------------------
514 # Control messages
515 #---------------------------------------------------------------------------
516
517 def abort_request(self, stream, ident, parent):
518 """abort a specifig msg by id"""
519 msg_ids = parent['content'].get('msg_ids', None)
520 if isinstance(msg_ids, string_types):
521 msg_ids = [msg_ids]
522 if not msg_ids:
523 self.abort_queues()
524 for mid in msg_ids:
525 self.aborted.add(str(mid))
526
527 content = dict(status='ok')
528 reply_msg = self.session.send(stream, 'abort_reply', content=content,
529 parent=parent, ident=ident)
530 self.log.debug("%s", reply_msg)
531
532 def clear_request(self, stream, idents, parent):
533 """Clear our namespace."""
534 content = self.do_clear()
535 self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
536 content = content)
537
538 def do_clear(self):
539 """Override in subclasses to clear the namespace
540
541 This is only required for IPython.parallel.
542 """
543 raise NotImplementedError
544
545 #---------------------------------------------------------------------------
546 # Protected interface
547 #---------------------------------------------------------------------------
548
549 def _topic(self, topic):
550 """prefixed topic for IOPub messages"""
551 if self.int_id >= 0:
552 base = "engine.%i" % self.int_id
553 else:
554 base = "kernel.%s" % self.ident
555
556 return py3compat.cast_bytes("%s.%s" % (base, topic))
557
558 def _abort_queues(self):
559 for stream in self.shell_streams:
560 if stream:
561 self._abort_queue(stream)
562
563 def _abort_queue(self, stream):
564 poller = zmq.Poller()
565 poller.register(stream.socket, zmq.POLLIN)
566 while True:
567 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
568 if msg is None:
569 return
570
571 self.log.info("Aborting:")
572 self.log.info("%s", msg)
573 msg_type = msg['header']['msg_type']
574 reply_type = msg_type.split('_')[0] + '_reply'
575
576 status = {'status' : 'aborted'}
577 md = {'engine' : self.ident}
578 md.update(status)
579 reply_msg = self.session.send(stream, reply_type, metadata=md,
580 content=status, parent=msg, ident=idents)
581 self.log.debug("%s", reply_msg)
582 # We need to wait a bit for requests to come in. This can probably
583 # be set shorter for true asynchronous clients.
584 poller.poll(50)
585
586
587 def _no_raw_input(self):
588 """Raise StdinNotImplentedError if active frontend doesn't support
589 stdin."""
590 raise StdinNotImplementedError("raw_input was called, but this "
591 "frontend does not support stdin.")
592
593 def getpass(self, prompt=''):
594 """Forward getpass to frontends
595
596 Raises
597 ------
598 StdinNotImplentedError if active frontend doesn't support stdin.
599 """
600 if not self._allow_stdin:
601 raise StdinNotImplementedError(
602 "getpass was called, but this frontend does not support input requests."
603 )
604 return self._input_request(prompt,
605 self._parent_ident,
606 self._parent_header,
607 password=True,
608 )
609
610 def raw_input(self, prompt=''):
611 """Forward raw_input to frontends
612
613 Raises
614 ------
615 StdinNotImplentedError if active frontend doesn't support stdin.
616 """
617 if not self._allow_stdin:
618 raise StdinNotImplementedError(
619 "raw_input was called, but this frontend does not support input requests."
620 )
621 return self._input_request(prompt,
622 self._parent_ident,
623 self._parent_header,
624 password=False,
625 )
626
627 def _input_request(self, prompt, ident, parent, password=False):
628 # Flush output before making the request.
629 sys.stderr.flush()
630 sys.stdout.flush()
631 # flush the stdin socket, to purge stale replies
632 while True:
633 try:
634 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
635 except zmq.ZMQError as e:
636 if e.errno == zmq.EAGAIN:
637 break
638 else:
639 raise
640
641 # Send the input request.
642 content = json_clean(dict(prompt=prompt, password=password))
643 self.session.send(self.stdin_socket, u'input_request', content, parent,
644 ident=ident)
645
646 # Await a response.
647 while True:
648 try:
649 ident, reply = self.session.recv(self.stdin_socket, 0)
650 except Exception:
651 self.log.warn("Invalid Message:", exc_info=True)
652 except KeyboardInterrupt:
653 # re-raise KeyboardInterrupt, to truncate traceback
654 raise KeyboardInterrupt
655 else:
656 break
657 try:
658 value = py3compat.unicode_to_str(reply['content']['value'])
659 except:
660 self.log.error("Bad input_reply: %s", parent)
661 value = ''
662 if value == '\x04':
663 # EOF
664 raise EOFError
665 return value
666
667 def _at_shutdown(self):
668 """Actions taken at shutdown by the kernel, called by python's atexit.
669 """
670 # io.rprint("Kernel at_shutdown") # dbg
671 if self._shutdown_message is not None:
672 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
673 self.log.debug("%s", self._shutdown_message)
674 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
675
@@ -0,0 +1,148 b''
1 Making simple Python wrapper kernels
2 ====================================
3
4 .. versionadded:: 3.0
5
6 You can now re-use the kernel machinery in IPython to easily make new kernels.
7 This is useful for languages that have Python bindings, such as `Octave
8 <http://www.gnu.org/software/octave/>`_ (via
9 `Oct2Py <http://blink1073.github.io/oct2py/docs/index.html>`_), or languages
10 where the REPL can be controlled in a tty using `pexpect <http://pexpect.readthedocs.org/en/latest/>`_,
11 such as bash.
12
13 Required steps
14 --------------
15
16 Subclass :class:`IPython.kernel.zmq.kernelbase.Kernel`, and implement the
17 following methods and attributes:
18
19 .. class:: MyKernel
20
21 .. attribute:: implementation
22 implementation_version
23 language
24 language_version
25 banner
26
27 Information for :ref:`msging_kernel_info` replies. 'Implementation' refers
28 to the kernel (e.g. IPython), and 'language' refers to the language it
29 interprets (e.g. Python). The 'banner' is displayed to the user in console
30 UIs before the first prompt. All of these values are strings.
31
32 .. method:: do_execute(code, silent, store_history=True, user_expressions=None, allow_stdin=False)
33
34 Execute user code.
35
36 :param str code: The code to be executed.
37 :param bool silent: Whether to display output.
38 :param bool store_history: Whether to record this code in history and
39 increase the execution count. If silent is True, this is implicitly
40 False.
41 :param dict user_expressions: Mapping of names to expressions to evaluate
42 after the code has run. You can ignore this if you need to.
43 :param bool allow_stdin: Whether the frontend can provide input on request
44 (e.g. for Python's :func:`raw_input`).
45
46 Your method should return a dict containing the fields described in
47 :ref:`execution_results`. To display output, it can send messages
48 using :meth:`~IPython.kernel.zmq.kernelbase.Kernel.send_response`.
49 See :doc:`messaging` for details of the different message types.
50
51 To launch your kernel, add this at the end of your module::
52
53 if __name__ == '__main__':
54 from IPython.kernel.zmq.kernelapp import IPKernelApp
55 IPKernelApp.launch_instance(kernel_class=MyKernel)
56
57 Example
58 -------
59
60 ``echokernel.py`` will simply echo any input it's given to stdout::
61
62 from IPython.kernel.zmq.kernelbase import Kernel
63
64 class EchoKernel(Kernel):
65 implementation = 'Echo'
66 implementation_version = '1.0'
67 language = 'no-op'
68 language_version = '0.1'
69 banner = "Echo kernel - as useful as a parrot"
70
71 def do_execute(self, code, silent, store_history=True, user_experssions=None,
72 allow_stdin=False):
73 if not silent:
74 stream_content = {'name': 'stdout', 'data':code}
75 self.send_response(self.iopub_socket, 'stream', stream_content)
76
77 return {'status': 'ok',
78 # The base class increments the execution count
79 'execution_count': self.execution_count,
80 'payload': [],
81 'user_expressions': {},
82 }
83
84 if __name__ == '__main__':
85 from IPython.kernel.zmq.kernelapp import IPKernelApp
86 IPKernelApp.launch_instance(kernel_class=EchoKernel)
87
88 Here's the Kernel spec ``kernel.json`` file for this::
89
90 {"argv":["python","-m","echokernel", "-f", "{connection_file}"],
91 "display_name":"Echo",
92 "language":"no-op"
93 }
94
95
96 Optional steps
97 --------------
98
99 You can override a number of other methods to improve the functionality of your
100 kernel. All of these methods should return a dictionary as described in the
101 relevant section of the :doc:`messaging spec <messaging>`.
102
103 .. class:: MyKernel
104
105 .. method:: do_complete(code, cusor_pos)
106
107 Code completion
108
109 :param str code: The code already present
110 :param int cursor_pos: The position in the code where completion is requested
111
112 .. seealso::
113
114 :ref:`msging_completion` messages
115
116 .. method:: do_inspect(code, cusor_pos, detail_level=0)
117
118 Object introspection
119
120 :param str code: The code
121 :param int cursor_pos: The position in the code where introspection is requested
122 :param int detail_level: 0 or 1 for more or less detail. In IPython, 1 gets
123 the source code.
124
125 .. seealso::
126
127 :ref:`msging_inspection` messages
128
129 .. method:: do_history(hist_access_type, output, raw, session=None, start=None, stop=None, n=None, pattern=None, unique=False)
130
131 History access. Only the relevant parameters for the type of history
132 request concerned will be passed, so your method definition must have defaults
133 for all the arguments shown with defaults here.
134
135 .. seealso::
136
137 :ref:`msging_history` messages
138
139 .. method:: do_shutdown(restart)
140
141 Shutdown the kernel. You only need to handle your own clean up - the kernel
142 machinery will take care of cleaning up its own things before stopping.
143
144 :param bool restart: Whether the kernel will be started again afterwards
145
146 .. seealso::
147
148 :ref:`msging_shutdown` messages
@@ -45,7 +45,7 b' class InProcessKernelClient(KernelClient):'
45 stdin_channel_class = Type(InProcessStdInChannel)
45 stdin_channel_class = Type(InProcessStdInChannel)
46 hb_channel_class = Type(InProcessHBChannel)
46 hb_channel_class = Type(InProcessHBChannel)
47
47
48 kernel = Instance('IPython.kernel.inprocess.ipkernel.Kernel')
48 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel')
49
49
50 #--------------------------------------------------------------------------
50 #--------------------------------------------------------------------------
51 # Channel management methods
51 # Channel management methods
@@ -10,7 +10,7 b' import sys'
10 from IPython.core.interactiveshell import InteractiveShellABC
10 from IPython.core.interactiveshell import InteractiveShellABC
11 from IPython.utils.jsonutil import json_clean
11 from IPython.utils.jsonutil import json_clean
12 from IPython.utils.traitlets import Any, Enum, Instance, List, Type
12 from IPython.utils.traitlets import Any, Enum, Instance, List, Type
13 from IPython.kernel.zmq.ipkernel import Kernel
13 from IPython.kernel.zmq.ipkernel import IPythonKernel
14 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
14 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
15
15
16 from .socket import DummySocket
16 from .socket import DummySocket
@@ -19,7 +19,7 b' from .socket import DummySocket'
19 # Main kernel class
19 # Main kernel class
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 class InProcessKernel(Kernel):
22 class InProcessKernel(IPythonKernel):
23
23
24 #-------------------------------------------------------------------------
24 #-------------------------------------------------------------------------
25 # InProcessKernel interface
25 # InProcessKernel interface
This diff has been collapsed as it changes many lines, (732 lines changed) Show them Hide them
@@ -1,143 +1,41 b''
1 """An interactive kernel that talks to frontends over 0MQ."""
1 """The IPython kernel implementation"""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 from __future__ import print_function
7
2
8 import getpass
3 import getpass
9 import sys
4 import sys
10 import time
11 import traceback
5 import traceback
12 import logging
13 import uuid
14
15 from datetime import datetime
16 from signal import (
17 signal, default_int_handler, SIGINT
18 )
19
6
20 import zmq
21 from zmq.eventloop import ioloop
22 from zmq.eventloop.zmqstream import ZMQStream
23
24 from IPython.config.configurable import Configurable
25 from IPython.core.error import StdinNotImplementedError
26 from IPython.core import release
7 from IPython.core import release
27 from IPython.utils import py3compat
8 from IPython.utils.py3compat import builtin_mod, PY3
28 from IPython.utils.py3compat import builtin_mod, unicode_type, string_types
29 from IPython.utils.jsonutil import json_clean
30 from IPython.utils.tokenutil import token_at_cursor
9 from IPython.utils.tokenutil import token_at_cursor
31 from IPython.utils.traitlets import (
10 from IPython.utils.traitlets import Instance, Type, Any
32 Any, Instance, Float, Dict, List, Set, Integer, Unicode,
11 from IPython.utils.decorators import undoc
33 Type, Bool,
34 )
35
12
13 from .kernelbase import Kernel as KernelBase
36 from .serialize import serialize_object, unpack_apply_message
14 from .serialize import serialize_object, unpack_apply_message
37 from .session import Session
38 from .zmqshell import ZMQInteractiveShell
15 from .zmqshell import ZMQInteractiveShell
39
16
40
17 class IPythonKernel(KernelBase):
41 #-----------------------------------------------------------------------------
42 # Main kernel class
43 #-----------------------------------------------------------------------------
44
45 protocol_version = release.kernel_protocol_version
46 ipython_version = release.version
47 language_version = sys.version.split()[0]
48
49
50 class Kernel(Configurable):
51
52 #---------------------------------------------------------------------------
53 # Kernel interface
54 #---------------------------------------------------------------------------
55
56 # attribute to override with a GUI
57 eventloop = Any(None)
58 def _eventloop_changed(self, name, old, new):
59 """schedule call to eventloop from IOLoop"""
60 loop = ioloop.IOLoop.instance()
61 loop.add_callback(self.enter_eventloop)
62
63 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
18 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
64 shell_class = Type(ZMQInteractiveShell)
19 shell_class = Type(ZMQInteractiveShell)
65
20
66 session = Instance(Session)
67 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
68 shell_streams = List()
69 control_stream = Instance(ZMQStream)
70 iopub_socket = Instance(zmq.Socket)
71 stdin_socket = Instance(zmq.Socket)
72 log = Instance(logging.Logger)
73
74 user_module = Any()
21 user_module = Any()
75 def _user_module_changed(self, name, old, new):
22 def _user_module_changed(self, name, old, new):
76 if self.shell is not None:
23 if self.shell is not None:
77 self.shell.user_module = new
24 self.shell.user_module = new
78
25
79 user_ns = Instance(dict, args=None, allow_none=True)
26 user_ns = Instance(dict, args=None, allow_none=True)
80 def _user_ns_changed(self, name, old, new):
27 def _user_ns_changed(self, name, old, new):
81 if self.shell is not None:
28 if self.shell is not None:
82 self.shell.user_ns = new
29 self.shell.user_ns = new
83 self.shell.init_user_ns()
30 self.shell.init_user_ns()
84
31
85 # identities:
86 int_id = Integer(-1)
87 ident = Unicode()
88
89 def _ident_default(self):
90 return unicode_type(uuid.uuid4())
91
92 # Private interface
93
94 _darwin_app_nap = Bool(True, config=True,
95 help="""Whether to use appnope for compatiblity with OS X App Nap.
96
97 Only affects OS X >= 10.9.
98 """
99 )
100
101 # track associations with current request
102 _allow_stdin = Bool(False)
103 _parent_header = Dict()
104 _parent_ident = Any(b'')
105 # Time to sleep after flushing the stdout/err buffers in each execute
106 # cycle. While this introduces a hard limit on the minimal latency of the
107 # execute cycle, it helps prevent output synchronization problems for
108 # clients.
109 # Units are in seconds. The minimum zmq latency on local host is probably
110 # ~150 microseconds, set this to 500us for now. We may need to increase it
111 # a little if it's not enough after more interactive testing.
112 _execute_sleep = Float(0.0005, config=True)
113
114 # Frequency of the kernel's event loop.
115 # Units are in seconds, kernel subclasses for GUI toolkits may need to
116 # adapt to milliseconds.
117 _poll_interval = Float(0.05, config=True)
118
119 # If the shutdown was requested over the network, we leave here the
120 # necessary reply message so it can be sent by our registered atexit
121 # handler. This ensures that the reply is only sent to clients truly at
122 # the end of our shutdown process (which happens after the underlying
123 # IPython shell's own shutdown).
124 _shutdown_message = None
125
126 # This is a dict of port number that the kernel is listening on. It is set
127 # by record_ports and used by connect_request.
128 _recorded_ports = Dict()
129
130 # A reference to the Python builtin 'raw_input' function.
32 # A reference to the Python builtin 'raw_input' function.
131 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
33 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
132 _sys_raw_input = Any()
34 _sys_raw_input = Any()
133 _sys_eval_input = Any()
35 _sys_eval_input = Any()
134
36
135 # set of aborted msg_ids
136 aborted = Set()
137
138
139 def __init__(self, **kwargs):
37 def __init__(self, **kwargs):
140 super(Kernel, self).__init__(**kwargs)
38 super(IPythonKernel, self).__init__(**kwargs)
141
39
142 # Initialize the InteractiveShell subclass
40 # Initialize the InteractiveShell subclass
143 self.shell = self.shell_class.instance(parent=self,
41 self.shell = self.shell_class.instance(parent=self,
@@ -157,197 +55,39 b' class Kernel(Configurable):'
157 # TMP - hack while developing
55 # TMP - hack while developing
158 self.shell._reply_content = None
56 self.shell._reply_content = None
159
57
160 # Build dict of handlers for message types
161 msg_types = [ 'execute_request', 'complete_request',
162 'inspect_request', 'history_request',
163 'kernel_info_request',
164 'connect_request', 'shutdown_request',
165 'apply_request',
166 ]
167 self.shell_handlers = {}
168 for msg_type in msg_types:
169 self.shell_handlers[msg_type] = getattr(self, msg_type)
170
171 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
58 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
172 comm_manager = self.shell.comm_manager
59 comm_manager = self.shell.comm_manager
173 for msg_type in comm_msg_types:
60 for msg_type in comm_msg_types:
174 self.shell_handlers[msg_type] = getattr(comm_manager, msg_type)
61 self.shell_handlers[msg_type] = getattr(comm_manager, msg_type)
175
176 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
177 self.control_handlers = {}
178 for msg_type in control_msg_types:
179 self.control_handlers[msg_type] = getattr(self, msg_type)
180
181
182 def dispatch_control(self, msg):
183 """dispatch control requests"""
184 idents,msg = self.session.feed_identities(msg, copy=False)
185 try:
186 msg = self.session.unserialize(msg, content=True, copy=False)
187 except:
188 self.log.error("Invalid Control Message", exc_info=True)
189 return
190
191 self.log.debug("Control received: %s", msg)
192
193 header = msg['header']
194 msg_id = header['msg_id']
195 msg_type = header['msg_type']
196
62
197 handler = self.control_handlers.get(msg_type, None)
63 # Kernel info fields
198 if handler is None:
64 implementation = 'ipython'
199 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
65 implementation_version = release.version
200 else:
66 language = 'python'
201 try:
67 language_version = sys.version.split()[0]
202 handler(self.control_stream, idents, msg)
68 @property
203 except Exception:
69 def banner(self):
204 self.log.error("Exception in control handler:", exc_info=True)
70 return self.shell.banner
205
206 def dispatch_shell(self, stream, msg):
207 """dispatch shell requests"""
208 # flush control requests first
209 if self.control_stream:
210 self.control_stream.flush()
211
212 idents,msg = self.session.feed_identities(msg, copy=False)
213 try:
214 msg = self.session.unserialize(msg, content=True, copy=False)
215 except:
216 self.log.error("Invalid Message", exc_info=True)
217 return
218
219 header = msg['header']
220 msg_id = header['msg_id']
221 msg_type = msg['header']['msg_type']
222
223 # Print some info about this message and leave a '--->' marker, so it's
224 # easier to trace visually the message chain when debugging. Each
225 # handler prints its message at the end.
226 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
227 self.log.debug(' Content: %s\n --->\n ', msg['content'])
228
229 if msg_id in self.aborted:
230 self.aborted.remove(msg_id)
231 # is it safe to assume a msg_id will not be resubmitted?
232 reply_type = msg_type.split('_')[0] + '_reply'
233 status = {'status' : 'aborted'}
234 md = {'engine' : self.ident}
235 md.update(status)
236 reply_msg = self.session.send(stream, reply_type, metadata=md,
237 content=status, parent=msg, ident=idents)
238 return
239
240 handler = self.shell_handlers.get(msg_type, None)
241 if handler is None:
242 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
243 else:
244 # ensure default_int_handler during handler call
245 sig = signal(SIGINT, default_int_handler)
246 self.log.debug("%s: %s", msg_type, msg)
247 try:
248 handler(stream, idents, msg)
249 except Exception:
250 self.log.error("Exception in message handler:", exc_info=True)
251 finally:
252 signal(SIGINT, sig)
253
254 def enter_eventloop(self):
255 """enter eventloop"""
256 self.log.info("entering eventloop %s", self.eventloop)
257 for stream in self.shell_streams:
258 # flush any pending replies,
259 # which may be skipped by entering the eventloop
260 stream.flush(zmq.POLLOUT)
261 # restore default_int_handler
262 signal(SIGINT, default_int_handler)
263 while self.eventloop is not None:
264 try:
265 self.eventloop(self)
266 except KeyboardInterrupt:
267 # Ctrl-C shouldn't crash the kernel
268 self.log.error("KeyboardInterrupt caught in kernel")
269 continue
270 else:
271 # eventloop exited cleanly, this means we should stop (right?)
272 self.eventloop = None
273 break
274 self.log.info("exiting eventloop")
275
71
276 def start(self):
72 def start(self):
277 """register dispatchers for streams"""
278 self.shell.exit_now = False
73 self.shell.exit_now = False
279 if self.control_stream:
74 super(IPythonKernel, self).start()
280 self.control_stream.on_recv(self.dispatch_control, copy=False)
75
281
76 def set_parent(self, ident, parent):
282 def make_dispatcher(stream):
77 """Overridden from parent to tell the display hook and output streams
283 def dispatcher(msg):
78 about the parent message.
284 return self.dispatch_shell(stream, msg)
285 return dispatcher
286
287 for s in self.shell_streams:
288 s.on_recv(make_dispatcher(s), copy=False)
289
290 # publish idle status
291 self._publish_status('starting')
292
293 def do_one_iteration(self):
294 """step eventloop just once"""
295 if self.control_stream:
296 self.control_stream.flush()
297 for stream in self.shell_streams:
298 # handle at most one request per iteration
299 stream.flush(zmq.POLLIN, 1)
300 stream.flush(zmq.POLLOUT)
301
302
303 def record_ports(self, ports):
304 """Record the ports that this kernel is using.
305
306 The creator of the Kernel instance must call this methods if they
307 want the :meth:`connect_request` method to return the port numbers.
308 """
79 """
309 self._recorded_ports = ports
80 super(IPythonKernel, self).set_parent(ident, parent)
310
81 self.shell.set_parent(parent)
311 #---------------------------------------------------------------------------
82
312 # Kernel request handlers
313 #---------------------------------------------------------------------------
314
315 def _make_metadata(self, other=None):
316 """init metadata dict, for execute/apply_reply"""
317 new_md = {
318 'dependencies_met' : True,
319 'engine' : self.ident,
320 'started': datetime.now(),
321 }
322 if other:
323 new_md.update(other)
324 return new_md
325
326 def _publish_execute_input(self, code, parent, execution_count):
327 """Publish the code request on the iopub stream."""
328
329 self.session.send(self.iopub_socket, u'execute_input',
330 {u'code':code, u'execution_count': execution_count},
331 parent=parent, ident=self._topic('execute_input')
332 )
333
334 def _publish_status(self, status, parent=None):
335 """send status (busy/idle) on IOPub"""
336 self.session.send(self.iopub_socket,
337 u'status',
338 {u'execution_state': status},
339 parent=parent,
340 ident=self._topic('status'),
341 )
342
343 def _forward_input(self, allow_stdin=False):
83 def _forward_input(self, allow_stdin=False):
344 """Forward raw_input and getpass to the current frontend.
84 """Forward raw_input and getpass to the current frontend.
345
85
346 via input_request
86 via input_request
347 """
87 """
348 self._allow_stdin = allow_stdin
88 self._allow_stdin = allow_stdin
349
89
350 if py3compat.PY3:
90 if PY3:
351 self._sys_raw_input = builtin_mod.input
91 self._sys_raw_input = builtin_mod.input
352 builtin_mod.input = self.raw_input
92 builtin_mod.input = self.raw_input
353 else:
93 else:
@@ -357,57 +97,32 b' class Kernel(Configurable):'
357 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
97 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
358 self._save_getpass = getpass.getpass
98 self._save_getpass = getpass.getpass
359 getpass.getpass = self.getpass
99 getpass.getpass = self.getpass
360
100
361 def _restore_input(self):
101 def _restore_input(self):
362 """Restore raw_input, getpass"""
102 """Restore raw_input, getpass"""
363 if py3compat.PY3:
103 if PY3:
364 builtin_mod.input = self._sys_raw_input
104 builtin_mod.input = self._sys_raw_input
365 else:
105 else:
366 builtin_mod.raw_input = self._sys_raw_input
106 builtin_mod.raw_input = self._sys_raw_input
367 builtin_mod.input = self._sys_eval_input
107 builtin_mod.input = self._sys_eval_input
368
108
369 getpass.getpass = self._save_getpass
109 getpass.getpass = self._save_getpass
370
110
371 def set_parent(self, ident, parent):
111 @property
372 """Set the current parent_header
112 def execution_count(self):
373
113 return self.shell.execution_count
374 Side effects (IOPub messages) and replies are associated with
114
375 the request that caused them via the parent_header.
115 @execution_count.setter
376
116 def execution_count(self, value):
377 The parent identity is used to route input_request messages
117 # Ignore the incrememnting done by KernelBase, in favour of our shell's
378 on the stdin channel.
118 # execution counter.
379 """
119 pass
380 self._parent_ident = ident
120
381 self._parent_header = parent
121 def do_execute(self, code, silent, store_history=True,
382 self.shell.set_parent(parent)
122 user_expressions=None, allow_stdin=False):
383
384 def execute_request(self, stream, ident, parent):
385 """handle an execute_request"""
386
387 self._publish_status(u'busy', parent)
388
389 try:
390 content = parent[u'content']
391 code = py3compat.cast_unicode_py2(content[u'code'])
392 silent = content[u'silent']
393 store_history = content.get(u'store_history', not silent)
394 except:
395 self.log.error("Got bad msg: ")
396 self.log.error("%s", parent)
397 return
398
399 md = self._make_metadata(parent['metadata'])
400
401 shell = self.shell # we'll need this a lot here
123 shell = self.shell # we'll need this a lot here
402
124
403 self._forward_input(content.get('allow_stdin', False))
125 self._forward_input(allow_stdin)
404 # Set the parent message of the display hook and out streams.
405 self.set_parent(ident, parent)
406
407 # Re-broadcast our input for the benefit of listening clients, and
408 # start computing output
409 if not silent:
410 self._publish_execute_input(code, parent, shell.execution_count)
411
126
412 reply_content = {}
127 reply_content = {}
413 # FIXME: the shell calls the exception handler itself.
128 # FIXME: the shell calls the exception handler itself.
@@ -443,16 +158,16 b' class Kernel(Configurable):'
443 reply_content['engine_info'] = e_info
158 reply_content['engine_info'] = e_info
444 # reset after use
159 # reset after use
445 shell._reply_content = None
160 shell._reply_content = None
446
161
447 if 'traceback' in reply_content:
162 if 'traceback' in reply_content:
448 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
163 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
449
164
450
165
451 # At this point, we can tell whether the main code execution succeeded
166 # At this point, we can tell whether the main code execution succeeded
452 # or not. If it did, we proceed to evaluate user_expressions
167 # or not. If it did, we proceed to evaluate user_expressions
453 if reply_content['status'] == 'ok':
168 if reply_content['status'] == 'ok':
454 reply_content[u'user_expressions'] = \
169 reply_content[u'user_expressions'] = \
455 shell.user_expressions(content.get(u'user_expressions', {}))
170 shell.user_expressions(user_expressions or {})
456 else:
171 else:
457 # If there was an error, don't even try to compute expressions
172 # If there was an error, don't even try to compute expressions
458 reply_content[u'user_expressions'] = {}
173 reply_content[u'user_expressions'] = {}
@@ -465,56 +180,20 b' class Kernel(Configurable):'
465 # it to sit in memory until the next execute_request comes in.
180 # it to sit in memory until the next execute_request comes in.
466 shell.payload_manager.clear_payload()
181 shell.payload_manager.clear_payload()
467
182
468 # Flush output before sending the reply.
183 return reply_content
469 sys.stdout.flush()
184
470 sys.stderr.flush()
185 def do_complete(self, code, cursor_pos):
471 # FIXME: on rare occasions, the flush doesn't seem to make it to the
472 # clients... This seems to mitigate the problem, but we definitely need
473 # to better understand what's going on.
474 if self._execute_sleep:
475 time.sleep(self._execute_sleep)
476
477 # Send the reply.
478 reply_content = json_clean(reply_content)
479
480 md['status'] = reply_content['status']
481 if reply_content['status'] == 'error' and \
482 reply_content['ename'] == 'UnmetDependency':
483 md['dependencies_met'] = False
484
485 reply_msg = self.session.send(stream, u'execute_reply',
486 reply_content, parent, metadata=md,
487 ident=ident)
488
489 self.log.debug("%s", reply_msg)
490
491 if not silent and reply_msg['content']['status'] == u'error':
492 self._abort_queues()
493
494 self._publish_status(u'idle', parent)
495
496 def complete_request(self, stream, ident, parent):
497 content = parent['content']
498 code = content['code']
499 cursor_pos = content['cursor_pos']
500
501 txt, matches = self.shell.complete('', code, cursor_pos)
186 txt, matches = self.shell.complete('', code, cursor_pos)
502 matches = {'matches' : matches,
187 return {'matches' : matches,
503 'cursor_end' : cursor_pos,
188 'cursor_end' : cursor_pos,
504 'cursor_start' : cursor_pos - len(txt),
189 'cursor_start' : cursor_pos - len(txt),
505 'metadata' : {},
190 'metadata' : {},
506 'status' : 'ok'}
191 'status' : 'ok'}
507 matches = json_clean(matches)
192
508 completion_msg = self.session.send(stream, 'complete_reply',
193 def do_inspect(self, code, cursor_pos, detail_level=0):
509 matches, parent, ident)
194 name = token_at_cursor(code, cursor_pos)
510 self.log.debug("%s", completion_msg)
511
512 def inspect_request(self, stream, ident, parent):
513 content = parent['content']
514
515 name = token_at_cursor(content['code'], content['cursor_pos'])
516 info = self.shell.object_inspect(name)
195 info = self.shell.object_inspect(name)
517
196
518 reply_content = {'status' : 'ok'}
197 reply_content = {'status' : 'ok'}
519 reply_content['data'] = data = {}
198 reply_content['data'] = data = {}
520 reply_content['metadata'] = {}
199 reply_content['metadata'] = {}
@@ -522,106 +201,36 b' class Kernel(Configurable):'
522 if info['found']:
201 if info['found']:
523 info_text = self.shell.object_inspect_text(
202 info_text = self.shell.object_inspect_text(
524 name,
203 name,
525 detail_level=content.get('detail_level', 0),
204 detail_level=detail_level,
526 )
205 )
527 reply_content['data']['text/plain'] = info_text
206 data['text/plain'] = info_text
528 # Before we send this object over, we scrub it for JSON usage
207
529 reply_content = json_clean(reply_content)
208 return reply_content
530 msg = self.session.send(stream, 'inspect_reply',
209
531 reply_content, parent, ident)
210 def do_history(self, hist_access_type, output, raw, session=None, start=None,
532 self.log.debug("%s", msg)
211 stop=None, n=None, pattern=None, unique=False):
533
534 def history_request(self, stream, ident, parent):
535 # We need to pull these out, as passing **kwargs doesn't work with
536 # unicode keys before Python 2.6.5.
537 hist_access_type = parent['content']['hist_access_type']
538 raw = parent['content']['raw']
539 output = parent['content']['output']
540 if hist_access_type == 'tail':
212 if hist_access_type == 'tail':
541 n = parent['content']['n']
542 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
213 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
543 include_latest=True)
214 include_latest=True)
544
215
545 elif hist_access_type == 'range':
216 elif hist_access_type == 'range':
546 session = parent['content']['session']
547 start = parent['content']['start']
548 stop = parent['content']['stop']
549 hist = self.shell.history_manager.get_range(session, start, stop,
217 hist = self.shell.history_manager.get_range(session, start, stop,
550 raw=raw, output=output)
218 raw=raw, output=output)
551
219
552 elif hist_access_type == 'search':
220 elif hist_access_type == 'search':
553 n = parent['content'].get('n')
554 unique = parent['content'].get('unique', False)
555 pattern = parent['content']['pattern']
556 hist = self.shell.history_manager.search(
221 hist = self.shell.history_manager.search(
557 pattern, raw=raw, output=output, n=n, unique=unique)
222 pattern, raw=raw, output=output, n=n, unique=unique)
558
559 else:
223 else:
560 hist = []
224 hist = []
561 hist = list(hist)
562 content = {'history' : hist}
563 content = json_clean(content)
564 msg = self.session.send(stream, 'history_reply',
565 content, parent, ident)
566 self.log.debug("Sending history reply with %i entries", len(hist))
567
568 def connect_request(self, stream, ident, parent):
569 if self._recorded_ports is not None:
570 content = self._recorded_ports.copy()
571 else:
572 content = {}
573 msg = self.session.send(stream, 'connect_reply',
574 content, parent, ident)
575 self.log.debug("%s", msg)
576
577 def kernel_info_request(self, stream, ident, parent):
578 vinfo = {
579 'protocol_version': protocol_version,
580 'implementation': 'ipython',
581 'implementation_version': ipython_version,
582 'language_version': language_version,
583 'language': 'python',
584 'banner': self.shell.banner,
585 }
586 msg = self.session.send(stream, 'kernel_info_reply',
587 vinfo, parent, ident)
588 self.log.debug("%s", msg)
589
590 def shutdown_request(self, stream, ident, parent):
591 self.shell.exit_now = True
592 content = dict(status='ok')
593 content.update(parent['content'])
594 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
595 # same content, but different msg_id for broadcasting on IOPub
596 self._shutdown_message = self.session.msg(u'shutdown_reply',
597 content, parent
598 )
599
600 self._at_shutdown()
601 # call sys.exit after a short delay
602 loop = ioloop.IOLoop.instance()
603 loop.add_timeout(time.time()+0.1, loop.stop)
604
225
605 #---------------------------------------------------------------------------
226 return {'history' : list(hist)}
606 # Engine methods
607 #---------------------------------------------------------------------------
608
227
609 def apply_request(self, stream, ident, parent):
228 def do_shutdown(self, restart):
610 try:
229 self.shell.exit_now = True
611 content = parent[u'content']
230 return dict(status='ok', restart=restart)
612 bufs = parent[u'buffers']
613 msg_id = parent['header']['msg_id']
614 except:
615 self.log.error("Got bad msg: %s", parent, exc_info=True)
616 return
617
618 self._publish_status(u'busy', parent)
619
231
620 # Set the parent message of the display hook and out streams.
232 def do_apply(self, content, bufs, msg_id, reply_metadata):
621 shell = self.shell
233 shell = self.shell
622 shell.set_parent(parent)
623
624 md = self._make_metadata(parent['metadata'])
625 try:
234 try:
626 working = shell.user_ns
235 working = shell.user_ns
627
236
@@ -651,7 +260,7 b' class Kernel(Configurable):'
651 buffer_threshold=self.session.buffer_threshold,
260 buffer_threshold=self.session.buffer_threshold,
652 item_threshold=self.session.item_threshold,
261 item_threshold=self.session.item_threshold,
653 )
262 )
654
263
655 except:
264 except:
656 # invoke IPython traceback formatting
265 # invoke IPython traceback formatting
657 shell.showtraceback()
266 shell.showtraceback()
@@ -664,191 +273,30 b' class Kernel(Configurable):'
664 reply_content['engine_info'] = e_info
273 reply_content['engine_info'] = e_info
665 # reset after use
274 # reset after use
666 shell._reply_content = None
275 shell._reply_content = None
667
276
668 self.session.send(self.iopub_socket, u'error', reply_content, parent=parent,
277 self.send_response(self.iopub_socket, u'error', reply_content,
669 ident=self._topic('error'))
278 ident=self._topic('error'))
670 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
279 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
671 result_buf = []
280 result_buf = []
672
281
673 if reply_content['ename'] == 'UnmetDependency':
282 if reply_content['ename'] == 'UnmetDependency':
674 md['dependencies_met'] = False
283 reply_metadata['dependencies_met'] = False
675 else:
284 else:
676 reply_content = {'status' : 'ok'}
285 reply_content = {'status' : 'ok'}
677
286
678 # put 'ok'/'error' status in header, for scheduler introspection:
287 return reply_content, result_buf
679 md['status'] = reply_content['status']
288
680
289 def do_clear(self):
681 # flush i/o
682 sys.stdout.flush()
683 sys.stderr.flush()
684
685 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
686 parent=parent, ident=ident,buffers=result_buf, metadata=md)
687
688 self._publish_status(u'idle', parent)
689
690 #---------------------------------------------------------------------------
691 # Control messages
692 #---------------------------------------------------------------------------
693
694 def abort_request(self, stream, ident, parent):
695 """abort a specifig msg by id"""
696 msg_ids = parent['content'].get('msg_ids', None)
697 if isinstance(msg_ids, string_types):
698 msg_ids = [msg_ids]
699 if not msg_ids:
700 self.abort_queues()
701 for mid in msg_ids:
702 self.aborted.add(str(mid))
703
704 content = dict(status='ok')
705 reply_msg = self.session.send(stream, 'abort_reply', content=content,
706 parent=parent, ident=ident)
707 self.log.debug("%s", reply_msg)
708
709 def clear_request(self, stream, idents, parent):
710 """Clear our namespace."""
711 self.shell.reset(False)
290 self.shell.reset(False)
712 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
291 return dict(status='ok')
713 content = dict(status='ok'))
292
714
293
715
294 # This exists only for backwards compatibility - use IPythonKernel instead
716 #---------------------------------------------------------------------------
717 # Protected interface
718 #---------------------------------------------------------------------------
719
720 def _wrap_exception(self, method=None):
721 # import here, because _wrap_exception is only used in parallel,
722 # and parallel has higher min pyzmq version
723 from IPython.parallel.error import wrap_exception
724 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
725 content = wrap_exception(e_info)
726 return content
727
728 def _topic(self, topic):
729 """prefixed topic for IOPub messages"""
730 if self.int_id >= 0:
731 base = "engine.%i" % self.int_id
732 else:
733 base = "kernel.%s" % self.ident
734
735 return py3compat.cast_bytes("%s.%s" % (base, topic))
736
737 def _abort_queues(self):
738 for stream in self.shell_streams:
739 if stream:
740 self._abort_queue(stream)
741
742 def _abort_queue(self, stream):
743 poller = zmq.Poller()
744 poller.register(stream.socket, zmq.POLLIN)
745 while True:
746 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
747 if msg is None:
748 return
749
750 self.log.info("Aborting:")
751 self.log.info("%s", msg)
752 msg_type = msg['header']['msg_type']
753 reply_type = msg_type.split('_')[0] + '_reply'
754
755 status = {'status' : 'aborted'}
756 md = {'engine' : self.ident}
757 md.update(status)
758 reply_msg = self.session.send(stream, reply_type, metadata=md,
759 content=status, parent=msg, ident=idents)
760 self.log.debug("%s", reply_msg)
761 # We need to wait a bit for requests to come in. This can probably
762 # be set shorter for true asynchronous clients.
763 poller.poll(50)
764
765
766 def _no_raw_input(self):
767 """Raise StdinNotImplentedError if active frontend doesn't support
768 stdin."""
769 raise StdinNotImplementedError("raw_input was called, but this "
770 "frontend does not support stdin.")
771
772 def getpass(self, prompt=''):
773 """Forward getpass to frontends
774
775 Raises
776 ------
777 StdinNotImplentedError if active frontend doesn't support stdin.
778 """
779 if not self._allow_stdin:
780 raise StdinNotImplementedError(
781 "getpass was called, but this frontend does not support input requests."
782 )
783 return self._input_request(prompt,
784 self._parent_ident,
785 self._parent_header,
786 password=True,
787 )
788
789 def raw_input(self, prompt=''):
790 """Forward raw_input to frontends
791
792 Raises
793 ------
794 StdinNotImplentedError if active frontend doesn't support stdin.
795 """
796 if not self._allow_stdin:
797 raise StdinNotImplementedError(
798 "raw_input was called, but this frontend does not support input requests."
799 )
800 return self._input_request(prompt,
801 self._parent_ident,
802 self._parent_header,
803 password=False,
804 )
805
806 def _input_request(self, prompt, ident, parent, password=False):
807 # Flush output before making the request.
808 sys.stderr.flush()
809 sys.stdout.flush()
810 # flush the stdin socket, to purge stale replies
811 while True:
812 try:
813 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
814 except zmq.ZMQError as e:
815 if e.errno == zmq.EAGAIN:
816 break
817 else:
818 raise
819
820 # Send the input request.
821 content = json_clean(dict(prompt=prompt, password=password))
822 self.session.send(self.stdin_socket, u'input_request', content, parent,
823 ident=ident)
824
825 # Await a response.
826 while True:
827 try:
828 ident, reply = self.session.recv(self.stdin_socket, 0)
829 except Exception:
830 self.log.warn("Invalid Message:", exc_info=True)
831 except KeyboardInterrupt:
832 # re-raise KeyboardInterrupt, to truncate traceback
833 raise KeyboardInterrupt
834 else:
835 break
836 try:
837 value = py3compat.unicode_to_str(reply['content']['value'])
838 except:
839 self.log.error("Bad input_reply: %s", parent)
840 value = ''
841 if value == '\x04':
842 # EOF
843 raise EOFError
844 return value
845
846 def _at_shutdown(self):
847 """Actions taken at shutdown by the kernel, called by python's atexit.
848 """
849 # io.rprint("Kernel at_shutdown") # dbg
850 if self._shutdown_message is not None:
851 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
852 self.log.debug("%s", self._shutdown_message)
853 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
854
295
296 @undoc
297 class Kernel(IPythonKernel):
298 def __init__(self, *args, **kwargs):
299 import warnings
300 warnings.warn('Kernel is a deprecated alias of IPython.kernel.zmq.ipkernel.IPythonKernel',
301 DeprecationWarning)
302 super(Kernel, self).__init__(*args, **kwargs) No newline at end of file
@@ -25,7 +25,7 b' from IPython.core.shellapp import ('
25 from IPython.utils import io
25 from IPython.utils import io
26 from IPython.utils.path import filefind
26 from IPython.utils.path import filefind
27 from IPython.utils.traitlets import (
27 from IPython.utils.traitlets import (
28 Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName,
28 Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type,
29 )
29 )
30 from IPython.utils.importstring import import_item
30 from IPython.utils.importstring import import_item
31 from IPython.kernel import write_connection_file
31 from IPython.kernel import write_connection_file
@@ -33,7 +33,7 b' from IPython.kernel.connect import ConnectionFileMixin'
33
33
34 # local imports
34 # local imports
35 from .heartbeat import Heartbeat
35 from .heartbeat import Heartbeat
36 from .ipkernel import Kernel
36 from .ipkernel import IPythonKernel
37 from .parentpoller import ParentPollerUnix, ParentPollerWindows
37 from .parentpoller import ParentPollerUnix, ParentPollerWindows
38 from .session import (
38 from .session import (
39 Session, session_flags, session_aliases, default_secure,
39 Session, session_flags, session_aliases, default_secure,
@@ -100,9 +100,10 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,'
100 name='ipkernel'
100 name='ipkernel'
101 aliases = Dict(kernel_aliases)
101 aliases = Dict(kernel_aliases)
102 flags = Dict(kernel_flags)
102 flags = Dict(kernel_flags)
103 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
103 classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session]
104 # the kernel class, as an importstring
104 # the kernel class, as an importstring
105 kernel_class = DottedObjectName('IPython.kernel.zmq.ipkernel.Kernel', config=True,
105 kernel_class = Type('IPython.kernel.zmq.ipkernel.IPythonKernel', config=True,
106 klass='IPython.kernel.zmq.kernelbase.Kernel',
106 help="""The Kernel subclass to be used.
107 help="""The Kernel subclass to be used.
107
108
108 This should allow easy re-use of the IPKernelApp entry point
109 This should allow easy re-use of the IPKernelApp entry point
@@ -315,7 +316,7 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,'
315 shell_stream = ZMQStream(self.shell_socket)
316 shell_stream = ZMQStream(self.shell_socket)
316 control_stream = ZMQStream(self.control_socket)
317 control_stream = ZMQStream(self.control_socket)
317
318
318 kernel_factory = import_item(str(self.kernel_class))
319 kernel_factory = self.kernel_class
319
320
320 kernel = kernel_factory(parent=self, session=self.session,
321 kernel = kernel_factory(parent=self, session=self.session,
321 shell_streams=[shell_stream, control_stream],
322 shell_streams=[shell_stream, control_stream],
@@ -351,8 +352,9 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,'
351 shell._showtraceback = _showtraceback
352 shell._showtraceback = _showtraceback
352
353
353 def init_shell(self):
354 def init_shell(self):
354 self.shell = self.kernel.shell
355 self.shell = getattr(self.kernel, 'shell', None)
355 self.shell.configurables.append(self)
356 if self.shell:
357 self.shell.configurables.append(self)
356
358
357 @catch_config_error
359 @catch_config_error
358 def initialize(self, argv=None):
360 def initialize(self, argv=None):
@@ -372,9 +374,10 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,'
372 # shell init steps
374 # shell init steps
373 self.init_path()
375 self.init_path()
374 self.init_shell()
376 self.init_shell()
375 self.init_gui_pylab()
377 if self.shell:
376 self.init_extensions()
378 self.init_gui_pylab()
377 self.init_code()
379 self.init_extensions()
380 self.init_code()
378 # flush stdout/stderr, so that anything written to these streams during
381 # flush stdout/stderr, so that anything written to these streams during
379 # initialization do not get associated with the first execution request
382 # initialization do not get associated with the first execution request
380 sys.stdout.flush()
383 sys.stdout.flush()
@@ -37,7 +37,7 b' from IPython.parallel.apps.baseapp import ('
37 catch_config_error,
37 catch_config_error,
38 )
38 )
39 from IPython.kernel.zmq.log import EnginePUBHandler
39 from IPython.kernel.zmq.log import EnginePUBHandler
40 from IPython.kernel.zmq.ipkernel import Kernel
40 from IPython.kernel.zmq.ipkernel import IPythonKernel as Kernel
41 from IPython.kernel.zmq.kernelapp import IPKernelApp
41 from IPython.kernel.zmq.kernelapp import IPKernelApp
42 from IPython.kernel.zmq.session import (
42 from IPython.kernel.zmq.session import (
43 Session, session_aliases, session_flags
43 Session, session_aliases, session_flags
@@ -26,7 +26,7 b' from IPython.parallel.factory import RegistrationFactory'
26 from IPython.parallel.util import disambiguate_url
26 from IPython.parallel.util import disambiguate_url
27
27
28 from IPython.kernel.zmq.session import Message
28 from IPython.kernel.zmq.session import Message
29 from IPython.kernel.zmq.ipkernel import Kernel
29 from IPython.kernel.zmq.ipkernel import IPythonKernel as Kernel
30 from IPython.kernel.zmq.kernelapp import IPKernelApp
30 from IPython.kernel.zmq.kernelapp import IPKernelApp
31
31
32 class EngineFactory(RegistrationFactory):
32 class EngineFactory(RegistrationFactory):
@@ -188,8 +188,8 b' class AsyncResultTest(ClusterTestCase):'
188 ar = v.apply_async(time.sleep, 0.25)
188 ar = v.apply_async(time.sleep, 0.25)
189 while not ar.ready():
189 while not ar.ready():
190 time.sleep(0.01)
190 time.sleep(0.01)
191 self.assertTrue(ar.elapsed < 1)
191 self.assertLess(ar.elapsed, 1)
192 self.assertTrue(ar.elapsed < 1)
192 self.assertLess(ar.elapsed, 1)
193 ar.get(2)
193 ar.get(2)
194
194
195 def test_hubresult_timestamps(self):
195 def test_hubresult_timestamps(self):
@@ -531,6 +531,15 b' class TestType(TestCase):'
531
531
532 self.assertRaises(TraitError, setattr, a, 'klass', 10)
532 self.assertRaises(TraitError, setattr, a, 'klass', 10)
533
533
534 def test_set_str_klass(self):
535
536 class A(HasTraits):
537 klass = Type()
538
539 a = A(klass='IPython.utils.ipstruct.Struct')
540 from IPython.utils.ipstruct import Struct
541 self.assertEqual(a.klass, Struct)
542
534 class TestInstance(TestCase):
543 class TestInstance(TestCase):
535
544
536 def test_basic(self):
545 def test_basic(self):
@@ -755,6 +755,12 b' class Type(ClassBasedTraitType):'
755
755
756 def validate(self, obj, value):
756 def validate(self, obj, value):
757 """Validates that the value is a valid object instance."""
757 """Validates that the value is a valid object instance."""
758 if isinstance(value, py3compat.string_types):
759 try:
760 value = import_item(value)
761 except ImportError:
762 raise TraitError("The '%s' trait of %s instance must be a type, but "
763 "%r could not be imported" % (self.name, obj, value))
758 try:
764 try:
759 if issubclass(value, self.klass):
765 if issubclass(value, self.klass):
760 return value
766 return value
@@ -20,6 +20,7 b' on the IPython GitHub wiki.'
20 :maxdepth: 1
20 :maxdepth: 1
21
21
22 messaging
22 messaging
23 wrapperkernels
23 execution
24 execution
24 parallel_messages
25 parallel_messages
25 parallel_connections
26 parallel_connections
@@ -409,6 +409,7 b" When status is 'error', the following extra fields are present::"
409 When status is 'abort', there are for now no additional data fields. This
409 When status is 'abort', there are for now no additional data fields. This
410 happens when the kernel was interrupted by a signal.
410 happens when the kernel was interrupted by a signal.
411
411
412 .. _msging_inspection:
412
413
413 Introspection
414 Introspection
414 -------------
415 -------------
@@ -465,6 +466,8 b' Message type: ``inspect_reply``::'
465
466
466 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
467 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
467
468
469 .. _msging_completion:
470
468 Completion
471 Completion
469 ----------
472 ----------
470
473
@@ -512,6 +515,7 b' Message type: ``complete_reply``::'
512 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
515 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
513 - ``metadata`` is added for extended information.
516 - ``metadata`` is added for extended information.
514
517
518 .. _msging_history:
515
519
516 History
520 History
517 -------
521 -------
@@ -590,6 +594,7 b' Message type: ``connect_reply``::'
590 'hb_port' : int, # The port the heartbeat socket is listening on.
594 'hb_port' : int, # The port the heartbeat socket is listening on.
591 }
595 }
592
596
597 .. _msging_kernel_info:
593
598
594 Kernel info
599 Kernel info
595 -----------
600 -----------
@@ -650,6 +655,7 b' Message type: ``kernel_info_reply``::'
650
655
651 ``implementation``, ``implementation_version``, and ``banner`` keys are added.
656 ``implementation``, ``implementation_version``, and ``banner`` keys are added.
652
657
658 .. _msging_shutdown:
653
659
654 Kernel shutdown
660 Kernel shutdown
655 ---------------
661 ---------------
General Comments 0
You need to be logged in to leave comments. Login now