Show More
@@ -357,7 +357,13 class FileContentsManager(ContentsManager): | |||||
357 | """Delete file by name and path.""" |
|
357 | """Delete file by name and path.""" | |
358 | path = path.strip('/') |
|
358 | path = path.strip('/') | |
359 | os_path = self._get_os_path(name, path) |
|
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 | raise web.HTTPError(404, u'File does not exist: %s' % os_path) |
|
367 | raise web.HTTPError(404, u'File does not exist: %s' % os_path) | |
362 |
|
368 | |||
363 | # clear checkpoints |
|
369 | # clear checkpoints | |
@@ -368,8 +374,12 class FileContentsManager(ContentsManager): | |||||
368 | self.log.debug("Unlinking checkpoint %s", cp_path) |
|
374 | self.log.debug("Unlinking checkpoint %s", cp_path) | |
369 | os.unlink(cp_path) |
|
375 | os.unlink(cp_path) | |
370 |
|
376 | |||
371 | self.log.debug("Unlinking file %s", os_path) |
|
377 | if os.path.isdir(os_path): | |
372 | os.unlink(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 | def rename(self, old_name, old_path, new_name, new_path): |
|
384 | def rename(self, old_name, old_path, new_name, new_path): | |
375 | """Rename a file.""" |
|
385 | """Rename a file.""" |
@@ -7,6 +7,8 from fnmatch import fnmatch | |||||
7 | import itertools |
|
7 | import itertools | |
8 | import os |
|
8 | import os | |
9 |
|
9 | |||
|
10 | from tornado.web import HTTPError | |||
|
11 | ||||
10 | from IPython.config.configurable import LoggingConfigurable |
|
12 | from IPython.config.configurable import LoggingConfigurable | |
11 | from IPython.nbformat import current, sign |
|
13 | from IPython.nbformat import current, sign | |
12 | from IPython.utils.traitlets import Instance, Unicode, List |
|
14 | from IPython.utils.traitlets import Instance, Unicode, List | |
@@ -187,6 +189,8 class ContentsManager(LoggingConfigurable): | |||||
187 | """ |
|
189 | """ | |
188 | path = path.strip('/') |
|
190 | path = path.strip('/') | |
189 | model = self.get_model(from_name, path) |
|
191 | model = self.get_model(from_name, path) | |
|
192 | if model['type'] == 'directory': | |||
|
193 | raise HTTPError(400, "Can't copy directories") | |||
190 | if not to_name: |
|
194 | if not to_name: | |
191 | base, ext = os.path.splitext(from_name) |
|
195 | base, ext = os.path.splitext(from_name) | |
192 | copy_name = u'{0}-Copy{1}'.format(base, ext) |
|
196 | copy_name = u'{0}-Copy{1}'.format(base, ext) |
@@ -69,6 +69,9 class API(object): | |||||
69 | def upload(self, name, body, path='/'): |
|
69 | def upload(self, name, body, path='/'): | |
70 | return self._req('PUT', url_path_join(path, name), body) |
|
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 | def copy(self, copy_from, copy_to, path='/'): |
|
75 | def copy(self, copy_from, copy_to, path='/'): | |
73 | body = json.dumps({'copy_from':copy_from}) |
|
76 | body = json.dumps({'copy_from':copy_from}) | |
74 | return self._req('PUT', url_path_join(path, copy_to), body) |
|
77 | return self._req('PUT', url_path_join(path, copy_to), body) | |
@@ -299,9 +302,7 class APITest(NotebookTestBase): | |||||
299 | self._check_created(resp, u'Upload tést.ipynb', u'å b') |
|
302 | self._check_created(resp, u'Upload tést.ipynb', u'å b') | |
300 |
|
303 | |||
301 | def test_mkdir(self): |
|
304 | def test_mkdir(self): | |
302 | model = {'type': 'directory'} |
|
305 | resp = self.api.mkdir(u'New ∂ir', path=u'å b') | |
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') |
|
306 | self._check_created(resp, u'New ∂ir', u'å b', type='directory') | |
306 |
|
307 | |||
307 | def test_upload_txt(self): |
|
308 | def test_upload_txt(self): | |
@@ -362,6 +363,11 class APITest(NotebookTestBase): | |||||
362 | resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b') |
|
363 | resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b') | |
363 | self._check_created(resp, u'cøpy.ipynb', u'å b') |
|
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 | def test_delete(self): |
|
371 | def test_delete(self): | |
366 | for d, name in self.dirs_nbs: |
|
372 | for d, name in self.dirs_nbs: | |
367 | resp = self.api.delete('%s.ipynb' % name, d) |
|
373 | resp = self.api.delete('%s.ipynb' % name, d) | |
@@ -371,6 +377,20 class APITest(NotebookTestBase): | |||||
371 | nbs = notebooks_only(self.api.list(d).json()) |
|
377 | nbs = notebooks_only(self.api.list(d).json()) | |
372 | self.assertEqual(len(nbs), 0) |
|
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 | def test_rename(self): |
|
394 | def test_rename(self): | |
375 | resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb') |
|
395 | resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb') | |
376 | self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb') |
|
396 | self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb') |
General Comments 0
You need to be logged in to leave comments.
Login now