Created
December 1, 2011 14:07
-
-
Save rkern/1416977 to your computer and use it in GitHub Desktop.
Inprocess Qt frontend for IPython
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from IPython.frontend.qt.console.ipython_widget import IPythonWidget | |
from IPython.lib import guisupport | |
from qtkernelmanager import QtKernelManager | |
def main(): | |
app = guisupport.get_app_qt4() | |
km = QtKernelManager() | |
km.start_kernel() | |
control = IPythonWidget() | |
control.gui_completion = True | |
control.kernel_manager = km | |
control.show() | |
guisupport.start_event_loop_qt4(app) | |
# Application entry point. | |
if __name__ == '__main__': | |
main() | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from contextlib import contextmanager | |
import logging | |
from Queue import Empty | |
import sys | |
from IPython.external.qt import QtCore, QtGui | |
import zmq | |
from IPython.utils.traitlets import Any, Bool, Type | |
from IPython.utils import io | |
from IPython.zmq.ipkernel import Kernel | |
from IPython.zmq.session import Message, Session | |
from IPython.zmq.displayhook import ZMQShellDisplayHook | |
from IPython.zmq.zmqshell import (ZMQDisplayPublisher, | |
ZMQInteractiveShell) | |
from IPython.zmq.iostream import OutStream | |
class LazyTerm(io.IOTerm): | |
""" Dynamically look up cin,cout,cerr from sys. | |
""" | |
def __init__(self, *args, **kwds): | |
pass | |
@property | |
def cin(self): | |
return sys.stdin | |
@property | |
def cout(self): | |
return sys.stdout | |
@property | |
def cerr(self): | |
return sys.stderr | |
@contextmanager | |
def redirect_output(session, pub_socket): | |
sys.stdout = OutStream(session, pub_socket, u'stdout') | |
sys.stderr = OutStream(session, pub_socket, u'stderr') | |
try: | |
yield | |
finally: | |
sys.stdout = sys.__stdout__ | |
sys.stderr = sys.__stderr__ | |
class QtDisplayHook(ZMQShellDisplayHook): | |
pub_socket = Any() | |
class QtDisplayPublisher(ZMQDisplayPublisher): | |
pub_socket = Any() | |
class QtInteractiveShell(ZMQInteractiveShell): | |
""" A subclass of ZMQInteractiveShell for inprocess Qt use. | |
""" | |
displayhook_class = Type(QtDisplayHook) | |
display_pub_class = Type(QtDisplayPublisher) | |
def init_io(self): | |
io.Term = LazyTerm() | |
class KernelSession(Session): | |
""" A session object for the kernel side. | |
""" | |
def send(self, socket, msg_or_type, content=None, parent=None, ident=None): | |
"""send a message via a socket, using a uniform message pattern. | |
Parameters | |
---------- | |
socket : zmq.Socket | |
The socket on which to send. | |
msg_or_type : Message/dict or str | |
if str : then a new message will be constructed from content,parent | |
if Message/dict : then content and parent are ignored, and the message | |
is sent. This is only for use when sending a Message for a second time. | |
content : dict, optional | |
The contents of the message | |
parent : dict, optional | |
The parent header, or parent message, of this message | |
ident : bytes, optional | |
The zmq.IDENTITY prefix of the destination. | |
Only for use on certain socket types. | |
Returns | |
------- | |
msg : dict | |
The message, as constructed by self.msg(msg_type,content,parent) | |
""" | |
if isinstance(msg_or_type, (Message, dict)): | |
msg = dict(msg_or_type) | |
else: | |
msg = self.msg(msg_or_type, content, parent) | |
socket.send_json(msg) | |
return msg | |
def recv(self, socket, mode=zmq.NOBLOCK): | |
"""recv a message on a socket. | |
Receive an optionally identity-prefixed message, as sent via session.send(). | |
Parameters | |
---------- | |
socket : zmq.Socket | |
The socket on which to recv a message. | |
mode : int, optional | |
the mode flag passed to socket.recv | |
default: zmq.NOBLOCK | |
Returns | |
------- | |
(ident,msg) : tuple | |
always length 2. If no message received, then return is (None,None) | |
ident : bytes or None | |
the identity prefix is there was one, None otherwise. | |
msg : dict or None | |
The actual message. If mode==zmq.NOBLOCK and no message was waiting, | |
it will be None. | |
""" | |
try: | |
msg = socket.recv_multipart(mode) | |
except Empty: | |
return None, None | |
return None, msg | |
class InprocQtKernel(Kernel): | |
""" An in-process Qt kernel. | |
""" | |
user_ns = Any() | |
user_module = Any() | |
stdin_socket = Any() | |
iopub_socket = Any() | |
shell_socket = Any() | |
started = Bool(False) | |
def __init__(self, **kwargs): | |
super(Kernel, self).__init__(**kwargs) | |
# Initialize the InteractiveShell subclass | |
self.shell = QtInteractiveShell.instance(user_ns=self.user_ns, | |
user_module=self.user_module) | |
self.shell.displayhook.session = self.session | |
self.shell.displayhook.pub_socket = self.iopub_socket | |
self.shell.display_pub.session = self.session | |
self.shell.display_pub.pub_socket = self.iopub_socket | |
# FIXME: find a better logger name. | |
self.log = logging.getLogger(__name__) | |
# TMP - hack while developing | |
self.shell._reply_content = None | |
# Build dict of handlers for message types | |
msg_types = [ 'execute_request', 'complete_request', | |
'object_info_request', 'history_request', | |
'connect_request', 'shutdown_request'] | |
self.handlers = {} | |
for msg_type in msg_types: | |
self.handlers[msg_type] = getattr(self, msg_type) | |
def start(self): | |
""" Start a Qt timer to do the event loop. | |
""" | |
if not self.started: | |
self.timer = QtCore.QTimer() | |
self.timer.timeout.connect(self.do_one_iteration) | |
self.timer.start(1000*self._poll_interval) | |
self.started = True | |
def do_one_iteration(self): | |
""" Do one iteration of the kernel's evaluation loop. | |
""" | |
ident, msg = self.session.recv(self.shell_socket, zmq.NOBLOCK) | |
if msg is None: | |
return | |
# Print some info about this message and leave a '--->' marker, so it's | |
# easier to trace visually the message chain when debugging. Each | |
# handler prints its message at the end. | |
# Eventually we'll move these from stdout to a logger. | |
self.log.debug('\n*** MESSAGE TYPE:'+str(msg['header']['msg_type'])+'***') | |
self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ') | |
# Find and call actual handler for message | |
handler = self.handlers.get(msg['header']['msg_type'], None) | |
if handler is None: | |
self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg)) | |
else: | |
with redirect_output(self.session, self.iopub_socket): | |
handler(ident, msg) | |
def _raw_input(self, prompt, ident, parent): | |
""" raw_input([prompt]) -> string | |
Read a string from an input dialog. | |
""" | |
# Parent widget? | |
dlg = QtGui.QInputDialog() | |
dlg.setInputMode(QtGui.QInputDialog.TextInput) | |
dlg.setLabelText(prompt) | |
dlg.setWindowTitle(u'Input') | |
code = dlg.exec_() | |
if code == QtGui.QDialog.Accepted: | |
text = unicode(dlg.textValue()) | |
else: | |
text = '' | |
return text | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from Queue import Queue | |
from IPython.external.qt import QtCore | |
import zmq | |
from IPython.frontend.qt.util import MetaQObjectHasTraits, SuperQObject | |
from IPython.utils.traitlets import Any, Bool, HasTraits, Instance | |
from IPython.zmq.session import Session | |
from IPython.zmq.kernelmanager import validate_string_dict, validate_string_list | |
from qtkernel import KernelSession, InprocQtKernel | |
class SocketChannelQObject(SuperQObject): | |
# The Session object. | |
session = None | |
# The queue of messages back to the kernel. | |
msg_queue = None | |
# Emitted when the channel is started. | |
started = QtCore.Signal() | |
# Emitted when the channel is stopped. | |
stopped = QtCore.Signal() | |
#--------------------------------------------------------------------------- | |
# 'ZmqSocketChannel' interface | |
#--------------------------------------------------------------------------- | |
def __init__(self, session): | |
"""Create a channel | |
Parameters | |
---------- | |
session : :class:`session.Session` | |
The session to use. | |
""" | |
super(SocketChannelQObject, self).__init__() | |
self.session = session | |
self.msg_queue = Queue() | |
def start(self): | |
""" Reimplemented to emit signal. | |
""" | |
self.started.emit() | |
def stop(self): | |
""" Reimplemented to emit signal. | |
""" | |
self.stopped.emit() | |
def send_json(self, msg, flags=0): | |
""" Call the frontend callback. | |
""" | |
self.call_handlers(msg) | |
def recv_multipart(self, mode=zmq.NOBLOCK): | |
""" Receive a message from the frontend. | |
""" | |
block = not (mode & zmq.NOBLOCK) | |
msg = self.msg_queue.get(block) | |
return msg | |
class QtShellSocketChannel(SocketChannelQObject): | |
"""The XREQ channel for issuing request/replies to the kernel. | |
""" | |
# Emitted when any message is received. | |
message_received = QtCore.Signal(object) | |
# Emitted when a reply has been received for the corresponding request | |
# type. | |
execute_reply = QtCore.Signal(object) | |
complete_reply = QtCore.Signal(object) | |
object_info_reply = QtCore.Signal(object) | |
history_reply = QtCore.Signal(object) | |
# Emitted when the first reply comes back. | |
first_reply = QtCore.Signal() | |
# Used by the first_reply signal logic to determine if a reply is the | |
# first. | |
_handlers_called = False | |
#--------------------------------------------------------------------------- | |
# 'XReqSocketChannel' interface | |
#--------------------------------------------------------------------------- | |
def call_handlers(self, msg): | |
""" Reimplemented to emit signals instead of making callbacks. | |
""" | |
# Emit the generic signal. | |
self.message_received.emit(msg) | |
# Emit signals for specialized message types. | |
msg_type = msg['header']['msg_type'] | |
signal = getattr(self, msg_type, None) | |
if signal: | |
signal.emit(msg) | |
if not self._handlers_called: | |
self.first_reply.emit() | |
self._handlers_called = True | |
def execute(self, code, silent=False, | |
user_variables=None, user_expressions=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. | |
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 with string keys and 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. | |
Returns | |
------- | |
The msg_id of the message sent. | |
""" | |
if user_variables is None: | |
user_variables = [] | |
if user_expressions is None: | |
user_expressions = {} | |
# Don't waste network traffic if inputs are invalid | |
if not isinstance(code, basestring): | |
raise ValueError('code %r must be a string' % code) | |
validate_string_list(user_variables) | |
validate_string_dict(user_expressions) | |
# Create class for content/msg creation. Related to, but possibly | |
# not in Session. | |
content = dict(code=code, silent=silent, | |
user_variables=user_variables, | |
user_expressions=user_expressions) | |
msg = self.session.msg('execute_request', content) | |
self._queue_request(msg) | |
return msg['header']['msg_id'] | |
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. | |
""" | |
content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos) | |
msg = self.session.msg('complete_request', content) | |
self._queue_request(msg) | |
return msg['header']['msg_id'] | |
def object_info(self, oname): | |
"""Get metadata information about an object. | |
Parameters | |
---------- | |
oname : str | |
A string specifying the object name. | |
Returns | |
------- | |
The msg_id of the message sent. | |
""" | |
content = dict(oname=oname) | |
msg = self.session.msg('object_info_request', content) | |
self._queue_request(msg) | |
return msg['header']['msg_id'] | |
def history(self, raw=True, output=False, hist_access_type='range', **kwargs): | |
"""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. | |
""" | |
content = dict(raw=raw, output=output, hist_access_type=hist_access_type, | |
**kwargs) | |
msg = self.session.msg('history_request', content) | |
self._queue_request(msg) | |
return msg['header']['msg_id'] | |
def shutdown(self, restart=False): | |
"""Request an immediate kernel shutdown. | |
Upon receipt of the (empty) reply, client code can safely assume that | |
the kernel has shut down and it's safe to forcefully terminate it if | |
it's still alive. | |
The kernel will send the reply via a function registered with Python's | |
atexit module, ensuring it's truly done as the kernel is done with all | |
normal operation. | |
""" | |
# Send quit message to kernel. Once we implement kernel-side setattr, | |
# this should probably be done that way, but for now this will do. | |
msg = self.session.msg('shutdown_request', {'restart':restart}) | |
self._queue_request(msg) | |
return msg['header']['msg_id'] | |
#--------------------------------------------------------------------------- | |
# 'QtXReqSocketChannel' interface | |
#--------------------------------------------------------------------------- | |
def reset_first_reply(self): | |
""" Reset the first_reply signal to fire again on the next reply. | |
""" | |
self._handlers_called = False | |
def _queue_request(self, msg): | |
self.msg_queue.put(msg) | |
class QtSubSocketChannel(SocketChannelQObject): | |
"""The SUB channel which listens for messages that the kernel publishes. | |
""" | |
# Emitted when any message is received. | |
message_received = QtCore.Signal(object) | |
# Emitted when a message of type 'stream' is received. | |
stream_received = QtCore.Signal(object) | |
# Emitted when a message of type 'pyin' is received. | |
pyin_received = QtCore.Signal(object) | |
# Emitted when a message of type 'pyout' is received. | |
pyout_received = QtCore.Signal(object) | |
# Emitted when a message of type 'pyerr' is received. | |
pyerr_received = QtCore.Signal(object) | |
# Emitted when a message of type 'display_data' is received | |
display_data_received = QtCore.Signal(object) | |
# Emitted when a crash report message is received from the kernel's | |
# last-resort sys.excepthook. | |
crash_received = QtCore.Signal(object) | |
# Emitted when a shutdown is noticed. | |
shutdown_reply_received = QtCore.Signal(object) | |
#--------------------------------------------------------------------------- | |
# 'SubSocketChannel' interface | |
#--------------------------------------------------------------------------- | |
def call_handlers(self, msg): | |
""" Reimplemented to emit signals instead of making callbacks. | |
""" | |
# Emit the generic signal. | |
self.message_received.emit(msg) | |
# Emit signals for specialized message types. | |
msg_type = msg['header']['msg_type'] | |
signal = getattr(self, msg_type + '_received', None) | |
if signal: | |
signal.emit(msg) | |
elif msg_type in ('stdout', 'stderr'): | |
self.stream_received.emit(msg) | |
def flush(self): | |
""" Reimplemented to ensure that signals are dispatched immediately. | |
""" | |
QtCore.QCoreApplication.instance().processEvents() | |
def _handle_recv(self): | |
# Get all of the messages we can | |
while True: | |
try: | |
ident,msg = self.session.recv(self.socket) | |
except zmq.ZMQError: | |
# Check the errno? | |
# Will this trigger POLLERR? | |
break | |
else: | |
if msg is None: | |
break | |
self.call_handlers(msg) | |
class QtStdInSocketChannel(SocketChannelQObject): | |
"""A reply channel to handle raw_input requests that the kernel makes. | |
""" | |
# Emitted when any message is received. | |
message_received = QtCore.Signal(object) | |
# Emitted when an input request is received. | |
input_requested = QtCore.Signal(object) | |
#--------------------------------------------------------------------------- | |
# 'RepSocketChannel' interface | |
#--------------------------------------------------------------------------- | |
def call_handlers(self, msg): | |
""" Reimplemented to emit signals instead of making callbacks. | |
""" | |
# Emit the generic signal. | |
self.message_received.emit(msg) | |
# Emit signals for specialized message types. | |
msg_type = msg['header']['msg_type'] | |
if msg_type == 'input_request': | |
self.input_requested.emit(msg) | |
def input(self, string): | |
"""Send a string of raw input to the kernel.""" | |
content = dict(value=string) | |
msg = self.session.msg('input_reply', content) | |
self._queue_reply(msg) | |
def _queue_reply(self, msg): | |
self.msg_queue.put(msg) | |
class QtHBSocketChannel(SocketChannelQObject): | |
""" Dummy heartbeat. | |
""" | |
_paused = False | |
# Emitted when the kernel has died. | |
kernel_died = QtCore.Signal(object) | |
#--------------------------------------------------------------------------- | |
# 'HBSocketChannel' interface | |
#--------------------------------------------------------------------------- | |
def call_handlers(self, since_last_heartbeat): | |
""" Reimplemented to emit signals instead of making callbacks. | |
""" | |
# Emit the generic signal. | |
self.kernel_died.emit(since_last_heartbeat) | |
def pause(self): | |
self._pause = True | |
def unpause(self): | |
self._pause = False | |
class QtKernelManager(HasTraits, SuperQObject): | |
""" A kernel manager for the frontend. | |
""" | |
__metaclass__ = MetaQObjectHasTraits | |
# The Session to use for communication with the kernel. | |
session = Instance(Session,(),{}) | |
# The kernel itself. | |
kernel = Instance(InprocQtKernel) | |
user_ns = Any() | |
user_module = Any() | |
shell_channel = Instance(QtShellSocketChannel) | |
sub_channel = Instance(QtSubSocketChannel) | |
stdin_channel = Instance(QtStdInSocketChannel) | |
hb_channel = Instance(QtHBSocketChannel) | |
# Emitted when the kernel manager has started listening. | |
started_channels = QtCore.Signal() | |
# Emitted when the kernel manager has stopped listening. | |
stopped_channels = QtCore.Signal() | |
# Whether channels are running or not. | |
channels_running = Bool(True) | |
#------ Kernel process management ------------------------------------------ | |
def start_kernel(self, *args, **kw): | |
self.kernel.start() | |
def shutdown_kernel(self, *args, **kw): | |
pass | |
def restart_kernel(self, *args, **kw): | |
pass | |
#------ Channel management ------------------------------------------------- | |
def start_channels(self, *args, **kw): | |
""" Reimplemented to emit signal. | |
""" | |
self.started_channels.emit() | |
def stop_channels(self): | |
""" Reimplemented to emit signal. | |
""" | |
self.stopped_channels.emit() | |
#### Traits stuff ######################################################### | |
def _stdin_channel_default(self): | |
return QtStdInSocketChannel(self.session) | |
def _sub_channel_default(self): | |
return QtSubSocketChannel(self.session) | |
def _shell_channel_default(self): | |
return QtShellSocketChannel(self.session) | |
def _hb_channel_default(self): | |
return QtHBSocketChannel(self.session) | |
def _kernel_default(self): | |
kernel = InprocQtKernel( | |
session=KernelSession(session=self.session.session), | |
user_ns=self.user_ns, | |
user_module=self.user_module, | |
iopub_socket=self.sub_channel, | |
shell_socket=self.shell_channel, | |
stdin_socket=self.stdin_channel, | |
) | |
return kernel |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment