Show More
@@ -357,7 +357,13 class FileContentsManager(ContentsManager): | |||
|
357 | 357 | """Delete file by name and path.""" |
|
358 | 358 | path = path.strip('/') |
|
359 | 359 | os_path = self._get_os_path(name, path) |
|
360 | if not os.path.isfile(os_path): | |
|
360 | rm = os.unlink | |
|
361 | if os.path.isdir(os_path): | |
|
362 | listing = os.listdir(os_path) | |
|
363 | # don't delete non-empty directories (checkpoints dir doesn't count) | |
|
364 | if listing and listing != ['.ipynb_checkpoints']: | |
|
365 | raise web.HTTPError(400, u'Directory %s not empty' % os_path) | |
|
366 | elif not os.path.isfile(os_path): | |
|
361 | 367 | raise web.HTTPError(404, u'File does not exist: %s' % os_path) |
|
362 | 368 | |
|
363 | 369 | # clear checkpoints |
@@ -368,8 +374,12 class FileContentsManager(ContentsManager): | |||
|
368 | 374 | self.log.debug("Unlinking checkpoint %s", cp_path) |
|
369 | 375 | os.unlink(cp_path) |
|
370 | 376 | |
|
371 | self.log.debug("Unlinking file %s", os_path) | |
|
372 | os.unlink(os_path) | |
|
377 | if os.path.isdir(os_path): | |
|
378 | self.log.debug("Removing directory %s", os_path) | |
|
379 | shutil.rmtree(os_path) | |
|
380 | else: | |
|
381 | self.log.debug("Unlinking file %s", os_path) | |
|
382 | rm(os_path) | |
|
373 | 383 | |
|
374 | 384 | def rename(self, old_name, old_path, new_name, new_path): |
|
375 | 385 | """Rename a file.""" |
@@ -7,6 +7,8 from fnmatch import fnmatch | |||
|
7 | 7 | import itertools |
|
8 | 8 | import os |
|
9 | 9 | |
|
10 | from tornado.web import HTTPError | |
|
11 | ||
|
10 | 12 | from IPython.config.configurable import LoggingConfigurable |
|
11 | 13 | from IPython.nbformat import current, sign |
|
12 | 14 | from IPython.utils.traitlets import Instance, Unicode, List |
@@ -187,6 +189,8 class ContentsManager(LoggingConfigurable): | |||
|
187 | 189 | """ |
|
188 | 190 | path = path.strip('/') |
|
189 | 191 | model = self.get_model(from_name, path) |
|
192 | if model['type'] == 'directory': | |
|
193 | raise HTTPError(400, "Can't copy directories") | |
|
190 | 194 | if not to_name: |
|
191 | 195 | base, ext = os.path.splitext(from_name) |
|
192 | 196 | copy_name = u'{0}-Copy{1}'.format(base, ext) |
@@ -69,6 +69,9 class API(object): | |||
|
69 | 69 | def upload(self, name, body, path='/'): |
|
70 | 70 | return self._req('PUT', url_path_join(path, name), body) |
|
71 | 71 | |
|
72 | def mkdir(self, name, path='/'): | |
|
73 | return self._req('PUT', url_path_join(path, name), json.dumps({'type': 'directory'})) | |
|
74 | ||
|
72 | 75 | def copy(self, copy_from, copy_to, path='/'): |
|
73 | 76 | body = json.dumps({'copy_from':copy_from}) |
|
74 | 77 | return self._req('PUT', url_path_join(path, copy_to), body) |
@@ -299,9 +302,7 class APITest(NotebookTestBase): | |||
|
299 | 302 | self._check_created(resp, u'Upload tést.ipynb', u'å b') |
|
300 | 303 | |
|
301 | 304 | 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 | resp = self.api.mkdir(u'New ∂ir', path=u'å b') | |
|
305 | 306 | self._check_created(resp, u'New ∂ir', u'å b', type='directory') |
|
306 | 307 | |
|
307 | 308 | def test_upload_txt(self): |
@@ -362,6 +363,11 class APITest(NotebookTestBase): | |||
|
362 | 363 | resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b') |
|
363 | 364 | self._check_created(resp, u'cøpy.ipynb', u'å b') |
|
364 | 365 | |
|
366 | def test_copy_dir_400(self): | |
|
367 | # can't copy directories | |
|
368 | with assert_http_error(400): | |
|
369 | resp = self.api.copy(u'å b', u'å c') | |
|
370 | ||
|
365 | 371 | def test_delete(self): |
|
366 | 372 | for d, name in self.dirs_nbs: |
|
367 | 373 | resp = self.api.delete('%s.ipynb' % name, d) |
@@ -371,6 +377,20 class APITest(NotebookTestBase): | |||
|
371 | 377 | nbs = notebooks_only(self.api.list(d).json()) |
|
372 | 378 | self.assertEqual(len(nbs), 0) |
|
373 | 379 | |
|
380 | def test_delete_dirs(self): | |
|
381 | # depth-first delete everything, so we don't try to delete empty directories | |
|
382 | for name in sorted(self.dirs + ['/'], key=len, reverse=True): | |
|
383 | listing = self.api.list(name).json()['content'] | |
|
384 | for model in listing: | |
|
385 | self.api.delete(model['name'], model['path']) | |
|
386 | listing = self.api.list('/').json()['content'] | |
|
387 | self.assertEqual(listing, []) | |
|
388 | ||
|
389 | def test_delete_non_empty_dir(self): | |
|
390 | """delete non-empty dir raises 400""" | |
|
391 | with assert_http_error(400): | |
|
392 | self.api.delete(u'å b') | |
|
393 | ||
|
374 | 394 | def test_rename(self): |
|
375 | 395 | resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb') |
|
376 | 396 | self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb') |
General Comments 0
You need to be logged in to leave comments.
Login now