diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
index 441a000..d14aae9 100644
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -1704,6 +1704,45 @@ class InteractiveShell(Configurable, Magic):
self.prefilter = self.prefilter_manager.prefilter_lines
#-------------------------------------------------------------------------
+ # Things related to extracting values/expressions from kernel and user_ns
+ #-------------------------------------------------------------------------
+
+ def _simple_error(self):
+ etype, value = sys.exc_info()[:2]
+ return u'[ERROR] {e.__name__}: {v}'.format(e=etype, v=value)
+
+ def get_user_variables(self, names):
+ """Get a list of variable names from the user's namespace.
+
+ The return value is a dict with the repr() of each value.
+ """
+ out = {}
+ user_ns = self.user_ns
+ for varname in names:
+ try:
+ value = repr(user_ns[varname])
+ except:
+ value = self._simple_error()
+ out[varname] = value
+ return out
+
+ def eval_expressions(self, expressions):
+ """Evaluate a dict of expressions in the user's namespace.
+
+ The return value is a dict with the repr() of each value.
+ """
+ out = {}
+ user_ns = self.user_ns
+ global_ns = self.user_global_ns
+ for key, expr in expressions.iteritems():
+ try:
+ value = repr(eval(expr, global_ns, user_ns))
+ except:
+ value = self._simple_error()
+ out[key] = value
+ return out
+
+ #-------------------------------------------------------------------------
# Things related to the running of code
#-------------------------------------------------------------------------
diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py
index 6537119..a1a6dab 100644
--- a/IPython/frontend/qt/console/frontend_widget.py
+++ b/IPython/frontend/qt/console/frontend_widget.py
@@ -131,10 +131,27 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
complete = not self._input_splitter.push_accepts_more()
return complete
- def _execute(self, source, hidden):
+ def _execute(self, source, hidden, user_variables=None,
+ user_expressions=None):
""" Execute 'source'. If 'hidden', do not show any output.
- """
- self.kernel_manager.xreq_channel.execute(source, hidden)
+
+ See parent class :meth:`execute` docstring for full details.
+ """
+ # tmp code for testing, disable in real use with 'if 0'. Only delete
+ # this code once we have automated tests for these fields.
+ if 0:
+ user_variables = ['x', 'y', 'z']
+ user_expressions = {'sum' : '1+1',
+ 'bad syntax' : 'klsdafj kasd f',
+ 'bad call' : 'range("hi")',
+ 'time' : 'time.time()',
+ }
+ # /end tmp code
+
+ # FIXME - user_variables/expressions are not visible in API above us.
+ self.kernel_manager.xreq_channel.execute(source, hidden,
+ user_variables,
+ user_expressions)
self._hidden = hidden
def _prompt_started_hook(self):
diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py
index d2828f5..1b3be7b 100644
--- a/IPython/frontend/qt/console/ipython_widget.py
+++ b/IPython/frontend/qt/console/ipython_widget.py
@@ -21,6 +21,7 @@ from PyQt4 import QtCore, QtGui
# Local imports
from IPython.core.inputsplitter import IPythonInputSplitter
from IPython.core.usage import default_banner
+from IPython.utils import io
from IPython.utils.traitlets import Bool, Str
from frontend_widget import FrontendWidget
@@ -50,9 +51,13 @@ default_dark_style_sheet = '''
'''
default_dark_syntax_style = 'monokai'
-# Default prompts.
+# Default strings to build and display input and output prompts (and separators
+# in between)
default_in_prompt = 'In [%i]: '
default_out_prompt = 'Out[%i]: '
+default_input_sep = '\n'
+default_output_sep = ''
+default_output_sep2 = ''
#-----------------------------------------------------------------------------
# IPythonWidget class
@@ -92,6 +97,9 @@ class IPythonWidget(FrontendWidget):
# Prompts.
in_prompt = Str(default_in_prompt, config=True)
out_prompt = Str(default_out_prompt, config=True)
+ input_sep = Str(default_input_sep, config=True)
+ output_sep = Str(default_output_sep, config=True)
+ output_sep2 = Str(default_output_sep2, config=True)
# FrontendWidget protected class variables.
_input_splitter_class = IPythonInputSplitter
@@ -155,20 +163,17 @@ class IPythonWidget(FrontendWidget):
""" Implemented to handle prompt number replies, which are only
supported by the IPython kernel.
"""
- content = msg['content']
- self._show_interpreter_prompt(content['prompt_number'],
- content['input_sep'])
+ self._show_interpreter_prompt(msg['content']['execution_count'])
def _handle_pyout(self, msg):
""" Reimplemented for IPython-style "display hook".
"""
if not self._hidden and self._is_from_this_session(msg):
content = msg['content']
- prompt_number = content['prompt_number']
- self._append_plain_text(content['output_sep'])
+ prompt_number = content['execution_count']
+ self._append_plain_text(self.output_sep)
self._append_html(self._make_out_prompt(prompt_number))
- self._append_plain_text(content['data'] + '\n' +
- content['output_sep2'])
+ self._append_plain_text(content['data']+self.output_sep2)
def _started_channels(self):
""" Reimplemented to make a history request.
@@ -244,17 +249,19 @@ class IPythonWidget(FrontendWidget):
else:
return False
- def _show_interpreter_prompt(self, number=None, input_sep='\n'):
+ def _show_interpreter_prompt(self, number=None):
""" Reimplemented for IPython-style prompts.
"""
# If a number was not specified, make a prompt number request.
if number is None:
- self.kernel_manager.xreq_channel.prompt()
- return
+ # FIXME - fperez: this should be a silent code request
+ number = 1
+ ##self.kernel_manager.xreq_channel.prompt()
+ ##return
# Show a new prompt and save information about it so that it can be
# updated later if the prompt number turns out to be wrong.
- self._prompt_sep = input_sep
+ self._prompt_sep = self.input_sep
self._show_prompt(self._make_in_prompt(number), html=True)
block = self._control.document().lastBlock()
length = len(self._prompt)
@@ -269,7 +276,8 @@ class IPythonWidget(FrontendWidget):
"""
# Update the old prompt number if necessary.
content = msg['content']
- previous_prompt_number = content['prompt_number']
+ ##io.rprint('_show_interpreter_prompt_for_reply\n', content) # dbg
+ previous_prompt_number = content['execution_count']
if self._previous_prompt_obj and \
self._previous_prompt_obj.number != previous_prompt_number:
block = self._previous_prompt_obj.block
@@ -293,9 +301,7 @@ class IPythonWidget(FrontendWidget):
self._previous_prompt_obj = None
# Show a new prompt with the kernel's estimated prompt number.
- next_prompt = content['next_prompt']
- self._show_interpreter_prompt(next_prompt['prompt_number'],
- next_prompt['input_sep'])
+ self._show_interpreter_prompt(previous_prompt_number+1)
#---------------------------------------------------------------------------
# 'IPythonWidget' interface
diff --git a/IPython/utils/_process_common.py b/IPython/utils/_process_common.py
index 47cbf0f..4369f34 100644
--- a/IPython/utils/_process_common.py
+++ b/IPython/utils/_process_common.py
@@ -63,7 +63,8 @@ def process_handler(cmd, callback, stderr=subprocess.PIPE):
"""
sys.stdout.flush()
sys.stderr.flush()
- close_fds = False if sys.platform=='win32' else True
+ # On win32, close_fds can't be true when using pipes for stdin/out/err
+ close_fds = sys.platform != 'win32'
p = subprocess.Popen(cmd, shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
diff --git a/IPython/zmq/blockingkernelmanager.py b/IPython/zmq/blockingkernelmanager.py
index 8f0ded0..5bbff3d 100644
--- a/IPython/zmq/blockingkernelmanager.py
+++ b/IPython/zmq/blockingkernelmanager.py
@@ -1,10 +1,32 @@
-from kernelmanager import SubSocketChannel
+"""Implement a fully blocking kernel manager.
+
+Useful for test suites and blocking terminal interfaces.
+"""
+#-----------------------------------------------------------------------------
+# Copyright (C) 2010 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING.txt, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+from __future__ import print_function
+
+# Stdlib
from Queue import Queue, Empty
+# Our own
+from IPython.utils import io
+from IPython.utils.traitlets import Type
-class MsgNotReady(Exception):
- pass
+from .kernelmanager import (KernelManager, SubSocketChannel,
+ XReqSocketChannel, RepSocketChannel, HBSocketChannel)
+#-----------------------------------------------------------------------------
+# Functions and classes
+#-----------------------------------------------------------------------------
class BlockingSubSocketChannel(SubSocketChannel):
@@ -13,6 +35,7 @@ class BlockingSubSocketChannel(SubSocketChannel):
self._in_queue = Queue()
def call_handlers(self, msg):
+ io.rprint('[[Sub]]', msg) # dbg
self._in_queue.put(msg)
def msg_ready(self):
@@ -24,21 +47,68 @@ class BlockingSubSocketChannel(SubSocketChannel):
def get_msg(self, block=True, timeout=None):
"""Get a message if there is one that is ready."""
- try:
- msg = self.in_queue.get(block, timeout)
- except Empty:
- raise MsgNotReady('No message has been received.')
+ return self.in_queue.get(block, timeout)
+
+ def get_msgs(self):
+ """Get all messages that are currently ready."""
+ msgs = []
+ while True:
+ try:
+ msgs.append(self.get_msg(block=False))
+ except Empty:
+ break
+ return msgs
+
+
+
+class BlockingXReqSocketChannel(XReqSocketChannel):
+
+ def __init__(self, context, session, address=None):
+ super(BlockingXReqSocketChannel, self).__init__(context, session, address)
+ self._in_queue = Queue()
+
+ def call_handlers(self, msg):
+ io.rprint('[[XReq]]', msg) # dbg
+
+ def msg_ready(self):
+ """Is there a message that has been received?"""
+ if self._in_queue.qsize() == 0:
+ return False
else:
- return msg
+ return True
+
+ def get_msg(self, block=True, timeout=None):
+ """Get a message if there is one that is ready."""
+ return self.in_queue.get(block, timeout)
def get_msgs(self):
"""Get all messages that are currently ready."""
msgs = []
while True:
try:
- msg = self.get_msg(block=False)
- except MsgNotReady:
+ msgs.append(self.get_msg(block=False))
+ except Empty:
break
- else:
- msgs.append(msg)
- return msgs
\ No newline at end of file
+ return msgs
+
+class BlockingRepSocketChannel(RepSocketChannel):
+ def call_handlers(self, msg):
+ io.rprint('[[Rep]]', msg) # dbg
+
+
+class BlockingHBSocketChannel(HBSocketChannel):
+ # This kernel needs rapid monitoring capabilities
+ time_to_dead = 0.2
+
+ def call_handlers(self, since_last_heartbeat):
+ io.rprint('[[Heart]]', since_last_heartbeat) # dbg
+
+
+class BlockingKernelManager(KernelManager):
+
+ # The classes to use for the various channels.
+ xreq_channel_class = Type(BlockingXReqSocketChannel)
+ sub_channel_class = Type(BlockingSubSocketChannel)
+ rep_channel_class = Type(BlockingRepSocketChannel)
+ hb_channel_class = Type(BlockingHBSocketChannel)
+
diff --git a/IPython/zmq/ipkernel.py b/IPython/zmq/ipkernel.py
index ddb4541..0039acc 100755
--- a/IPython/zmq/ipkernel.py
+++ b/IPython/zmq/ipkernel.py
@@ -35,6 +35,7 @@ from iostream import OutStream
from session import Session, Message
from zmqshell import ZMQInteractiveShell
+
#-----------------------------------------------------------------------------
# Main kernel class
#-----------------------------------------------------------------------------
@@ -64,8 +65,7 @@ class Kernel(Configurable):
# Build dict of handlers for message types
msg_types = [ 'execute_request', 'complete_request',
- 'object_info_request', 'prompt_request',
- 'history_request' ]
+ 'object_info_request', 'history_request' ]
self.handlers = {}
for msg_type in msg_types:
self.handlers[msg_type] = getattr(self, msg_type)
@@ -81,14 +81,21 @@ class Kernel(Configurable):
# FIXME: Bug in pyzmq/zmq?
# assert self.reply_socket.rcvmore(), "Missing message part."
msg = self.reply_socket.recv_json()
- omsg = Message(msg)
- io.raw_print('\n')
- io.raw_print(omsg)
- handler = self.handlers.get(omsg.msg_type, None)
+
+ # 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.
+ io.raw_print('\n*** MESSAGE TYPE:', msg['msg_type'], '***')
+ io.raw_print(' Content: ', msg['content'],
+ '\n --->\n ', sep='', end='')
+
+ # Find and call actual handler for message
+ handler = self.handlers.get(msg['msg_type'], None)
if handler is None:
- io.raw_print_err("UNKNOWN MESSAGE TYPE:", omsg)
+ io.raw_print_err("UNKNOWN MESSAGE TYPE:", msg)
else:
- handler(ident, omsg)
+ handler(ident, msg)
def start(self):
""" Start the kernel main loop.
@@ -97,37 +104,56 @@ class Kernel(Configurable):
time.sleep(0.05)
self.do_one_iteration()
-
#---------------------------------------------------------------------------
# Kernel request handlers
#---------------------------------------------------------------------------
+ def _publish_pyin(self, code, parent):
+ """Publish the code request on the pyin stream."""
+
+ pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
+ self.pub_socket.send_json(pyin_msg)
+
def execute_request(self, ident, parent):
try:
- code = parent[u'content'][u'code']
+ content = parent[u'content']
+ code = content[u'code']
+ silent = content[u'silent']
except:
io.raw_print_err("Got bad msg: ")
io.raw_print_err(Message(parent))
return
- pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
- self.pub_socket.send_json(pyin_msg)
+ shell = self.shell # we'll need this a lot here
+
+ # Replace raw_input. Note that is not sufficient to replace
+ # raw_input in the user namespace.
+ raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
+ __builtin__.raw_input = raw_input
+
+ # Set the parent message of the display hook and out streams.
+ shell.displayhook.set_parent(parent)
+ sys.stdout.set_parent(parent)
+ sys.stderr.set_parent(parent)
+
+ # Re-broadcast our input for the benefit of listening clients, and
+ # start computing output
+ if not silent:
+ self._publish_pyin(code, parent)
+
+ reply_content = {}
try:
- # Replace raw_input. Note that is not sufficient to replace
- # raw_input in the user namespace.
- raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
- __builtin__.raw_input = raw_input
-
- # Set the parent message of the display hook and out streams.
- self.shell.displayhook.set_parent(parent)
- sys.stdout.set_parent(parent)
- sys.stderr.set_parent(parent)
-
- # FIXME: runlines calls the exception handler itself. We should
- # clean this up.
- self.shell._reply_content = None
- self.shell.runlines(code)
+ if silent:
+ # runcode uses 'exec' mode, so no displayhook will fire, and it
+ # doesn't call logging or history manipulations. Print
+ # statements in that code will obviously still execute.
+ shell.runcode(code)
+ else:
+ # FIXME: runlines calls the exception handler itself.
+ shell._reply_content = None
+ shell.runlines(code)
except:
+ status = u'error'
# 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
@@ -136,35 +162,39 @@ class Kernel(Configurable):
# single location in the codbase.
etype, evalue, tb = sys.exc_info()
tb_list = traceback.format_exception(etype, evalue, tb)
- reply_content = self.shell._showtraceback(etype, evalue, tb_list)
+ reply_content.update(shell._showtraceback(etype, evalue, tb_list))
else:
- payload = self.shell.payload_manager.read_payload()
+ status = u'ok'
+ reply_content[u'payload'] = 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 }
-
- # 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,
- 'prompt_number' : prompt_number+1,
- 'input_sep' : self.shell.displayhook.input_sep}
- reply_content['next_prompt'] = next_prompt
-
- # 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)
-
- # Flush output before sending the reply.
- sys.stderr.flush()
- sys.stdout.flush()
-
+ shell.payload_manager.clear_payload()
+
+ reply_content[u'status'] = status
+ # Compute the execution counter so clients can display prompts
+ reply_content['execution_count'] = shell.displayhook.prompt_count
+
+ # FIXME - fish exception info out of shell, possibly left there by
+ # runlines. We'll need to clean up this logic later.
+ if shell._reply_content is not None:
+ reply_content.update(shell._reply_content)
+
+ # At this point, we can tell whether the main code execution succeeded
+ # or not. If it did, we proceed to evaluate user_variables/expressions
+ if reply_content['status'] == 'ok':
+ reply_content[u'user_variables'] = \
+ shell.get_user_variables(content[u'user_variables'])
+ reply_content[u'user_expressions'] = \
+ shell.eval_expressions(content[u'user_expressions'])
+ else:
+ # If there was an error, don't even try to compute variables or
+ # expressions
+ reply_content[u'user_variables'] = {}
+ reply_content[u'user_expressions'] = {}
+
# Send the reply.
reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
- io.raw_print(Message(reply_msg))
+ io.raw_print(reply_msg)
self.reply_socket.send(ident, zmq.SNDMORE)
self.reply_socket.send_json(reply_msg)
if reply_msg['content']['status'] == u'error':
@@ -186,16 +216,6 @@ class Kernel(Configurable):
object_info, parent, ident)
io.raw_print(msg)
- 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,
- 'prompt_number' : prompt_number+1,
- 'input_sep' : self.shell.displayhook.input_sep}
- msg = self.session.send(self.reply_socket, 'prompt_reply',
- content, parent, ident)
- io.raw_print(msg)
-
def history_request(self, ident, parent):
output = parent['content']['output']
index = parent['content']['index']
@@ -218,13 +238,14 @@ class Kernel(Configurable):
if e.errno == zmq.EAGAIN:
break
else:
- assert self.reply_socket.rcvmore(), "Unexpected missing message part."
+ assert self.reply_socket.rcvmore(), \
+ "Unexpected missing message part."
msg = self.reply_socket.recv_json()
io.raw_print("Aborting:\n", Message(msg))
msg_type = msg['msg_type']
reply_type = msg_type.split('_')[0] + '_reply'
reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
- io.raw_print(Message(reply_msg))
+ io.raw_print(reply_msg)
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
@@ -312,6 +333,7 @@ class QtKernel(Kernel):
self.timer.start(50)
start_event_loop_qt4(self.app)
+
class WxKernel(Kernel):
"""A Kernel subclass with Wx support."""
@@ -421,6 +443,7 @@ def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
xrep_port, pub_port, req_port, hb_port,
independent, extra_arguments)
+
def main():
""" The IPython kernel main entry point.
"""
@@ -458,5 +481,6 @@ given, the GUI backend is matplotlib's, otherwise use one of: \
start_kernel(namespace, kernel)
+
if __name__ == '__main__':
main()
diff --git a/IPython/zmq/kernelmanager.py b/IPython/zmq/kernelmanager.py
index cebc4b5..79f0844 100644
--- a/IPython/zmq/kernelmanager.py
+++ b/IPython/zmq/kernelmanager.py
@@ -29,6 +29,7 @@ from zmq import POLLIN, POLLOUT, POLLERR
from zmq.eventloop import ioloop
# Local imports.
+from IPython.utils import io
from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
from session import Session
@@ -42,6 +43,35 @@ class InvalidPortNumber(Exception):
pass
#-----------------------------------------------------------------------------
+# Utility functions
+#-----------------------------------------------------------------------------
+
+# some utilities to validate message structure, these might get moved elsewhere
+# if they prove to have more generic utility
+
+def validate_string_list(lst):
+ """Validate that the input is a list of strings.
+
+ Raises ValueError if not."""
+ if not isinstance(lst, list):
+ raise ValueError('input %r must be a list' % lst)
+ for x in lst:
+ if not isinstance(x, basestring):
+ raise ValueError('element %r in list must be a string' % x)
+
+
+def validate_string_dict(dct):
+ """Validate that the input is a dict with string keys and values.
+
+ Raises ValueError if not."""
+ for k,v in dct.iteritems():
+ if not isinstance(k, basestring):
+ raise ValueError('key %r in dict must be a string' % k)
+ if not isinstance(v, basestring):
+ raise ValueError('value %r in dict must be a string' % v)
+
+
+#-----------------------------------------------------------------------------
# ZMQ Socket Channel classes
#-----------------------------------------------------------------------------
@@ -163,23 +193,49 @@ class XReqSocketChannel(ZmqSocketChannel):
"""
raise NotImplementedError('call_handlers must be defined in a subclass.')
- def execute(self, code, silent=False):
+ 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)
+ 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']
@@ -249,17 +305,6 @@ class XReqSocketChannel(ZmqSocketChannel):
self._queue_request(msg)
return msg['header']['msg_id']
- def prompt(self):
- """Requests a prompt number from the kernel.
-
- Returns
- -------
- The msg_id of the message sent.
- """
- msg = self.session.msg('prompt_request')
- self._queue_request(msg)
- return msg['header']['msg_id']
-
def _handle_events(self, socket, events):
if events & POLLERR:
self._handle_err()
@@ -479,9 +524,12 @@ class HBSocketChannel(ZmqSocketChannel):
since_last_heartbeat = 0.0
request_time = time.time()
try:
+ #io.rprint('Ping from HB channel') # dbg
self.socket.send_json('ping')
except zmq.ZMQError, e:
+ #io.rprint('*** HB Error:', e) # dbg
if e.errno == zmq.EFSM:
+ #io.rprint('sleep...', self.time_to_dead) # dbg
time.sleep(self.time_to_dead)
self._create_socket()
else:
@@ -489,13 +537,21 @@ class HBSocketChannel(ZmqSocketChannel):
else:
while True:
try:
- reply = self.socket.recv_json(zmq.NOBLOCK)
+ self.socket.recv_json(zmq.NOBLOCK)
except zmq.ZMQError, e:
+ #io.rprint('*** HB Error 2:', e) # dbg
if e.errno == zmq.EAGAIN:
- until_dead = self.time_to_dead - (time.time() -
+ before_poll = time.time()
+ until_dead = self.time_to_dead - (before_poll -
request_time)
- # poll timeout is in milliseconds.
- poll_result = self.poller.poll(1000*until_dead)
+
+ # When the return value of poll() is an empty list,
+ # that is when things have gone wrong (zeromq bug).
+ # As long as it is not an empty list, poll is
+ # working correctly even if it returns quickly.
+ # Note: poll timeout is in milliseconds.
+ self.poller.poll(1000*until_dead)
+
since_last_heartbeat = time.time() - request_time
if since_last_heartbeat > self.time_to_dead:
self.call_handlers(since_last_heartbeat)
@@ -507,6 +563,7 @@ class HBSocketChannel(ZmqSocketChannel):
until_dead = self.time_to_dead - (time.time() -
request_time)
if until_dead > 0.0:
+ #io.rprint('sleep...', self.time_to_dead) # dbg
time.sleep(until_dead)
break
diff --git a/IPython/zmq/tests/__init__.py b/IPython/zmq/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/IPython/zmq/tests/__init__.py
diff --git a/IPython/zmq/tests/test_message_spec.py b/IPython/zmq/tests/test_message_spec.py
new file mode 100644
index 0000000..817113f
--- /dev/null
+++ b/IPython/zmq/tests/test_message_spec.py
@@ -0,0 +1,40 @@
+"""Test suite for our zeromq-based messaging specification.
+"""
+#-----------------------------------------------------------------------------
+# Copyright (C) 2010 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING.txt, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+import sys
+import time
+
+import nose.tools as nt
+
+from ..blockingkernelmanager import BlockingKernelManager
+
+from IPython.utils import io
+
+def setup():
+ global KM
+ KM = BlockingKernelManager()
+
+ KM.start_kernel()
+ KM.start_channels()
+ # Give the kernel a chance to come up.
+ time.sleep(1)
+
+def teardown():
+ io.rprint('Entering teardown...') # dbg
+ io.rprint('Stopping channels and kernel...') # dbg
+ KM.stop_channels()
+ KM.kill_kernel()
+
+
+# Actual tests
+
+def test_execute():
+ KM.xreq_channel.execute(code='x=1')
+ KM.xreq_channel.execute(code='print 1')
+
diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py
index 5b811e8..845b0f4 100644
--- a/IPython/zmq/zmqshell.py
+++ b/IPython/zmq/zmqshell.py
@@ -61,10 +61,7 @@ class ZMQDisplayHook(DisplayHook):
def write_output_prompt(self):
"""Write the output prompt."""
if self.do_full_cache:
- self.msg['content']['output_sep'] = self.output_sep
- self.msg['content']['prompt_string'] = str(self.prompt_out)
- self.msg['content']['prompt_number'] = self.prompt_count
- self.msg['content']['output_sep2'] = self.output_sep2
+ self.msg['content']['execution_count'] = self.prompt_count
def write_result_repr(self, result_repr):
self.msg['content']['data'] = result_repr
@@ -383,7 +380,6 @@ class ZMQInteractiveShell(InteractiveShell):
def _showtraceback(self, etype, evalue, stb):
exc_content = {
- u'status' : u'error',
u'traceback' : stb,
u'ename' : unicode(etype.__name__),
u'evalue' : unicode(evalue)
@@ -397,7 +393,10 @@ class ZMQInteractiveShell(InteractiveShell):
# FIXME - Hack: store exception info in shell object. Right now, the
# caller is reading this info after the fact, we need to fix this logic
- # to remove this hack.
+ # to remove this hack. Even uglier, we need to store the error status
+ # here, because in the main loop, the logic that sets it is being
+ # skipped because runlines swallows the exceptions.
+ exc_content[u'status'] = u'error'
self._reply_content = exc_content
# /FIXME
diff --git a/docs/source/development/messaging.txt b/docs/source/development/messaging.txt
index 82b6394..3b4035d 100644
--- a/docs/source/development/messaging.txt
+++ b/docs/source/development/messaging.txt
@@ -242,28 +242,27 @@ Message type: ``execute_reply``::
# prompt numbers to the user. If the request was a silent one, this will
# be the current value of the counter in the kernel.
'execution_count' : int,
-
- # If the state_template was provided, this will contain the evaluated
- # form of the template.
- 'state' : str,
}
When status is 'ok', the following extra fields are present::
{
- # The kernel will often transform the input provided to it. If the
- # '---->' transform had been applied, this is filled, otherwise it's the
- # empty string. So transformations like magics don't appear here, only
- # autocall ones.
-
- 'transformed_code' : str,
-
# The execution payload is a dict with string keys that may have been
# produced by the code being executed. It is retrieved by the kernel at
# the end of the execution and sent back to the front end, which can take
# action on it as needed. See main text for further details.
'payload' : dict,
- }
+
+ # Results for the user_variables and user_expressions.
+ 'user_variables' : dict,
+ 'user_expressions' : dict,
+
+ # The kernel will often transform the input provided to it. If the
+ # '---->' transform had been applied, this is filled, otherwise it's the
+ # empty string. So transformations like magics don't appear here, only
+ # autocall ones.
+ 'transformed_code' : str,
+ }
.. admonition:: Execution payloads