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