##// END OF EJS Templates
Merge pull request #5752 from minrk/msgspec-adapter...
Min RK -
r16699:aeae57d5 merge
parent child Browse files
Show More
@@ -0,0 +1,336 b''
1 """Adapters for IPython msg spec versions."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 import json
7
8 from IPython.core.release import kernel_protocol_version_info
9 from IPython.utils.tokenutil import token_at_cursor
10
11
12 def code_to_line(code, cursor_pos):
13 """Turn a multiline code block and cursor position into a single line
14 and new cursor position.
15
16 For adapting complete_ and object_info_requests.
17 """
18 for line in code.splitlines(True):
19 n = len(line)
20 if cursor_pos > n:
21 cursor_pos -= n
22 else:
23 break
24 return line, cursor_pos
25
26
27 class Adapter(object):
28 """Base class for adapting messages
29
30 Override message_type(msg) methods to create adapters.
31 """
32
33 msg_type_map = {}
34
35 def update_header(self, msg):
36 return msg
37
38 def update_metadata(self, msg):
39 return msg
40
41 def update_msg_type(self, msg):
42 header = msg['header']
43 msg_type = header['msg_type']
44 if msg_type in self.msg_type_map:
45 msg['msg_type'] = header['msg_type'] = self.msg_type_map[msg_type]
46 return msg
47
48 def handle_reply_status_error(msg):
49 """This will be called *instead of* the regular handler
50
51 on any reply with status != ok
52 """
53 return msg
54
55 def __call__(self, msg):
56 msg = self.update_header(msg)
57 msg = self.update_metadata(msg)
58 msg = self.update_msg_type(msg)
59 header = msg['header']
60
61 handler = getattr(self, header['msg_type'], None)
62 if handler is None:
63 return msg
64
65 # handle status=error replies separately (no change, at present)
66 if msg['content'].get('status', None) in {'error', 'aborted'}:
67 return self.handle_reply_status_error(msg)
68 return handler(msg)
69
70 def _version_str_to_list(version):
71 """convert a version string to a list of ints
72
73 non-int segments are excluded
74 """
75 v = []
76 for part in version.split('.'):
77 try:
78 v.append(int(part))
79 except ValueError:
80 pass
81 return v
82
83 class V5toV4(Adapter):
84 """Adapt msg protocol v5 to v4"""
85
86 version = '4.1'
87
88 msg_type_map = {
89 'execute_result' : 'pyout',
90 'execute_input' : 'pyin',
91 'error' : 'pyerr',
92 'inspect_request' : 'object_info_request',
93 'inspect_reply' : 'object_info_reply',
94 }
95
96 def update_header(self, msg):
97 msg['header'].pop('version', None)
98 return msg
99
100 # shell channel
101
102 def kernel_info_reply(self, msg):
103 content = msg['content']
104 content.pop('banner', None)
105 for key in ('language_version', 'protocol_version'):
106 if key in content:
107 content[key] = _version_str_to_list(content[key])
108 if content.pop('implementation', '') == 'ipython' \
109 and 'implementation_version' in content:
110 content['ipython_version'] = content.pop('implmentation_version')
111 content.pop('implementation_version', None)
112 content.setdefault("implmentation", content['language'])
113 return msg
114
115 def execute_request(self, msg):
116 content = msg['content']
117 content.setdefault('user_variables', [])
118 return msg
119
120 def execute_reply(self, msg):
121 content = msg['content']
122 content.setdefault('user_variables', {})
123 # TODO: handle payloads
124 return msg
125
126 def complete_request(self, msg):
127 content = msg['content']
128 code = content['code']
129 cursor_pos = content['cursor_pos']
130 line, cursor_pos = code_to_line(code, cursor_pos)
131
132 new_content = msg['content'] = {}
133 new_content['text'] = ''
134 new_content['line'] = line
135 new_content['block'] = None
136 new_content['cursor_pos'] = cursor_pos
137 return msg
138
139 def complete_reply(self, msg):
140 content = msg['content']
141 cursor_start = content.pop('cursor_start')
142 cursor_end = content.pop('cursor_end')
143 match_len = cursor_end - cursor_start
144 content['matched_text'] = content['matches'][0][:match_len]
145 content.pop('metadata', None)
146 return msg
147
148 def object_info_request(self, msg):
149 content = msg['content']
150 code = content['code']
151 cursor_pos = content['cursor_pos']
152 line, _ = code_to_line(code, cursor_pos)
153
154 new_content = msg['content'] = {}
155 new_content['oname'] = token_at_cursor(code, cursor_pos)
156 new_content['detail_level'] = content['detail_level']
157 return msg
158
159 def object_info_reply(self, msg):
160 """inspect_reply can't be easily backward compatible"""
161 msg['content'] = {'found' : False, 'name' : 'unknown'}
162 return msg
163
164 # iopub channel
165
166 def display_data(self, msg):
167 content = msg['content']
168 content.setdefault("source", "display")
169 data = content['data']
170 if 'application/json' in data:
171 try:
172 data['application/json'] = json.dumps(data['application/json'])
173 except Exception:
174 # warn?
175 pass
176 return msg
177
178 # stdin channel
179
180 def input_request(self, msg):
181 msg['content'].pop('password', None)
182 return msg
183
184
185 class V4toV5(Adapter):
186 """Convert msg spec V4 to V5"""
187 version = '5.0'
188
189 # invert message renames above
190 msg_type_map = {v:k for k,v in V5toV4.msg_type_map.items()}
191
192 def update_header(self, msg):
193 msg['header']['version'] = self.version
194 return msg
195
196 # shell channel
197
198 def kernel_info_reply(self, msg):
199 content = msg['content']
200 for key in ('language_version', 'protocol_version', 'ipython_version'):
201 if key in content:
202 content[key] = ".".join(map(str, content[key]))
203
204 if content['language'].startswith('python') and 'ipython_version' in content:
205 content['implementation'] = 'ipython'
206 content['implementation_version'] = content.pop('ipython_version')
207
208 content['banner'] = ''
209 return msg
210
211 def execute_request(self, msg):
212 content = msg['content']
213 user_variables = content.pop('user_variables', [])
214 user_expressions = content.setdefault('user_expressions', {})
215 for v in user_variables:
216 user_expressions[v] = v
217 return msg
218
219 def execute_reply(self, msg):
220 content = msg['content']
221 user_expressions = content.setdefault('user_expressions', {})
222 user_variables = content.pop('user_variables', {})
223 if user_variables:
224 user_expressions.update(user_variables)
225 return msg
226
227 def complete_request(self, msg):
228 old_content = msg['content']
229
230 new_content = msg['content'] = {}
231 new_content['code'] = old_content['line']
232 new_content['cursor_pos'] = old_content['cursor_pos']
233 return msg
234
235 def complete_reply(self, msg):
236 # complete_reply needs more context than we have to get cursor_start and end.
237 # use special value of `-1` to indicate to frontend that it should be at
238 # the current cursor position.
239 content = msg['content']
240 new_content = msg['content'] = {'status' : 'ok'}
241 new_content['matches'] = content['matches']
242 new_content['cursor_start'] = -len(content['matched_text'])
243 new_content['cursor_end'] = None
244 new_content['metadata'] = {}
245 return msg
246
247 def inspect_request(self, msg):
248 content = msg['content']
249 name = content['oname']
250
251 new_content = msg['content'] = {}
252 new_content['code'] = name
253 new_content['cursor_pos'] = len(name)
254 new_content['detail_level'] = content['detail_level']
255 return msg
256
257 def inspect_reply(self, msg):
258 """inspect_reply can't be easily backward compatible"""
259 content = msg['content']
260 new_content = msg['content'] = {'status' : 'ok'}
261 found = new_content['found'] = content['found']
262 new_content['name'] = content['name']
263 new_content['data'] = data = {}
264 new_content['metadata'] = {}
265 if found:
266 lines = []
267 for key in ('call_def', 'init_definition', 'definition'):
268 if content.get(key, False):
269 lines.append(content[key])
270 break
271 for key in ('call_docstring', 'init_docstring', 'docstring'):
272 if content.get(key, False):
273 lines.append(content[key])
274 break
275 if not lines:
276 lines.append("<empty docstring>")
277 data['text/plain'] = '\n'.join(lines)
278 return msg
279
280 # iopub channel
281
282 def display_data(self, msg):
283 content = msg['content']
284 content.pop("source", None)
285 data = content['data']
286 if 'application/json' in data:
287 try:
288 data['application/json'] = json.loads(data['application/json'])
289 except Exception:
290 # warn?
291 pass
292 return msg
293
294 # stdin channel
295
296 def input_request(self, msg):
297 msg['content'].setdefault('password', False)
298 return msg
299
300
301
302 def adapt(msg, to_version=kernel_protocol_version_info[0]):
303 """Adapt a single message to a target version
304
305 Parameters
306 ----------
307
308 msg : dict
309 An IPython message.
310 to_version : int, optional
311 The target major version.
312 If unspecified, adapt to the current version for IPython.
313
314 Returns
315 -------
316
317 msg : dict
318 An IPython message appropriate in the new version.
319 """
320 header = msg['header']
321 if 'version' in header:
322 from_version = int(header['version'].split('.')[0])
323 else:
324 # assume last version before adding the key to the header
325 from_version = 4
326 adapter = adapters.get((from_version, to_version), None)
327 if adapter is None:
328 return msg
329 return adapter(msg)
330
331
332 # one adapter per major version from,to
333 adapters = {
334 (5,4) : V5toV4(),
335 (4,5) : V4toV5(),
336 } No newline at end of file
@@ -0,0 +1,314 b''
1 """Tests for adapting IPython msg spec versions"""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 import copy
7 import json
8 from unittest import TestCase
9 import nose.tools as nt
10
11 from IPython.kernel.adapter import adapt, V4toV5, V5toV4
12 from IPython.kernel.zmq.session import Session
13
14
15 def test_default_version():
16 s = Session()
17 msg = s.msg("msg_type")
18 msg['header'].pop('version')
19 original = copy.deepcopy(msg)
20 adapted = adapt(original)
21 nt.assert_equal(adapted['header']['version'], V4toV5.version)
22
23
24 class AdapterTest(TestCase):
25
26 def setUp(self):
27 self.session = Session()
28
29 def adapt(self, msg, version=None):
30 original = copy.deepcopy(msg)
31 adapted = adapt(msg, version or self.to_version)
32 return original, adapted
33
34 def check_header(self, msg):
35 pass
36
37
38 class V4toV5TestCase(AdapterTest):
39 from_version = 4
40 to_version = 5
41
42 def msg(self, msg_type, content):
43 """Create a v4 msg (same as v5, minus version header)"""
44 msg = self.session.msg(msg_type, content)
45 msg['header'].pop('version')
46 return msg
47
48 def test_same_version(self):
49 msg = self.msg("execute_result",
50 content={'status' : 'ok'}
51 )
52 original, adapted = self.adapt(msg, self.from_version)
53
54 self.assertEqual(original, adapted)
55
56 def test_no_adapt(self):
57 msg = self.msg("input_reply", {'value' : 'some text'})
58 v4, v5 = self.adapt(msg)
59 self.assertEqual(v5['header']['version'], V4toV5.version)
60 v5['header'].pop('version')
61 self.assertEqual(v4, v5)
62
63 def test_rename_type(self):
64 for v5_type, v4_type in [
65 ('execute_result', 'pyout'),
66 ('execute_input', 'pyin'),
67 ('error', 'pyerr'),
68 ]:
69 msg = self.msg(v4_type, {'key' : 'value'})
70 v4, v5 = self.adapt(msg)
71 self.assertEqual(v5['header']['version'], V4toV5.version)
72 self.assertEqual(v5['header']['msg_type'], v5_type)
73 self.assertEqual(v4['content'], v5['content'])
74
75 def test_execute_request(self):
76 msg = self.msg("execute_request", {
77 'code' : 'a=5',
78 'silent' : False,
79 'user_expressions' : {'a' : 'apple'},
80 'user_variables' : ['b'],
81 })
82 v4, v5 = self.adapt(msg)
83 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
84 v4c = v4['content']
85 v5c = v5['content']
86 self.assertEqual(v5c['user_expressions'], {'a' : 'apple', 'b': 'b'})
87 self.assertNotIn('user_variables', v5c)
88 self.assertEqual(v5c['code'], v4c['code'])
89
90 def test_complete_request(self):
91 msg = self.msg("complete_request", {
92 'text' : 'a.is',
93 'line' : 'foo = a.is',
94 'block' : None,
95 'cursor_pos' : 10,
96 })
97 v4, v5 = self.adapt(msg)
98 v4c = v4['content']
99 v5c = v5['content']
100 for key in ('text', 'line', 'block'):
101 self.assertNotIn(key, v5c)
102 self.assertEqual(v5c['cursor_pos'], v4c['cursor_pos'])
103 self.assertEqual(v5c['code'], v4c['line'])
104
105 def test_complete_reply(self):
106 msg = self.msg("complete_reply", {
107 'matched_text' : 'a.is',
108 'matches' : ['a.isalnum',
109 'a.isalpha',
110 'a.isdigit',
111 'a.islower',
112 ],
113 })
114 v4, v5 = self.adapt(msg)
115 v4c = v4['content']
116 v5c = v5['content']
117
118 self.assertEqual(v5c['matches'], v4c['matches'])
119 self.assertEqual(v5c['metadata'], {})
120 self.assertEqual(v5c['cursor_start'], -4)
121 self.assertEqual(v5c['cursor_end'], None)
122
123 def test_object_info_request(self):
124 msg = self.msg("object_info_request", {
125 'oname' : 'foo',
126 'detail_level' : 1,
127 })
128 v4, v5 = self.adapt(msg)
129 self.assertEqual(v5['header']['msg_type'], 'inspect_request')
130 v4c = v4['content']
131 v5c = v5['content']
132 self.assertEqual(v5c['code'], v4c['oname'])
133 self.assertEqual(v5c['cursor_pos'], len(v4c['oname']))
134 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
135
136 def test_object_info_reply(self):
137 msg = self.msg("object_info_reply", {
138 'name' : 'foo',
139 'found' : True,
140 'status' : 'ok',
141 'definition' : 'foo(a=5)',
142 'docstring' : "the docstring",
143 })
144 v4, v5 = self.adapt(msg)
145 self.assertEqual(v5['header']['msg_type'], 'inspect_reply')
146 v4c = v4['content']
147 v5c = v5['content']
148 self.assertEqual(sorted(v5c), [ 'data', 'found', 'metadata', 'name', 'status'])
149 text = v5c['data']['text/plain']
150 self.assertEqual(text, '\n'.join([v4c['definition'], v4c['docstring']]))
151
152 # iopub channel
153
154 def test_display_data(self):
155 jsondata = dict(a=5)
156 msg = self.msg("display_data", {
157 'data' : {
158 'text/plain' : 'some text',
159 'application/json' : json.dumps(jsondata)
160 },
161 'metadata' : {'text/plain' : { 'key' : 'value' }},
162 })
163 v4, v5 = self.adapt(msg)
164 v4c = v4['content']
165 v5c = v5['content']
166 self.assertEqual(v5c['metadata'], v4c['metadata'])
167 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
168 self.assertEqual(v5c['data']['application/json'], jsondata)
169
170 # stdin channel
171
172 def test_input_request(self):
173 msg = self.msg('input_request', {'prompt': "$>"})
174 v4, v5 = self.adapt(msg)
175 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
176 self.assertFalse(v5['content']['password'])
177
178
179 class V5toV4TestCase(AdapterTest):
180 from_version = 5
181 to_version = 4
182
183 def msg(self, msg_type, content):
184 return self.session.msg(msg_type, content)
185
186 def test_same_version(self):
187 msg = self.msg("execute_result",
188 content={'status' : 'ok'}
189 )
190 original, adapted = self.adapt(msg, self.from_version)
191
192 self.assertEqual(original, adapted)
193
194 def test_no_adapt(self):
195 msg = self.msg("input_reply", {'value' : 'some text'})
196 v5, v4 = self.adapt(msg)
197 self.assertNotIn('version', v4['header'])
198 v5['header'].pop('version')
199 self.assertEqual(v4, v5)
200
201 def test_rename_type(self):
202 for v5_type, v4_type in [
203 ('execute_result', 'pyout'),
204 ('execute_input', 'pyin'),
205 ('error', 'pyerr'),
206 ]:
207 msg = self.msg(v5_type, {'key' : 'value'})
208 v5, v4 = self.adapt(msg)
209 self.assertEqual(v4['header']['msg_type'], v4_type)
210 nt.assert_not_in('version', v4['header'])
211 self.assertEqual(v4['content'], v5['content'])
212
213 def test_execute_request(self):
214 msg = self.msg("execute_request", {
215 'code' : 'a=5',
216 'silent' : False,
217 'user_expressions' : {'a' : 'apple'},
218 })
219 v5, v4 = self.adapt(msg)
220 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
221 v4c = v4['content']
222 v5c = v5['content']
223 self.assertEqual(v4c['user_variables'], [])
224 self.assertEqual(v5c['code'], v4c['code'])
225
226 def test_complete_request(self):
227 msg = self.msg("complete_request", {
228 'code' : 'def foo():\n'
229 ' a.is\n'
230 'foo()',
231 'cursor_pos': 19,
232 })
233 v5, v4 = self.adapt(msg)
234 v4c = v4['content']
235 v5c = v5['content']
236 self.assertNotIn('code', v4c)
237 self.assertEqual(v4c['line'], v5c['code'].splitlines(True)[1])
238 self.assertEqual(v4c['cursor_pos'], 8)
239 self.assertEqual(v4c['text'], '')
240 self.assertEqual(v4c['block'], None)
241
242 def test_complete_reply(self):
243 msg = self.msg("complete_reply", {
244 'cursor_start' : 10,
245 'cursor_end' : 14,
246 'matches' : ['a.isalnum',
247 'a.isalpha',
248 'a.isdigit',
249 'a.islower',
250 ],
251 'metadata' : {},
252 })
253 v5, v4 = self.adapt(msg)
254 v4c = v4['content']
255 v5c = v5['content']
256 self.assertEqual(v4c['matched_text'], 'a.is')
257 self.assertEqual(v4c['matches'], v5c['matches'])
258
259 def test_inspect_request(self):
260 msg = self.msg("inspect_request", {
261 'code' : 'def foo():\n'
262 ' apple\n'
263 'bar()',
264 'cursor_pos': 18,
265 'detail_level' : 1,
266 })
267 v5, v4 = self.adapt(msg)
268 self.assertEqual(v4['header']['msg_type'], 'object_info_request')
269 v4c = v4['content']
270 v5c = v5['content']
271 self.assertEqual(v4c['oname'], 'apple')
272 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
273
274 def test_inspect_reply(self):
275 msg = self.msg("inspect_reply", {
276 'name' : 'foo',
277 'found' : True,
278 'data' : {'text/plain' : 'some text'},
279 'metadata' : {},
280 })
281 v5, v4 = self.adapt(msg)
282 self.assertEqual(v4['header']['msg_type'], 'object_info_reply')
283 v4c = v4['content']
284 v5c = v5['content']
285 self.assertEqual(sorted(v4c), ['found', 'name'])
286 self.assertEqual(v4c['found'], False)
287
288 # iopub channel
289
290 def test_display_data(self):
291 jsondata = dict(a=5)
292 msg = self.msg("display_data", {
293 'data' : {
294 'text/plain' : 'some text',
295 'application/json' : jsondata,
296 },
297 'metadata' : {'text/plain' : { 'key' : 'value' }},
298 })
299 v5, v4 = self.adapt(msg)
300 v4c = v4['content']
301 v5c = v5['content']
302 self.assertEqual(v5c['metadata'], v4c['metadata'])
303 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
304 self.assertEqual(v4c['data']['application/json'], json.dumps(jsondata))
305
306 # stdin channel
307
308 def test_input_request(self):
309 msg = self.msg('input_request', {'prompt': "$>", 'password' : True})
310 v5, v4 = self.adapt(msg)
311 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
312 self.assertNotIn('password', v4['content'])
313
314
@@ -1,20 +1,7 b''
1 """Tornado handlers for WebSocket <-> ZMQ sockets.
1 """Tornado handlers for WebSocket <-> ZMQ sockets."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 try:
6 try:
20 from urllib.parse import urlparse # Py 3
7 from urllib.parse import urlparse # Py 3
@@ -37,9 +24,6 b' from IPython.utils.py3compat import PY3, cast_unicode'
37
24
38 from .handlers import IPythonHandler
25 from .handlers import IPythonHandler
39
26
40 #-----------------------------------------------------------------------------
41 # ZMQ handlers
42 #-----------------------------------------------------------------------------
43
27
44 class ZMQStreamHandler(websocket.WebSocketHandler):
28 class ZMQStreamHandler(websocket.WebSocketHandler):
45
29
@@ -1,20 +1,7 b''
1 """Tornado handlers for the notebook.
1 """Tornado handlers for kernels."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 import logging
6 import logging
20 from tornado import web
7 from tornado import web
@@ -22,15 +9,13 b' from tornado import web'
22 from zmq.utils import jsonapi
9 from zmq.utils import jsonapi
23
10
24 from IPython.utils.jsonutil import date_default
11 from IPython.utils.jsonutil import date_default
12 from IPython.utils.py3compat import string_types
25 from IPython.html.utils import url_path_join, url_escape
13 from IPython.html.utils import url_path_join, url_escape
26
14
27 from ...base.handlers import IPythonHandler, json_errors
15 from ...base.handlers import IPythonHandler, json_errors
28 from ...base.zmqhandlers import AuthenticatedZMQStreamHandler
16 from ...base.zmqhandlers import AuthenticatedZMQStreamHandler
29
17
30 #-----------------------------------------------------------------------------
18 from IPython.core.release import kernel_protocol_version
31 # Kernel handlers
32 #-----------------------------------------------------------------------------
33
34
19
35 class MainKernelHandler(IPythonHandler):
20 class MainKernelHandler(IPythonHandler):
36
21
@@ -96,6 +81,41 b' class ZMQChannelHandler(AuthenticatedZMQStreamHandler):'
96 km = self.kernel_manager
81 km = self.kernel_manager
97 meth = getattr(km, 'connect_%s' % self.channel)
82 meth = getattr(km, 'connect_%s' % self.channel)
98 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
83 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
84 # Create a kernel_info channel to query the kernel protocol version.
85 # This channel will be closed after the kernel_info reply is received.
86 self.kernel_info_channel = None
87 self.kernel_info_channel = km.connect_shell(self.kernel_id)
88 self.kernel_info_channel.on_recv(self._handle_kernel_info_reply)
89 self._request_kernel_info()
90
91 def _request_kernel_info(self):
92 """send a request for kernel_info"""
93 self.log.debug("requesting kernel info")
94 self.session.send(self.kernel_info_channel, "kernel_info_request")
95
96 def _handle_kernel_info_reply(self, msg):
97 """process the kernel_info_reply
98
99 enabling msg spec adaptation, if necessary
100 """
101 idents,msg = self.session.feed_identities(msg)
102 try:
103 msg = self.session.unserialize(msg)
104 except:
105 self.log.error("Bad kernel_info reply", exc_info=True)
106 self._request_kernel_info()
107 return
108 else:
109 if msg['msg_type'] != 'kernel_info_reply' or 'protocol_version' not in msg['content']:
110 self.log.error("Kernel info request failed, assuming current %s", msg['content'])
111 else:
112 protocol_version = msg['content']['protocol_version']
113 if protocol_version != kernel_protocol_version:
114 self.session.adapt_version = int(protocol_version.split('.')[0])
115 self.log.info("adapting kernel to %s" % protocol_version)
116 self.kernel_info_channel.close()
117 self.kernel_info_channel = None
118
99
119
100 def initialize(self, *args, **kwargs):
120 def initialize(self, *args, **kwargs):
101 self.zmq_stream = None
121 self.zmq_stream = None
@@ -171,6 +171,15 b' var IPython = (function (IPython) {'
171 var matches = content.matches;
171 var matches = content.matches;
172
172
173 var cur = this.editor.getCursor();
173 var cur = this.editor.getCursor();
174 if (end === null) {
175 // adapted message spec replies don't have cursor position info,
176 // interpret end=null as current position,
177 // and negative start relative to that
178 end = utils.to_absolute_cursor_pos(this.editor, cur);
179 if (start < 0) {
180 start = end + start;
181 }
182 }
174 var results = CodeMirror.contextHint(this.editor);
183 var results = CodeMirror.contextHint(this.editor);
175 var filtered_results = [];
184 var filtered_results = [];
176 //remove results from context completion
185 //remove results from context completion
@@ -63,7 +63,10 b' class BlockingIOPubChannel(BlockingChannelMixin, IOPubChannel):'
63
63
64
64
65 class BlockingShellChannel(BlockingChannelMixin, ShellChannel):
65 class BlockingShellChannel(BlockingChannelMixin, ShellChannel):
66 pass
66 def call_handlers(self, msg):
67 if msg['msg_type'] == 'kernel_info_reply':
68 self._handle_kernel_info_reply(msg)
69 return super(BlockingShellChannel, self).call_handlers(msg)
67
70
68
71
69 class BlockingStdInChannel(BlockingChannelMixin, StdInChannel):
72 class BlockingStdInChannel(BlockingChannelMixin, StdInChannel):
@@ -16,7 +16,8 b' import zmq'
16 from zmq import ZMQError
16 from zmq import ZMQError
17 from zmq.eventloop import ioloop, zmqstream
17 from zmq.eventloop import ioloop, zmqstream
18
18
19 # Local imports
19 from IPython.core.release import kernel_protocol_version_info
20
20 from .channelsabc import (
21 from .channelsabc import (
21 ShellChannelABC, IOPubChannelABC,
22 ShellChannelABC, IOPubChannelABC,
22 HBChannelABC, StdInChannelABC,
23 HBChannelABC, StdInChannelABC,
@@ -27,6 +28,8 b' from IPython.utils.py3compat import string_types, iteritems'
27 # Constants and exceptions
28 # Constants and exceptions
28 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
29
30
31 major_protocol_version = kernel_protocol_version_info[0]
32
30 class InvalidPortNumber(Exception):
33 class InvalidPortNumber(Exception):
31 pass
34 pass
32
35
@@ -173,7 +176,8 b' class ZMQSocketChannel(Thread):'
173 Unpacks message, and calls handlers with it.
176 Unpacks message, and calls handlers with it.
174 """
177 """
175 ident,smsg = self.session.feed_identities(msg)
178 ident,smsg = self.session.feed_identities(msg)
176 self.call_handlers(self.session.unserialize(smsg))
179 msg = self.session.unserialize(smsg)
180 self.call_handlers(msg)
177
181
178
182
179
183
@@ -195,7 +199,7 b' class ShellChannel(ZMQSocketChannel):'
195 def __init__(self, context, session, address):
199 def __init__(self, context, session, address):
196 super(ShellChannel, self).__init__(context, session, address)
200 super(ShellChannel, self).__init__(context, session, address)
197 self.ioloop = ioloop.IOLoop()
201 self.ioloop = ioloop.IOLoop()
198
202
199 def run(self):
203 def run(self):
200 """The thread's main activity. Call start() instead."""
204 """The thread's main activity. Call start() instead."""
201 self.socket = self.context.socket(zmq.DEALER)
205 self.socket = self.context.socket(zmq.DEALER)
@@ -365,6 +369,15 b' class ShellChannel(ZMQSocketChannel):'
365 msg = self.session.msg('kernel_info_request')
369 msg = self.session.msg('kernel_info_request')
366 self._queue_send(msg)
370 self._queue_send(msg)
367 return msg['header']['msg_id']
371 return msg['header']['msg_id']
372
373 def _handle_kernel_info_reply(self, msg):
374 """handle kernel info reply
375
376 sets protocol adaptation version
377 """
378 adapt_version = int(msg['content']['protocol_version'].split('.')[0])
379 if adapt_version != major_protocol_version:
380 self.session.adapt_version = adapt_version
368
381
369 def shutdown(self, restart=False):
382 def shutdown(self, restart=False):
370 """Request an immediate kernel shutdown.
383 """Request an immediate kernel shutdown.
@@ -32,7 +32,7 b' from zmq.utils import jsonapi'
32 from zmq.eventloop.ioloop import IOLoop
32 from zmq.eventloop.ioloop import IOLoop
33 from zmq.eventloop.zmqstream import ZMQStream
33 from zmq.eventloop.zmqstream import ZMQStream
34
34
35 from IPython.core.release import kernel_protocol_version
35 from IPython.core.release import kernel_protocol_version, kernel_protocol_version_info
36 from IPython.config.configurable import Configurable, LoggingConfigurable
36 from IPython.config.configurable import Configurable, LoggingConfigurable
37 from IPython.utils import io
37 from IPython.utils import io
38 from IPython.utils.importstring import import_item
38 from IPython.utils.importstring import import_item
@@ -43,6 +43,7 b' from IPython.utils.traitlets import (CBytes, Unicode, Bool, Any, Instance, Set,'
43 DottedObjectName, CUnicode, Dict, Integer,
43 DottedObjectName, CUnicode, Dict, Integer,
44 TraitError,
44 TraitError,
45 )
45 )
46 from IPython.kernel.adapter import adapt
46 from IPython.kernel.zmq.serialize import MAX_ITEMS, MAX_BYTES
47 from IPython.kernel.zmq.serialize import MAX_ITEMS, MAX_BYTES
47
48
48 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
@@ -298,6 +299,9 b' class Session(Configurable):'
298
299
299 metadata = Dict({}, config=True,
300 metadata = Dict({}, config=True,
300 help="""Metadata dictionary, which serves as the default top-level metadata dict for each message.""")
301 help="""Metadata dictionary, which serves as the default top-level metadata dict for each message.""")
302
303 # if 0, no adapting to do.
304 adapt_version = Integer(0)
301
305
302 # message signature related traits:
306 # message signature related traits:
303
307
@@ -625,6 +629,8 b' class Session(Configurable):'
625 io.rprint(msg)
629 io.rprint(msg)
626 return
630 return
627 buffers = [] if buffers is None else buffers
631 buffers = [] if buffers is None else buffers
632 if self.adapt_version:
633 msg = adapt(msg, self.adapt_version)
628 to_send = self.serialize(msg, ident)
634 to_send = self.serialize(msg, ident)
629 to_send.extend(buffers)
635 to_send.extend(buffers)
630 longest = max([ len(s) for s in to_send ])
636 longest = max([ len(s) for s in to_send ])
@@ -823,7 +829,10 b' class Session(Configurable):'
823 message['content'] = msg_list[4]
829 message['content'] = msg_list[4]
824
830
825 message['buffers'] = msg_list[5:]
831 message['buffers'] = msg_list[5:]
826 return message
832 # print("received: %s: %s\n %s" % (message['msg_type'], message['header'], message['content']))
833 # adapt to the current version
834 return adapt(message)
835 # print("adapted: %s: %s\n %s" % (adapted['msg_type'], adapted['header'], adapted['content']))
827
836
828 def test_msg2obj():
837 def test_msg2obj():
829 am = dict(x=1)
838 am = dict(x=1)
@@ -59,6 +59,7 b' class QtShellChannelMixin(ChannelQObject):'
59 complete_reply = QtCore.Signal(object)
59 complete_reply = QtCore.Signal(object)
60 inspect_reply = QtCore.Signal(object)
60 inspect_reply = QtCore.Signal(object)
61 history_reply = QtCore.Signal(object)
61 history_reply = QtCore.Signal(object)
62 kernel_info_reply = QtCore.Signal(object)
62
63
63 #---------------------------------------------------------------------------
64 #---------------------------------------------------------------------------
64 # 'ShellChannel' interface
65 # 'ShellChannel' interface
@@ -72,6 +73,9 b' class QtShellChannelMixin(ChannelQObject):'
72
73
73 # Emit signals for specialized message types.
74 # Emit signals for specialized message types.
74 msg_type = msg['header']['msg_type']
75 msg_type = msg['header']['msg_type']
76 if msg_type == 'kernel_info_reply':
77 self._handle_kernel_info_reply(msg)
78
75 signal = getattr(self, msg_type, None)
79 signal = getattr(self, msg_type, None)
76 if signal:
80 if signal:
77 signal.emit(msg)
81 signal.emit(msg)
General Comments 0
You need to be logged in to leave comments. Login now