ipkernel.py
459 lines
| 16.7 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r2598 | #!/usr/bin/env python | ||
"""A simple interactive kernel that talks to a frontend over 0MQ. | ||||
Things to do: | ||||
* Implement `set_parent` logic. Right before doing exec, the Kernel should | ||||
call set_parent on all the PUB objects with the message about to be executed. | ||||
* Implement random port and security key logic. | ||||
* Implement control messages. | ||||
* Implement event loop and poll version. | ||||
""" | ||||
epatters
|
r2712 | #----------------------------------------------------------------------------- | ||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
Fernando Perez
|
r2856 | from __future__ import print_function | ||
epatters
|
r2712 | |||
epatters
|
r2641 | # Standard library imports. | ||
Fernando Perez
|
r2598 | import __builtin__ | ||
import sys | ||||
import time | ||||
import traceback | ||||
epatters
|
r2641 | # System library imports. | ||
Fernando Perez
|
r2598 | import zmq | ||
epatters
|
r2641 | # Local imports. | ||
Brian Granger
|
r2759 | from IPython.config.configurable import Configurable | ||
Fernando Perez
|
r2856 | from IPython.utils import io | ||
Brian Granger
|
r2868 | from IPython.lib import pylabtools | ||
Brian Granger
|
r2759 | from IPython.utils.traitlets import Instance | ||
epatters
|
r2778 | from entry_point import base_launch_kernel, make_argument_parser, make_kernel, \ | ||
start_kernel | ||||
epatters
|
r2796 | from iostream import OutStream | ||
epatters
|
r2778 | from session import Session, Message | ||
from zmqshell import ZMQInteractiveShell | ||||
Fernando Perez
|
r2598 | |||
epatters
|
r2712 | #----------------------------------------------------------------------------- | ||
Brian Granger
|
r2754 | # Main kernel class | ||
epatters
|
r2712 | #----------------------------------------------------------------------------- | ||
epatters
|
r2641 | |||
Brian Granger
|
r2759 | class Kernel(Configurable): | ||
epatters
|
r2778 | #--------------------------------------------------------------------------- | ||
# Kernel interface | ||||
#--------------------------------------------------------------------------- | ||||
Brian Granger
|
r2760 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') | ||
epatters
|
r2778 | session = Instance(Session) | ||
Brian Granger
|
r2759 | reply_socket = Instance('zmq.Socket') | ||
pub_socket = Instance('zmq.Socket') | ||||
req_socket = Instance('zmq.Socket') | ||||
def __init__(self, **kwargs): | ||||
super(Kernel, self).__init__(**kwargs) | ||||
Brian Granger
|
r2786 | |||
# Initialize the InteractiveShell subclass | ||||
Brian Granger
|
r2773 | self.shell = ZMQInteractiveShell.instance() | ||
Brian Granger
|
r2786 | self.shell.displayhook.session = self.session | ||
self.shell.displayhook.pub_socket = self.pub_socket | ||||
epatters
|
r2778 | |||
Fernando Perez
|
r2838 | # TMP - hack while developing | ||
self.shell._reply_content = None | ||||
Fernando Perez
|
r2598 | # Build dict of handlers for message types | ||
epatters
|
r2612 | msg_types = [ 'execute_request', 'complete_request', | ||
Brian Granger
|
r2794 | 'object_info_request', 'prompt_request', | ||
'history_request' ] | ||||
Fernando Perez
|
r2598 | self.handlers = {} | ||
epatters
|
r2612 | for msg_type in msg_types: | ||
Fernando Perez
|
r2598 | self.handlers[msg_type] = getattr(self, msg_type) | ||
Brian Granger
|
r2868 | def do_one_iteration(self): | ||
try: | ||||
ident = self.reply_socket.recv(zmq.NOBLOCK) | ||||
except zmq.ZMQError, e: | ||||
if e.errno == zmq.EAGAIN: | ||||
return | ||||
epatters
|
r2778 | else: | ||
Brian Granger
|
r2868 | raise | ||
# FIXME: Bug in pyzmq/zmq? | ||||
# assert self.reply_socket.rcvmore(), "Missing message part." | ||||
msg = self.reply_socket.recv_json() | ||||
omsg = Message(msg) | ||||
Fernando Perez
|
r2875 | io.raw_print('\n') | ||
io.raw_print(omsg) | ||||
Brian Granger
|
r2868 | handler = self.handlers.get(omsg.msg_type, None) | ||
if handler is None: | ||||
Fernando Perez
|
r2875 | io.raw_print_err("UNKNOWN MESSAGE TYPE:", omsg) | ||
Brian Granger
|
r2868 | else: | ||
handler(ident, omsg) | ||||
epatters
|
r2778 | |||
def start(self): | ||||
""" Start the kernel main loop. | ||||
""" | ||||
Fernando Perez
|
r2598 | while True: | ||
Brian Granger
|
r2868 | time.sleep(0.05) | ||
self.do_one_iteration() | ||||
epatters
|
r2778 | |||
#--------------------------------------------------------------------------- | ||||
# Kernel request handlers | ||||
#--------------------------------------------------------------------------- | ||||
Fernando Perez
|
r2598 | |||
def execute_request(self, ident, parent): | ||||
try: | ||||
code = parent[u'content'][u'code'] | ||||
except: | ||||
Fernando Perez
|
r2875 | io.raw_print_err("Got bad msg: ") | ||
io.raw_print_err(Message(parent)) | ||||
Fernando Perez
|
r2598 | return | ||
pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent) | ||||
self.pub_socket.send_json(pyin_msg) | ||||
epatters
|
r2722 | |||
Fernando Perez
|
r2598 | try: | ||
epatters
|
r2730 | # Replace raw_input. Note that is not sufficient to replace | ||
# raw_input in the user namespace. | ||||
epatters
|
r2778 | raw_input = lambda prompt='': self._raw_input(prompt, ident, parent) | ||
epatters
|
r2730 | __builtin__.raw_input = raw_input | ||
epatters
|
r2824 | # Set the parent message of the display hook and out streams. | ||
Brian Granger
|
r2786 | self.shell.displayhook.set_parent(parent) | ||
epatters
|
r2824 | sys.stdout.set_parent(parent) | ||
sys.stderr.set_parent(parent) | ||||
epatters
|
r2730 | |||
Fernando Perez
|
r2838 | # FIXME: runlines calls the exception handler itself. We should | ||
# clean this up. | ||||
self.shell._reply_content = None | ||||
Brian Granger
|
r2759 | self.shell.runlines(code) | ||
Fernando Perez
|
r2598 | except: | ||
Fernando Perez
|
r2838 | # FIXME: this code right now isn't being used yet by default, | ||
# because the runlines() call above directly fires off exception | ||||
# reporting. This code, therefore, is only active in the scenario | ||||
# where runlines itself has an unhandled exception. We need to | ||||
# uniformize this, for all exception construction to come from a | ||||
# single location in the codbase. | ||||
Fernando Perez
|
r2598 | etype, evalue, tb = sys.exc_info() | ||
Fernando Perez
|
r2838 | tb_list = traceback.format_exception(etype, evalue, tb) | ||
reply_content = self.shell._showtraceback(etype, evalue, tb_list) | ||||
Fernando Perez
|
r2598 | else: | ||
Brian Granger
|
r2814 | payload = self.shell.payload_manager.read_payload() | ||
# Be agressive about clearing the payload because we don't want | ||||
# it to sit in memory until the next execute_request comes in. | ||||
self.shell.payload_manager.clear_payload() | ||||
reply_content = { 'status' : 'ok', 'payload' : payload } | ||||
Brian Granger
|
r2791 | |||
# Compute the prompt information | ||||
prompt_number = self.shell.displayhook.prompt_count | ||||
reply_content['prompt_number'] = prompt_number | ||||
prompt_string = self.shell.displayhook.prompt1.peek_next_prompt() | ||||
next_prompt = {'prompt_string' : prompt_string, | ||||
Brian Granger
|
r2803 | 'prompt_number' : prompt_number+1, | ||
'input_sep' : self.shell.displayhook.input_sep} | ||||
Brian Granger
|
r2791 | reply_content['next_prompt'] = next_prompt | ||
Fernando Perez
|
r2838 | # TMP - fish exception info out of shell, possibly left there by | ||
# runlines | ||||
if self.shell._reply_content is not None: | ||||
reply_content.update(self.shell._reply_content) | ||||
epatters
|
r2722 | # Flush output before sending the reply. | ||
sys.stderr.flush() | ||||
sys.stdout.flush() | ||||
# Send the reply. | ||||
Fernando Perez
|
r2598 | reply_msg = self.session.msg(u'execute_reply', reply_content, parent) | ||
Fernando Perez
|
r2875 | io.raw_print(Message(reply_msg)) | ||
Fernando Perez
|
r2598 | self.reply_socket.send(ident, zmq.SNDMORE) | ||
self.reply_socket.send_json(reply_msg) | ||||
if reply_msg['content']['status'] == u'error': | ||||
epatters
|
r2778 | self._abort_queue() | ||
def complete_request(self, ident, parent): | ||||
Fernando Perez
|
r2856 | txt, matches = self._complete(parent) | ||
matches = {'matches' : matches, | ||||
'matched_text' : txt, | ||||
epatters
|
r2778 | 'status' : 'ok'} | ||
completion_msg = self.session.send(self.reply_socket, 'complete_reply', | ||||
matches, parent, ident) | ||||
Fernando Perez
|
r2875 | io.raw_print(completion_msg) | ||
epatters
|
r2778 | |||
def object_info_request(self, ident, parent): | ||||
context = parent['content']['oname'].split('.') | ||||
object_info = self._object_info(context) | ||||
msg = self.session.send(self.reply_socket, 'object_info_reply', | ||||
object_info, parent, ident) | ||||
Fernando Perez
|
r2875 | io.raw_print(msg) | ||
epatters
|
r2795 | |||
def prompt_request(self, ident, parent): | ||||
prompt_number = self.shell.displayhook.prompt_count | ||||
prompt_string = self.shell.displayhook.prompt1.peek_next_prompt() | ||||
content = {'prompt_string' : prompt_string, | ||||
epatters
|
r2844 | 'prompt_number' : prompt_number+1, | ||
'input_sep' : self.shell.displayhook.input_sep} | ||||
epatters
|
r2795 | msg = self.session.send(self.reply_socket, 'prompt_reply', | ||
content, parent, ident) | ||||
Fernando Perez
|
r2875 | io.raw_print(msg) | ||
Fernando Perez
|
r2598 | |||
epatters
|
r2795 | def history_request(self, ident, parent): | ||
epatters
|
r2844 | output = parent['content']['output'] | ||
index = parent['content']['index'] | ||||
raw = parent['content']['raw'] | ||||
epatters
|
r2795 | hist = self.shell.get_history(index=index, raw=raw, output=output) | ||
content = {'history' : hist} | ||||
msg = self.session.send(self.reply_socket, 'history_reply', | ||||
content, parent, ident) | ||||
Fernando Perez
|
r2875 | io.raw_print(msg) | ||
epatters
|
r2778 | |||
#--------------------------------------------------------------------------- | ||||
# Protected interface | ||||
#--------------------------------------------------------------------------- | ||||
Fernando Perez
|
r2598 | |||
epatters
|
r2778 | def _abort_queue(self): | ||
while True: | ||||
try: | ||||
ident = self.reply_socket.recv(zmq.NOBLOCK) | ||||
except zmq.ZMQError, e: | ||||
if e.errno == zmq.EAGAIN: | ||||
break | ||||
else: | ||||
assert self.reply_socket.rcvmore(), "Unexpected missing message part." | ||||
msg = self.reply_socket.recv_json() | ||||
Fernando Perez
|
r2875 | io.raw_print("Aborting:\n", Message(msg)) | ||
epatters
|
r2778 | msg_type = msg['msg_type'] | ||
reply_type = msg_type.split('_')[0] + '_reply' | ||||
reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg) | ||||
Fernando Perez
|
r2875 | io.raw_print(Message(reply_msg)) | ||
epatters
|
r2778 | self.reply_socket.send(ident,zmq.SNDMORE) | ||
self.reply_socket.send_json(reply_msg) | ||||
# We need to wait a bit for requests to come in. This can probably | ||||
# be set shorter for true asynchronous clients. | ||||
time.sleep(0.1) | ||||
def _raw_input(self, prompt, ident, parent): | ||||
epatters
|
r2730 | # Flush output before making the request. | ||
sys.stderr.flush() | ||||
sys.stdout.flush() | ||||
# Send the input request. | ||||
content = dict(prompt=prompt) | ||||
msg = self.session.msg(u'input_request', content, parent) | ||||
self.req_socket.send_json(msg) | ||||
# Await a response. | ||||
reply = self.req_socket.recv_json() | ||||
try: | ||||
value = reply['content']['value'] | ||||
except: | ||||
Fernando Perez
|
r2875 | io.raw_print_err("Got bad raw_input reply: ") | ||
io.raw_print_err(Message(parent)) | ||||
epatters
|
r2730 | value = '' | ||
return value | ||||
epatters
|
r2778 | |||
def _complete(self, msg): | ||||
Fernando Perez
|
r2839 | c = msg['content'] | ||
try: | ||||
cpos = int(c['cursor_pos']) | ||||
except: | ||||
# If we don't get something that we can convert to an integer, at | ||||
Fernando Perez
|
r2856 | # least attempt the completion guessing the cursor is at the end of | ||
# the text, if there's any, and otherwise of the line | ||||
Fernando Perez
|
r2839 | cpos = len(c['text']) | ||
Fernando Perez
|
r2856 | if cpos==0: | ||
cpos = len(c['line']) | ||||
Fernando Perez
|
r2839 | return self.shell.complete(c['text'], c['line'], cpos) | ||
Fernando Perez
|
r2598 | |||
epatters
|
r2778 | def _object_info(self, context): | ||
symbol, leftover = self._symbol_from_context(context) | ||||
epatters
|
r2612 | if symbol is not None and not leftover: | ||
doc = getattr(symbol, '__doc__', '') | ||||
else: | ||||
doc = '' | ||||
object_info = dict(docstring = doc) | ||||
return object_info | ||||
epatters
|
r2778 | def _symbol_from_context(self, context): | ||
epatters
|
r2612 | if not context: | ||
return None, context | ||||
base_symbol_string = context[0] | ||||
Brian Granger
|
r2759 | symbol = self.shell.user_ns.get(base_symbol_string, None) | ||
epatters
|
r2612 | if symbol is None: | ||
symbol = __builtin__.__dict__.get(base_symbol_string, None) | ||||
if symbol is None: | ||||
return None, context | ||||
context = context[1:] | ||||
for i, name in enumerate(context): | ||||
new_symbol = getattr(symbol, name, None) | ||||
if new_symbol is None: | ||||
return symbol, context[i:] | ||||
else: | ||||
symbol = new_symbol | ||||
return symbol, [] | ||||
Brian Granger
|
r2868 | |||
class QtKernel(Kernel): | ||||
Brian Granger
|
r2872 | """A Kernel subclass with Qt support.""" | ||
Brian Granger
|
r2868 | |||
def start(self): | ||||
"""Start a kernel with QtPy4 event loop integration.""" | ||||
Brian Granger
|
r2872 | |||
Brian Granger
|
r2868 | from PyQt4 import QtGui, QtCore | ||
Brian Granger
|
r2900 | from IPython.lib.guisupport import ( | ||
get_app_qt4, start_event_loop_qt4 | ||||
) | ||||
self.app = get_app_qt4([" "]) | ||||
self.app.setQuitOnLastWindowClosed(False) | ||||
Brian Granger
|
r2872 | self.timer = QtCore.QTimer() | ||
self.timer.timeout.connect(self.do_one_iteration) | ||||
self.timer.start(50) | ||||
Brian Granger
|
r2900 | start_event_loop_qt4(self.app) | ||
Brian Granger
|
r2872 | |||
class WxKernel(Kernel): | ||||
"""A Kernel subclass with Wx support.""" | ||||
Brian Granger
|
r2868 | |||
Brian Granger
|
r2872 | def start(self): | ||
"""Start a kernel with wx event loop support.""" | ||||
import wx | ||||
Brian Granger
|
r2900 | from IPython.lib.guisupport import start_event_loop_wx | ||
Brian Granger
|
r2872 | doi = self.do_one_iteration | ||
# We have to put the wx.Timer in a wx.Frame for it to fire properly. | ||||
# We make the Frame hidden when we create it in the main app below. | ||||
class TimerFrame(wx.Frame): | ||||
def __init__(self, func): | ||||
wx.Frame.__init__(self, None, -1) | ||||
self.timer = wx.Timer(self) | ||||
self.timer.Start(50) | ||||
self.Bind(wx.EVT_TIMER, self.on_timer) | ||||
self.func = func | ||||
def on_timer(self, event): | ||||
self.func() | ||||
# We need a custom wx.App to create our Frame subclass that has the | ||||
# wx.Timer to drive the ZMQ event loop. | ||||
class IPWxApp(wx.App): | ||||
def OnInit(self): | ||||
self.frame = TimerFrame(doi) | ||||
self.frame.Show(False) | ||||
return True | ||||
# The redirect=False here makes sure that wx doesn't replace | ||||
# sys.stdout/stderr with its own classes. | ||||
self.app = IPWxApp(redirect=False) | ||||
Brian Granger
|
r2900 | start_event_loop_wx(self.app) | ||
Brian Granger
|
r2872 | |||
class TkKernel(Kernel): | ||||
"""A Kernel subclass with Tk support.""" | ||||
def start(self): | ||||
"""Start a Tk enabled event loop.""" | ||||
import Tkinter | ||||
doi = self.do_one_iteration | ||||
# For Tkinter, we create a Tk object and call its withdraw method. | ||||
class Timer(object): | ||||
def __init__(self, func): | ||||
self.app = Tkinter.Tk() | ||||
self.app.withdraw() | ||||
self.func = func | ||||
def on_timer(self): | ||||
self.func() | ||||
self.app.after(50, self.on_timer) | ||||
def start(self): | ||||
self.on_timer() # Call it once to get things going. | ||||
self.app.mainloop() | ||||
self.timer = Timer(doi) | ||||
self.timer.start() | ||||
Brian Granger
|
r2868 | |||
epatters
|
r2712 | #----------------------------------------------------------------------------- | ||
# Kernel main and launch functions | ||||
#----------------------------------------------------------------------------- | ||||
epatters
|
r2778 | def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False, | ||
pylab=False): | ||||
epatters
|
r2700 | """ Launches a localhost kernel, binding to the specified ports. | ||
Parameters | ||||
---------- | ||||
xrep_port : int, optional | ||||
The port to use for XREP channel. | ||||
pub_port : int, optional | ||||
epatters
|
r2702 | The port to use for the SUB channel. | ||
req_port : int, optional | ||||
The port to use for the REQ (raw input) channel. | ||||
epatters
|
r2641 | |||
epatters
|
r2700 | independent : bool, optional (default False) | ||
If set, the kernel process is guaranteed to survive if this process | ||||
dies. If not set, an effort is made to ensure that the kernel is killed | ||||
when this process dies. Note that in this case it is still good practice | ||||
epatters
|
r2712 | to kill kernels manually before exiting. | ||
epatters
|
r2700 | |||
epatters
|
r2778 | pylab : bool or string, optional (default False) | ||
If not False, the kernel will be launched with pylab enabled. If a | ||||
string is passed, matplotlib will use the specified backend. Otherwise, | ||||
matplotlib's default backend will be used. | ||||
epatters
|
r2700 | Returns | ||
------- | ||||
A tuple of form: | ||||
epatters
|
r2702 | (kernel_process, xrep_port, pub_port, req_port) | ||
where kernel_process is a Popen object and the ports are integers. | ||||
epatters
|
r2641 | """ | ||
epatters
|
r2778 | extra_arguments = [] | ||
if pylab: | ||||
extra_arguments.append('--pylab') | ||||
if isinstance(pylab, basestring): | ||||
extra_arguments.append(pylab) | ||||
return base_launch_kernel('from IPython.zmq.ipkernel import main; main()', | ||||
xrep_port, pub_port, req_port, independent, | ||||
extra_arguments) | ||||
epatters
|
r2700 | |||
epatters
|
r2778 | def main(): | ||
""" The IPython kernel main entry point. | ||||
""" | ||||
parser = make_argument_parser() | ||||
parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?', | ||||
const='auto', help = \ | ||||
"Pre-load matplotlib and numpy for interactive use. If GUI is not \ | ||||
given, the GUI backend is matplotlib's, otherwise use one of: \ | ||||
['tk', 'gtk', 'qt', 'wx', 'payload-svg'].") | ||||
namespace = parser.parse_args() | ||||
Brian Granger
|
r2868 | kernel_class = Kernel | ||
_kernel_classes = { | ||||
'qt' : QtKernel, | ||||
'qt4' : QtKernel, | ||||
Brian Granger
|
r2872 | 'payload-svg':Kernel, | ||
'wx' : WxKernel, | ||||
'tk' : TkKernel | ||||
Brian Granger
|
r2868 | } | ||
epatters
|
r2778 | if namespace.pylab: | ||
if namespace.pylab == 'auto': | ||||
Brian Granger
|
r2868 | gui, backend = pylabtools.find_gui_and_backend() | ||
epatters
|
r2778 | else: | ||
Brian Granger
|
r2868 | gui, backend = pylabtools.find_gui_and_backend(namespace.pylab) | ||
kernel_class = _kernel_classes.get(gui) | ||||
if kernel_class is None: | ||||
raise ValueError('GUI is not supported: %r' % gui) | ||||
pylabtools.activate_matplotlib(backend) | ||||
kernel = make_kernel(namespace, kernel_class, OutStream) | ||||
if namespace.pylab: | ||||
pylabtools.import_pylab(kernel.shell.user_ns) | ||||
epatters
|
r2778 | start_kernel(namespace, kernel) | ||
Fernando Perez
|
r2598 | |||
if __name__ == '__main__': | ||||
epatters
|
r2667 | main() | ||