##// END OF EJS Templates
Minimal updates to pure Python kernel to keep it (somewhat) API compatible with the IPython Kernel....
epatters -
Show More
@@ -1,267 +1,286 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 # Standard library imports.
17 # Standard library imports.
18 import __builtin__
18 import __builtin__
19 from code import CommandCompiler
19 from code import CommandCompiler
20 import sys
20 import sys
21 import time
21 import time
22 import traceback
22 import traceback
23
23
24 # System library imports.
24 # System library imports.
25 import zmq
25 import zmq
26
26
27 # Local imports.
27 # Local imports.
28 from IPython.utils.traitlets import HasTraits, Instance
28 from IPython.utils.traitlets import HasTraits, Instance
29 from completer import KernelCompleter
29 from completer import KernelCompleter
30 from entry_point import base_launch_kernel, make_default_main
30 from entry_point import base_launch_kernel, make_default_main
31 from session import Session, Message
31 from session import Session, Message
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Main kernel class
34 # Main kernel class
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class Kernel(HasTraits):
37 class Kernel(HasTraits):
38
38
39 # Private interface
40
41 # This is a dict of port number that the kernel is listening on. It is set
42 # by record_ports and used by connect_request.
43 _recorded_ports = None
44
39 #---------------------------------------------------------------------------
45 #---------------------------------------------------------------------------
40 # Kernel interface
46 # Kernel interface
41 #---------------------------------------------------------------------------
47 #---------------------------------------------------------------------------
42
48
43 session = Instance(Session)
49 session = Instance(Session)
44 reply_socket = Instance('zmq.Socket')
50 reply_socket = Instance('zmq.Socket')
45 pub_socket = Instance('zmq.Socket')
51 pub_socket = Instance('zmq.Socket')
46 req_socket = Instance('zmq.Socket')
52 req_socket = Instance('zmq.Socket')
47
53
48 def __init__(self, **kwargs):
54 def __init__(self, **kwargs):
49 super(Kernel, self).__init__(**kwargs)
55 super(Kernel, self).__init__(**kwargs)
50 self.user_ns = {}
56 self.user_ns = {}
51 self.history = []
57 self.history = []
52 self.compiler = CommandCompiler()
58 self.compiler = CommandCompiler()
53 self.completer = KernelCompleter(self.user_ns)
59 self.completer = KernelCompleter(self.user_ns)
54
60
55 # Build dict of handlers for message types
61 # Build dict of handlers for message types
56 msg_types = [ 'execute_request', 'complete_request',
62 msg_types = [ 'execute_request', 'complete_request',
57 'object_info_request' ]
63 'object_info_request' ]
58 self.handlers = {}
64 self.handlers = {}
59 for msg_type in msg_types:
65 for msg_type in msg_types:
60 self.handlers[msg_type] = getattr(self, msg_type)
66 self.handlers[msg_type] = getattr(self, msg_type)
61
67
62 def start(self):
68 def start(self):
63 """ Start the kernel main loop.
69 """ Start the kernel main loop.
64 """
70 """
65 while True:
71 while True:
66 ident = self.reply_socket.recv()
72 ident = self.reply_socket.recv()
67 assert self.reply_socket.rcvmore(), "Missing message part."
73 assert self.reply_socket.rcvmore(), "Missing message part."
68 msg = self.reply_socket.recv_json()
74 msg = self.reply_socket.recv_json()
69 omsg = Message(msg)
75 omsg = Message(msg)
70 print>>sys.__stdout__
76 print>>sys.__stdout__
71 print>>sys.__stdout__, omsg
77 print>>sys.__stdout__, omsg
72 handler = self.handlers.get(omsg.msg_type, None)
78 handler = self.handlers.get(omsg.msg_type, None)
73 if handler is None:
79 if handler is None:
74 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
80 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
75 else:
81 else:
76 handler(ident, omsg)
82 handler(ident, omsg)
77
83
84 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
85 """Record the ports that this kernel is using.
86
87 The creator of the Kernel instance must call this methods if they
88 want the :meth:`connect_request` method to return the port numbers.
89 """
90 self._recorded_ports = {
91 'xrep_port' : xrep_port,
92 'pub_port' : pub_port,
93 'req_port' : req_port,
94 'hb_port' : hb_port
95 }
96
78 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
79 # Kernel request handlers
98 # Kernel request handlers
80 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
81
100
82 def execute_request(self, ident, parent):
101 def execute_request(self, ident, parent):
83 try:
102 try:
84 code = parent[u'content'][u'code']
103 code = parent[u'content'][u'code']
85 except:
104 except:
86 print>>sys.__stderr__, "Got bad msg: "
105 print>>sys.__stderr__, "Got bad msg: "
87 print>>sys.__stderr__, Message(parent)
106 print>>sys.__stderr__, Message(parent)
88 return
107 return
89 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
108 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
90 self.pub_socket.send_json(pyin_msg)
109 self.pub_socket.send_json(pyin_msg)
91
110
92 try:
111 try:
93 comp_code = self.compiler(code, '<zmq-kernel>')
112 comp_code = self.compiler(code, '<zmq-kernel>')
94
113
95 # Replace raw_input. Note that is not sufficient to replace
114 # Replace raw_input. Note that is not sufficient to replace
96 # raw_input in the user namespace.
115 # raw_input in the user namespace.
97 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
116 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
98 __builtin__.raw_input = raw_input
117 __builtin__.raw_input = raw_input
99
118
100 # Set the parent message of the display hook and out streams.
119 # Set the parent message of the display hook and out streams.
101 sys.displayhook.set_parent(parent)
120 sys.displayhook.set_parent(parent)
102 sys.stdout.set_parent(parent)
121 sys.stdout.set_parent(parent)
103 sys.stderr.set_parent(parent)
122 sys.stderr.set_parent(parent)
104
123
105 exec comp_code in self.user_ns, self.user_ns
124 exec comp_code in self.user_ns, self.user_ns
106 except:
125 except:
107 etype, evalue, tb = sys.exc_info()
126 etype, evalue, tb = sys.exc_info()
108 tb = traceback.format_exception(etype, evalue, tb)
127 tb = traceback.format_exception(etype, evalue, tb)
109 exc_content = {
128 exc_content = {
110 u'status' : u'error',
129 u'status' : u'error',
111 u'traceback' : tb,
130 u'traceback' : tb,
112 u'ename' : unicode(etype.__name__),
131 u'ename' : unicode(etype.__name__),
113 u'evalue' : unicode(evalue)
132 u'evalue' : unicode(evalue)
114 }
133 }
115 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
134 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
116 self.pub_socket.send_json(exc_msg)
135 self.pub_socket.send_json(exc_msg)
117 reply_content = exc_content
136 reply_content = exc_content
118 else:
137 else:
119 reply_content = { 'status' : 'ok', 'payload' : {} }
138 reply_content = { 'status' : 'ok', 'payload' : {} }
120
139
121 # Flush output before sending the reply.
140 # Flush output before sending the reply.
122 sys.stderr.flush()
141 sys.stderr.flush()
123 sys.stdout.flush()
142 sys.stdout.flush()
124
143
125 # Send the reply.
144 # Send the reply.
126 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
145 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
127 print>>sys.__stdout__, Message(reply_msg)
146 print>>sys.__stdout__, Message(reply_msg)
128 self.reply_socket.send(ident, zmq.SNDMORE)
147 self.reply_socket.send(ident, zmq.SNDMORE)
129 self.reply_socket.send_json(reply_msg)
148 self.reply_socket.send_json(reply_msg)
130 if reply_msg['content']['status'] == u'error':
149 if reply_msg['content']['status'] == u'error':
131 self._abort_queue()
150 self._abort_queue()
132
151
133 def complete_request(self, ident, parent):
152 def complete_request(self, ident, parent):
134 matches = {'matches' : self._complete(parent),
153 matches = {'matches' : self._complete(parent),
135 'status' : 'ok'}
154 'status' : 'ok'}
136 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
155 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
137 matches, parent, ident)
156 matches, parent, ident)
138 print >> sys.__stdout__, completion_msg
157 print >> sys.__stdout__, completion_msg
139
158
140 def object_info_request(self, ident, parent):
159 def object_info_request(self, ident, parent):
141 context = parent['content']['oname'].split('.')
160 context = parent['content']['oname'].split('.')
142 object_info = self._object_info(context)
161 object_info = self._object_info(context)
143 msg = self.session.send(self.reply_socket, 'object_info_reply',
162 msg = self.session.send(self.reply_socket, 'object_info_reply',
144 object_info, parent, ident)
163 object_info, parent, ident)
145 print >> sys.__stdout__, msg
164 print >> sys.__stdout__, msg
146
165
147 #---------------------------------------------------------------------------
166 #---------------------------------------------------------------------------
148 # Protected interface
167 # Protected interface
149 #---------------------------------------------------------------------------
168 #---------------------------------------------------------------------------
150
169
151 def _abort_queue(self):
170 def _abort_queue(self):
152 while True:
171 while True:
153 try:
172 try:
154 ident = self.reply_socket.recv(zmq.NOBLOCK)
173 ident = self.reply_socket.recv(zmq.NOBLOCK)
155 except zmq.ZMQError, e:
174 except zmq.ZMQError, e:
156 if e.errno == zmq.EAGAIN:
175 if e.errno == zmq.EAGAIN:
157 break
176 break
158 else:
177 else:
159 assert self.reply_socket.rcvmore(), "Missing message part."
178 assert self.reply_socket.rcvmore(), "Missing message part."
160 msg = self.reply_socket.recv_json()
179 msg = self.reply_socket.recv_json()
161 print>>sys.__stdout__, "Aborting:"
180 print>>sys.__stdout__, "Aborting:"
162 print>>sys.__stdout__, Message(msg)
181 print>>sys.__stdout__, Message(msg)
163 msg_type = msg['msg_type']
182 msg_type = msg['msg_type']
164 reply_type = msg_type.split('_')[0] + '_reply'
183 reply_type = msg_type.split('_')[0] + '_reply'
165 reply_msg = self.session.msg(reply_type, {'status':'aborted'}, msg)
184 reply_msg = self.session.msg(reply_type, {'status':'aborted'}, msg)
166 print>>sys.__stdout__, Message(reply_msg)
185 print>>sys.__stdout__, Message(reply_msg)
167 self.reply_socket.send(ident,zmq.SNDMORE)
186 self.reply_socket.send(ident,zmq.SNDMORE)
168 self.reply_socket.send_json(reply_msg)
187 self.reply_socket.send_json(reply_msg)
169 # We need to wait a bit for requests to come in. This can probably
188 # We need to wait a bit for requests to come in. This can probably
170 # be set shorter for true asynchronous clients.
189 # be set shorter for true asynchronous clients.
171 time.sleep(0.1)
190 time.sleep(0.1)
172
191
173 def _raw_input(self, prompt, ident, parent):
192 def _raw_input(self, prompt, ident, parent):
174 # Flush output before making the request.
193 # Flush output before making the request.
175 sys.stderr.flush()
194 sys.stderr.flush()
176 sys.stdout.flush()
195 sys.stdout.flush()
177
196
178 # Send the input request.
197 # Send the input request.
179 content = dict(prompt=prompt)
198 content = dict(prompt=prompt)
180 msg = self.session.msg(u'input_request', content, parent)
199 msg = self.session.msg(u'input_request', content, parent)
181 self.req_socket.send_json(msg)
200 self.req_socket.send_json(msg)
182
201
183 # Await a response.
202 # Await a response.
184 reply = self.req_socket.recv_json()
203 reply = self.req_socket.recv_json()
185 try:
204 try:
186 value = reply['content']['value']
205 value = reply['content']['value']
187 except:
206 except:
188 print>>sys.__stderr__, "Got bad raw_input reply: "
207 print>>sys.__stderr__, "Got bad raw_input reply: "
189 print>>sys.__stderr__, Message(parent)
208 print>>sys.__stderr__, Message(parent)
190 value = ''
209 value = ''
191 return value
210 return value
192
211
193 def _complete(self, msg):
212 def _complete(self, msg):
194 return self.completer.complete(msg.content.line, msg.content.text)
213 return self.completer.complete(msg.content.line, msg.content.text)
195
214
196 def _object_info(self, context):
215 def _object_info(self, context):
197 symbol, leftover = self._symbol_from_context(context)
216 symbol, leftover = self._symbol_from_context(context)
198 if symbol is not None and not leftover:
217 if symbol is not None and not leftover:
199 doc = getattr(symbol, '__doc__', '')
218 doc = getattr(symbol, '__doc__', '')
200 else:
219 else:
201 doc = ''
220 doc = ''
202 object_info = dict(docstring = doc)
221 object_info = dict(docstring = doc)
203 return object_info
222 return object_info
204
223
205 def _symbol_from_context(self, context):
224 def _symbol_from_context(self, context):
206 if not context:
225 if not context:
207 return None, context
226 return None, context
208
227
209 base_symbol_string = context[0]
228 base_symbol_string = context[0]
210 symbol = self.user_ns.get(base_symbol_string, None)
229 symbol = self.user_ns.get(base_symbol_string, None)
211 if symbol is None:
230 if symbol is None:
212 symbol = __builtin__.__dict__.get(base_symbol_string, None)
231 symbol = __builtin__.__dict__.get(base_symbol_string, None)
213 if symbol is None:
232 if symbol is None:
214 return None, context
233 return None, context
215
234
216 context = context[1:]
235 context = context[1:]
217 for i, name in enumerate(context):
236 for i, name in enumerate(context):
218 new_symbol = getattr(symbol, name, None)
237 new_symbol = getattr(symbol, name, None)
219 if new_symbol is None:
238 if new_symbol is None:
220 return symbol, context[i:]
239 return symbol, context[i:]
221 else:
240 else:
222 symbol = new_symbol
241 symbol = new_symbol
223
242
224 return symbol, []
243 return symbol, []
225
244
226 #-----------------------------------------------------------------------------
245 #-----------------------------------------------------------------------------
227 # Kernel main and launch functions
246 # Kernel main and launch functions
228 #-----------------------------------------------------------------------------
247 #-----------------------------------------------------------------------------
229
248
230 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
249 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
231 independent=False):
250 independent=False):
232 """ Launches a localhost kernel, binding to the specified ports.
251 """ Launches a localhost kernel, binding to the specified ports.
233
252
234 Parameters
253 Parameters
235 ----------
254 ----------
236 xrep_port : int, optional
255 xrep_port : int, optional
237 The port to use for XREP channel.
256 The port to use for XREP channel.
238
257
239 pub_port : int, optional
258 pub_port : int, optional
240 The port to use for the SUB channel.
259 The port to use for the SUB channel.
241
260
242 req_port : int, optional
261 req_port : int, optional
243 The port to use for the REQ (raw input) channel.
262 The port to use for the REQ (raw input) channel.
244
263
245 hb_port : int, optional
264 hb_port : int, optional
246 The port to use for the hearbeat REP channel.
265 The port to use for the hearbeat REP channel.
247
266
248 independent : bool, optional (default False)
267 independent : bool, optional (default False)
249 If set, the kernel process is guaranteed to survive if this process
268 If set, the kernel process is guaranteed to survive if this process
250 dies. If not set, an effort is made to ensure that the kernel is killed
269 dies. If not set, an effort is made to ensure that the kernel is killed
251 when this process dies. Note that in this case it is still good practice
270 when this process dies. Note that in this case it is still good practice
252 to kill kernels manually before exiting.
271 to kill kernels manually before exiting.
253
272
254 Returns
273 Returns
255 -------
274 -------
256 A tuple of form:
275 A tuple of form:
257 (kernel_process, xrep_port, pub_port, req_port)
276 (kernel_process, xrep_port, pub_port, req_port)
258 where kernel_process is a Popen object and the ports are integers.
277 where kernel_process is a Popen object and the ports are integers.
259 """
278 """
260 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
279 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
261 xrep_port, pub_port, req_port, hb_port,
280 xrep_port, pub_port, req_port, hb_port,
262 independent)
281 independent)
263
282
264 main = make_default_main(Kernel)
283 main = make_default_main(Kernel)
265
284
266 if __name__ == '__main__':
285 if __name__ == '__main__':
267 main()
286 main()
General Comments 0
You need to be logged in to leave comments. Login now