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