##// END OF EJS Templates
test creating a directory with PUT
MinRK -
Show More
@@ -1,449 +1,459 b''
1 1 # coding: utf-8
2 2 """Test the contents webservice API."""
3 3
4 4 import base64
5 5 import io
6 6 import json
7 7 import os
8 8 import shutil
9 9 from unicodedata import normalize
10 10
11 11 pjoin = os.path.join
12 12
13 13 import requests
14 14
15 15 from IPython.html.utils import url_path_join, url_escape
16 16 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
17 17 from IPython.nbformat import current
18 18 from IPython.nbformat.current import (new_notebook, write, read, new_worksheet,
19 19 new_heading_cell, to_notebook_json)
20 20 from IPython.nbformat import v2
21 21 from IPython.utils import py3compat
22 22 from IPython.utils.data import uniq_stable
23 23
24 24
25 25 # TODO: Remove this after we create the contents web service and directories are
26 26 # no longer listed by the notebook web service.
27 27 def notebooks_only(dir_model):
28 28 return [nb for nb in dir_model['content'] if nb['type']=='notebook']
29 29
30 30 def dirs_only(dir_model):
31 31 return [x for x in dir_model['content'] if x['type']=='directory']
32 32
33 33
34 34 class API(object):
35 35 """Wrapper for contents API calls."""
36 36 def __init__(self, base_url):
37 37 self.base_url = base_url
38 38
39 39 def _req(self, verb, path, body=None):
40 40 response = requests.request(verb,
41 41 url_path_join(self.base_url, 'api/contents', path),
42 42 data=body,
43 43 )
44 44 response.raise_for_status()
45 45 return response
46 46
47 47 def list(self, path='/'):
48 48 return self._req('GET', path)
49 49
50 50 def read(self, name, path='/'):
51 51 return self._req('GET', url_path_join(path, name))
52 52
53 53 def create_untitled(self, path='/', ext=None):
54 54 body = None
55 55 if ext:
56 56 body = json.dumps({'ext': ext})
57 57 return self._req('POST', path, body)
58 58
59 59 def upload_untitled(self, body, path='/'):
60 60 return self._req('POST', path, body)
61 61
62 62 def copy_untitled(self, copy_from, path='/'):
63 63 body = json.dumps({'copy_from':copy_from})
64 64 return self._req('POST', path, body)
65 65
66 66 def create(self, name, path='/'):
67 67 return self._req('PUT', url_path_join(path, name))
68 68
69 69 def upload(self, name, body, path='/'):
70 70 return self._req('PUT', url_path_join(path, name), body)
71 71
72 72 def copy(self, copy_from, copy_to, path='/'):
73 73 body = json.dumps({'copy_from':copy_from})
74 74 return self._req('PUT', url_path_join(path, copy_to), body)
75 75
76 76 def save(self, name, body, path='/'):
77 77 return self._req('PUT', url_path_join(path, name), body)
78 78
79 79 def delete(self, name, path='/'):
80 80 return self._req('DELETE', url_path_join(path, name))
81 81
82 82 def rename(self, name, path, new_name):
83 83 body = json.dumps({'name': new_name})
84 84 return self._req('PATCH', url_path_join(path, name), body)
85 85
86 86 def get_checkpoints(self, name, path):
87 87 return self._req('GET', url_path_join(path, name, 'checkpoints'))
88 88
89 89 def new_checkpoint(self, name, path):
90 90 return self._req('POST', url_path_join(path, name, 'checkpoints'))
91 91
92 92 def restore_checkpoint(self, name, path, checkpoint_id):
93 93 return self._req('POST', url_path_join(path, name, 'checkpoints', checkpoint_id))
94 94
95 95 def delete_checkpoint(self, name, path, checkpoint_id):
96 96 return self._req('DELETE', url_path_join(path, name, 'checkpoints', checkpoint_id))
97 97
98 98 class APITest(NotebookTestBase):
99 99 """Test the kernels web service API"""
100 100 dirs_nbs = [('', 'inroot'),
101 101 ('Directory with spaces in', 'inspace'),
102 102 (u'unicodΓ©', 'innonascii'),
103 103 ('foo', 'a'),
104 104 ('foo', 'b'),
105 105 ('foo', 'name with spaces'),
106 106 ('foo', u'unicodΓ©'),
107 107 ('foo/bar', 'baz'),
108 108 ('ordering', 'A'),
109 109 ('ordering', 'b'),
110 110 ('ordering', 'C'),
111 111 (u'Γ₯ b', u'Γ§ d'),
112 112 ]
113 113 hidden_dirs = ['.hidden', '__pycache__']
114 114
115 115 dirs = uniq_stable([py3compat.cast_unicode(d) for (d,n) in dirs_nbs])
116 116 del dirs[0] # remove ''
117 117 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
118 118
119 119 @staticmethod
120 120 def _blob_for_name(name):
121 121 return name.encode('utf-8') + b'\xFF'
122 122
123 123 @staticmethod
124 124 def _txt_for_name(name):
125 125 return u'%s text file' % name
126 126
127 127 def setUp(self):
128 128 nbdir = self.notebook_dir.name
129 129 self.blob = os.urandom(100)
130 130 self.b64_blob = base64.encodestring(self.blob).decode('ascii')
131 131
132 132
133 133
134 134 for d in (self.dirs + self.hidden_dirs):
135 135 d.replace('/', os.sep)
136 136 if not os.path.isdir(pjoin(nbdir, d)):
137 137 os.mkdir(pjoin(nbdir, d))
138 138
139 139 for d, name in self.dirs_nbs:
140 140 d = d.replace('/', os.sep)
141 141 # create a notebook
142 142 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
143 143 encoding='utf-8') as f:
144 144 nb = new_notebook(name=name)
145 145 write(nb, f, format='ipynb')
146 146
147 147 # create a text file
148 148 with io.open(pjoin(nbdir, d, '%s.txt' % name), 'w',
149 149 encoding='utf-8') as f:
150 150 f.write(self._txt_for_name(name))
151 151
152 152 # create a binary file
153 153 with io.open(pjoin(nbdir, d, '%s.blob' % name), 'wb') as f:
154 154 f.write(self._blob_for_name(name))
155 155
156 156 self.api = API(self.base_url())
157 157
158 158 def tearDown(self):
159 159 nbdir = self.notebook_dir.name
160 160
161 161 for dname in (list(self.top_level_dirs) + self.hidden_dirs):
162 162 shutil.rmtree(pjoin(nbdir, dname), ignore_errors=True)
163 163
164 164 if os.path.isfile(pjoin(nbdir, 'inroot.ipynb')):
165 165 os.unlink(pjoin(nbdir, 'inroot.ipynb'))
166 166
167 167 def test_list_notebooks(self):
168 168 nbs = notebooks_only(self.api.list().json())
169 169 self.assertEqual(len(nbs), 1)
170 170 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
171 171
172 172 nbs = notebooks_only(self.api.list('/Directory with spaces in/').json())
173 173 self.assertEqual(len(nbs), 1)
174 174 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
175 175
176 176 nbs = notebooks_only(self.api.list(u'/unicodΓ©/').json())
177 177 self.assertEqual(len(nbs), 1)
178 178 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
179 179 self.assertEqual(nbs[0]['path'], u'unicodΓ©')
180 180
181 181 nbs = notebooks_only(self.api.list('/foo/bar/').json())
182 182 self.assertEqual(len(nbs), 1)
183 183 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
184 184 self.assertEqual(nbs[0]['path'], 'foo/bar')
185 185
186 186 nbs = notebooks_only(self.api.list('foo').json())
187 187 self.assertEqual(len(nbs), 4)
188 188 nbnames = { normalize('NFC', n['name']) for n in nbs }
189 189 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodΓ©.ipynb']
190 190 expected = { normalize('NFC', name) for name in expected }
191 191 self.assertEqual(nbnames, expected)
192 192
193 193 nbs = notebooks_only(self.api.list('ordering').json())
194 194 nbnames = [n['name'] for n in nbs]
195 195 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
196 196 self.assertEqual(nbnames, expected)
197 197
198 198 def test_list_dirs(self):
199 199 dirs = dirs_only(self.api.list().json())
200 200 dir_names = {normalize('NFC', d['name']) for d in dirs}
201 201 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
202 202
203 203 def test_list_nonexistant_dir(self):
204 204 with assert_http_error(404):
205 205 self.api.list('nonexistant')
206 206
207 207 def test_get_nb_contents(self):
208 208 for d, name in self.dirs_nbs:
209 209 nb = self.api.read('%s.ipynb' % name, d+'/').json()
210 210 self.assertEqual(nb['name'], u'%s.ipynb' % name)
211 211 self.assertEqual(nb['type'], 'notebook')
212 212 self.assertIn('content', nb)
213 213 self.assertEqual(nb['format'], 'json')
214 214 self.assertIn('content', nb)
215 215 self.assertIn('metadata', nb['content'])
216 216 self.assertIsInstance(nb['content']['metadata'], dict)
217 217
218 218 def test_get_contents_no_such_file(self):
219 219 # Name that doesn't exist - should be a 404
220 220 with assert_http_error(404):
221 221 self.api.read('q.ipynb', 'foo')
222 222
223 223 def test_get_text_file_contents(self):
224 224 for d, name in self.dirs_nbs:
225 225 model = self.api.read(u'%s.txt' % name, d+'/').json()
226 226 self.assertEqual(model['name'], u'%s.txt' % name)
227 227 self.assertIn('content', model)
228 228 self.assertEqual(model['format'], 'text')
229 229 self.assertEqual(model['type'], 'file')
230 230 self.assertEqual(model['content'], self._txt_for_name(name))
231 231
232 232 # Name that doesn't exist - should be a 404
233 233 with assert_http_error(404):
234 234 self.api.read('q.txt', 'foo')
235 235
236 236 def test_get_binary_file_contents(self):
237 237 for d, name in self.dirs_nbs:
238 238 model = self.api.read(u'%s.blob' % name, d+'/').json()
239 239 self.assertEqual(model['name'], u'%s.blob' % name)
240 240 self.assertIn('content', model)
241 241 self.assertEqual(model['format'], 'base64')
242 242 self.assertEqual(model['type'], 'file')
243 243 b64_data = base64.encodestring(self._blob_for_name(name)).decode('ascii')
244 244 self.assertEqual(model['content'], b64_data)
245 245
246 246 # Name that doesn't exist - should be a 404
247 247 with assert_http_error(404):
248 248 self.api.read('q.txt', 'foo')
249 249
250 def _check_nb_created(self, resp, name, path):
250 def _check_created(self, resp, name, path, type='notebook'):
251 251 self.assertEqual(resp.status_code, 201)
252 252 location_header = py3compat.str_to_unicode(resp.headers['Location'])
253 253 self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path, name)))
254 self.assertEqual(resp.json()['name'], name)
255 assert os.path.isfile(pjoin(
254 rjson = resp.json()
255 self.assertEqual(rjson['name'], name)
256 self.assertEqual(rjson['path'], path)
257 self.assertEqual(rjson['type'], type)
258 isright = os.path.isdir if type == 'directory' else os.path.isfile
259 assert isright(pjoin(
256 260 self.notebook_dir.name,
257 261 path.replace('/', os.sep),
258 262 name,
259 263 ))
260 264
261 265 def test_create_untitled(self):
262 266 resp = self.api.create_untitled(path=u'Γ₯ b')
263 self._check_nb_created(resp, 'Untitled0.ipynb', u'Γ₯ b')
267 self._check_created(resp, 'Untitled0.ipynb', u'Γ₯ b')
264 268
265 269 # Second time
266 270 resp = self.api.create_untitled(path=u'Γ₯ b')
267 self._check_nb_created(resp, 'Untitled1.ipynb', u'Γ₯ b')
271 self._check_created(resp, 'Untitled1.ipynb', u'Γ₯ b')
268 272
269 273 # And two directories down
270 274 resp = self.api.create_untitled(path='foo/bar')
271 self._check_nb_created(resp, 'Untitled0.ipynb', 'foo/bar')
275 self._check_created(resp, 'Untitled0.ipynb', 'foo/bar')
272 276
273 277 def test_create_untitled_txt(self):
274 278 resp = self.api.create_untitled(path='foo/bar', ext='.txt')
275 self._check_nb_created(resp, 'Untitled0.txt', 'foo/bar')
279 self._check_created(resp, 'Untitled0.txt', 'foo/bar', type='file')
276 280
277 281 resp = self.api.read(path='foo/bar', name='Untitled0.txt')
278 282 model = resp.json()
279 283 self.assertEqual(model['type'], 'file')
280 284 self.assertEqual(model['format'], 'text')
281 285 self.assertEqual(model['content'], '')
282 286
283 287 def test_upload_untitled(self):
284 288 nb = new_notebook(name='Upload test')
285 289 nbmodel = {'content': nb, 'type': 'notebook'}
286 290 resp = self.api.upload_untitled(path=u'Γ₯ b',
287 291 body=json.dumps(nbmodel))
288 self._check_nb_created(resp, 'Untitled0.ipynb', u'Γ₯ b')
292 self._check_created(resp, 'Untitled0.ipynb', u'Γ₯ b')
289 293
290 294 def test_upload(self):
291 295 nb = new_notebook(name=u'ignored')
292 296 nbmodel = {'content': nb, 'type': 'notebook'}
293 297 resp = self.api.upload(u'Upload tΓ©st.ipynb', path=u'Γ₯ b',
294 298 body=json.dumps(nbmodel))
295 self._check_nb_created(resp, u'Upload tΓ©st.ipynb', u'Γ₯ b')
299 self._check_created(resp, u'Upload tΓ©st.ipynb', u'Γ₯ b')
300
301 def test_mkdir(self):
302 model = {'type': 'directory'}
303 resp = self.api.upload(u'New βˆ‚ir', path=u'Γ₯ b',
304 body=json.dumps(model))
305 self._check_created(resp, u'New βˆ‚ir', u'Γ₯ b', type='directory')
296 306
297 307 def test_upload_txt(self):
298 308 body = u'ΓΌnicode tΓ©xt'
299 309 model = {
300 310 'content' : body,
301 311 'format' : 'text',
302 312 'type' : 'file',
303 313 }
304 314 resp = self.api.upload(u'Upload tΓ©st.txt', path=u'Γ₯ b',
305 315 body=json.dumps(model))
306 316
307 317 # check roundtrip
308 318 resp = self.api.read(path=u'Γ₯ b', name=u'Upload tΓ©st.txt')
309 319 model = resp.json()
310 320 self.assertEqual(model['type'], 'file')
311 321 self.assertEqual(model['format'], 'text')
312 322 self.assertEqual(model['content'], body)
313 323
314 324 def test_upload_b64(self):
315 325 body = b'\xFFblob'
316 326 b64body = base64.encodestring(body).decode('ascii')
317 327 model = {
318 328 'content' : b64body,
319 329 'format' : 'base64',
320 330 'type' : 'file',
321 331 }
322 332 resp = self.api.upload(u'Upload tΓ©st.blob', path=u'Γ₯ b',
323 333 body=json.dumps(model))
324 334
325 335 # check roundtrip
326 336 resp = self.api.read(path=u'Γ₯ b', name=u'Upload tΓ©st.blob')
327 337 model = resp.json()
328 338 self.assertEqual(model['type'], 'file')
329 339 self.assertEqual(model['format'], 'base64')
330 340 decoded = base64.decodestring(model['content'].encode('ascii'))
331 341 self.assertEqual(decoded, body)
332 342
333 343 def test_upload_v2(self):
334 344 nb = v2.new_notebook()
335 345 ws = v2.new_worksheet()
336 346 nb.worksheets.append(ws)
337 347 ws.cells.append(v2.new_code_cell(input='print("hi")'))
338 348 nbmodel = {'content': nb, 'type': 'notebook'}
339 349 resp = self.api.upload(u'Upload tΓ©st.ipynb', path=u'Γ₯ b',
340 350 body=json.dumps(nbmodel))
341 self._check_nb_created(resp, u'Upload tΓ©st.ipynb', u'Γ₯ b')
351 self._check_created(resp, u'Upload tΓ©st.ipynb', u'Γ₯ b')
342 352 resp = self.api.read(u'Upload tΓ©st.ipynb', u'Γ₯ b')
343 353 data = resp.json()
344 354 self.assertEqual(data['content']['nbformat'], current.nbformat)
345 355 self.assertEqual(data['content']['orig_nbformat'], 2)
346 356
347 357 def test_copy_untitled(self):
348 358 resp = self.api.copy_untitled(u'Γ§ d.ipynb', path=u'Γ₯ b')
349 self._check_nb_created(resp, u'Γ§ d-Copy0.ipynb', u'Γ₯ b')
359 self._check_created(resp, u'Γ§ d-Copy0.ipynb', u'Γ₯ b')
350 360
351 361 def test_copy(self):
352 362 resp = self.api.copy(u'Γ§ d.ipynb', u'cΓΈpy.ipynb', path=u'Γ₯ b')
353 self._check_nb_created(resp, u'cΓΈpy.ipynb', u'Γ₯ b')
363 self._check_created(resp, u'cΓΈpy.ipynb', u'Γ₯ b')
354 364
355 365 def test_delete(self):
356 366 for d, name in self.dirs_nbs:
357 367 resp = self.api.delete('%s.ipynb' % name, d)
358 368 self.assertEqual(resp.status_code, 204)
359 369
360 370 for d in self.dirs + ['/']:
361 371 nbs = notebooks_only(self.api.list(d).json())
362 372 self.assertEqual(len(nbs), 0)
363 373
364 374 def test_rename(self):
365 375 resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb')
366 376 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
367 377 self.assertEqual(resp.json()['name'], 'z.ipynb')
368 378 assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
369 379
370 380 nbs = notebooks_only(self.api.list('foo').json())
371 381 nbnames = set(n['name'] for n in nbs)
372 382 self.assertIn('z.ipynb', nbnames)
373 383 self.assertNotIn('a.ipynb', nbnames)
374 384
375 385 def test_rename_existing(self):
376 386 with assert_http_error(409):
377 387 self.api.rename('a.ipynb', 'foo', 'b.ipynb')
378 388
379 389 def test_save(self):
380 390 resp = self.api.read('a.ipynb', 'foo')
381 391 nbcontent = json.loads(resp.text)['content']
382 392 nb = to_notebook_json(nbcontent)
383 393 ws = new_worksheet()
384 394 nb.worksheets = [ws]
385 395 ws.cells.append(new_heading_cell(u'Created by test Β³'))
386 396
387 397 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
388 398 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
389 399
390 400 nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
391 401 with io.open(nbfile, 'r', encoding='utf-8') as f:
392 402 newnb = read(f, format='ipynb')
393 403 self.assertEqual(newnb.worksheets[0].cells[0].source,
394 404 u'Created by test Β³')
395 405 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
396 406 newnb = to_notebook_json(nbcontent)
397 407 self.assertEqual(newnb.worksheets[0].cells[0].source,
398 408 u'Created by test Β³')
399 409
400 410 # Save and rename
401 411 nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb, 'type': 'notebook'}
402 412 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
403 413 saved = resp.json()
404 414 self.assertEqual(saved['name'], 'a2.ipynb')
405 415 self.assertEqual(saved['path'], 'foo/bar')
406 416 assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
407 417 assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
408 418 with assert_http_error(404):
409 419 self.api.read('a.ipynb', 'foo')
410 420
411 421 def test_checkpoints(self):
412 422 resp = self.api.read('a.ipynb', 'foo')
413 423 r = self.api.new_checkpoint('a.ipynb', 'foo')
414 424 self.assertEqual(r.status_code, 201)
415 425 cp1 = r.json()
416 426 self.assertEqual(set(cp1), {'id', 'last_modified'})
417 427 self.assertEqual(r.headers['Location'].split('/')[-1], cp1['id'])
418 428
419 429 # Modify it
420 430 nbcontent = json.loads(resp.text)['content']
421 431 nb = to_notebook_json(nbcontent)
422 432 ws = new_worksheet()
423 433 nb.worksheets = [ws]
424 434 hcell = new_heading_cell('Created by test')
425 435 ws.cells.append(hcell)
426 436 # Save
427 437 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
428 438 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
429 439
430 440 # List checkpoints
431 441 cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
432 442 self.assertEqual(cps, [cp1])
433 443
434 444 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
435 445 nb = to_notebook_json(nbcontent)
436 446 self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
437 447
438 448 # Restore cp1
439 449 r = self.api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
440 450 self.assertEqual(r.status_code, 204)
441 451 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
442 452 nb = to_notebook_json(nbcontent)
443 453 self.assertEqual(nb.worksheets, [])
444 454
445 455 # Delete cp1
446 456 r = self.api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
447 457 self.assertEqual(r.status_code, 204)
448 458 cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
449 459 self.assertEqual(cps, [])
General Comments 0
You need to be logged in to leave comments. Login now