##// END OF EJS Templates
Merge pull request #8451 from minrk/adapt-object-info...
Thomas Kluyver -
r21368:7c969a7e merge
parent child Browse files
Show More
@@ -1,374 +1,373 b''
1 """Adapters for IPython msg spec versions."""
1 """Adapters for IPython msg spec versions."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import json
6 import json
7
7
8 from IPython.core.release import kernel_protocol_version_info
8 from IPython.core.release import kernel_protocol_version_info
9 from IPython.utils.tokenutil import token_at_cursor
9 from IPython.utils.tokenutil import token_at_cursor
10
10
11
11
12 def code_to_line(code, cursor_pos):
12 def code_to_line(code, cursor_pos):
13 """Turn a multiline code block and cursor position into a single line
13 """Turn a multiline code block and cursor position into a single line
14 and new cursor position.
14 and new cursor position.
15
15
16 For adapting ``complete_`` and ``object_info_request``.
16 For adapting ``complete_`` and ``object_info_request``.
17 """
17 """
18 if not code:
18 if not code:
19 return "", 0
19 return "", 0
20 for line in code.splitlines(True):
20 for line in code.splitlines(True):
21 n = len(line)
21 n = len(line)
22 if cursor_pos > n:
22 if cursor_pos > n:
23 cursor_pos -= n
23 cursor_pos -= n
24 else:
24 else:
25 break
25 break
26 return line, cursor_pos
26 return line, cursor_pos
27
27
28
28
29 class Adapter(object):
29 class Adapter(object):
30 """Base class for adapting messages
30 """Base class for adapting messages
31
31
32 Override message_type(msg) methods to create adapters.
32 Override message_type(msg) methods to create adapters.
33 """
33 """
34
34
35 msg_type_map = {}
35 msg_type_map = {}
36
36
37 def update_header(self, msg):
37 def update_header(self, msg):
38 return msg
38 return msg
39
39
40 def update_metadata(self, msg):
40 def update_metadata(self, msg):
41 return msg
41 return msg
42
42
43 def update_msg_type(self, msg):
43 def update_msg_type(self, msg):
44 header = msg['header']
44 header = msg['header']
45 msg_type = header['msg_type']
45 msg_type = header['msg_type']
46 if msg_type in self.msg_type_map:
46 if msg_type in self.msg_type_map:
47 msg['msg_type'] = header['msg_type'] = self.msg_type_map[msg_type]
47 msg['msg_type'] = header['msg_type'] = self.msg_type_map[msg_type]
48 return msg
48 return msg
49
49
50 def handle_reply_status_error(self, msg):
50 def handle_reply_status_error(self, msg):
51 """This will be called *instead of* the regular handler
51 """This will be called *instead of* the regular handler
52
52
53 on any reply with status != ok
53 on any reply with status != ok
54 """
54 """
55 return msg
55 return msg
56
56
57 def __call__(self, msg):
57 def __call__(self, msg):
58 msg = self.update_header(msg)
58 msg = self.update_header(msg)
59 msg = self.update_metadata(msg)
59 msg = self.update_metadata(msg)
60 msg = self.update_msg_type(msg)
60 msg = self.update_msg_type(msg)
61 header = msg['header']
61 header = msg['header']
62
62
63 handler = getattr(self, header['msg_type'], None)
63 handler = getattr(self, header['msg_type'], None)
64 if handler is None:
64 if handler is None:
65 return msg
65 return msg
66
66
67 # handle status=error replies separately (no change, at present)
67 # handle status=error replies separately (no change, at present)
68 if msg['content'].get('status', None) in {'error', 'aborted'}:
68 if msg['content'].get('status', None) in {'error', 'aborted'}:
69 return self.handle_reply_status_error(msg)
69 return self.handle_reply_status_error(msg)
70 return handler(msg)
70 return handler(msg)
71
71
72 def _version_str_to_list(version):
72 def _version_str_to_list(version):
73 """convert a version string to a list of ints
73 """convert a version string to a list of ints
74
74
75 non-int segments are excluded
75 non-int segments are excluded
76 """
76 """
77 v = []
77 v = []
78 for part in version.split('.'):
78 for part in version.split('.'):
79 try:
79 try:
80 v.append(int(part))
80 v.append(int(part))
81 except ValueError:
81 except ValueError:
82 pass
82 pass
83 return v
83 return v
84
84
85 class V5toV4(Adapter):
85 class V5toV4(Adapter):
86 """Adapt msg protocol v5 to v4"""
86 """Adapt msg protocol v5 to v4"""
87
87
88 version = '4.1'
88 version = '4.1'
89
89
90 msg_type_map = {
90 msg_type_map = {
91 'execute_result' : 'pyout',
91 'execute_result' : 'pyout',
92 'execute_input' : 'pyin',
92 'execute_input' : 'pyin',
93 'error' : 'pyerr',
93 'error' : 'pyerr',
94 'inspect_request' : 'object_info_request',
94 'inspect_request' : 'object_info_request',
95 'inspect_reply' : 'object_info_reply',
95 'inspect_reply' : 'object_info_reply',
96 }
96 }
97
97
98 def update_header(self, msg):
98 def update_header(self, msg):
99 msg['header'].pop('version', None)
99 msg['header'].pop('version', None)
100 return msg
100 return msg
101
101
102 # shell channel
102 # shell channel
103
103
104 def kernel_info_reply(self, msg):
104 def kernel_info_reply(self, msg):
105 v4c = {}
105 v4c = {}
106 content = msg['content']
106 content = msg['content']
107 for key in ('language_version', 'protocol_version'):
107 for key in ('language_version', 'protocol_version'):
108 if key in content:
108 if key in content:
109 v4c[key] = _version_str_to_list(content[key])
109 v4c[key] = _version_str_to_list(content[key])
110 if content.get('implementation', '') == 'ipython' \
110 if content.get('implementation', '') == 'ipython' \
111 and 'implementation_version' in content:
111 and 'implementation_version' in content:
112 v4c['ipython_version'] = _version_str_to_list(content['implementation_version'])
112 v4c['ipython_version'] = _version_str_to_list(content['implementation_version'])
113 language_info = content.get('language_info', {})
113 language_info = content.get('language_info', {})
114 language = language_info.get('name', '')
114 language = language_info.get('name', '')
115 v4c.setdefault('language', language)
115 v4c.setdefault('language', language)
116 if 'version' in language_info:
116 if 'version' in language_info:
117 v4c.setdefault('language_version', _version_str_to_list(language_info['version']))
117 v4c.setdefault('language_version', _version_str_to_list(language_info['version']))
118 msg['content'] = v4c
118 msg['content'] = v4c
119 return msg
119 return msg
120
120
121 def execute_request(self, msg):
121 def execute_request(self, msg):
122 content = msg['content']
122 content = msg['content']
123 content.setdefault('user_variables', [])
123 content.setdefault('user_variables', [])
124 return msg
124 return msg
125
125
126 def execute_reply(self, msg):
126 def execute_reply(self, msg):
127 content = msg['content']
127 content = msg['content']
128 content.setdefault('user_variables', {})
128 content.setdefault('user_variables', {})
129 # TODO: handle payloads
129 # TODO: handle payloads
130 return msg
130 return msg
131
131
132 def complete_request(self, msg):
132 def complete_request(self, msg):
133 content = msg['content']
133 content = msg['content']
134 code = content['code']
134 code = content['code']
135 cursor_pos = content['cursor_pos']
135 cursor_pos = content['cursor_pos']
136 line, cursor_pos = code_to_line(code, cursor_pos)
136 line, cursor_pos = code_to_line(code, cursor_pos)
137
137
138 new_content = msg['content'] = {}
138 new_content = msg['content'] = {}
139 new_content['text'] = ''
139 new_content['text'] = ''
140 new_content['line'] = line
140 new_content['line'] = line
141 new_content['block'] = None
141 new_content['block'] = None
142 new_content['cursor_pos'] = cursor_pos
142 new_content['cursor_pos'] = cursor_pos
143 return msg
143 return msg
144
144
145 def complete_reply(self, msg):
145 def complete_reply(self, msg):
146 content = msg['content']
146 content = msg['content']
147 cursor_start = content.pop('cursor_start')
147 cursor_start = content.pop('cursor_start')
148 cursor_end = content.pop('cursor_end')
148 cursor_end = content.pop('cursor_end')
149 match_len = cursor_end - cursor_start
149 match_len = cursor_end - cursor_start
150 content['matched_text'] = content['matches'][0][:match_len]
150 content['matched_text'] = content['matches'][0][:match_len]
151 content.pop('metadata', None)
151 content.pop('metadata', None)
152 return msg
152 return msg
153
153
154 def object_info_request(self, msg):
154 def object_info_request(self, msg):
155 content = msg['content']
155 content = msg['content']
156 code = content['code']
156 code = content['code']
157 cursor_pos = content['cursor_pos']
157 cursor_pos = content['cursor_pos']
158 line, _ = code_to_line(code, cursor_pos)
158 line, _ = code_to_line(code, cursor_pos)
159
159
160 new_content = msg['content'] = {}
160 new_content = msg['content'] = {}
161 new_content['oname'] = token_at_cursor(code, cursor_pos)
161 new_content['oname'] = token_at_cursor(code, cursor_pos)
162 new_content['detail_level'] = content['detail_level']
162 new_content['detail_level'] = content['detail_level']
163 return msg
163 return msg
164
164
165 def object_info_reply(self, msg):
165 def object_info_reply(self, msg):
166 """inspect_reply can't be easily backward compatible"""
166 """inspect_reply can't be easily backward compatible"""
167 msg['content'] = {'found' : False, 'oname' : 'unknown'}
167 msg['content'] = {'found' : False, 'oname' : 'unknown'}
168 return msg
168 return msg
169
169
170 # iopub channel
170 # iopub channel
171
171
172 def stream(self, msg):
172 def stream(self, msg):
173 content = msg['content']
173 content = msg['content']
174 content['data'] = content.pop('text')
174 content['data'] = content.pop('text')
175 return msg
175 return msg
176
176
177 def display_data(self, msg):
177 def display_data(self, msg):
178 content = msg['content']
178 content = msg['content']
179 content.setdefault("source", "display")
179 content.setdefault("source", "display")
180 data = content['data']
180 data = content['data']
181 if 'application/json' in data:
181 if 'application/json' in data:
182 try:
182 try:
183 data['application/json'] = json.dumps(data['application/json'])
183 data['application/json'] = json.dumps(data['application/json'])
184 except Exception:
184 except Exception:
185 # warn?
185 # warn?
186 pass
186 pass
187 return msg
187 return msg
188
188
189 # stdin channel
189 # stdin channel
190
190
191 def input_request(self, msg):
191 def input_request(self, msg):
192 msg['content'].pop('password', None)
192 msg['content'].pop('password', None)
193 return msg
193 return msg
194
194
195
195
196 class V4toV5(Adapter):
196 class V4toV5(Adapter):
197 """Convert msg spec V4 to V5"""
197 """Convert msg spec V4 to V5"""
198 version = '5.0'
198 version = '5.0'
199
199
200 # invert message renames above
200 # invert message renames above
201 msg_type_map = {v:k for k,v in V5toV4.msg_type_map.items()}
201 msg_type_map = {v:k for k,v in V5toV4.msg_type_map.items()}
202
202
203 def update_header(self, msg):
203 def update_header(self, msg):
204 msg['header']['version'] = self.version
204 msg['header']['version'] = self.version
205 return msg
205 return msg
206
206
207 # shell channel
207 # shell channel
208
208
209 def kernel_info_reply(self, msg):
209 def kernel_info_reply(self, msg):
210 content = msg['content']
210 content = msg['content']
211 for key in ('protocol_version', 'ipython_version'):
211 for key in ('protocol_version', 'ipython_version'):
212 if key in content:
212 if key in content:
213 content[key] = '.'.join(map(str, content[key]))
213 content[key] = '.'.join(map(str, content[key]))
214
214
215 content.setdefault('protocol_version', '4.1')
215 content.setdefault('protocol_version', '4.1')
216
216
217 if content['language'].startswith('python') and 'ipython_version' in content:
217 if content['language'].startswith('python') and 'ipython_version' in content:
218 content['implementation'] = 'ipython'
218 content['implementation'] = 'ipython'
219 content['implementation_version'] = content.pop('ipython_version')
219 content['implementation_version'] = content.pop('ipython_version')
220
220
221 language = content.pop('language')
221 language = content.pop('language')
222 language_info = content.setdefault('language_info', {})
222 language_info = content.setdefault('language_info', {})
223 language_info.setdefault('name', language)
223 language_info.setdefault('name', language)
224 if 'language_version' in content:
224 if 'language_version' in content:
225 language_version = '.'.join(map(str, content.pop('language_version')))
225 language_version = '.'.join(map(str, content.pop('language_version')))
226 language_info.setdefault('version', language_version)
226 language_info.setdefault('version', language_version)
227
227
228 content['banner'] = ''
228 content['banner'] = ''
229 return msg
229 return msg
230
230
231 def execute_request(self, msg):
231 def execute_request(self, msg):
232 content = msg['content']
232 content = msg['content']
233 user_variables = content.pop('user_variables', [])
233 user_variables = content.pop('user_variables', [])
234 user_expressions = content.setdefault('user_expressions', {})
234 user_expressions = content.setdefault('user_expressions', {})
235 for v in user_variables:
235 for v in user_variables:
236 user_expressions[v] = v
236 user_expressions[v] = v
237 return msg
237 return msg
238
238
239 def execute_reply(self, msg):
239 def execute_reply(self, msg):
240 content = msg['content']
240 content = msg['content']
241 user_expressions = content.setdefault('user_expressions', {})
241 user_expressions = content.setdefault('user_expressions', {})
242 user_variables = content.pop('user_variables', {})
242 user_variables = content.pop('user_variables', {})
243 if user_variables:
243 if user_variables:
244 user_expressions.update(user_variables)
244 user_expressions.update(user_variables)
245
245
246 # Pager payloads became a mime bundle
246 # Pager payloads became a mime bundle
247 for payload in content.get('payload', []):
247 for payload in content.get('payload', []):
248 if payload.get('source', None) == 'page' and ('text' in payload):
248 if payload.get('source', None) == 'page' and ('text' in payload):
249 if 'data' not in payload:
249 if 'data' not in payload:
250 payload['data'] = {}
250 payload['data'] = {}
251 payload['data']['text/plain'] = payload.pop('text')
251 payload['data']['text/plain'] = payload.pop('text')
252
252
253 return msg
253 return msg
254
254
255 def complete_request(self, msg):
255 def complete_request(self, msg):
256 old_content = msg['content']
256 old_content = msg['content']
257
257
258 new_content = msg['content'] = {}
258 new_content = msg['content'] = {}
259 new_content['code'] = old_content['line']
259 new_content['code'] = old_content['line']
260 new_content['cursor_pos'] = old_content['cursor_pos']
260 new_content['cursor_pos'] = old_content['cursor_pos']
261 return msg
261 return msg
262
262
263 def complete_reply(self, msg):
263 def complete_reply(self, msg):
264 # complete_reply needs more context than we have to get cursor_start and end.
264 # complete_reply needs more context than we have to get cursor_start and end.
265 # use special end=null to indicate current cursor position and negative offset
265 # use special end=null to indicate current cursor position and negative offset
266 # for start relative to the cursor.
266 # for start relative to the cursor.
267 # start=None indicates that start == end (accounts for no -0).
267 # start=None indicates that start == end (accounts for no -0).
268 content = msg['content']
268 content = msg['content']
269 new_content = msg['content'] = {'status' : 'ok'}
269 new_content = msg['content'] = {'status' : 'ok'}
270 new_content['matches'] = content['matches']
270 new_content['matches'] = content['matches']
271 if content['matched_text']:
271 if content['matched_text']:
272 new_content['cursor_start'] = -len(content['matched_text'])
272 new_content['cursor_start'] = -len(content['matched_text'])
273 else:
273 else:
274 # no -0, use None to indicate that start == end
274 # no -0, use None to indicate that start == end
275 new_content['cursor_start'] = None
275 new_content['cursor_start'] = None
276 new_content['cursor_end'] = None
276 new_content['cursor_end'] = None
277 new_content['metadata'] = {}
277 new_content['metadata'] = {}
278 return msg
278 return msg
279
279
280 def inspect_request(self, msg):
280 def inspect_request(self, msg):
281 content = msg['content']
281 content = msg['content']
282 name = content['oname']
282 name = content['oname']
283
283
284 new_content = msg['content'] = {}
284 new_content = msg['content'] = {}
285 new_content['code'] = name
285 new_content['code'] = name
286 new_content['cursor_pos'] = len(name)
286 new_content['cursor_pos'] = len(name)
287 new_content['detail_level'] = content['detail_level']
287 new_content['detail_level'] = content['detail_level']
288 return msg
288 return msg
289
289
290 def inspect_reply(self, msg):
290 def inspect_reply(self, msg):
291 """inspect_reply can't be easily backward compatible"""
291 """inspect_reply can't be easily backward compatible"""
292 content = msg['content']
292 content = msg['content']
293 new_content = msg['content'] = {'status' : 'ok'}
293 new_content = msg['content'] = {'status' : 'ok'}
294 found = new_content['found'] = content['found']
294 found = new_content['found'] = content['found']
295 new_content['name'] = content['oname']
296 new_content['data'] = data = {}
295 new_content['data'] = data = {}
297 new_content['metadata'] = {}
296 new_content['metadata'] = {}
298 if found:
297 if found:
299 lines = []
298 lines = []
300 for key in ('call_def', 'init_definition', 'definition'):
299 for key in ('call_def', 'init_definition', 'definition'):
301 if content.get(key, False):
300 if content.get(key, False):
302 lines.append(content[key])
301 lines.append(content[key])
303 break
302 break
304 for key in ('call_docstring', 'init_docstring', 'docstring'):
303 for key in ('call_docstring', 'init_docstring', 'docstring'):
305 if content.get(key, False):
304 if content.get(key, False):
306 lines.append(content[key])
305 lines.append(content[key])
307 break
306 break
308 if not lines:
307 if not lines:
309 lines.append("<empty docstring>")
308 lines.append("<empty docstring>")
310 data['text/plain'] = '\n'.join(lines)
309 data['text/plain'] = '\n'.join(lines)
311 return msg
310 return msg
312
311
313 # iopub channel
312 # iopub channel
314
313
315 def stream(self, msg):
314 def stream(self, msg):
316 content = msg['content']
315 content = msg['content']
317 content['text'] = content.pop('data')
316 content['text'] = content.pop('data')
318 return msg
317 return msg
319
318
320 def display_data(self, msg):
319 def display_data(self, msg):
321 content = msg['content']
320 content = msg['content']
322 content.pop("source", None)
321 content.pop("source", None)
323 data = content['data']
322 data = content['data']
324 if 'application/json' in data:
323 if 'application/json' in data:
325 try:
324 try:
326 data['application/json'] = json.loads(data['application/json'])
325 data['application/json'] = json.loads(data['application/json'])
327 except Exception:
326 except Exception:
328 # warn?
327 # warn?
329 pass
328 pass
330 return msg
329 return msg
331
330
332 # stdin channel
331 # stdin channel
333
332
334 def input_request(self, msg):
333 def input_request(self, msg):
335 msg['content'].setdefault('password', False)
334 msg['content'].setdefault('password', False)
336 return msg
335 return msg
337
336
338
337
339
338
340 def adapt(msg, to_version=kernel_protocol_version_info[0]):
339 def adapt(msg, to_version=kernel_protocol_version_info[0]):
341 """Adapt a single message to a target version
340 """Adapt a single message to a target version
342
341
343 Parameters
342 Parameters
344 ----------
343 ----------
345
344
346 msg : dict
345 msg : dict
347 An IPython message.
346 An IPython message.
348 to_version : int, optional
347 to_version : int, optional
349 The target major version.
348 The target major version.
350 If unspecified, adapt to the current version for IPython.
349 If unspecified, adapt to the current version for IPython.
351
350
352 Returns
351 Returns
353 -------
352 -------
354
353
355 msg : dict
354 msg : dict
356 An IPython message appropriate in the new version.
355 An IPython message appropriate in the new version.
357 """
356 """
358 header = msg['header']
357 header = msg['header']
359 if 'version' in header:
358 if 'version' in header:
360 from_version = int(header['version'].split('.')[0])
359 from_version = int(header['version'].split('.')[0])
361 else:
360 else:
362 # assume last version before adding the key to the header
361 # assume last version before adding the key to the header
363 from_version = 4
362 from_version = 4
364 adapter = adapters.get((from_version, to_version), None)
363 adapter = adapters.get((from_version, to_version), None)
365 if adapter is None:
364 if adapter is None:
366 return msg
365 return msg
367 return adapter(msg)
366 return adapter(msg)
368
367
369
368
370 # one adapter per major version from,to
369 # one adapter per major version from,to
371 adapters = {
370 adapters = {
372 (5,4) : V5toV4(),
371 (5,4) : V5toV4(),
373 (4,5) : V4toV5(),
372 (4,5) : V4toV5(),
374 }
373 }
@@ -1,377 +1,393 b''
1 """Tests for adapting IPython msg spec versions"""
1 """Tests for adapting IPython msg spec versions"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import copy
6 import copy
7 import json
7 import json
8 from unittest import TestCase
8 from unittest import TestCase
9 import nose.tools as nt
9 import nose.tools as nt
10
10
11 from IPython.kernel.adapter import adapt, V4toV5, V5toV4, code_to_line
11 from IPython.kernel.adapter import adapt, V4toV5, V5toV4, code_to_line
12 from IPython.kernel.zmq.session import Session
12 from IPython.kernel.zmq.session import Session
13
13
14
14
15 def test_default_version():
15 def test_default_version():
16 s = Session()
16 s = Session()
17 msg = s.msg("msg_type")
17 msg = s.msg("msg_type")
18 msg['header'].pop('version')
18 msg['header'].pop('version')
19 original = copy.deepcopy(msg)
19 original = copy.deepcopy(msg)
20 adapted = adapt(original)
20 adapted = adapt(original)
21 nt.assert_equal(adapted['header']['version'], V4toV5.version)
21 nt.assert_equal(adapted['header']['version'], V4toV5.version)
22
22
23 def test_code_to_line_no_code():
23 def test_code_to_line_no_code():
24 line, pos = code_to_line("", 0)
24 line, pos = code_to_line("", 0)
25 nt.assert_equal(line, "")
25 nt.assert_equal(line, "")
26 nt.assert_equal(pos, 0)
26 nt.assert_equal(pos, 0)
27
27
28 class AdapterTest(TestCase):
28 class AdapterTest(TestCase):
29
29
30 def setUp(self):
30 def setUp(self):
31 self.session = Session()
31 self.session = Session()
32
32
33 def adapt(self, msg, version=None):
33 def adapt(self, msg, version=None):
34 original = copy.deepcopy(msg)
34 original = copy.deepcopy(msg)
35 adapted = adapt(msg, version or self.to_version)
35 adapted = adapt(msg, version or self.to_version)
36 return original, adapted
36 return original, adapted
37
37
38 def check_header(self, msg):
38 def check_header(self, msg):
39 pass
39 pass
40
40
41
41
42 class V4toV5TestCase(AdapterTest):
42 class V4toV5TestCase(AdapterTest):
43 from_version = 4
43 from_version = 4
44 to_version = 5
44 to_version = 5
45
45
46 def msg(self, msg_type, content):
46 def msg(self, msg_type, content):
47 """Create a v4 msg (same as v5, minus version header)"""
47 """Create a v4 msg (same as v5, minus version header)"""
48 msg = self.session.msg(msg_type, content)
48 msg = self.session.msg(msg_type, content)
49 msg['header'].pop('version')
49 msg['header'].pop('version')
50 return msg
50 return msg
51
51
52 def test_same_version(self):
52 def test_same_version(self):
53 msg = self.msg("execute_result",
53 msg = self.msg("execute_result",
54 content={'status' : 'ok'}
54 content={'status' : 'ok'}
55 )
55 )
56 original, adapted = self.adapt(msg, self.from_version)
56 original, adapted = self.adapt(msg, self.from_version)
57
57
58 self.assertEqual(original, adapted)
58 self.assertEqual(original, adapted)
59
59
60 def test_no_adapt(self):
60 def test_no_adapt(self):
61 msg = self.msg("input_reply", {'value' : 'some text'})
61 msg = self.msg("input_reply", {'value' : 'some text'})
62 v4, v5 = self.adapt(msg)
62 v4, v5 = self.adapt(msg)
63 self.assertEqual(v5['header']['version'], V4toV5.version)
63 self.assertEqual(v5['header']['version'], V4toV5.version)
64 v5['header'].pop('version')
64 v5['header'].pop('version')
65 self.assertEqual(v4, v5)
65 self.assertEqual(v4, v5)
66
66
67 def test_rename_type(self):
67 def test_rename_type(self):
68 for v5_type, v4_type in [
68 for v5_type, v4_type in [
69 ('execute_result', 'pyout'),
69 ('execute_result', 'pyout'),
70 ('execute_input', 'pyin'),
70 ('execute_input', 'pyin'),
71 ('error', 'pyerr'),
71 ('error', 'pyerr'),
72 ]:
72 ]:
73 msg = self.msg(v4_type, {'key' : 'value'})
73 msg = self.msg(v4_type, {'key' : 'value'})
74 v4, v5 = self.adapt(msg)
74 v4, v5 = self.adapt(msg)
75 self.assertEqual(v5['header']['version'], V4toV5.version)
75 self.assertEqual(v5['header']['version'], V4toV5.version)
76 self.assertEqual(v5['header']['msg_type'], v5_type)
76 self.assertEqual(v5['header']['msg_type'], v5_type)
77 self.assertEqual(v4['content'], v5['content'])
77 self.assertEqual(v4['content'], v5['content'])
78
78
79 def test_execute_request(self):
79 def test_execute_request(self):
80 msg = self.msg("execute_request", {
80 msg = self.msg("execute_request", {
81 'code' : 'a=5',
81 'code' : 'a=5',
82 'silent' : False,
82 'silent' : False,
83 'user_expressions' : {'a' : 'apple'},
83 'user_expressions' : {'a' : 'apple'},
84 'user_variables' : ['b'],
84 'user_variables' : ['b'],
85 })
85 })
86 v4, v5 = self.adapt(msg)
86 v4, v5 = self.adapt(msg)
87 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
87 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
88 v4c = v4['content']
88 v4c = v4['content']
89 v5c = v5['content']
89 v5c = v5['content']
90 self.assertEqual(v5c['user_expressions'], {'a' : 'apple', 'b': 'b'})
90 self.assertEqual(v5c['user_expressions'], {'a' : 'apple', 'b': 'b'})
91 self.assertNotIn('user_variables', v5c)
91 self.assertNotIn('user_variables', v5c)
92 self.assertEqual(v5c['code'], v4c['code'])
92 self.assertEqual(v5c['code'], v4c['code'])
93
93
94 def test_execute_reply(self):
94 def test_execute_reply(self):
95 msg = self.msg("execute_reply", {
95 msg = self.msg("execute_reply", {
96 'status': 'ok',
96 'status': 'ok',
97 'execution_count': 7,
97 'execution_count': 7,
98 'user_variables': {'a': 1},
98 'user_variables': {'a': 1},
99 'user_expressions': {'a+a': 2},
99 'user_expressions': {'a+a': 2},
100 'payload': [{'source':'page', 'text':'blah'}]
100 'payload': [{'source':'page', 'text':'blah'}]
101 })
101 })
102 v4, v5 = self.adapt(msg)
102 v4, v5 = self.adapt(msg)
103 v5c = v5['content']
103 v5c = v5['content']
104 self.assertNotIn('user_variables', v5c)
104 self.assertNotIn('user_variables', v5c)
105 self.assertEqual(v5c['user_expressions'], {'a': 1, 'a+a': 2})
105 self.assertEqual(v5c['user_expressions'], {'a': 1, 'a+a': 2})
106 self.assertEqual(v5c['payload'], [{'source': 'page',
106 self.assertEqual(v5c['payload'], [{'source': 'page',
107 'data': {'text/plain': 'blah'}}
107 'data': {'text/plain': 'blah'}}
108 ])
108 ])
109
109
110 def test_complete_request(self):
110 def test_complete_request(self):
111 msg = self.msg("complete_request", {
111 msg = self.msg("complete_request", {
112 'text' : 'a.is',
112 'text' : 'a.is',
113 'line' : 'foo = a.is',
113 'line' : 'foo = a.is',
114 'block' : None,
114 'block' : None,
115 'cursor_pos' : 10,
115 'cursor_pos' : 10,
116 })
116 })
117 v4, v5 = self.adapt(msg)
117 v4, v5 = self.adapt(msg)
118 v4c = v4['content']
118 v4c = v4['content']
119 v5c = v5['content']
119 v5c = v5['content']
120 for key in ('text', 'line', 'block'):
120 for key in ('text', 'line', 'block'):
121 self.assertNotIn(key, v5c)
121 self.assertNotIn(key, v5c)
122 self.assertEqual(v5c['cursor_pos'], v4c['cursor_pos'])
122 self.assertEqual(v5c['cursor_pos'], v4c['cursor_pos'])
123 self.assertEqual(v5c['code'], v4c['line'])
123 self.assertEqual(v5c['code'], v4c['line'])
124
124
125 def test_complete_reply(self):
125 def test_complete_reply(self):
126 msg = self.msg("complete_reply", {
126 msg = self.msg("complete_reply", {
127 'matched_text' : 'a.is',
127 'matched_text' : 'a.is',
128 'matches' : ['a.isalnum',
128 'matches' : ['a.isalnum',
129 'a.isalpha',
129 'a.isalpha',
130 'a.isdigit',
130 'a.isdigit',
131 'a.islower',
131 'a.islower',
132 ],
132 ],
133 })
133 })
134 v4, v5 = self.adapt(msg)
134 v4, v5 = self.adapt(msg)
135 v4c = v4['content']
135 v4c = v4['content']
136 v5c = v5['content']
136 v5c = v5['content']
137
137
138 self.assertEqual(v5c['matches'], v4c['matches'])
138 self.assertEqual(v5c['matches'], v4c['matches'])
139 self.assertEqual(v5c['metadata'], {})
139 self.assertEqual(v5c['metadata'], {})
140 self.assertEqual(v5c['cursor_start'], -4)
140 self.assertEqual(v5c['cursor_start'], -4)
141 self.assertEqual(v5c['cursor_end'], None)
141 self.assertEqual(v5c['cursor_end'], None)
142
142
143 def test_object_info_request(self):
143 def test_object_info_request(self):
144 msg = self.msg("object_info_request", {
144 msg = self.msg("object_info_request", {
145 'oname' : 'foo',
145 'oname' : 'foo',
146 'detail_level' : 1,
146 'detail_level' : 1,
147 })
147 })
148 v4, v5 = self.adapt(msg)
148 v4, v5 = self.adapt(msg)
149 self.assertEqual(v5['header']['msg_type'], 'inspect_request')
149 self.assertEqual(v5['header']['msg_type'], 'inspect_request')
150 v4c = v4['content']
150 v4c = v4['content']
151 v5c = v5['content']
151 v5c = v5['content']
152 self.assertEqual(v5c['code'], v4c['oname'])
152 self.assertEqual(v5c['code'], v4c['oname'])
153 self.assertEqual(v5c['cursor_pos'], len(v4c['oname']))
153 self.assertEqual(v5c['cursor_pos'], len(v4c['oname']))
154 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
154 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
155
155
156 def test_object_info_reply(self):
156 def test_object_info_reply(self):
157 msg = self.msg("object_info_reply", {
157 msg = self.msg("object_info_reply", {
158 'oname' : 'foo',
158 'name' : 'foo',
159 'found' : True,
159 'found' : True,
160 'status' : 'ok',
160 'status' : 'ok',
161 'definition' : 'foo(a=5)',
161 'definition' : 'foo(a=5)',
162 'docstring' : "the docstring",
162 'docstring' : "the docstring",
163 })
163 })
164 v4, v5 = self.adapt(msg)
164 v4, v5 = self.adapt(msg)
165 self.assertEqual(v5['header']['msg_type'], 'inspect_reply')
165 self.assertEqual(v5['header']['msg_type'], 'inspect_reply')
166 v4c = v4['content']
166 v4c = v4['content']
167 v5c = v5['content']
167 v5c = v5['content']
168 self.assertEqual(sorted(v5c), [ 'data', 'found', 'metadata', 'name', 'status'])
168 self.assertEqual(sorted(v5c), [ 'data', 'found', 'metadata', 'status'])
169 text = v5c['data']['text/plain']
169 text = v5c['data']['text/plain']
170 self.assertEqual(text, '\n'.join([v4c['definition'], v4c['docstring']]))
170 self.assertEqual(text, '\n'.join([v4c['definition'], v4c['docstring']]))
171
171
172 def test_object_info_reply_not_found(self):
173 msg = self.msg("object_info_reply", {
174 'name' : 'foo',
175 'found' : False,
176 })
177 v4, v5 = self.adapt(msg)
178 self.assertEqual(v5['header']['msg_type'], 'inspect_reply')
179 v4c = v4['content']
180 v5c = v5['content']
181 self.assertEqual(v5c, {
182 'status': 'ok',
183 'found': False,
184 'data': {},
185 'metadata': {},
186 })
187
172 def test_kernel_info_reply(self):
188 def test_kernel_info_reply(self):
173 msg = self.msg("kernel_info_reply", {
189 msg = self.msg("kernel_info_reply", {
174 'language': 'python',
190 'language': 'python',
175 'language_version': [2,8,0],
191 'language_version': [2,8,0],
176 'ipython_version': [1,2,3],
192 'ipython_version': [1,2,3],
177 })
193 })
178 v4, v5 = self.adapt(msg)
194 v4, v5 = self.adapt(msg)
179 v4c = v4['content']
195 v4c = v4['content']
180 v5c = v5['content']
196 v5c = v5['content']
181 self.assertEqual(v5c, {
197 self.assertEqual(v5c, {
182 'protocol_version': '4.1',
198 'protocol_version': '4.1',
183 'implementation': 'ipython',
199 'implementation': 'ipython',
184 'implementation_version': '1.2.3',
200 'implementation_version': '1.2.3',
185 'language_info': {
201 'language_info': {
186 'name': 'python',
202 'name': 'python',
187 'version': '2.8.0',
203 'version': '2.8.0',
188 },
204 },
189 'banner' : '',
205 'banner' : '',
190 })
206 })
191
207
192 # iopub channel
208 # iopub channel
193
209
194 def test_display_data(self):
210 def test_display_data(self):
195 jsondata = dict(a=5)
211 jsondata = dict(a=5)
196 msg = self.msg("display_data", {
212 msg = self.msg("display_data", {
197 'data' : {
213 'data' : {
198 'text/plain' : 'some text',
214 'text/plain' : 'some text',
199 'application/json' : json.dumps(jsondata)
215 'application/json' : json.dumps(jsondata)
200 },
216 },
201 'metadata' : {'text/plain' : { 'key' : 'value' }},
217 'metadata' : {'text/plain' : { 'key' : 'value' }},
202 })
218 })
203 v4, v5 = self.adapt(msg)
219 v4, v5 = self.adapt(msg)
204 v4c = v4['content']
220 v4c = v4['content']
205 v5c = v5['content']
221 v5c = v5['content']
206 self.assertEqual(v5c['metadata'], v4c['metadata'])
222 self.assertEqual(v5c['metadata'], v4c['metadata'])
207 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
223 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
208 self.assertEqual(v5c['data']['application/json'], jsondata)
224 self.assertEqual(v5c['data']['application/json'], jsondata)
209
225
210 # stdin channel
226 # stdin channel
211
227
212 def test_input_request(self):
228 def test_input_request(self):
213 msg = self.msg('input_request', {'prompt': "$>"})
229 msg = self.msg('input_request', {'prompt': "$>"})
214 v4, v5 = self.adapt(msg)
230 v4, v5 = self.adapt(msg)
215 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
231 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
216 self.assertFalse(v5['content']['password'])
232 self.assertFalse(v5['content']['password'])
217
233
218
234
219 class V5toV4TestCase(AdapterTest):
235 class V5toV4TestCase(AdapterTest):
220 from_version = 5
236 from_version = 5
221 to_version = 4
237 to_version = 4
222
238
223 def msg(self, msg_type, content):
239 def msg(self, msg_type, content):
224 return self.session.msg(msg_type, content)
240 return self.session.msg(msg_type, content)
225
241
226 def test_same_version(self):
242 def test_same_version(self):
227 msg = self.msg("execute_result",
243 msg = self.msg("execute_result",
228 content={'status' : 'ok'}
244 content={'status' : 'ok'}
229 )
245 )
230 original, adapted = self.adapt(msg, self.from_version)
246 original, adapted = self.adapt(msg, self.from_version)
231
247
232 self.assertEqual(original, adapted)
248 self.assertEqual(original, adapted)
233
249
234 def test_no_adapt(self):
250 def test_no_adapt(self):
235 msg = self.msg("input_reply", {'value' : 'some text'})
251 msg = self.msg("input_reply", {'value' : 'some text'})
236 v5, v4 = self.adapt(msg)
252 v5, v4 = self.adapt(msg)
237 self.assertNotIn('version', v4['header'])
253 self.assertNotIn('version', v4['header'])
238 v5['header'].pop('version')
254 v5['header'].pop('version')
239 self.assertEqual(v4, v5)
255 self.assertEqual(v4, v5)
240
256
241 def test_rename_type(self):
257 def test_rename_type(self):
242 for v5_type, v4_type in [
258 for v5_type, v4_type in [
243 ('execute_result', 'pyout'),
259 ('execute_result', 'pyout'),
244 ('execute_input', 'pyin'),
260 ('execute_input', 'pyin'),
245 ('error', 'pyerr'),
261 ('error', 'pyerr'),
246 ]:
262 ]:
247 msg = self.msg(v5_type, {'key' : 'value'})
263 msg = self.msg(v5_type, {'key' : 'value'})
248 v5, v4 = self.adapt(msg)
264 v5, v4 = self.adapt(msg)
249 self.assertEqual(v4['header']['msg_type'], v4_type)
265 self.assertEqual(v4['header']['msg_type'], v4_type)
250 nt.assert_not_in('version', v4['header'])
266 nt.assert_not_in('version', v4['header'])
251 self.assertEqual(v4['content'], v5['content'])
267 self.assertEqual(v4['content'], v5['content'])
252
268
253 def test_execute_request(self):
269 def test_execute_request(self):
254 msg = self.msg("execute_request", {
270 msg = self.msg("execute_request", {
255 'code' : 'a=5',
271 'code' : 'a=5',
256 'silent' : False,
272 'silent' : False,
257 'user_expressions' : {'a' : 'apple'},
273 'user_expressions' : {'a' : 'apple'},
258 })
274 })
259 v5, v4 = self.adapt(msg)
275 v5, v4 = self.adapt(msg)
260 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
276 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
261 v4c = v4['content']
277 v4c = v4['content']
262 v5c = v5['content']
278 v5c = v5['content']
263 self.assertEqual(v4c['user_variables'], [])
279 self.assertEqual(v4c['user_variables'], [])
264 self.assertEqual(v5c['code'], v4c['code'])
280 self.assertEqual(v5c['code'], v4c['code'])
265
281
266 def test_complete_request(self):
282 def test_complete_request(self):
267 msg = self.msg("complete_request", {
283 msg = self.msg("complete_request", {
268 'code' : 'def foo():\n'
284 'code' : 'def foo():\n'
269 ' a.is\n'
285 ' a.is\n'
270 'foo()',
286 'foo()',
271 'cursor_pos': 19,
287 'cursor_pos': 19,
272 })
288 })
273 v5, v4 = self.adapt(msg)
289 v5, v4 = self.adapt(msg)
274 v4c = v4['content']
290 v4c = v4['content']
275 v5c = v5['content']
291 v5c = v5['content']
276 self.assertNotIn('code', v4c)
292 self.assertNotIn('code', v4c)
277 self.assertEqual(v4c['line'], v5c['code'].splitlines(True)[1])
293 self.assertEqual(v4c['line'], v5c['code'].splitlines(True)[1])
278 self.assertEqual(v4c['cursor_pos'], 8)
294 self.assertEqual(v4c['cursor_pos'], 8)
279 self.assertEqual(v4c['text'], '')
295 self.assertEqual(v4c['text'], '')
280 self.assertEqual(v4c['block'], None)
296 self.assertEqual(v4c['block'], None)
281
297
282 def test_complete_reply(self):
298 def test_complete_reply(self):
283 msg = self.msg("complete_reply", {
299 msg = self.msg("complete_reply", {
284 'cursor_start' : 10,
300 'cursor_start' : 10,
285 'cursor_end' : 14,
301 'cursor_end' : 14,
286 'matches' : ['a.isalnum',
302 'matches' : ['a.isalnum',
287 'a.isalpha',
303 'a.isalpha',
288 'a.isdigit',
304 'a.isdigit',
289 'a.islower',
305 'a.islower',
290 ],
306 ],
291 'metadata' : {},
307 'metadata' : {},
292 })
308 })
293 v5, v4 = self.adapt(msg)
309 v5, v4 = self.adapt(msg)
294 v4c = v4['content']
310 v4c = v4['content']
295 v5c = v5['content']
311 v5c = v5['content']
296 self.assertEqual(v4c['matched_text'], 'a.is')
312 self.assertEqual(v4c['matched_text'], 'a.is')
297 self.assertEqual(v4c['matches'], v5c['matches'])
313 self.assertEqual(v4c['matches'], v5c['matches'])
298
314
299 def test_inspect_request(self):
315 def test_inspect_request(self):
300 msg = self.msg("inspect_request", {
316 msg = self.msg("inspect_request", {
301 'code' : 'def foo():\n'
317 'code' : 'def foo():\n'
302 ' apple\n'
318 ' apple\n'
303 'bar()',
319 'bar()',
304 'cursor_pos': 18,
320 'cursor_pos': 18,
305 'detail_level' : 1,
321 'detail_level' : 1,
306 })
322 })
307 v5, v4 = self.adapt(msg)
323 v5, v4 = self.adapt(msg)
308 self.assertEqual(v4['header']['msg_type'], 'object_info_request')
324 self.assertEqual(v4['header']['msg_type'], 'object_info_request')
309 v4c = v4['content']
325 v4c = v4['content']
310 v5c = v5['content']
326 v5c = v5['content']
311 self.assertEqual(v4c['oname'], 'apple')
327 self.assertEqual(v4c['oname'], 'apple')
312 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
328 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
313
329
314 def test_inspect_reply(self):
330 def test_inspect_reply(self):
315 msg = self.msg("inspect_reply", {
331 msg = self.msg("inspect_reply", {
316 'name' : 'foo',
332 'name' : 'foo',
317 'found' : True,
333 'found' : True,
318 'data' : {'text/plain' : 'some text'},
334 'data' : {'text/plain' : 'some text'},
319 'metadata' : {},
335 'metadata' : {},
320 })
336 })
321 v5, v4 = self.adapt(msg)
337 v5, v4 = self.adapt(msg)
322 self.assertEqual(v4['header']['msg_type'], 'object_info_reply')
338 self.assertEqual(v4['header']['msg_type'], 'object_info_reply')
323 v4c = v4['content']
339 v4c = v4['content']
324 v5c = v5['content']
340 v5c = v5['content']
325 self.assertEqual(sorted(v4c), ['found', 'oname'])
341 self.assertEqual(sorted(v4c), ['found', 'oname'])
326 self.assertEqual(v4c['found'], False)
342 self.assertEqual(v4c['found'], False)
327
343
328 def test_kernel_info_reply(self):
344 def test_kernel_info_reply(self):
329 msg = self.msg("kernel_info_reply", {
345 msg = self.msg("kernel_info_reply", {
330 'protocol_version': '5.0',
346 'protocol_version': '5.0',
331 'implementation': 'ipython',
347 'implementation': 'ipython',
332 'implementation_version': '1.2.3',
348 'implementation_version': '1.2.3',
333 'language_info': {
349 'language_info': {
334 'name': 'python',
350 'name': 'python',
335 'version': '2.8.0',
351 'version': '2.8.0',
336 'mimetype': 'text/x-python',
352 'mimetype': 'text/x-python',
337 },
353 },
338 'banner' : 'the banner',
354 'banner' : 'the banner',
339 })
355 })
340 v5, v4 = self.adapt(msg)
356 v5, v4 = self.adapt(msg)
341 v4c = v4['content']
357 v4c = v4['content']
342 v5c = v5['content']
358 v5c = v5['content']
343 info = v5c['language_info']
359 info = v5c['language_info']
344 self.assertEqual(v4c, {
360 self.assertEqual(v4c, {
345 'protocol_version': [5,0],
361 'protocol_version': [5,0],
346 'language': 'python',
362 'language': 'python',
347 'language_version': [2,8,0],
363 'language_version': [2,8,0],
348 'ipython_version': [1,2,3],
364 'ipython_version': [1,2,3],
349 })
365 })
350
366
351 # iopub channel
367 # iopub channel
352
368
353 def test_display_data(self):
369 def test_display_data(self):
354 jsondata = dict(a=5)
370 jsondata = dict(a=5)
355 msg = self.msg("display_data", {
371 msg = self.msg("display_data", {
356 'data' : {
372 'data' : {
357 'text/plain' : 'some text',
373 'text/plain' : 'some text',
358 'application/json' : jsondata,
374 'application/json' : jsondata,
359 },
375 },
360 'metadata' : {'text/plain' : { 'key' : 'value' }},
376 'metadata' : {'text/plain' : { 'key' : 'value' }},
361 })
377 })
362 v5, v4 = self.adapt(msg)
378 v5, v4 = self.adapt(msg)
363 v4c = v4['content']
379 v4c = v4['content']
364 v5c = v5['content']
380 v5c = v5['content']
365 self.assertEqual(v5c['metadata'], v4c['metadata'])
381 self.assertEqual(v5c['metadata'], v4c['metadata'])
366 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
382 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
367 self.assertEqual(v4c['data']['application/json'], json.dumps(jsondata))
383 self.assertEqual(v4c['data']['application/json'], json.dumps(jsondata))
368
384
369 # stdin channel
385 # stdin channel
370
386
371 def test_input_request(self):
387 def test_input_request(self):
372 msg = self.msg('input_request', {'prompt': "$>", 'password' : True})
388 msg = self.msg('input_request', {'prompt': "$>", 'password' : True})
373 v5, v4 = self.adapt(msg)
389 v5, v4 = self.adapt(msg)
374 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
390 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
375 self.assertNotIn('password', v4['content'])
391 self.assertNotIn('password', v4['content'])
376
392
377
393
General Comments 0
You need to be logged in to leave comments. Login now