##// END OF EJS Templates
more explicit error catching...
Paul Ivanov -
Show More
@@ -1,147 +1,203 b''
1 """A base class contents manager.
1 """A base class contents manager.
2
2
3 Authors:
3 Authors:
4
4
5 * Zach Sailer
5 * Zach Sailer
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import datetime
19 import datetime
20 import io
20 import io
21 import os
21 import os
22 import glob
22 import glob
23 import shutil
23 import shutil
24 import ast
24 import errno
25 import base64
26
25
27 from tornado import web
26 from tornado import web
28
27
29 from IPython.config.configurable import LoggingConfigurable
28 from IPython.config.configurable import LoggingConfigurable
30 from IPython.nbformat import current
29 from IPython.utils.traitlets import Unicode, TraitError
31 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
32 from IPython.utils import tz
30 from IPython.utils import tz
33
31
34 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
35 # Classes
33 # Classes
36 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
37
35
38 class ContentManager(LoggingConfigurable):
36 class ContentManager(LoggingConfigurable):
39
37
40 content_dir = Unicode(os.getcwdu(), config=True, help="""
38 content_dir = Unicode(os.getcwdu(), config=True, help="""
41 The directory to use for contents.
39 The directory to use for contents.
42 """)
40 """)
43
41
44 def get_os_path(self, fname=None, path='/'):
42 def get_os_path(self, fname=None, path='/'):
45 """Given a notebook name and a server URL path, return its file system
43 """Given a notebook name and a server URL path, return its file system
46 path.
44 path.
47
45
48 Parameters
46 Parameters
49 ----------
47 ----------
50 fname : string
48 fname : string
51 The name of a notebook file with the .ipynb extension
49 The name of a notebook file with the .ipynb extension
52 path : string
50 path : string
53 The relative URL path (with '/' as separator) to the named
51 The relative URL path (with '/' as separator) to the named
54 notebook.
52 notebook.
55
53
56 Returns
54 Returns
57 -------
55 -------
58 path : string
56 path : string
59 A file system path that combines notebook_dir (location where
57 A file system path that combines notebook_dir (location where
60 server started), the relative path, and the filename with the
58 server started), the relative path, and the filename with the
61 current operating system's url.
59 current operating system's url.
62 """
60 """
63 parts = path.split('/')
61 parts = path.split('/')
64 parts = [p for p in parts if p != ''] # remove duplicate splits
62 parts = [p for p in parts if p != ''] # remove duplicate splits
65 if fname is not None:
63 if fname is not None:
66 parts += [fname]
64 parts += [fname]
67 path = os.path.join(self.content_dir, *parts)
65 path = os.path.join(self.content_dir, *parts)
68 return path
66 return path
69
67
70 def get_content_names(self, content_path='/'):
68 def get_content_names(self, content_path='/'):
71 """Returns list of names in the server's root + relative
69 """Returns list of names in the server's root + relative
72 location given by 'content_path'."""
70 location given by 'content_path'."""
73 names = os.listdir(self.get_os_path(None, content_path))
71 names = os.listdir(self.get_os_path(None, content_path))
74 return names
72 return names
75
73
76 def list_contents(self, content_path='/'):
74 def list_contents(self, content_path='/'):
77 """Returns a list of dictionaries including info for all
75 """Returns a list of dictionaries including info for all
78 contents in the location given by 'content_path'.
76 contents in the location given by 'content_path'.
79
77
80 Parameters
78 Parameters
81 ----------
79 ----------
82 content_path: str
80 content_path: str
83 the relative path/location of the desired files.
81 the relative path/location of the desired files.
84
82
85 Returns
83 Returns
86 -------
84 -------
87 contents: list of dicts
85 contents: list of dicts
88 a the contents of each dict includes information for each item
86 a the contents of each dict includes information for each item
89 in the named location given by 'content_path'.
87 in the named location given by 'content_path'.
90 """
88 """
91 names = self.get_content_names(content_path)
89 names = self.get_content_names(content_path)
92 contents = list()
90 contents = list()
93 dirs = list()
91 dirs = list()
94 notebooks = list()
92 notebooks = list()
95 for name in names:
93 for name in names:
96 if os.path.isdir(name) == True:
94 if os.path.isdir(name) == True:
97 dirs.append(os.path.split(name)[1])
95 dirs.append(os.path.split(name)[1])
98 elif os.path.splitext(name)[1] == '.ipynb':
96 elif os.path.splitext(name)[1] == '.ipynb':
99 notebooks.append(os.path.split(name)[1])
97 notebooks.append(os.path.split(name)[1])
100 else:
98 else:
101 contents.append(os.path.split(name)[1])
99 contents.append(os.path.split(name)[1])
102 return dirs, notebooks, contents
100 return dirs, notebooks, contents
103
101
104 def list_contents(self, content_path):
102 def list_contents(self, content_path):
105 """List all contents in the named path."""
103 """List all contents in the named path."""
106 dir_names, notebook_names, content_names = self.get_content_names(content_path)
104 dir_names, notebook_names, content_names = self.get_content_names(content_path)
107 content_mapping = []
105 content_mapping = []
108 for name in dir_names:
106 for name in dir_names:
109 model = self.content_model(name, content_path, type='dir')
107 model = self.content_model(name, content_path, type='dir')
110 content_mapping.append(model)
108 content_mapping.append(model)
111 for name in content_names:
109 for name in content_names:
112 model = self.content_model(name, content_path, type='file')
110 model = self.content_model(name, content_path, type='file')
113 content_mapping.append(model)
111 content_mapping.append(model)
114 for name in notebook_names:
112 for name in notebook_names:
115 model = self.content_model(name, content_path, type='notebook')
113 model = self.content_model(name, content_path, type='notebook')
116 content_mapping.append(model)
114 content_mapping.append(model)
117 return content_mapping
115 return content_mapping
118
116
119 def get_path_by_name(self, name, content_path):
117 def get_path_by_name(self, name, content_path):
120 """Return a full path to content"""
118 """Return a full path to content"""
121 path = os.path.join(self.content_dir, content_path, name)
119 path = os.path.join(self.content_dir, content_path, name)
122 return path
120 return path
123
121
124 def content_info(self, name, content_path):
122 def content_info(self, name, content_path):
125 """Read the content of a named file"""
123 """Read the content of a named file"""
126 file_type = os.path.splitext(os.path.basename(name))[1]
124 file_type = os.path.splitext(os.path.basename(name))[1]
127 full_path = self.get_path_by_name(name, content_path)
125 full_path = self.get_path_by_name(name, content_path)
128 info = os.stat(full_path)
126 info = os.stat(full_path)
129 size = info.st_size
127 size = info.st_size
130 last_modified = tz.utcfromtimestamp(info.st_mtime)
128 last_modified = tz.utcfromtimestamp(info.st_mtime)
131 return last_modified, file_type, size
129 return last_modified, file_type, size
132
130
133 def content_model(self, name, content_path, type=None):
131 def content_model(self, name, content_path, type=None):
134 """Create a dict standard model for any file (other than notebooks)"""
132 """Create a dict standard model for any file (other than notebooks)"""
135 last_modified, file_type, size = self.content_info(name, content_path)
133 last_modified, file_type, size = self.content_info(name, content_path)
136 model = {"name": name,
134 model = {"name": name,
137 "path": content_path,
135 "path": content_path,
138 "type": type,
136 "type": type,
139 "MIME-type": "",
137 "MIME-type": "",
140 "last_modified (UTC)": last_modified.ctime(),
138 "last_modified (UTC)": last_modified.ctime(),
141 "size": size}
139 "size": size}
142 return model
140 return model
143
141
144 def delete_content(self, content_path):
142 def create_folder(self, name, path):
145 """Delete a file"""
143 """
146 os.unlink(os.path.join(self.content_dir, content_path))
144 Parameters
145 ----------
146 name : str
147 The name you want give to the folder thats created.
148 If this is None, it will assign an incremented name
149 'new_folder'.
150 path : str
151 The relative location to put the created folder.
152
153 Returns
154 -------
155 The name of the created folder.
156 """
157 if name is None:
158 name = self.increment_filename("new_folder", path)
159 new_path = self.get_os_path(name, path)
160 # Raise an error if the file exists
161 try:
162 os.makedirs(new_path)
163 except OSError as e:
164 if e.errno == errno.EEXIST:
165 raise web.HTTPError(409, u'Directory already exists.')
166 elif e.errno == errno.EACCES:
167 raise web.HTTPError(403, u'Create dir: permission denied.')
168 else:
169 raise web.HTTPError(400, str(e))
170 return name
147
171
172 def increment_filename(self, basename, content_path='/'):
173 """Return a non-used filename of the form basename<int>.
174
175 This searches through the filenames (basename0, basename1, ...)
176 until is find one that is not already being used. It is used to
177 create Untitled and Copy names that are unique.
178 """
179 i = 0
180 while True:
181 name = u'%s%i' % (basename,i)
182 path = self.get_os_path(name, content_path)
183 if not os.path.isdir(path):
184 break
185 else:
186 i = i+1
187 return name
188
189 def delete_content(self, name=None, content_path='/'):
190 """Delete a file or folder in the named location.
191 Raises an error if the named file/folder doesn't exist
192 """
193 path = self.get_os_path(name, content_path)
194 if path != self.content_dir:
195 try:
196 shutil.rmtree(path)
197 except OSError as e:
198 if e.errno == errno.ENOENT:
199 raise web.HTTPError(404, u'Directory or file does not exist.')
200 else:
201 raise web.HTTPError(400, str(e))
202 else:
203 raise web.HTTPError(403, "Cannot delete root directory where notebook server lives.")
@@ -1,111 +1,115 b''
1 """Tests for the content manager."""
1 """Tests for the content manager."""
2
2
3 import os
3 import os
4 from unittest import TestCase
4 from unittest import TestCase
5 from tempfile import NamedTemporaryFile
5 from tempfile import NamedTemporaryFile
6
6
7 from IPython.utils.tempdir import TemporaryDirectory
7 from IPython.utils.tempdir import TemporaryDirectory
8 from IPython.utils.traitlets import TraitError
8 from IPython.utils.traitlets import TraitError
9
9
10 from ..contentmanager import ContentManager
10 from ..contentmanager import ContentManager
11
11
12
12
13 class TestContentManager(TestCase):
13 class TestContentManager(TestCase):
14
14
15 def test_new_folder(self):
15 def test_new_folder(self):
16 with TemporaryDirectory() as td:
16 with TemporaryDirectory() as td:
17 # Test that a new directory/folder is created
17 # Test that a new directory/folder is created
18 cm = ContentManager(content_dir=td)
18 cm = ContentManager(content_dir=td)
19 name = cm.new_folder(None, '/')
19 name = cm.new_folder(None, '/')
20 path = cm.get_os_path(name, '/')
20 path = cm.get_os_path(name, '/')
21 self.assertTrue(os.path.isdir(path))
21 self.assertTrue(os.path.isdir(path))
22
22
23 # Test that a new directory is created with
23 # Test that a new directory is created with
24 # the name given.
24 # the name given.
25 name = cm.new_folder('foo')
25 name = cm.new_folder('foo')
26 path = cm.get_os_path(name)
26 path = cm.get_os_path(name)
27 self.assertTrue(os.path.isdir(path))
27 self.assertTrue(os.path.isdir(path))
28
28
29 # Test that a new directory/folder is created
29 # Test that a new directory/folder is created
30 # in the '/foo' subdirectory
30 # in the '/foo' subdirectory
31 name1 = cm.new_folder(None, '/foo/')
31 name1 = cm.new_folder(None, '/foo/')
32 path1 = cm.get_os_path(name1, '/foo/')
32 path1 = cm.get_os_path(name1, '/foo/')
33 self.assertTrue(os.path.isdir(path1))
33 self.assertTrue(os.path.isdir(path1))
34
34
35 # make another file and make sure it incremented
35 # make another file and make sure it incremented
36 # the name and does not write over another file.
36 # the name and does not write over another file.
37 name2 = cm.new_folder(None, '/foo/')
37 name2 = cm.new_folder(None, '/foo/')
38 path2 = cm.get_os_path(name, '/foo/')
38 path2 = cm.get_os_path(name, '/foo/')
39 self.assertEqual(name2, 'new_folder1')
39 self.assertEqual(name2, 'new_folder1')
40
40
41 # Test that an HTTP Error is raised when the user
41 # Test that an HTTP Error is raised when the user
42 # tries to create a new folder with a name that
42 # tries to create a new folder with a name that
43 # already exists
43 # already exists
44 bad_name = 'new_folder1'
44 bad_name = 'new_folder1'
45 self.assertRaises(HTTPError, cm.new_folder, name=bad_name, path='/foo/')
45 self.assertRaises(HTTPError, cm.new_folder, name=bad_name, path='/foo/')
46
46
47 def test_delete_folder(self):
47 def test_delete_folder(self):
48 with TemporaryDirectory() as td:
48 with TemporaryDirectory() as td:
49 # Create a folder
49 # Create a folder
50 cm = ContentManager(content_dir=td)
50 cm = ContentManager(content_dir=td)
51 name = cm.new_folder('test_folder', '/')
51 name = cm.new_folder('test_folder', '/')
52 path = cm.get_os_path(name, '/')
52 path = cm.get_os_path(name, '/')
53
53
54 # Raise an exception when trying to delete a
54 # Raise an exception when trying to delete a
55 # folder that does not exist.
55 # folder that does not exist.
56 self.assertRaises(TraitError, cm.delete_content, name='non_existing_folder', content_path='/')
56 self.assertRaises(HTTPError, cm.delete_content, name='non_existing_folder', content_path='/')
57
57
58 # Create a subfolder in the folder created above.
58 # Create a subfolder in the folder created above.
59 # *Recall 'name' = 'test_folder' (the new path for
59 # *Recall 'name' = 'test_folder' (the new path for
60 # subfolder)
60 # subfolder)
61 name01 = cm.new_folder(None, name)
61 name01 = cm.new_folder(None, name)
62 path01 = cm.get_os_path(name01, name)
62 path01 = cm.get_os_path(name01, name)
63 # Try to delete a subfolder that does not exist.
63 # Try to delete a subfolder that does not exist.
64 self.assertRaises(TraitError, cm.delete_content, name='non_existing_folder', content_path='/')
64 self.assertRaises(HTTPError, cm.delete_content, name='non_existing_folder', content_path='/')
65 # Delete the created subfolder
65 # Delete the created subfolder
66 cm.delete_content(name01, name)
66 cm.delete_content(name01, name)
67 self.assertFalse(os.path.isdir(path01))
67 self.assertFalse(os.path.isdir(path01))
68
68
69 # Delete the created folder
69 # Delete the created folder
70 cm.delete_content(name, '/')
70 cm.delete_content(name, '/')
71 self.assertFalse(os.path.isdir(path))
71 self.assertFalse(os.path.isdir(path))
72
72
73 self.assertRaises(HTTPError, cm.delete_content, name=None, content_path='/')
74 self.assertRaises(HTTPError, cm.delete_content, name='/', content_path='/')
75
76
73 def test_get_content_names(self):
77 def test_get_content_names(self):
74 with TemporaryDirectory() as td:
78 with TemporaryDirectory() as td:
75 # Create a few folders and subfolders
79 # Create a few folders and subfolders
76 cm = ContentManager(content_dir=td)
80 cm = ContentManager(content_dir=td)
77 name1 = cm.new_folder('fold1', '/')
81 name1 = cm.new_folder('fold1', '/')
78 name2 = cm.new_folder('fold2', '/')
82 name2 = cm.new_folder('fold2', '/')
79 name3 = cm.new_folder('fold3', '/')
83 name3 = cm.new_folder('fold3', '/')
80 name01 = cm.new_folder('fold01', 'fold1')
84 name01 = cm.new_folder('fold01', 'fold1')
81 name02 = cm.new_folder('fold02', 'fold1')
85 name02 = cm.new_folder('fold02', 'fold1')
82 name03 = cm.new_folder('fold03', 'fold1')
86 name03 = cm.new_folder('fold03', 'fold1')
83
87
84 # List the names in the root folder
88 # List the names in the root folder
85 names = cm.get_content_names('/')
89 names = cm.get_content_names('/')
86 expected = ['fold1', 'fold2', 'fold3']
90 expected = ['fold1', 'fold2', 'fold3']
87 self.assertEqual(set(names), set(expected))
91 self.assertEqual(set(names), set(expected))
88
92
89 # List the names in the subfolder 'fold1'.
93 # List the names in the subfolder 'fold1'.
90 names = cm.get_content_names('fold1')
94 names = cm.get_content_names('fold1')
91 expected = ['fold01', 'fold02', 'fold03']
95 expected = ['fold01', 'fold02', 'fold03']
92 self.assertEqual(set(names), set(expected))
96 self.assertEqual(set(names), set(expected))
93
97
94 def test_content_model(self):
98 def test_content_model(self):
95 with TemporaryDirectory() as td:
99 with TemporaryDirectory() as td:
96 # Create a few folders and subfolders
100 # Create a few folders and subfolders
97 cm = ContentManager(content_dir=td)
101 cm = ContentManager(content_dir=td)
98 name1 = cm.new_folder('fold1', '/')
102 name1 = cm.new_folder('fold1', '/')
99 name2 = cm.new_folder('fold2', '/')
103 name2 = cm.new_folder('fold2', '/')
100 name01 = cm.new_folder('fold01', 'fold1')
104 name01 = cm.new_folder('fold01', 'fold1')
101 name02 = cm.new_folder('fold02', 'fold1')
105 name02 = cm.new_folder('fold02', 'fold1')
102
106
103 # Check to see if the correct model and list of
107 # Check to see if the correct model and list of
104 # model dicts are returned for root directory
108 # model dicts are returned for root directory
105 # and subdirectory.
109 # and subdirectory.
106 contents = cm.list_contents('/')
110 contents = cm.list_contents('/')
107 contents1 = cm.list_contents('fold1')
111 contents1 = cm.list_contents('fold1')
108 self.assertEqual(type(contents), type(list()))
112 self.assertEqual(type(contents), type(list()))
109 self.assertEqual(type(contents[0]), type(dict()))
113 self.assertEqual(type(contents[0]), type(dict()))
110 self.assertEqual(contents[0]['path'], '/')
114 self.assertEqual(contents[0]['path'], '/')
111 self.assertEqual(contents1[0]['path'], 'fold1')
115 self.assertEqual(contents1[0]['path'], 'fold1')
General Comments 0
You need to be logged in to leave comments. Login now