##// END OF EJS Templates
Rework messaging to better conform to our spec....
Fernando Perez -
Show More
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,40 b''
1 """Test suite for our zeromq-based messaging specification.
2 """
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 import sys
11 import time
12
13 import nose.tools as nt
14
15 from ..blockingkernelmanager import BlockingKernelManager
16
17 from IPython.utils import io
18
19 def setup():
20 global KM
21 KM = BlockingKernelManager()
22
23 KM.start_kernel()
24 KM.start_channels()
25 # Give the kernel a chance to come up.
26 time.sleep(1)
27
28 def teardown():
29 io.rprint('Entering teardown...') # dbg
30 io.rprint('Stopping channels and kernel...') # dbg
31 KM.stop_channels()
32 KM.kill_kernel()
33
34
35 # Actual tests
36
37 def test_execute():
38 KM.xreq_channel.execute(code='x=1')
39 KM.xreq_channel.execute(code='print 1')
40
@@ -1704,6 +1704,45 b' class InteractiveShell(Configurable, Magic):'
1704 self.prefilter = self.prefilter_manager.prefilter_lines
1704 self.prefilter = self.prefilter_manager.prefilter_lines
1705
1705
1706 #-------------------------------------------------------------------------
1706 #-------------------------------------------------------------------------
1707 # Things related to extracting values/expressions from kernel and user_ns
1708 #-------------------------------------------------------------------------
1709
1710 def _simple_error(self):
1711 etype, value = sys.exc_info()[:2]
1712 return u'[ERROR] {e.__name__}: {v}'.format(e=etype, v=value)
1713
1714 def get_user_variables(self, names):
1715 """Get a list of variable names from the user's namespace.
1716
1717 The return value is a dict with the repr() of each value.
1718 """
1719 out = {}
1720 user_ns = self.user_ns
1721 for varname in names:
1722 try:
1723 value = repr(user_ns[varname])
1724 except:
1725 value = self._simple_error()
1726 out[varname] = value
1727 return out
1728
1729 def eval_expressions(self, expressions):
1730 """Evaluate a dict of expressions in the user's namespace.
1731
1732 The return value is a dict with the repr() of each value.
1733 """
1734 out = {}
1735 user_ns = self.user_ns
1736 global_ns = self.user_global_ns
1737 for key, expr in expressions.iteritems():
1738 try:
1739 value = repr(eval(expr, global_ns, user_ns))
1740 except:
1741 value = self._simple_error()
1742 out[key] = value
1743 return out
1744
1745 #-------------------------------------------------------------------------
1707 # Things related to the running of code
1746 # Things related to the running of code
1708 #-------------------------------------------------------------------------
1747 #-------------------------------------------------------------------------
1709
1748
@@ -131,10 +131,27 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
131 complete = not self._input_splitter.push_accepts_more()
131 complete = not self._input_splitter.push_accepts_more()
132 return complete
132 return complete
133
133
134 def _execute(self, source, hidden):
134 def _execute(self, source, hidden, user_variables=None,
135 user_expressions=None):
135 """ Execute 'source'. If 'hidden', do not show any output.
136 """ Execute 'source'. If 'hidden', do not show any output.
136 """
137
137 self.kernel_manager.xreq_channel.execute(source, hidden)
138 See parent class :meth:`execute` docstring for full details.
139 """
140 # tmp code for testing, disable in real use with 'if 0'. Only delete
141 # this code once we have automated tests for these fields.
142 if 0:
143 user_variables = ['x', 'y', 'z']
144 user_expressions = {'sum' : '1+1',
145 'bad syntax' : 'klsdafj kasd f',
146 'bad call' : 'range("hi")',
147 'time' : 'time.time()',
148 }
149 # /end tmp code
150
151 # FIXME - user_variables/expressions are not visible in API above us.
152 self.kernel_manager.xreq_channel.execute(source, hidden,
153 user_variables,
154 user_expressions)
138 self._hidden = hidden
155 self._hidden = hidden
139
156
140 def _prompt_started_hook(self):
157 def _prompt_started_hook(self):
@@ -21,6 +21,7 b' from PyQt4 import QtCore, QtGui'
21 # Local imports
21 # Local imports
22 from IPython.core.inputsplitter import IPythonInputSplitter
22 from IPython.core.inputsplitter import IPythonInputSplitter
23 from IPython.core.usage import default_banner
23 from IPython.core.usage import default_banner
24 from IPython.utils import io
24 from IPython.utils.traitlets import Bool, Str
25 from IPython.utils.traitlets import Bool, Str
25 from frontend_widget import FrontendWidget
26 from frontend_widget import FrontendWidget
26
27
@@ -50,9 +51,13 b" default_dark_style_sheet = '''"
50 '''
51 '''
51 default_dark_syntax_style = 'monokai'
52 default_dark_syntax_style = 'monokai'
52
53
53 # Default prompts.
54 # Default strings to build and display input and output prompts (and separators
55 # in between)
54 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
56 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
55 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
57 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
58 default_input_sep = '\n'
59 default_output_sep = ''
60 default_output_sep2 = ''
56
61
57 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
58 # IPythonWidget class
63 # IPythonWidget class
@@ -92,6 +97,9 b' class IPythonWidget(FrontendWidget):'
92 # Prompts.
97 # Prompts.
93 in_prompt = Str(default_in_prompt, config=True)
98 in_prompt = Str(default_in_prompt, config=True)
94 out_prompt = Str(default_out_prompt, config=True)
99 out_prompt = Str(default_out_prompt, config=True)
100 input_sep = Str(default_input_sep, config=True)
101 output_sep = Str(default_output_sep, config=True)
102 output_sep2 = Str(default_output_sep2, config=True)
95
103
96 # FrontendWidget protected class variables.
104 # FrontendWidget protected class variables.
97 _input_splitter_class = IPythonInputSplitter
105 _input_splitter_class = IPythonInputSplitter
@@ -155,20 +163,17 b' class IPythonWidget(FrontendWidget):'
155 """ Implemented to handle prompt number replies, which are only
163 """ Implemented to handle prompt number replies, which are only
156 supported by the IPython kernel.
164 supported by the IPython kernel.
157 """
165 """
158 content = msg['content']
166 self._show_interpreter_prompt(msg['content']['execution_count'])
159 self._show_interpreter_prompt(content['prompt_number'],
160 content['input_sep'])
161
167
162 def _handle_pyout(self, msg):
168 def _handle_pyout(self, msg):
163 """ Reimplemented for IPython-style "display hook".
169 """ Reimplemented for IPython-style "display hook".
164 """
170 """
165 if not self._hidden and self._is_from_this_session(msg):
171 if not self._hidden and self._is_from_this_session(msg):
166 content = msg['content']
172 content = msg['content']
167 prompt_number = content['prompt_number']
173 prompt_number = content['execution_count']
168 self._append_plain_text(content['output_sep'])
174 self._append_plain_text(self.output_sep)
169 self._append_html(self._make_out_prompt(prompt_number))
175 self._append_html(self._make_out_prompt(prompt_number))
170 self._append_plain_text(content['data'] + '\n' +
176 self._append_plain_text(content['data']+self.output_sep2)
171 content['output_sep2'])
172
177
173 def _started_channels(self):
178 def _started_channels(self):
174 """ Reimplemented to make a history request.
179 """ Reimplemented to make a history request.
@@ -244,17 +249,19 b' class IPythonWidget(FrontendWidget):'
244 else:
249 else:
245 return False
250 return False
246
251
247 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
252 def _show_interpreter_prompt(self, number=None):
248 """ Reimplemented for IPython-style prompts.
253 """ Reimplemented for IPython-style prompts.
249 """
254 """
250 # If a number was not specified, make a prompt number request.
255 # If a number was not specified, make a prompt number request.
251 if number is None:
256 if number is None:
252 self.kernel_manager.xreq_channel.prompt()
257 # FIXME - fperez: this should be a silent code request
253 return
258 number = 1
259 ##self.kernel_manager.xreq_channel.prompt()
260 ##return
254
261
255 # Show a new prompt and save information about it so that it can be
262 # Show a new prompt and save information about it so that it can be
256 # updated later if the prompt number turns out to be wrong.
263 # updated later if the prompt number turns out to be wrong.
257 self._prompt_sep = input_sep
264 self._prompt_sep = self.input_sep
258 self._show_prompt(self._make_in_prompt(number), html=True)
265 self._show_prompt(self._make_in_prompt(number), html=True)
259 block = self._control.document().lastBlock()
266 block = self._control.document().lastBlock()
260 length = len(self._prompt)
267 length = len(self._prompt)
@@ -269,7 +276,8 b' class IPythonWidget(FrontendWidget):'
269 """
276 """
270 # Update the old prompt number if necessary.
277 # Update the old prompt number if necessary.
271 content = msg['content']
278 content = msg['content']
272 previous_prompt_number = content['prompt_number']
279 ##io.rprint('_show_interpreter_prompt_for_reply\n', content) # dbg
280 previous_prompt_number = content['execution_count']
273 if self._previous_prompt_obj and \
281 if self._previous_prompt_obj and \
274 self._previous_prompt_obj.number != previous_prompt_number:
282 self._previous_prompt_obj.number != previous_prompt_number:
275 block = self._previous_prompt_obj.block
283 block = self._previous_prompt_obj.block
@@ -293,9 +301,7 b' class IPythonWidget(FrontendWidget):'
293 self._previous_prompt_obj = None
301 self._previous_prompt_obj = None
294
302
295 # Show a new prompt with the kernel's estimated prompt number.
303 # Show a new prompt with the kernel's estimated prompt number.
296 next_prompt = content['next_prompt']
304 self._show_interpreter_prompt(previous_prompt_number+1)
297 self._show_interpreter_prompt(next_prompt['prompt_number'],
298 next_prompt['input_sep'])
299
305
300 #---------------------------------------------------------------------------
306 #---------------------------------------------------------------------------
301 # 'IPythonWidget' interface
307 # 'IPythonWidget' interface
@@ -63,7 +63,8 b' def process_handler(cmd, callback, stderr=subprocess.PIPE):'
63 """
63 """
64 sys.stdout.flush()
64 sys.stdout.flush()
65 sys.stderr.flush()
65 sys.stderr.flush()
66 close_fds = False if sys.platform=='win32' else True
66 # On win32, close_fds can't be true when using pipes for stdin/out/err
67 close_fds = sys.platform != 'win32'
67 p = subprocess.Popen(cmd, shell=True,
68 p = subprocess.Popen(cmd, shell=True,
68 stdin=subprocess.PIPE,
69 stdin=subprocess.PIPE,
69 stdout=subprocess.PIPE,
70 stdout=subprocess.PIPE,
@@ -1,10 +1,32 b''
1 from kernelmanager import SubSocketChannel
1 """Implement a fully blocking kernel manager.
2
3 Useful for test suites and blocking terminal interfaces.
4 """
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2010 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING.txt, distributed as part of this software.
10 #-----------------------------------------------------------------------------
11
12 #-----------------------------------------------------------------------------
13 # Imports
14 #-----------------------------------------------------------------------------
15 from __future__ import print_function
16
17 # Stdlib
2 from Queue import Queue, Empty
18 from Queue import Queue, Empty
3
19
20 # Our own
21 from IPython.utils import io
22 from IPython.utils.traitlets import Type
4
23
5 class MsgNotReady(Exception):
24 from .kernelmanager import (KernelManager, SubSocketChannel,
6 pass
25 XReqSocketChannel, RepSocketChannel, HBSocketChannel)
7
26
27 #-----------------------------------------------------------------------------
28 # Functions and classes
29 #-----------------------------------------------------------------------------
8
30
9 class BlockingSubSocketChannel(SubSocketChannel):
31 class BlockingSubSocketChannel(SubSocketChannel):
10
32
@@ -13,6 +35,7 b' class BlockingSubSocketChannel(SubSocketChannel):'
13 self._in_queue = Queue()
35 self._in_queue = Queue()
14
36
15 def call_handlers(self, msg):
37 def call_handlers(self, msg):
38 io.rprint('[[Sub]]', msg) # dbg
16 self._in_queue.put(msg)
39 self._in_queue.put(msg)
17
40
18 def msg_ready(self):
41 def msg_ready(self):
@@ -24,21 +47,68 b' class BlockingSubSocketChannel(SubSocketChannel):'
24
47
25 def get_msg(self, block=True, timeout=None):
48 def get_msg(self, block=True, timeout=None):
26 """Get a message if there is one that is ready."""
49 """Get a message if there is one that is ready."""
27 try:
50 return self.in_queue.get(block, timeout)
28 msg = self.in_queue.get(block, timeout)
51
29 except Empty:
52 def get_msgs(self):
30 raise MsgNotReady('No message has been received.')
53 """Get all messages that are currently ready."""
54 msgs = []
55 while True:
56 try:
57 msgs.append(self.get_msg(block=False))
58 except Empty:
59 break
60 return msgs
61
62
63
64 class BlockingXReqSocketChannel(XReqSocketChannel):
65
66 def __init__(self, context, session, address=None):
67 super(BlockingXReqSocketChannel, self).__init__(context, session, address)
68 self._in_queue = Queue()
69
70 def call_handlers(self, msg):
71 io.rprint('[[XReq]]', msg) # dbg
72
73 def msg_ready(self):
74 """Is there a message that has been received?"""
75 if self._in_queue.qsize() == 0:
76 return False
31 else:
77 else:
32 return msg
78 return True
79
80 def get_msg(self, block=True, timeout=None):
81 """Get a message if there is one that is ready."""
82 return self.in_queue.get(block, timeout)
33
83
34 def get_msgs(self):
84 def get_msgs(self):
35 """Get all messages that are currently ready."""
85 """Get all messages that are currently ready."""
36 msgs = []
86 msgs = []
37 while True:
87 while True:
38 try:
88 try:
39 msg = self.get_msg(block=False)
89 msgs.append(self.get_msg(block=False))
40 except MsgNotReady:
90 except Empty:
41 break
91 break
42 else:
92 return msgs
43 msgs.append(msg)
93
44 return msgs No newline at end of file
94 class BlockingRepSocketChannel(RepSocketChannel):
95 def call_handlers(self, msg):
96 io.rprint('[[Rep]]', msg) # dbg
97
98
99 class BlockingHBSocketChannel(HBSocketChannel):
100 # This kernel needs rapid monitoring capabilities
101 time_to_dead = 0.2
102
103 def call_handlers(self, since_last_heartbeat):
104 io.rprint('[[Heart]]', since_last_heartbeat) # dbg
105
106
107 class BlockingKernelManager(KernelManager):
108
109 # The classes to use for the various channels.
110 xreq_channel_class = Type(BlockingXReqSocketChannel)
111 sub_channel_class = Type(BlockingSubSocketChannel)
112 rep_channel_class = Type(BlockingRepSocketChannel)
113 hb_channel_class = Type(BlockingHBSocketChannel)
114
@@ -35,6 +35,7 b' from iostream import OutStream'
35 from session import Session, Message
35 from session import Session, Message
36 from zmqshell import ZMQInteractiveShell
36 from zmqshell import ZMQInteractiveShell
37
37
38
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39 # Main kernel class
40 # Main kernel class
40 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
@@ -64,8 +65,7 b' class Kernel(Configurable):'
64
65
65 # Build dict of handlers for message types
66 # Build dict of handlers for message types
66 msg_types = [ 'execute_request', 'complete_request',
67 msg_types = [ 'execute_request', 'complete_request',
67 'object_info_request', 'prompt_request',
68 'object_info_request', 'history_request' ]
68 'history_request' ]
69 self.handlers = {}
69 self.handlers = {}
70 for msg_type in msg_types:
70 for msg_type in msg_types:
71 self.handlers[msg_type] = getattr(self, msg_type)
71 self.handlers[msg_type] = getattr(self, msg_type)
@@ -81,14 +81,21 b' class Kernel(Configurable):'
81 # FIXME: Bug in pyzmq/zmq?
81 # FIXME: Bug in pyzmq/zmq?
82 # assert self.reply_socket.rcvmore(), "Missing message part."
82 # assert self.reply_socket.rcvmore(), "Missing message part."
83 msg = self.reply_socket.recv_json()
83 msg = self.reply_socket.recv_json()
84 omsg = Message(msg)
84
85 io.raw_print('\n')
85 # Print some info about this message and leave a '--->' marker, so it's
86 io.raw_print(omsg)
86 # easier to trace visually the message chain when debugging. Each
87 handler = self.handlers.get(omsg.msg_type, None)
87 # handler prints its message at the end.
88 # Eventually we'll move these from stdout to a logger.
89 io.raw_print('\n*** MESSAGE TYPE:', msg['msg_type'], '***')
90 io.raw_print(' Content: ', msg['content'],
91 '\n --->\n ', sep='', end='')
92
93 # Find and call actual handler for message
94 handler = self.handlers.get(msg['msg_type'], None)
88 if handler is None:
95 if handler is None:
89 io.raw_print_err("UNKNOWN MESSAGE TYPE:", omsg)
96 io.raw_print_err("UNKNOWN MESSAGE TYPE:", msg)
90 else:
97 else:
91 handler(ident, omsg)
98 handler(ident, msg)
92
99
93 def start(self):
100 def start(self):
94 """ Start the kernel main loop.
101 """ Start the kernel main loop.
@@ -97,37 +104,56 b' class Kernel(Configurable):'
97 time.sleep(0.05)
104 time.sleep(0.05)
98 self.do_one_iteration()
105 self.do_one_iteration()
99
106
100
101 #---------------------------------------------------------------------------
107 #---------------------------------------------------------------------------
102 # Kernel request handlers
108 # Kernel request handlers
103 #---------------------------------------------------------------------------
109 #---------------------------------------------------------------------------
104
110
111 def _publish_pyin(self, code, parent):
112 """Publish the code request on the pyin stream."""
113
114 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
115 self.pub_socket.send_json(pyin_msg)
116
105 def execute_request(self, ident, parent):
117 def execute_request(self, ident, parent):
106 try:
118 try:
107 code = parent[u'content'][u'code']
119 content = parent[u'content']
120 code = content[u'code']
121 silent = content[u'silent']
108 except:
122 except:
109 io.raw_print_err("Got bad msg: ")
123 io.raw_print_err("Got bad msg: ")
110 io.raw_print_err(Message(parent))
124 io.raw_print_err(Message(parent))
111 return
125 return
112 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
113 self.pub_socket.send_json(pyin_msg)
114
126
127 shell = self.shell # we'll need this a lot here
128
129 # Replace raw_input. Note that is not sufficient to replace
130 # raw_input in the user namespace.
131 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
132 __builtin__.raw_input = raw_input
133
134 # Set the parent message of the display hook and out streams.
135 shell.displayhook.set_parent(parent)
136 sys.stdout.set_parent(parent)
137 sys.stderr.set_parent(parent)
138
139 # Re-broadcast our input for the benefit of listening clients, and
140 # start computing output
141 if not silent:
142 self._publish_pyin(code, parent)
143
144 reply_content = {}
115 try:
145 try:
116 # Replace raw_input. Note that is not sufficient to replace
146 if silent:
117 # raw_input in the user namespace.
147 # runcode uses 'exec' mode, so no displayhook will fire, and it
118 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
148 # doesn't call logging or history manipulations. Print
119 __builtin__.raw_input = raw_input
149 # statements in that code will obviously still execute.
120
150 shell.runcode(code)
121 # Set the parent message of the display hook and out streams.
151 else:
122 self.shell.displayhook.set_parent(parent)
152 # FIXME: runlines calls the exception handler itself.
123 sys.stdout.set_parent(parent)
153 shell._reply_content = None
124 sys.stderr.set_parent(parent)
154 shell.runlines(code)
125
126 # FIXME: runlines calls the exception handler itself. We should
127 # clean this up.
128 self.shell._reply_content = None
129 self.shell.runlines(code)
130 except:
155 except:
156 status = u'error'
131 # FIXME: this code right now isn't being used yet by default,
157 # FIXME: this code right now isn't being used yet by default,
132 # because the runlines() call above directly fires off exception
158 # because the runlines() call above directly fires off exception
133 # reporting. This code, therefore, is only active in the scenario
159 # reporting. This code, therefore, is only active in the scenario
@@ -136,35 +162,39 b' class Kernel(Configurable):'
136 # single location in the codbase.
162 # single location in the codbase.
137 etype, evalue, tb = sys.exc_info()
163 etype, evalue, tb = sys.exc_info()
138 tb_list = traceback.format_exception(etype, evalue, tb)
164 tb_list = traceback.format_exception(etype, evalue, tb)
139 reply_content = self.shell._showtraceback(etype, evalue, tb_list)
165 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
140 else:
166 else:
141 payload = self.shell.payload_manager.read_payload()
167 status = u'ok'
168 reply_content[u'payload'] = shell.payload_manager.read_payload()
142 # Be agressive about clearing the payload because we don't want
169 # Be agressive about clearing the payload because we don't want
143 # it to sit in memory until the next execute_request comes in.
170 # it to sit in memory until the next execute_request comes in.
144 self.shell.payload_manager.clear_payload()
171 shell.payload_manager.clear_payload()
145 reply_content = { 'status' : 'ok', 'payload' : payload }
172
146
173 reply_content[u'status'] = status
147 # Compute the prompt information
174 # Compute the execution counter so clients can display prompts
148 prompt_number = self.shell.displayhook.prompt_count
175 reply_content['execution_count'] = shell.displayhook.prompt_count
149 reply_content['prompt_number'] = prompt_number
176
150 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
177 # FIXME - fish exception info out of shell, possibly left there by
151 next_prompt = {'prompt_string' : prompt_string,
178 # runlines. We'll need to clean up this logic later.
152 'prompt_number' : prompt_number+1,
179 if shell._reply_content is not None:
153 'input_sep' : self.shell.displayhook.input_sep}
180 reply_content.update(shell._reply_content)
154 reply_content['next_prompt'] = next_prompt
181
155
182 # At this point, we can tell whether the main code execution succeeded
156 # TMP - fish exception info out of shell, possibly left there by
183 # or not. If it did, we proceed to evaluate user_variables/expressions
157 # runlines
184 if reply_content['status'] == 'ok':
158 if self.shell._reply_content is not None:
185 reply_content[u'user_variables'] = \
159 reply_content.update(self.shell._reply_content)
186 shell.get_user_variables(content[u'user_variables'])
160
187 reply_content[u'user_expressions'] = \
161 # Flush output before sending the reply.
188 shell.eval_expressions(content[u'user_expressions'])
162 sys.stderr.flush()
189 else:
163 sys.stdout.flush()
190 # If there was an error, don't even try to compute variables or
164
191 # expressions
192 reply_content[u'user_variables'] = {}
193 reply_content[u'user_expressions'] = {}
194
165 # Send the reply.
195 # Send the reply.
166 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
196 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
167 io.raw_print(Message(reply_msg))
197 io.raw_print(reply_msg)
168 self.reply_socket.send(ident, zmq.SNDMORE)
198 self.reply_socket.send(ident, zmq.SNDMORE)
169 self.reply_socket.send_json(reply_msg)
199 self.reply_socket.send_json(reply_msg)
170 if reply_msg['content']['status'] == u'error':
200 if reply_msg['content']['status'] == u'error':
@@ -186,16 +216,6 b' class Kernel(Configurable):'
186 object_info, parent, ident)
216 object_info, parent, ident)
187 io.raw_print(msg)
217 io.raw_print(msg)
188
218
189 def prompt_request(self, ident, parent):
190 prompt_number = self.shell.displayhook.prompt_count
191 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
192 content = {'prompt_string' : prompt_string,
193 'prompt_number' : prompt_number+1,
194 'input_sep' : self.shell.displayhook.input_sep}
195 msg = self.session.send(self.reply_socket, 'prompt_reply',
196 content, parent, ident)
197 io.raw_print(msg)
198
199 def history_request(self, ident, parent):
219 def history_request(self, ident, parent):
200 output = parent['content']['output']
220 output = parent['content']['output']
201 index = parent['content']['index']
221 index = parent['content']['index']
@@ -218,13 +238,14 b' class Kernel(Configurable):'
218 if e.errno == zmq.EAGAIN:
238 if e.errno == zmq.EAGAIN:
219 break
239 break
220 else:
240 else:
221 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
241 assert self.reply_socket.rcvmore(), \
242 "Unexpected missing message part."
222 msg = self.reply_socket.recv_json()
243 msg = self.reply_socket.recv_json()
223 io.raw_print("Aborting:\n", Message(msg))
244 io.raw_print("Aborting:\n", Message(msg))
224 msg_type = msg['msg_type']
245 msg_type = msg['msg_type']
225 reply_type = msg_type.split('_')[0] + '_reply'
246 reply_type = msg_type.split('_')[0] + '_reply'
226 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
247 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
227 io.raw_print(Message(reply_msg))
248 io.raw_print(reply_msg)
228 self.reply_socket.send(ident,zmq.SNDMORE)
249 self.reply_socket.send(ident,zmq.SNDMORE)
229 self.reply_socket.send_json(reply_msg)
250 self.reply_socket.send_json(reply_msg)
230 # We need to wait a bit for requests to come in. This can probably
251 # We need to wait a bit for requests to come in. This can probably
@@ -312,6 +333,7 b' class QtKernel(Kernel):'
312 self.timer.start(50)
333 self.timer.start(50)
313 start_event_loop_qt4(self.app)
334 start_event_loop_qt4(self.app)
314
335
336
315 class WxKernel(Kernel):
337 class WxKernel(Kernel):
316 """A Kernel subclass with Wx support."""
338 """A Kernel subclass with Wx support."""
317
339
@@ -421,6 +443,7 b' def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,'
421 xrep_port, pub_port, req_port, hb_port,
443 xrep_port, pub_port, req_port, hb_port,
422 independent, extra_arguments)
444 independent, extra_arguments)
423
445
446
424 def main():
447 def main():
425 """ The IPython kernel main entry point.
448 """ The IPython kernel main entry point.
426 """
449 """
@@ -458,5 +481,6 b" given, the GUI backend is matplotlib's, otherwise use one of: \\"
458
481
459 start_kernel(namespace, kernel)
482 start_kernel(namespace, kernel)
460
483
484
461 if __name__ == '__main__':
485 if __name__ == '__main__':
462 main()
486 main()
@@ -29,6 +29,7 b' from zmq import POLLIN, POLLOUT, POLLERR'
29 from zmq.eventloop import ioloop
29 from zmq.eventloop import ioloop
30
30
31 # Local imports.
31 # Local imports.
32 from IPython.utils import io
32 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
33 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
33 from session import Session
34 from session import Session
34
35
@@ -42,6 +43,35 b' class InvalidPortNumber(Exception):'
42 pass
43 pass
43
44
44 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Utility functions
47 #-----------------------------------------------------------------------------
48
49 # some utilities to validate message structure, these might get moved elsewhere
50 # if they prove to have more generic utility
51
52 def validate_string_list(lst):
53 """Validate that the input is a list of strings.
54
55 Raises ValueError if not."""
56 if not isinstance(lst, list):
57 raise ValueError('input %r must be a list' % lst)
58 for x in lst:
59 if not isinstance(x, basestring):
60 raise ValueError('element %r in list must be a string' % x)
61
62
63 def validate_string_dict(dct):
64 """Validate that the input is a dict with string keys and values.
65
66 Raises ValueError if not."""
67 for k,v in dct.iteritems():
68 if not isinstance(k, basestring):
69 raise ValueError('key %r in dict must be a string' % k)
70 if not isinstance(v, basestring):
71 raise ValueError('value %r in dict must be a string' % v)
72
73
74 #-----------------------------------------------------------------------------
45 # ZMQ Socket Channel classes
75 # ZMQ Socket Channel classes
46 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
47
77
@@ -163,23 +193,49 b' class XReqSocketChannel(ZmqSocketChannel):'
163 """
193 """
164 raise NotImplementedError('call_handlers must be defined in a subclass.')
194 raise NotImplementedError('call_handlers must be defined in a subclass.')
165
195
166 def execute(self, code, silent=False):
196 def execute(self, code, silent=False,
197 user_variables=None, user_expressions=None):
167 """Execute code in the kernel.
198 """Execute code in the kernel.
168
199
169 Parameters
200 Parameters
170 ----------
201 ----------
171 code : str
202 code : str
172 A string of Python code.
203 A string of Python code.
204
173 silent : bool, optional (default False)
205 silent : bool, optional (default False)
174 If set, the kernel will execute the code as quietly possible.
206 If set, the kernel will execute the code as quietly possible.
175
207
208 user_variables : list, optional
209
210 A list of variable names to pull from the user's namespace. They
211 will come back as a dict with these names as keys and their
212 :func:`repr` as values.
213
214 user_expressions : dict, optional
215 A dict with string keys and to pull from the user's
216 namespace. They will come back as a dict with these names as keys
217 and their :func:`repr` as values.
218
176 Returns
219 Returns
177 -------
220 -------
178 The msg_id of the message sent.
221 The msg_id of the message sent.
179 """
222 """
223 if user_variables is None:
224 user_variables = []
225 if user_expressions is None:
226 user_expressions = {}
227
228 # Don't waste network traffic if inputs are invalid
229 if not isinstance(code, basestring):
230 raise ValueError('code %r must be a string' % code)
231 validate_string_list(user_variables)
232 validate_string_dict(user_expressions)
233
180 # Create class for content/msg creation. Related to, but possibly
234 # Create class for content/msg creation. Related to, but possibly
181 # not in Session.
235 # not in Session.
182 content = dict(code=code, silent=silent)
236 content = dict(code=code, silent=silent,
237 user_variables=user_variables,
238 user_expressions=user_expressions)
183 msg = self.session.msg('execute_request', content)
239 msg = self.session.msg('execute_request', content)
184 self._queue_request(msg)
240 self._queue_request(msg)
185 return msg['header']['msg_id']
241 return msg['header']['msg_id']
@@ -249,17 +305,6 b' class XReqSocketChannel(ZmqSocketChannel):'
249 self._queue_request(msg)
305 self._queue_request(msg)
250 return msg['header']['msg_id']
306 return msg['header']['msg_id']
251
307
252 def prompt(self):
253 """Requests a prompt number from the kernel.
254
255 Returns
256 -------
257 The msg_id of the message sent.
258 """
259 msg = self.session.msg('prompt_request')
260 self._queue_request(msg)
261 return msg['header']['msg_id']
262
263 def _handle_events(self, socket, events):
308 def _handle_events(self, socket, events):
264 if events & POLLERR:
309 if events & POLLERR:
265 self._handle_err()
310 self._handle_err()
@@ -479,9 +524,12 b' class HBSocketChannel(ZmqSocketChannel):'
479 since_last_heartbeat = 0.0
524 since_last_heartbeat = 0.0
480 request_time = time.time()
525 request_time = time.time()
481 try:
526 try:
527 #io.rprint('Ping from HB channel') # dbg
482 self.socket.send_json('ping')
528 self.socket.send_json('ping')
483 except zmq.ZMQError, e:
529 except zmq.ZMQError, e:
530 #io.rprint('*** HB Error:', e) # dbg
484 if e.errno == zmq.EFSM:
531 if e.errno == zmq.EFSM:
532 #io.rprint('sleep...', self.time_to_dead) # dbg
485 time.sleep(self.time_to_dead)
533 time.sleep(self.time_to_dead)
486 self._create_socket()
534 self._create_socket()
487 else:
535 else:
@@ -489,13 +537,21 b' class HBSocketChannel(ZmqSocketChannel):'
489 else:
537 else:
490 while True:
538 while True:
491 try:
539 try:
492 reply = self.socket.recv_json(zmq.NOBLOCK)
540 self.socket.recv_json(zmq.NOBLOCK)
493 except zmq.ZMQError, e:
541 except zmq.ZMQError, e:
542 #io.rprint('*** HB Error 2:', e) # dbg
494 if e.errno == zmq.EAGAIN:
543 if e.errno == zmq.EAGAIN:
495 until_dead = self.time_to_dead - (time.time() -
544 before_poll = time.time()
545 until_dead = self.time_to_dead - (before_poll -
496 request_time)
546 request_time)
497 # poll timeout is in milliseconds.
547
498 poll_result = self.poller.poll(1000*until_dead)
548 # When the return value of poll() is an empty list,
549 # that is when things have gone wrong (zeromq bug).
550 # As long as it is not an empty list, poll is
551 # working correctly even if it returns quickly.
552 # Note: poll timeout is in milliseconds.
553 self.poller.poll(1000*until_dead)
554
499 since_last_heartbeat = time.time() - request_time
555 since_last_heartbeat = time.time() - request_time
500 if since_last_heartbeat > self.time_to_dead:
556 if since_last_heartbeat > self.time_to_dead:
501 self.call_handlers(since_last_heartbeat)
557 self.call_handlers(since_last_heartbeat)
@@ -507,6 +563,7 b' class HBSocketChannel(ZmqSocketChannel):'
507 until_dead = self.time_to_dead - (time.time() -
563 until_dead = self.time_to_dead - (time.time() -
508 request_time)
564 request_time)
509 if until_dead > 0.0:
565 if until_dead > 0.0:
566 #io.rprint('sleep...', self.time_to_dead) # dbg
510 time.sleep(until_dead)
567 time.sleep(until_dead)
511 break
568 break
512
569
@@ -61,10 +61,7 b' class ZMQDisplayHook(DisplayHook):'
61 def write_output_prompt(self):
61 def write_output_prompt(self):
62 """Write the output prompt."""
62 """Write the output prompt."""
63 if self.do_full_cache:
63 if self.do_full_cache:
64 self.msg['content']['output_sep'] = self.output_sep
64 self.msg['content']['execution_count'] = self.prompt_count
65 self.msg['content']['prompt_string'] = str(self.prompt_out)
66 self.msg['content']['prompt_number'] = self.prompt_count
67 self.msg['content']['output_sep2'] = self.output_sep2
68
65
69 def write_result_repr(self, result_repr):
66 def write_result_repr(self, result_repr):
70 self.msg['content']['data'] = result_repr
67 self.msg['content']['data'] = result_repr
@@ -383,7 +380,6 b' class ZMQInteractiveShell(InteractiveShell):'
383 def _showtraceback(self, etype, evalue, stb):
380 def _showtraceback(self, etype, evalue, stb):
384
381
385 exc_content = {
382 exc_content = {
386 u'status' : u'error',
387 u'traceback' : stb,
383 u'traceback' : stb,
388 u'ename' : unicode(etype.__name__),
384 u'ename' : unicode(etype.__name__),
389 u'evalue' : unicode(evalue)
385 u'evalue' : unicode(evalue)
@@ -397,7 +393,10 b' class ZMQInteractiveShell(InteractiveShell):'
397
393
398 # FIXME - Hack: store exception info in shell object. Right now, the
394 # FIXME - Hack: store exception info in shell object. Right now, the
399 # caller is reading this info after the fact, we need to fix this logic
395 # caller is reading this info after the fact, we need to fix this logic
400 # to remove this hack.
396 # to remove this hack. Even uglier, we need to store the error status
397 # here, because in the main loop, the logic that sets it is being
398 # skipped because runlines swallows the exceptions.
399 exc_content[u'status'] = u'error'
401 self._reply_content = exc_content
400 self._reply_content = exc_content
402 # /FIXME
401 # /FIXME
403
402
@@ -242,28 +242,27 b' Message type: ``execute_reply``::'
242 # prompt numbers to the user. If the request was a silent one, this will
242 # prompt numbers to the user. If the request was a silent one, this will
243 # be the current value of the counter in the kernel.
243 # be the current value of the counter in the kernel.
244 'execution_count' : int,
244 'execution_count' : int,
245
246 # If the state_template was provided, this will contain the evaluated
247 # form of the template.
248 'state' : str,
249 }
245 }
250
246
251 When status is 'ok', the following extra fields are present::
247 When status is 'ok', the following extra fields are present::
252
248
253 {
249 {
254 # The kernel will often transform the input provided to it. If the
255 # '---->' transform had been applied, this is filled, otherwise it's the
256 # empty string. So transformations like magics don't appear here, only
257 # autocall ones.
258
259 'transformed_code' : str,
260
261 # The execution payload is a dict with string keys that may have been
250 # The execution payload is a dict with string keys that may have been
262 # produced by the code being executed. It is retrieved by the kernel at
251 # produced by the code being executed. It is retrieved by the kernel at
263 # the end of the execution and sent back to the front end, which can take
252 # the end of the execution and sent back to the front end, which can take
264 # action on it as needed. See main text for further details.
253 # action on it as needed. See main text for further details.
265 'payload' : dict,
254 'payload' : dict,
266 }
255
256 # Results for the user_variables and user_expressions.
257 'user_variables' : dict,
258 'user_expressions' : dict,
259
260 # The kernel will often transform the input provided to it. If the
261 # '---->' transform had been applied, this is filled, otherwise it's the
262 # empty string. So transformations like magics don't appear here, only
263 # autocall ones.
264 'transformed_code' : str,
265 }
267
266
268 .. admonition:: Execution payloads
267 .. admonition:: Execution payloads
269
268
General Comments 0
You need to be logged in to leave comments. Login now