##// END OF EJS Templates
Fix contentsmanager api error message to give the path name
Jason Grout -
Show More
@@ -1,472 +1,472 b''
1 """A contents manager that uses the local file system for storage."""
1 """A contents manager that uses the local file system for storage."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6
6
7 import io
7 import io
8 import os
8 import os
9 import shutil
9 import shutil
10 import mimetypes
10 import mimetypes
11
11
12 from tornado import web
12 from tornado import web
13
13
14 from .filecheckpoints import FileCheckpoints
14 from .filecheckpoints import FileCheckpoints
15 from .fileio import FileManagerMixin
15 from .fileio import FileManagerMixin
16 from .manager import ContentsManager
16 from .manager import ContentsManager
17
17
18 from IPython import nbformat
18 from IPython import nbformat
19 from IPython.utils.importstring import import_item
19 from IPython.utils.importstring import import_item
20 from IPython.utils.traitlets import Any, Unicode, Bool, TraitError
20 from IPython.utils.traitlets import Any, Unicode, Bool, TraitError
21 from IPython.utils.py3compat import getcwd, string_types
21 from IPython.utils.py3compat import getcwd, string_types
22 from IPython.utils import tz
22 from IPython.utils import tz
23 from IPython.html.utils import (
23 from IPython.html.utils import (
24 is_hidden,
24 is_hidden,
25 to_api_path,
25 to_api_path,
26 )
26 )
27
27
28 _script_exporter = None
28 _script_exporter = None
29
29
30
30
31 def _post_save_script(model, os_path, contents_manager, **kwargs):
31 def _post_save_script(model, os_path, contents_manager, **kwargs):
32 """convert notebooks to Python script after save with nbconvert
32 """convert notebooks to Python script after save with nbconvert
33
33
34 replaces `ipython notebook --script`
34 replaces `ipython notebook --script`
35 """
35 """
36 from IPython.nbconvert.exporters.script import ScriptExporter
36 from IPython.nbconvert.exporters.script import ScriptExporter
37
37
38 if model['type'] != 'notebook':
38 if model['type'] != 'notebook':
39 return
39 return
40
40
41 global _script_exporter
41 global _script_exporter
42 if _script_exporter is None:
42 if _script_exporter is None:
43 _script_exporter = ScriptExporter(parent=contents_manager)
43 _script_exporter = ScriptExporter(parent=contents_manager)
44 log = contents_manager.log
44 log = contents_manager.log
45
45
46 base, ext = os.path.splitext(os_path)
46 base, ext = os.path.splitext(os_path)
47 py_fname = base + '.py'
47 py_fname = base + '.py'
48 script, resources = _script_exporter.from_filename(os_path)
48 script, resources = _script_exporter.from_filename(os_path)
49 script_fname = base + resources.get('output_extension', '.txt')
49 script_fname = base + resources.get('output_extension', '.txt')
50 log.info("Saving script /%s", to_api_path(script_fname, contents_manager.root_dir))
50 log.info("Saving script /%s", to_api_path(script_fname, contents_manager.root_dir))
51 with io.open(script_fname, 'w', encoding='utf-8') as f:
51 with io.open(script_fname, 'w', encoding='utf-8') as f:
52 f.write(script)
52 f.write(script)
53
53
54
54
55 class FileContentsManager(FileManagerMixin, ContentsManager):
55 class FileContentsManager(FileManagerMixin, ContentsManager):
56
56
57 root_dir = Unicode(config=True)
57 root_dir = Unicode(config=True)
58
58
59 def _root_dir_default(self):
59 def _root_dir_default(self):
60 try:
60 try:
61 return self.parent.notebook_dir
61 return self.parent.notebook_dir
62 except AttributeError:
62 except AttributeError:
63 return getcwd()
63 return getcwd()
64
64
65 save_script = Bool(False, config=True, help='DEPRECATED, use post_save_hook')
65 save_script = Bool(False, config=True, help='DEPRECATED, use post_save_hook')
66 def _save_script_changed(self):
66 def _save_script_changed(self):
67 self.log.warn("""
67 self.log.warn("""
68 `--script` is deprecated. You can trigger nbconvert via pre- or post-save hooks:
68 `--script` is deprecated. You can trigger nbconvert via pre- or post-save hooks:
69
69
70 ContentsManager.pre_save_hook
70 ContentsManager.pre_save_hook
71 FileContentsManager.post_save_hook
71 FileContentsManager.post_save_hook
72
72
73 A post-save hook has been registered that calls:
73 A post-save hook has been registered that calls:
74
74
75 ipython nbconvert --to script [notebook]
75 ipython nbconvert --to script [notebook]
76
76
77 which behaves similarly to `--script`.
77 which behaves similarly to `--script`.
78 """)
78 """)
79
79
80 self.post_save_hook = _post_save_script
80 self.post_save_hook = _post_save_script
81
81
82 post_save_hook = Any(None, config=True,
82 post_save_hook = Any(None, config=True,
83 help="""Python callable or importstring thereof
83 help="""Python callable or importstring thereof
84
84
85 to be called on the path of a file just saved.
85 to be called on the path of a file just saved.
86
86
87 This can be used to process the file on disk,
87 This can be used to process the file on disk,
88 such as converting the notebook to a script or HTML via nbconvert.
88 such as converting the notebook to a script or HTML via nbconvert.
89
89
90 It will be called as (all arguments passed by keyword):
90 It will be called as (all arguments passed by keyword):
91
91
92 hook(os_path=os_path, model=model, contents_manager=instance)
92 hook(os_path=os_path, model=model, contents_manager=instance)
93
93
94 path: the filesystem path to the file just written
94 path: the filesystem path to the file just written
95 model: the model representing the file
95 model: the model representing the file
96 contents_manager: this ContentsManager instance
96 contents_manager: this ContentsManager instance
97 """
97 """
98 )
98 )
99 def _post_save_hook_changed(self, name, old, new):
99 def _post_save_hook_changed(self, name, old, new):
100 if new and isinstance(new, string_types):
100 if new and isinstance(new, string_types):
101 self.post_save_hook = import_item(self.post_save_hook)
101 self.post_save_hook = import_item(self.post_save_hook)
102 elif new:
102 elif new:
103 if not callable(new):
103 if not callable(new):
104 raise TraitError("post_save_hook must be callable")
104 raise TraitError("post_save_hook must be callable")
105
105
106 def run_post_save_hook(self, model, os_path):
106 def run_post_save_hook(self, model, os_path):
107 """Run the post-save hook if defined, and log errors"""
107 """Run the post-save hook if defined, and log errors"""
108 if self.post_save_hook:
108 if self.post_save_hook:
109 try:
109 try:
110 self.log.debug("Running post-save hook on %s", os_path)
110 self.log.debug("Running post-save hook on %s", os_path)
111 self.post_save_hook(os_path=os_path, model=model, contents_manager=self)
111 self.post_save_hook(os_path=os_path, model=model, contents_manager=self)
112 except Exception:
112 except Exception:
113 self.log.error("Post-save hook failed on %s", os_path, exc_info=True)
113 self.log.error("Post-save hook failed on %s", os_path, exc_info=True)
114
114
115 def _root_dir_changed(self, name, old, new):
115 def _root_dir_changed(self, name, old, new):
116 """Do a bit of validation of the root_dir."""
116 """Do a bit of validation of the root_dir."""
117 if not os.path.isabs(new):
117 if not os.path.isabs(new):
118 # If we receive a non-absolute path, make it absolute.
118 # If we receive a non-absolute path, make it absolute.
119 self.root_dir = os.path.abspath(new)
119 self.root_dir = os.path.abspath(new)
120 return
120 return
121 if not os.path.isdir(new):
121 if not os.path.isdir(new):
122 raise TraitError("%r is not a directory" % new)
122 raise TraitError("%r is not a directory" % new)
123
123
124 def _checkpoints_class_default(self):
124 def _checkpoints_class_default(self):
125 return FileCheckpoints
125 return FileCheckpoints
126
126
127 def is_hidden(self, path):
127 def is_hidden(self, path):
128 """Does the API style path correspond to a hidden directory or file?
128 """Does the API style path correspond to a hidden directory or file?
129
129
130 Parameters
130 Parameters
131 ----------
131 ----------
132 path : string
132 path : string
133 The path to check. This is an API path (`/` separated,
133 The path to check. This is an API path (`/` separated,
134 relative to root_dir).
134 relative to root_dir).
135
135
136 Returns
136 Returns
137 -------
137 -------
138 hidden : bool
138 hidden : bool
139 Whether the path exists and is hidden.
139 Whether the path exists and is hidden.
140 """
140 """
141 path = path.strip('/')
141 path = path.strip('/')
142 os_path = self._get_os_path(path=path)
142 os_path = self._get_os_path(path=path)
143 return is_hidden(os_path, self.root_dir)
143 return is_hidden(os_path, self.root_dir)
144
144
145 def file_exists(self, path):
145 def file_exists(self, path):
146 """Returns True if the file exists, else returns False.
146 """Returns True if the file exists, else returns False.
147
147
148 API-style wrapper for os.path.isfile
148 API-style wrapper for os.path.isfile
149
149
150 Parameters
150 Parameters
151 ----------
151 ----------
152 path : string
152 path : string
153 The relative path to the file (with '/' as separator)
153 The relative path to the file (with '/' as separator)
154
154
155 Returns
155 Returns
156 -------
156 -------
157 exists : bool
157 exists : bool
158 Whether the file exists.
158 Whether the file exists.
159 """
159 """
160 path = path.strip('/')
160 path = path.strip('/')
161 os_path = self._get_os_path(path)
161 os_path = self._get_os_path(path)
162 return os.path.isfile(os_path)
162 return os.path.isfile(os_path)
163
163
164 def dir_exists(self, path):
164 def dir_exists(self, path):
165 """Does the API-style path refer to an extant directory?
165 """Does the API-style path refer to an extant directory?
166
166
167 API-style wrapper for os.path.isdir
167 API-style wrapper for os.path.isdir
168
168
169 Parameters
169 Parameters
170 ----------
170 ----------
171 path : string
171 path : string
172 The path to check. This is an API path (`/` separated,
172 The path to check. This is an API path (`/` separated,
173 relative to root_dir).
173 relative to root_dir).
174
174
175 Returns
175 Returns
176 -------
176 -------
177 exists : bool
177 exists : bool
178 Whether the path is indeed a directory.
178 Whether the path is indeed a directory.
179 """
179 """
180 path = path.strip('/')
180 path = path.strip('/')
181 os_path = self._get_os_path(path=path)
181 os_path = self._get_os_path(path=path)
182 return os.path.isdir(os_path)
182 return os.path.isdir(os_path)
183
183
184 def exists(self, path):
184 def exists(self, path):
185 """Returns True if the path exists, else returns False.
185 """Returns True if the path exists, else returns False.
186
186
187 API-style wrapper for os.path.exists
187 API-style wrapper for os.path.exists
188
188
189 Parameters
189 Parameters
190 ----------
190 ----------
191 path : string
191 path : string
192 The API path to the file (with '/' as separator)
192 The API path to the file (with '/' as separator)
193
193
194 Returns
194 Returns
195 -------
195 -------
196 exists : bool
196 exists : bool
197 Whether the target exists.
197 Whether the target exists.
198 """
198 """
199 path = path.strip('/')
199 path = path.strip('/')
200 os_path = self._get_os_path(path=path)
200 os_path = self._get_os_path(path=path)
201 return os.path.exists(os_path)
201 return os.path.exists(os_path)
202
202
203 def _base_model(self, path):
203 def _base_model(self, path):
204 """Build the common base of a contents model"""
204 """Build the common base of a contents model"""
205 os_path = self._get_os_path(path)
205 os_path = self._get_os_path(path)
206 info = os.stat(os_path)
206 info = os.stat(os_path)
207 last_modified = tz.utcfromtimestamp(info.st_mtime)
207 last_modified = tz.utcfromtimestamp(info.st_mtime)
208 created = tz.utcfromtimestamp(info.st_ctime)
208 created = tz.utcfromtimestamp(info.st_ctime)
209 # Create the base model.
209 # Create the base model.
210 model = {}
210 model = {}
211 model['name'] = path.rsplit('/', 1)[-1]
211 model['name'] = path.rsplit('/', 1)[-1]
212 model['path'] = path
212 model['path'] = path
213 model['last_modified'] = last_modified
213 model['last_modified'] = last_modified
214 model['created'] = created
214 model['created'] = created
215 model['content'] = None
215 model['content'] = None
216 model['format'] = None
216 model['format'] = None
217 model['mimetype'] = None
217 model['mimetype'] = None
218 try:
218 try:
219 model['writable'] = os.access(os_path, os.W_OK)
219 model['writable'] = os.access(os_path, os.W_OK)
220 except OSError:
220 except OSError:
221 self.log.error("Failed to check write permissions on %s", os_path)
221 self.log.error("Failed to check write permissions on %s", os_path)
222 model['writable'] = False
222 model['writable'] = False
223 return model
223 return model
224
224
225 def _dir_model(self, path, content=True):
225 def _dir_model(self, path, content=True):
226 """Build a model for a directory
226 """Build a model for a directory
227
227
228 if content is requested, will include a listing of the directory
228 if content is requested, will include a listing of the directory
229 """
229 """
230 os_path = self._get_os_path(path)
230 os_path = self._get_os_path(path)
231
231
232 four_o_four = u'directory does not exist: %r' % path
232 four_o_four = u'directory does not exist: %r' % path
233
233
234 if not os.path.isdir(os_path):
234 if not os.path.isdir(os_path):
235 raise web.HTTPError(404, four_o_four)
235 raise web.HTTPError(404, four_o_four)
236 elif is_hidden(os_path, self.root_dir):
236 elif is_hidden(os_path, self.root_dir):
237 self.log.info("Refusing to serve hidden directory %r, via 404 Error",
237 self.log.info("Refusing to serve hidden directory %r, via 404 Error",
238 os_path
238 os_path
239 )
239 )
240 raise web.HTTPError(404, four_o_four)
240 raise web.HTTPError(404, four_o_four)
241
241
242 model = self._base_model(path)
242 model = self._base_model(path)
243 model['type'] = 'directory'
243 model['type'] = 'directory'
244 if content:
244 if content:
245 model['content'] = contents = []
245 model['content'] = contents = []
246 os_dir = self._get_os_path(path)
246 os_dir = self._get_os_path(path)
247 for name in os.listdir(os_dir):
247 for name in os.listdir(os_dir):
248 os_path = os.path.join(os_dir, name)
248 os_path = os.path.join(os_dir, name)
249 # skip over broken symlinks in listing
249 # skip over broken symlinks in listing
250 if not os.path.exists(os_path):
250 if not os.path.exists(os_path):
251 self.log.warn("%s doesn't exist", os_path)
251 self.log.warn("%s doesn't exist", os_path)
252 continue
252 continue
253 elif not os.path.isfile(os_path) and not os.path.isdir(os_path):
253 elif not os.path.isfile(os_path) and not os.path.isdir(os_path):
254 self.log.debug("%s not a regular file", os_path)
254 self.log.debug("%s not a regular file", os_path)
255 continue
255 continue
256 if self.should_list(name) and not is_hidden(os_path, self.root_dir):
256 if self.should_list(name) and not is_hidden(os_path, self.root_dir):
257 contents.append(self.get(
257 contents.append(self.get(
258 path='%s/%s' % (path, name),
258 path='%s/%s' % (path, name),
259 content=False)
259 content=False)
260 )
260 )
261
261
262 model['format'] = 'json'
262 model['format'] = 'json'
263
263
264 return model
264 return model
265
265
266 def _file_model(self, path, content=True, format=None):
266 def _file_model(self, path, content=True, format=None):
267 """Build a model for a file
267 """Build a model for a file
268
268
269 if content is requested, include the file contents.
269 if content is requested, include the file contents.
270
270
271 format:
271 format:
272 If 'text', the contents will be decoded as UTF-8.
272 If 'text', the contents will be decoded as UTF-8.
273 If 'base64', the raw bytes contents will be encoded as base64.
273 If 'base64', the raw bytes contents will be encoded as base64.
274 If not specified, try to decode as UTF-8, and fall back to base64
274 If not specified, try to decode as UTF-8, and fall back to base64
275 """
275 """
276 model = self._base_model(path)
276 model = self._base_model(path)
277 model['type'] = 'file'
277 model['type'] = 'file'
278
278
279 os_path = self._get_os_path(path)
279 os_path = self._get_os_path(path)
280
280
281 if content:
281 if content:
282 content, format = self._read_file(os_path, format)
282 content, format = self._read_file(os_path, format)
283 default_mime = {
283 default_mime = {
284 'text': 'text/plain',
284 'text': 'text/plain',
285 'base64': 'application/octet-stream'
285 'base64': 'application/octet-stream'
286 }[format]
286 }[format]
287
287
288 model.update(
288 model.update(
289 content=content,
289 content=content,
290 format=format,
290 format=format,
291 mimetype=mimetypes.guess_type(os_path)[0] or default_mime,
291 mimetype=mimetypes.guess_type(os_path)[0] or default_mime,
292 )
292 )
293
293
294 return model
294 return model
295
295
296 def _notebook_model(self, path, content=True):
296 def _notebook_model(self, path, content=True):
297 """Build a notebook model
297 """Build a notebook model
298
298
299 if content is requested, the notebook content will be populated
299 if content is requested, the notebook content will be populated
300 as a JSON structure (not double-serialized)
300 as a JSON structure (not double-serialized)
301 """
301 """
302 model = self._base_model(path)
302 model = self._base_model(path)
303 model['type'] = 'notebook'
303 model['type'] = 'notebook'
304 if content:
304 if content:
305 os_path = self._get_os_path(path)
305 os_path = self._get_os_path(path)
306 nb = self._read_notebook(os_path, as_version=4)
306 nb = self._read_notebook(os_path, as_version=4)
307 self.mark_trusted_cells(nb, path)
307 self.mark_trusted_cells(nb, path)
308 model['content'] = nb
308 model['content'] = nb
309 model['format'] = 'json'
309 model['format'] = 'json'
310 self.validate_notebook_model(model)
310 self.validate_notebook_model(model)
311 return model
311 return model
312
312
313 def get(self, path, content=True, type=None, format=None):
313 def get(self, path, content=True, type=None, format=None):
314 """ Takes a path for an entity and returns its model
314 """ Takes a path for an entity and returns its model
315
315
316 Parameters
316 Parameters
317 ----------
317 ----------
318 path : str
318 path : str
319 the API path that describes the relative path for the target
319 the API path that describes the relative path for the target
320 content : bool
320 content : bool
321 Whether to include the contents in the reply
321 Whether to include the contents in the reply
322 type : str, optional
322 type : str, optional
323 The requested type - 'file', 'notebook', or 'directory'.
323 The requested type - 'file', 'notebook', or 'directory'.
324 Will raise HTTPError 400 if the content doesn't match.
324 Will raise HTTPError 400 if the content doesn't match.
325 format : str, optional
325 format : str, optional
326 The requested format for file contents. 'text' or 'base64'.
326 The requested format for file contents. 'text' or 'base64'.
327 Ignored if this returns a notebook or directory model.
327 Ignored if this returns a notebook or directory model.
328
328
329 Returns
329 Returns
330 -------
330 -------
331 model : dict
331 model : dict
332 the contents model. If content=True, returns the contents
332 the contents model. If content=True, returns the contents
333 of the file or directory as well.
333 of the file or directory as well.
334 """
334 """
335 path = path.strip('/')
335 path = path.strip('/')
336
336
337 if not self.exists(path):
337 if not self.exists(path):
338 raise web.HTTPError(404, u'No such file or directory: %s' % path)
338 raise web.HTTPError(404, u'No such file or directory: %s' % path)
339
339
340 os_path = self._get_os_path(path)
340 os_path = self._get_os_path(path)
341 if os.path.isdir(os_path):
341 if os.path.isdir(os_path):
342 if type not in (None, 'directory'):
342 if type not in (None, 'directory'):
343 raise web.HTTPError(400,
343 raise web.HTTPError(400,
344 u'%s is a directory, not a %s' % (path, type), reason='bad type')
344 u'%s is a directory, not a %s' % (path, type), reason='bad type')
345 model = self._dir_model(path, content=content)
345 model = self._dir_model(path, content=content)
346 elif type == 'notebook' or (type is None and path.endswith('.ipynb')):
346 elif type == 'notebook' or (type is None and path.endswith('.ipynb')):
347 model = self._notebook_model(path, content=content)
347 model = self._notebook_model(path, content=content)
348 else:
348 else:
349 if type == 'directory':
349 if type == 'directory':
350 raise web.HTTPError(400,
350 raise web.HTTPError(400,
351 u'%s is not a directory', reason='bad type')
351 u'%s is not a directory' % path, reason='bad type')
352 model = self._file_model(path, content=content, format=format)
352 model = self._file_model(path, content=content, format=format)
353 return model
353 return model
354
354
355 def _save_directory(self, os_path, model, path=''):
355 def _save_directory(self, os_path, model, path=''):
356 """create a directory"""
356 """create a directory"""
357 if is_hidden(os_path, self.root_dir):
357 if is_hidden(os_path, self.root_dir):
358 raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path)
358 raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path)
359 if not os.path.exists(os_path):
359 if not os.path.exists(os_path):
360 with self.perm_to_403():
360 with self.perm_to_403():
361 os.mkdir(os_path)
361 os.mkdir(os_path)
362 elif not os.path.isdir(os_path):
362 elif not os.path.isdir(os_path):
363 raise web.HTTPError(400, u'Not a directory: %s' % (os_path))
363 raise web.HTTPError(400, u'Not a directory: %s' % (os_path))
364 else:
364 else:
365 self.log.debug("Directory %r already exists", os_path)
365 self.log.debug("Directory %r already exists", os_path)
366
366
367 def save(self, model, path=''):
367 def save(self, model, path=''):
368 """Save the file model and return the model with no content."""
368 """Save the file model and return the model with no content."""
369 path = path.strip('/')
369 path = path.strip('/')
370
370
371 if 'type' not in model:
371 if 'type' not in model:
372 raise web.HTTPError(400, u'No file type provided')
372 raise web.HTTPError(400, u'No file type provided')
373 if 'content' not in model and model['type'] != 'directory':
373 if 'content' not in model and model['type'] != 'directory':
374 raise web.HTTPError(400, u'No file content provided')
374 raise web.HTTPError(400, u'No file content provided')
375
375
376 self.run_pre_save_hook(model=model, path=path)
376 self.run_pre_save_hook(model=model, path=path)
377
377
378 os_path = self._get_os_path(path)
378 os_path = self._get_os_path(path)
379 self.log.debug("Saving %s", os_path)
379 self.log.debug("Saving %s", os_path)
380 try:
380 try:
381 if model['type'] == 'notebook':
381 if model['type'] == 'notebook':
382 nb = nbformat.from_dict(model['content'])
382 nb = nbformat.from_dict(model['content'])
383 self.check_and_sign(nb, path)
383 self.check_and_sign(nb, path)
384 self._save_notebook(os_path, nb)
384 self._save_notebook(os_path, nb)
385 # One checkpoint should always exist for notebooks.
385 # One checkpoint should always exist for notebooks.
386 if not self.checkpoints.list_checkpoints(path):
386 if not self.checkpoints.list_checkpoints(path):
387 self.create_checkpoint(path)
387 self.create_checkpoint(path)
388 elif model['type'] == 'file':
388 elif model['type'] == 'file':
389 # Missing format will be handled internally by _save_file.
389 # Missing format will be handled internally by _save_file.
390 self._save_file(os_path, model['content'], model.get('format'))
390 self._save_file(os_path, model['content'], model.get('format'))
391 elif model['type'] == 'directory':
391 elif model['type'] == 'directory':
392 self._save_directory(os_path, model, path)
392 self._save_directory(os_path, model, path)
393 else:
393 else:
394 raise web.HTTPError(400, "Unhandled contents type: %s" % model['type'])
394 raise web.HTTPError(400, "Unhandled contents type: %s" % model['type'])
395 except web.HTTPError:
395 except web.HTTPError:
396 raise
396 raise
397 except Exception as e:
397 except Exception as e:
398 self.log.error(u'Error while saving file: %s %s', path, e, exc_info=True)
398 self.log.error(u'Error while saving file: %s %s', path, e, exc_info=True)
399 raise web.HTTPError(500, u'Unexpected error while saving file: %s %s' % (path, e))
399 raise web.HTTPError(500, u'Unexpected error while saving file: %s %s' % (path, e))
400
400
401 validation_message = None
401 validation_message = None
402 if model['type'] == 'notebook':
402 if model['type'] == 'notebook':
403 self.validate_notebook_model(model)
403 self.validate_notebook_model(model)
404 validation_message = model.get('message', None)
404 validation_message = model.get('message', None)
405
405
406 model = self.get(path, content=False)
406 model = self.get(path, content=False)
407 if validation_message:
407 if validation_message:
408 model['message'] = validation_message
408 model['message'] = validation_message
409
409
410 self.run_post_save_hook(model=model, os_path=os_path)
410 self.run_post_save_hook(model=model, os_path=os_path)
411
411
412 return model
412 return model
413
413
414 def delete_file(self, path):
414 def delete_file(self, path):
415 """Delete file at path."""
415 """Delete file at path."""
416 path = path.strip('/')
416 path = path.strip('/')
417 os_path = self._get_os_path(path)
417 os_path = self._get_os_path(path)
418 rm = os.unlink
418 rm = os.unlink
419 if os.path.isdir(os_path):
419 if os.path.isdir(os_path):
420 listing = os.listdir(os_path)
420 listing = os.listdir(os_path)
421 # Don't delete non-empty directories.
421 # Don't delete non-empty directories.
422 # A directory containing only leftover checkpoints is
422 # A directory containing only leftover checkpoints is
423 # considered empty.
423 # considered empty.
424 cp_dir = getattr(self.checkpoints, 'checkpoint_dir', None)
424 cp_dir = getattr(self.checkpoints, 'checkpoint_dir', None)
425 for entry in listing:
425 for entry in listing:
426 if entry != cp_dir:
426 if entry != cp_dir:
427 raise web.HTTPError(400, u'Directory %s not empty' % os_path)
427 raise web.HTTPError(400, u'Directory %s not empty' % os_path)
428 elif not os.path.isfile(os_path):
428 elif not os.path.isfile(os_path):
429 raise web.HTTPError(404, u'File does not exist: %s' % os_path)
429 raise web.HTTPError(404, u'File does not exist: %s' % os_path)
430
430
431 if os.path.isdir(os_path):
431 if os.path.isdir(os_path):
432 self.log.debug("Removing directory %s", os_path)
432 self.log.debug("Removing directory %s", os_path)
433 with self.perm_to_403():
433 with self.perm_to_403():
434 shutil.rmtree(os_path)
434 shutil.rmtree(os_path)
435 else:
435 else:
436 self.log.debug("Unlinking file %s", os_path)
436 self.log.debug("Unlinking file %s", os_path)
437 with self.perm_to_403():
437 with self.perm_to_403():
438 rm(os_path)
438 rm(os_path)
439
439
440 def rename_file(self, old_path, new_path):
440 def rename_file(self, old_path, new_path):
441 """Rename a file."""
441 """Rename a file."""
442 old_path = old_path.strip('/')
442 old_path = old_path.strip('/')
443 new_path = new_path.strip('/')
443 new_path = new_path.strip('/')
444 if new_path == old_path:
444 if new_path == old_path:
445 return
445 return
446
446
447 new_os_path = self._get_os_path(new_path)
447 new_os_path = self._get_os_path(new_path)
448 old_os_path = self._get_os_path(old_path)
448 old_os_path = self._get_os_path(old_path)
449
449
450 # Should we proceed with the move?
450 # Should we proceed with the move?
451 if os.path.exists(new_os_path):
451 if os.path.exists(new_os_path):
452 raise web.HTTPError(409, u'File already exists: %s' % new_path)
452 raise web.HTTPError(409, u'File already exists: %s' % new_path)
453
453
454 # Move the file
454 # Move the file
455 try:
455 try:
456 with self.perm_to_403():
456 with self.perm_to_403():
457 shutil.move(old_os_path, new_os_path)
457 shutil.move(old_os_path, new_os_path)
458 except web.HTTPError:
458 except web.HTTPError:
459 raise
459 raise
460 except Exception as e:
460 except Exception as e:
461 raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e))
461 raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e))
462
462
463 def info_string(self):
463 def info_string(self):
464 return "Serving notebooks from local directory: %s" % self.root_dir
464 return "Serving notebooks from local directory: %s" % self.root_dir
465
465
466 def get_kernel_path(self, path, model=None):
466 def get_kernel_path(self, path, model=None):
467 """Return the initial API path of a kernel associated with a given notebook"""
467 """Return the initial API path of a kernel associated with a given notebook"""
468 if '/' in path:
468 if '/' in path:
469 parent_dir = path.rsplit('/', 1)[0]
469 parent_dir = path.rsplit('/', 1)[0]
470 else:
470 else:
471 parent_dir = ''
471 parent_dir = ''
472 return parent_dir
472 return parent_dir
General Comments 0
You need to be logged in to leave comments. Login now