##// END OF EJS Templates
Merge pull request #4601 from minrk/test-rename-existing...
Thomas Kluyver -
r13711:a4217d3f merge
parent child Browse files
Show More
@@ -1,319 +1,323 b''
1 1 # coding: utf-8
2 2 """Test the notebooks webservice API."""
3 3
4 4 import io
5 5 import json
6 6 import os
7 7 import shutil
8 8 from unicodedata import normalize
9 9
10 10 pjoin = os.path.join
11 11
12 12 import requests
13 13
14 14 from IPython.html.utils import url_path_join, url_escape
15 15 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
16 16 from IPython.nbformat import current
17 17 from IPython.nbformat.current import (new_notebook, write, read, new_worksheet,
18 18 new_heading_cell, to_notebook_json)
19 19 from IPython.nbformat import v2
20 20 from IPython.utils import py3compat
21 21 from IPython.utils.data import uniq_stable
22 22
23 23
24 24 class NBAPI(object):
25 25 """Wrapper for notebook API calls."""
26 26 def __init__(self, base_url):
27 27 self.base_url = base_url
28 28
29 29 def _req(self, verb, path, body=None):
30 30 response = requests.request(verb,
31 31 url_path_join(self.base_url, 'api/notebooks', path),
32 32 data=body,
33 33 )
34 34 response.raise_for_status()
35 35 return response
36 36
37 37 def list(self, path='/'):
38 38 return self._req('GET', path)
39 39
40 40 def read(self, name, path='/'):
41 41 return self._req('GET', url_path_join(path, name))
42 42
43 43 def create_untitled(self, path='/'):
44 44 return self._req('POST', path)
45 45
46 46 def upload_untitled(self, body, path='/'):
47 47 return self._req('POST', path, body)
48 48
49 49 def copy_untitled(self, copy_from, path='/'):
50 50 body = json.dumps({'copy_from':copy_from})
51 51 return self._req('POST', path, body)
52 52
53 53 def create(self, name, path='/'):
54 54 return self._req('PUT', url_path_join(path, name))
55 55
56 56 def upload(self, name, body, path='/'):
57 57 return self._req('PUT', url_path_join(path, name), body)
58 58
59 59 def copy(self, copy_from, copy_to, path='/'):
60 60 body = json.dumps({'copy_from':copy_from})
61 61 return self._req('PUT', url_path_join(path, copy_to), body)
62 62
63 63 def save(self, name, body, path='/'):
64 64 return self._req('PUT', url_path_join(path, name), body)
65 65
66 66 def delete(self, name, path='/'):
67 67 return self._req('DELETE', url_path_join(path, name))
68 68
69 69 def rename(self, name, path, new_name):
70 70 body = json.dumps({'name': new_name})
71 71 return self._req('PATCH', url_path_join(path, name), body)
72 72
73 73 def get_checkpoints(self, name, path):
74 74 return self._req('GET', url_path_join(path, name, 'checkpoints'))
75 75
76 76 def new_checkpoint(self, name, path):
77 77 return self._req('POST', url_path_join(path, name, 'checkpoints'))
78 78
79 79 def restore_checkpoint(self, name, path, checkpoint_id):
80 80 return self._req('POST', url_path_join(path, name, 'checkpoints', checkpoint_id))
81 81
82 82 def delete_checkpoint(self, name, path, checkpoint_id):
83 83 return self._req('DELETE', url_path_join(path, name, 'checkpoints', checkpoint_id))
84 84
85 85 class APITest(NotebookTestBase):
86 86 """Test the kernels web service API"""
87 87 dirs_nbs = [('', 'inroot'),
88 88 ('Directory with spaces in', 'inspace'),
89 89 (u'unicodΓ©', 'innonascii'),
90 90 ('foo', 'a'),
91 91 ('foo', 'b'),
92 92 ('foo', 'name with spaces'),
93 93 ('foo', u'unicodΓ©'),
94 94 ('foo/bar', 'baz'),
95 95 (u'Γ₯ b', u'Γ§ d')
96 96 ]
97 97
98 98 dirs = uniq_stable([d for (d,n) in dirs_nbs])
99 99 del dirs[0] # remove ''
100 100
101 101 def setUp(self):
102 102 nbdir = self.notebook_dir.name
103 103
104 104 for d in self.dirs:
105 105 d.replace('/', os.sep)
106 106 if not os.path.isdir(pjoin(nbdir, d)):
107 107 os.mkdir(pjoin(nbdir, d))
108 108
109 109 for d, name in self.dirs_nbs:
110 110 d = d.replace('/', os.sep)
111 111 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
112 112 encoding='utf-8') as f:
113 113 nb = new_notebook(name=name)
114 114 write(nb, f, format='ipynb')
115 115
116 116 self.nb_api = NBAPI(self.base_url())
117 117
118 118 def tearDown(self):
119 119 nbdir = self.notebook_dir.name
120 120
121 121 for dname in ['foo', 'Directory with spaces in', u'unicodΓ©', u'Γ₯ b']:
122 122 shutil.rmtree(pjoin(nbdir, dname), ignore_errors=True)
123 123
124 124 if os.path.isfile(pjoin(nbdir, 'inroot.ipynb')):
125 125 os.unlink(pjoin(nbdir, 'inroot.ipynb'))
126 126
127 127 def test_list_notebooks(self):
128 128 nbs = self.nb_api.list().json()
129 129 self.assertEqual(len(nbs), 1)
130 130 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
131 131
132 132 nbs = self.nb_api.list('/Directory with spaces in/').json()
133 133 self.assertEqual(len(nbs), 1)
134 134 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
135 135
136 136 nbs = self.nb_api.list(u'/unicodΓ©/').json()
137 137 self.assertEqual(len(nbs), 1)
138 138 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
139 139 self.assertEqual(nbs[0]['path'], u'unicodΓ©')
140 140
141 141 nbs = self.nb_api.list('/foo/bar/').json()
142 142 self.assertEqual(len(nbs), 1)
143 143 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
144 144 self.assertEqual(nbs[0]['path'], 'foo/bar')
145 145
146 146 nbs = self.nb_api.list('foo').json()
147 147 self.assertEqual(len(nbs), 4)
148 148 nbnames = { normalize('NFC', n['name']) for n in nbs }
149 149 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodΓ©.ipynb']
150 150 expected = { normalize('NFC', name) for name in expected }
151 151 self.assertEqual(nbnames, expected)
152 152
153 153 def test_list_nonexistant_dir(self):
154 154 with assert_http_error(404):
155 155 self.nb_api.list('nonexistant')
156 156
157 157 def test_get_contents(self):
158 158 for d, name in self.dirs_nbs:
159 159 nb = self.nb_api.read('%s.ipynb' % name, d+'/').json()
160 160 self.assertEqual(nb['name'], u'%s.ipynb' % name)
161 161 self.assertIn('content', nb)
162 162 self.assertIn('metadata', nb['content'])
163 163 self.assertIsInstance(nb['content']['metadata'], dict)
164 164
165 165 # Name that doesn't exist - should be a 404
166 166 with assert_http_error(404):
167 167 self.nb_api.read('q.ipynb', 'foo')
168 168
169 169 def _check_nb_created(self, resp, name, path):
170 170 self.assertEqual(resp.status_code, 201)
171 171 location_header = py3compat.str_to_unicode(resp.headers['Location'])
172 172 self.assertEqual(location_header, url_escape(url_path_join(u'/api/notebooks', path, name)))
173 173 self.assertEqual(resp.json()['name'], name)
174 174 assert os.path.isfile(pjoin(
175 175 self.notebook_dir.name,
176 176 path.replace('/', os.sep),
177 177 name,
178 178 ))
179 179
180 180 def test_create_untitled(self):
181 181 resp = self.nb_api.create_untitled(path=u'Γ₯ b')
182 182 self._check_nb_created(resp, 'Untitled0.ipynb', u'Γ₯ b')
183 183
184 184 # Second time
185 185 resp = self.nb_api.create_untitled(path=u'Γ₯ b')
186 186 self._check_nb_created(resp, 'Untitled1.ipynb', u'Γ₯ b')
187 187
188 188 # And two directories down
189 189 resp = self.nb_api.create_untitled(path='foo/bar')
190 190 self._check_nb_created(resp, 'Untitled0.ipynb', 'foo/bar')
191 191
192 192 def test_upload_untitled(self):
193 193 nb = new_notebook(name='Upload test')
194 194 nbmodel = {'content': nb}
195 195 resp = self.nb_api.upload_untitled(path=u'Γ₯ b',
196 196 body=json.dumps(nbmodel))
197 197 self._check_nb_created(resp, 'Untitled0.ipynb', u'Γ₯ b')
198 198
199 199 def test_upload(self):
200 200 nb = new_notebook(name=u'ignored')
201 201 nbmodel = {'content': nb}
202 202 resp = self.nb_api.upload(u'Upload tΓ©st.ipynb', path=u'Γ₯ b',
203 203 body=json.dumps(nbmodel))
204 204 self._check_nb_created(resp, u'Upload tΓ©st.ipynb', u'Γ₯ b')
205 205
206 206 def test_upload_v2(self):
207 207 nb = v2.new_notebook()
208 208 ws = v2.new_worksheet()
209 209 nb.worksheets.append(ws)
210 210 ws.cells.append(v2.new_code_cell(input='print("hi")'))
211 211 nbmodel = {'content': nb}
212 212 resp = self.nb_api.upload(u'Upload tΓ©st.ipynb', path=u'Γ₯ b',
213 213 body=json.dumps(nbmodel))
214 214 self._check_nb_created(resp, u'Upload tΓ©st.ipynb', u'Γ₯ b')
215 215 resp = self.nb_api.read(u'Upload tΓ©st.ipynb', u'Γ₯ b')
216 216 data = resp.json()
217 217 self.assertEqual(data['content']['nbformat'], current.nbformat)
218 218 self.assertEqual(data['content']['orig_nbformat'], 2)
219 219
220 220 def test_copy_untitled(self):
221 221 resp = self.nb_api.copy_untitled(u'Γ§ d.ipynb', path=u'Γ₯ b')
222 222 self._check_nb_created(resp, u'Γ§ d-Copy0.ipynb', u'Γ₯ b')
223 223
224 224 def test_copy(self):
225 225 resp = self.nb_api.copy(u'Γ§ d.ipynb', u'cΓΈpy.ipynb', path=u'Γ₯ b')
226 226 self._check_nb_created(resp, u'cΓΈpy.ipynb', u'Γ₯ b')
227 227
228 228 def test_delete(self):
229 229 for d, name in self.dirs_nbs:
230 230 resp = self.nb_api.delete('%s.ipynb' % name, d)
231 231 self.assertEqual(resp.status_code, 204)
232 232
233 233 for d in self.dirs + ['/']:
234 234 nbs = self.nb_api.list(d).json()
235 235 self.assertEqual(len(nbs), 0)
236 236
237 237 def test_rename(self):
238 238 resp = self.nb_api.rename('a.ipynb', 'foo', 'z.ipynb')
239 239 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
240 240 self.assertEqual(resp.json()['name'], 'z.ipynb')
241 241 assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
242 242
243 243 nbs = self.nb_api.list('foo').json()
244 244 nbnames = set(n['name'] for n in nbs)
245 245 self.assertIn('z.ipynb', nbnames)
246 246 self.assertNotIn('a.ipynb', nbnames)
247 247
248 def test_rename_existing(self):
249 with assert_http_error(409):
250 self.nb_api.rename('a.ipynb', 'foo', 'b.ipynb')
251
248 252 def test_save(self):
249 253 resp = self.nb_api.read('a.ipynb', 'foo')
250 254 nbcontent = json.loads(resp.text)['content']
251 255 nb = to_notebook_json(nbcontent)
252 256 ws = new_worksheet()
253 257 nb.worksheets = [ws]
254 258 ws.cells.append(new_heading_cell(u'Created by test Β³'))
255 259
256 260 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
257 261 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
258 262
259 263 nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
260 264 with io.open(nbfile, 'r', encoding='utf-8') as f:
261 265 newnb = read(f, format='ipynb')
262 266 self.assertEqual(newnb.worksheets[0].cells[0].source,
263 267 u'Created by test Β³')
264 268 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
265 269 newnb = to_notebook_json(nbcontent)
266 270 self.assertEqual(newnb.worksheets[0].cells[0].source,
267 271 u'Created by test Β³')
268 272
269 273 # Save and rename
270 274 nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb}
271 275 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
272 276 saved = resp.json()
273 277 self.assertEqual(saved['name'], 'a2.ipynb')
274 278 self.assertEqual(saved['path'], 'foo/bar')
275 279 assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
276 280 assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
277 281 with assert_http_error(404):
278 282 self.nb_api.read('a.ipynb', 'foo')
279 283
280 284 def test_checkpoints(self):
281 285 resp = self.nb_api.read('a.ipynb', 'foo')
282 286 r = self.nb_api.new_checkpoint('a.ipynb', 'foo')
283 287 self.assertEqual(r.status_code, 201)
284 288 cp1 = r.json()
285 289 self.assertEqual(set(cp1), {'id', 'last_modified'})
286 290 self.assertEqual(r.headers['Location'].split('/')[-1], cp1['id'])
287 291
288 292 # Modify it
289 293 nbcontent = json.loads(resp.text)['content']
290 294 nb = to_notebook_json(nbcontent)
291 295 ws = new_worksheet()
292 296 nb.worksheets = [ws]
293 297 hcell = new_heading_cell('Created by test')
294 298 ws.cells.append(hcell)
295 299 # Save
296 300 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
297 301 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
298 302
299 303 # List checkpoints
300 304 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
301 305 self.assertEqual(cps, [cp1])
302 306
303 307 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
304 308 nb = to_notebook_json(nbcontent)
305 309 self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
306 310
307 311 # Restore cp1
308 312 r = self.nb_api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
309 313 self.assertEqual(r.status_code, 204)
310 314 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
311 315 nb = to_notebook_json(nbcontent)
312 316 self.assertEqual(nb.worksheets, [])
313 317
314 318 # Delete cp1
315 319 r = self.nb_api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
316 320 self.assertEqual(r.status_code, 204)
317 321 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
318 322 self.assertEqual(cps, [])
319 323
General Comments 0
You need to be logged in to leave comments. Login now