##// END OF EJS Templates
Refactor kernel managers in preparation for the EmbeddedKernel.
Refactor kernel managers in preparation for the EmbeddedKernel.

File last commit:

r8408:e367f3e4
r8408:e367f3e4
Show More
kernelmanager.py
398 lines | 13.1 KiB | text/x-python | PythonLexer
""" A kernel manager for embedded (in-process) kernels. """
#-----------------------------------------------------------------------------
# Copyright (C) 2012 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Local imports.
from IPython.config.loader import Config
from IPython.utils.traitlets import HasTraits, Any, Instance, Type
#-----------------------------------------------------------------------------
# Channel classes
#-----------------------------------------------------------------------------
class EmbeddedChannel(object):
""" Base class for embedded channels.
"""
def __init__(self, manager):
super(EmbeddedChannel, self).__init__()
self.manager = manager
self._is_alive = False
#--------------------------------------------------------------------------
# Channel interface
#--------------------------------------------------------------------------
def is_alive(self):
return self._is_alive
def start(self):
self._is_alive = True
def stop(self):
self._is_alive = False
def call_handlers(self, msg):
""" This method is called in the main thread when a message arrives.
Subclasses should override this method to handle incoming messages.
"""
raise NotImplementedError('call_handlers must be defined in a subclass.')
#--------------------------------------------------------------------------
# EmbeddedChannel interface
#--------------------------------------------------------------------------
def call_handlers_later(self, *args, **kwds):
""" Call the message handlers later.
The default implementation just calls the handlers immediately, but this
method exists so that GUI toolkits can defer calling the handlers until
after the event loop has run, as expected by GUI frontends.
"""
self.call_handlers(*args, **kwds)
def process_events(self):
""" Process any pending GUI events.
This method will be never be called from a frontend without an event
loop (e.g., a terminal frontend).
"""
raise NotImplementedError
class ShellEmbeddedChannel(EmbeddedChannel):
"""The DEALER channel for issues request/replies to the kernel.
"""
# flag for whether execute requests should be allowed to call raw_input
allow_stdin = True
def execute(self, code, silent=False, store_history=True,
user_variables=[], user_expressions={}, allow_stdin=None):
"""Execute code in the kernel.
Parameters
----------
code : str
A string of Python code.
silent : bool, optional (default False)
If set, the kernel will execute the code as quietly possible, and
will force store_history to be False.
store_history : bool, optional (default True)
If set, the kernel will store command history. This is forced
to be False if silent is True.
user_variables : list, optional
A list of variable names to pull from the user's namespace. They
will come back as a dict with these names as keys and their
:func:`repr` as values.
user_expressions : dict, optional
A dict mapping names to expressions to be evaluated in the user's
dict. The expression values are returned as strings formatted using
:func:`repr`.
allow_stdin : bool, optional (default self.allow_stdin)
Flag for whether the kernel can send stdin requests to frontends.
Some frontends (e.g. the Notebook) do not support stdin requests.
If raw_input is called from code executed from such a frontend, a
StdinNotImplementedError will be raised.
Returns
-------
The msg_id of the message sent.
"""
raise NotImplementedError
def complete(self, text, line, cursor_pos, block=None):
"""Tab complete text in the kernel's namespace.
Parameters
----------
text : str
The text to complete.
line : str
The full line of text that is the surrounding context for the
text to complete.
cursor_pos : int
The position of the cursor in the line where the completion was
requested.
block : str, optional
The full block of code in which the completion is being requested.
Returns
-------
The msg_id of the message sent.
"""
raise NotImplementedError
def object_info(self, oname, detail_level=0):
"""Get metadata information about an object.
Parameters
----------
oname : str
A string specifying the object name.
detail_level : int, optional
The level of detail for the introspection (0-2)
Returns
-------
The msg_id of the message sent.
"""
raise NotImplementedError
def history(self, raw=True, output=False, hist_access_type='range', **kwds):
"""Get entries from the history list.
Parameters
----------
raw : bool
If True, return the raw input.
output : bool
If True, then return the output as well.
hist_access_type : str
'range' (fill in session, start and stop params), 'tail' (fill in n)
or 'search' (fill in pattern param).
session : int
For a range request, the session from which to get lines. Session
numbers are positive integers; negative ones count back from the
current session.
start : int
The first line number of a history range.
stop : int
The final (excluded) line number of a history range.
n : int
The number of lines of history to get for a tail request.
pattern : str
The glob-syntax pattern for a search request.
Returns
-------
The msg_id of the message sent.
"""
raise NotImplementedError
def shutdown(self, restart=False):
""" Request an immediate kernel shutdown.
A dummy method for the embedded kernel.
"""
# FIXME: What to do here?
raise NotImplementedError('Shutdown not supported for embedded kernel')
class SubEmbeddedChannel(EmbeddedChannel):
"""The SUB channel which listens for messages that the kernel publishes.
"""
def flush(self, timeout=1.0):
""" Immediately processes all pending messages on the SUB channel.
A dummy method for the embedded kernel.
"""
pass
class StdInEmbeddedChannel(EmbeddedChannel):
""" A reply channel to handle raw_input requests that the kernel makes. """
def input(self, string):
""" Send a string of raw input to the kernel.
"""
raise NotImplementedError
class HBEmbeddedChannel(EmbeddedChannel):
""" A dummy heartbeat channel. """
time_to_dead = 3.0
def __init__(self, *args, **kwds):
super(HBEmbeddedChannel, self).__init__(*args, **kwds)
self._pause = True
def pause(self):
""" Pause the heartbeat. """
self._pause = True
def unpause(self):
""" Unpause the heartbeat. """
self._pause = False
def is_beating(self):
""" Is the heartbeat running and responsive (and not paused). """
return not self._pause
#-----------------------------------------------------------------------------
# Main kernel manager class
#-----------------------------------------------------------------------------
class EmbeddedKernelManager(HasTraits):
""" A manager for an embedded kernel.
This class implements most of the interface of
``IPython.zmq.kernelmanager.KernelManager`` and allows (asynchronous)
frontends to be used seamlessly with an in-process kernel.
"""
# Config object for passing to child configurables
config = Instance(Config)
# The Session to use for building messages.
session = Instance('IPython.zmq.session.Session')
def _session_default(self):
from IPython.zmq.session import Session
return Session(config=self.config)
# The kernel process with which the KernelManager is communicating.
kernel = Instance('IPython.embedded.ipkernel.EmbeddedKernel')
# The classes to use for the various channels.
shell_channel_class = Type(ShellEmbeddedChannel)
sub_channel_class = Type(SubEmbeddedChannel)
stdin_channel_class = Type(StdInEmbeddedChannel)
hb_channel_class = Type(HBEmbeddedChannel)
# Protected traits.
_shell_channel = Any
_sub_channel = Any
_stdin_channel = Any
_hb_channel = Any
#--------------------------------------------------------------------------
# Channel management methods:
#--------------------------------------------------------------------------
def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
""" Starts the channels for this kernel.
"""
if shell:
self.shell_channel.start()
if sub:
self.sub_channel.start()
if stdin:
self.stdin_channel.start()
self.shell_channel.allow_stdin = True
else:
self.shell_channel.allow_stdin = False
if hb:
self.hb_channel.start()
def stop_channels(self):
""" Stops all the running channels for this kernel.
"""
if self.shell_channel.is_alive():
self.shell_channel.stop()
if self.sub_channel.is_alive():
self.sub_channel.stop()
if self.stdin_channel.is_alive():
self.stdin_channel.stop()
if self.hb_channel.is_alive():
self.hb_channel.stop()
@property
def channels_running(self):
""" Are any of the channels created and running? """
return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
self.stdin_channel.is_alive() or self.hb_channel.is_alive())
#--------------------------------------------------------------------------
# Kernel management methods:
#--------------------------------------------------------------------------
def start_kernel(self, **kwds):
""" Starts a kernel process and configures the manager to use it.
"""
from IPython.embedded.ipkernel import EmbeddedKernel
self.kernel = EmbeddedKernel()
self.kernel.frontends.append(self)
def shutdown_kernel(self):
""" Attempts to the stop the kernel process cleanly. If the kernel
cannot be stopped and the kernel is local, it is killed.
"""
self.kill_kernel()
def restart_kernel(self, now=False, **kwds):
""" Restarts a kernel with the arguments that were used to launch it.
The 'now' parameter is ignored.
"""
self.shutdown_kernel()
self.start_kernel(**kwds)
@property
def has_kernel(self):
""" Returns whether a kernel process has been specified for the kernel
manager.
"""
return self.kernel is not None
def kill_kernel(self):
""" Kill the running kernel.
"""
self.kernel.frontends.remove(self)
self.kernel = None
def interrupt_kernel(self):
""" Interrupts the kernel. """
raise NotImplementedError("Cannot interrupt embedded kernel.")
def signal_kernel(self, signum):
""" Sends a signal to the kernel. """
raise NotImplementedError("Cannot signal embedded kernel.")
@property
def is_alive(self):
""" Is the kernel process still running? """
return True
#--------------------------------------------------------------------------
# Channels used for communication with the kernel:
#--------------------------------------------------------------------------
@property
def shell_channel(self):
"""Get the REQ socket channel object to make requests of the kernel."""
if self._shell_channel is None:
self._shell_channel = self.shell_channel_class(self)
return self._shell_channel
@property
def sub_channel(self):
"""Get the SUB socket channel object."""
if self._sub_channel is None:
self._sub_channel = self.sub_channel_class(self)
return self._sub_channel
@property
def stdin_channel(self):
"""Get the REP socket channel object to handle stdin (raw_input)."""
if self._stdin_channel is None:
self._stdin_channel = self.stdin_channel_class(self)
return self._stdin_channel
@property
def hb_channel(self):
"""Get the heartbeat socket channel object to check that the
kernel is alive."""
if self._hb_channel is None:
self._hb_channel = self.hb_channel_class(self)
return self._hb_channel