##// END OF EJS Templates
address review in contents service...
Min RK -
Show More
@@ -17,7 +17,7 b' from ..utils import url_escape'
17 class NotebookHandler(IPythonHandler):
17 class NotebookHandler(IPythonHandler):
18
18
19 @web.authenticated
19 @web.authenticated
20 def get(self, path=''):
20 def get(self, path):
21 """get renders the notebook template if a name is given, or
21 """get renders the notebook template if a name is given, or
22 redirects to the '/files/' handler if the name is not given."""
22 redirects to the '/files/' handler if the name is not given."""
23 path = path.strip('/')
23 path = path.strip('/')
@@ -187,7 +187,9 b' class NotebookWebApplication(web.Application):'
187 return settings
187 return settings
188
188
189 def init_handlers(self, settings):
189 def init_handlers(self, settings):
190 # Load the (URL pattern, handler) tuples for each component.
190 """Load the (URL pattern, handler) tuples for each component."""
191
192 # Order matters. The first handler to match the URL will handle the request.
191 handlers = []
193 handlers = []
192 handlers.extend(load_handlers('tree.handlers'))
194 handlers.extend(load_handlers('tree.handlers'))
193 handlers.extend(load_handlers('auth.login'))
195 handlers.extend(load_handlers('auth.login'))
@@ -205,6 +207,7 b' class NotebookWebApplication(web.Application):'
205 handlers.append(
207 handlers.append(
206 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
208 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
207 )
209 )
210 # register base handlers last
208 handlers.extend(load_handlers('base.handlers'))
211 handlers.extend(load_handlers('base.handlers'))
209 # set the URL that will be redirected from `/`
212 # set the URL that will be redirected from `/`
210 handlers.append(
213 handlers.append(
@@ -61,9 +61,8 b' class FileContentsManager(ContentsManager):'
61 except OSError as e:
61 except OSError as e:
62 self.log.debug("copystat on %s failed", dest, exc_info=True)
62 self.log.debug("copystat on %s failed", dest, exc_info=True)
63
63
64 def _get_os_path(self, path=''):
64 def _get_os_path(self, path):
65 """Given a filename and API path, return its file system
65 """Given an API path, return its file system path.
66 path.
67
66
68 Parameters
67 Parameters
69 ----------
68 ----------
@@ -131,8 +130,8 b' class FileContentsManager(ContentsManager):'
131 Whether the file exists.
130 Whether the file exists.
132 """
131 """
133 path = path.strip('/')
132 path = path.strip('/')
134 nbpath = self._get_os_path(path)
133 os_path = self._get_os_path(path)
135 return os.path.isfile(nbpath)
134 return os.path.isfile(os_path)
136
135
137 def exists(self, path):
136 def exists(self, path):
138 """Returns True if the path exists, else returns False.
137 """Returns True if the path exists, else returns False.
@@ -167,7 +166,6 b' class FileContentsManager(ContentsManager):'
167 model['created'] = created
166 model['created'] = created
168 model['content'] = None
167 model['content'] = None
169 model['format'] = None
168 model['format'] = None
170 model['message'] = None
171 return model
169 return model
172
170
173 def _dir_model(self, path, content=True):
171 def _dir_model(self, path, content=True):
@@ -191,12 +189,16 b' class FileContentsManager(ContentsManager):'
191 model['type'] = 'directory'
189 model['type'] = 'directory'
192 if content:
190 if content:
193 model['content'] = contents = []
191 model['content'] = contents = []
194 for os_path in glob.glob(self._get_os_path('%s/*' % path)):
192 os_dir = self._get_os_path(path)
195 name = os.path.basename(os_path)
193 for name in os.listdir(os_dir):
194 os_path = os.path.join(os_dir, name)
196 # skip over broken symlinks in listing
195 # skip over broken symlinks in listing
197 if not os.path.exists(os_path):
196 if not os.path.exists(os_path):
198 self.log.warn("%s doesn't exist", os_path)
197 self.log.warn("%s doesn't exist", os_path)
199 continue
198 continue
199 elif not os.path.isfile(os_path) and not os.path.isdir(os_path):
200 self.log.debug("%s not a regular file", os_path)
201 continue
200 if self.should_list(name) and not is_hidden(os_path, self.root_dir):
202 if self.should_list(name) and not is_hidden(os_path, self.root_dir):
201 contents.append(self.get_model(
203 contents.append(self.get_model(
202 path='%s/%s' % (path, name),
204 path='%s/%s' % (path, name),
@@ -217,6 +219,9 b' class FileContentsManager(ContentsManager):'
217 model['type'] = 'file'
219 model['type'] = 'file'
218 if content:
220 if content:
219 os_path = self._get_os_path(path)
221 os_path = self._get_os_path(path)
222 if not os.path.isfile(os_path):
223 # could be FIFO
224 raise web.HTTPError(400, "Cannot get content of non-file %s" % os_path)
220 with io.open(os_path, 'rb') as f:
225 with io.open(os_path, 'rb') as f:
221 bcontent = f.read()
226 bcontent = f.read()
222 try:
227 try:
@@ -407,14 +412,14 b' class FileContentsManager(ContentsManager):'
407 old_os_path = self._get_os_path(old_path)
412 old_os_path = self._get_os_path(old_path)
408
413
409 # Should we proceed with the move?
414 # Should we proceed with the move?
410 if os.path.isfile(new_os_path):
415 if os.path.exists(new_os_path):
411 raise web.HTTPError(409, u'File already exists: %s' % new_os_path)
416 raise web.HTTPError(409, u'File already exists: %s' % new_path)
412
417
413 # Move the file
418 # Move the file
414 try:
419 try:
415 shutil.move(old_os_path, new_os_path)
420 shutil.move(old_os_path, new_os_path)
416 except Exception as e:
421 except Exception as e:
417 raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_os_path, e))
422 raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e))
418
423
419 # Move the checkpoints
424 # Move the checkpoints
420 old_checkpoints = self.list_checkpoints(old_path)
425 old_checkpoints = self.list_checkpoints(old_path)
@@ -462,6 +467,8 b' class FileContentsManager(ContentsManager):'
462 def create_checkpoint(self, path):
467 def create_checkpoint(self, path):
463 """Create a checkpoint from the current state of a file"""
468 """Create a checkpoint from the current state of a file"""
464 path = path.strip('/')
469 path = path.strip('/')
470 if not self.file_exists(path):
471 raise web.HTTPError(404)
465 src_path = self._get_os_path(path)
472 src_path = self._get_os_path(path)
466 # only the one checkpoint ID:
473 # only the one checkpoint ID:
467 checkpoint_id = u"checkpoint"
474 checkpoint_id = u"checkpoint"
@@ -521,7 +528,7 b' class FileContentsManager(ContentsManager):'
521 def get_kernel_path(self, path, model=None):
528 def get_kernel_path(self, path, model=None):
522 """Return the initial working dir a kernel associated with a given notebook"""
529 """Return the initial working dir a kernel associated with a given notebook"""
523 if '/' in path:
530 if '/' in path:
524 os_dir = path.rsplit('/', 1)[0]
531 parent_dir = path.rsplit('/', 1)[0]
525 else:
532 else:
526 os_dir = ''
533 parent_dir = ''
527 return self._get_os_path(os_dir)
534 return self._get_os_path(parent_dir)
@@ -73,14 +73,11 b' class ContentsHandler(IPythonHandler):'
73 model = self.get_json_body()
73 model = self.get_json_body()
74 if model is None:
74 if model is None:
75 raise web.HTTPError(400, u'JSON body missing')
75 raise web.HTTPError(400, u'JSON body missing')
76 print('before', model)
77 model = cm.update(model, path)
76 model = cm.update(model, path)
78 print('after', model)
79 self._finish_model(model)
77 self._finish_model(model)
80
78
81 def _copy(self, copy_from, copy_to=None):
79 def _copy(self, copy_from, copy_to=None):
82 """Copy a file, optionally specifying the new path.
80 """Copy a file, optionally specifying a target directory."""
83 """
84 self.log.info(u"Copying {copy_from} to {copy_to}".format(
81 self.log.info(u"Copying {copy_from} to {copy_to}".format(
85 copy_from=copy_from,
82 copy_from=copy_from,
86 copy_to=copy_to or '',
83 copy_to=copy_to or '',
@@ -96,13 +93,17 b' class ContentsHandler(IPythonHandler):'
96 self.set_status(201)
93 self.set_status(201)
97 self._finish_model(model)
94 self._finish_model(model)
98
95
99 def _create_empty_file(self, path, ext='.ipynb'):
96 def _new(self, path, type='notebook', ext=''):
100 """Create an empty file in path
97 """Create an empty file or directory in directory specified by path
101
98
102 If name specified, create it in path.
99 ContentsManager picks the filename.
103 """
100 """
104 self.log.info(u"Creating new file in %s", path)
101 self.log.info(u"Creating new %s in %s", type or 'file', path)
105 model = self.contents_manager.new(path=path, ext=ext)
102 if type:
103 model = {'type': type}
104 else:
105 model = None
106 model = self.contents_manager.new(model, path=path, ext=ext)
106 self.set_status(201)
107 self.set_status(201)
107 self._finish_model(model)
108 self._finish_model(model)
108
109
@@ -115,9 +116,9 b' class ContentsHandler(IPythonHandler):'
115 @web.authenticated
116 @web.authenticated
116 @json_errors
117 @json_errors
117 def post(self, path=''):
118 def post(self, path=''):
118 """Create a new file or directory in the specified path.
119 """Create a new file in the specified path.
119
120
120 POST creates new files or directories. The server always decides on the name.
121 POST creates new files. The server always decides on the name.
121
122
122 POST /api/contents/path
123 POST /api/contents/path
123 New untitled, empty file or directory.
124 New untitled, empty file or directory.
@@ -129,7 +130,7 b' class ContentsHandler(IPythonHandler):'
129 cm = self.contents_manager
130 cm = self.contents_manager
130
131
131 if cm.file_exists(path):
132 if cm.file_exists(path):
132 raise web.HTTPError(400, "Cannot POST to existing files, use PUT instead.")
133 raise web.HTTPError(400, "Cannot POST to files, use PUT instead.")
133
134
134 if not cm.dir_exists(path):
135 if not cm.dir_exists(path):
135 raise web.HTTPError(404, "No such directory: %s" % path)
136 raise web.HTTPError(404, "No such directory: %s" % path)
@@ -138,13 +139,14 b' class ContentsHandler(IPythonHandler):'
138
139
139 if model is not None:
140 if model is not None:
140 copy_from = model.get('copy_from')
141 copy_from = model.get('copy_from')
141 ext = model.get('ext', '.ipynb')
142 ext = model.get('ext', '')
143 type = model.get('type')
142 if copy_from:
144 if copy_from:
143 self._copy(copy_from, path)
145 self._copy(copy_from, path)
144 else:
146 else:
145 self._create_empty_file(path, ext=ext)
147 self._new(path, type=type, ext=ext)
146 else:
148 else:
147 self._create_empty_file(path)
149 self._new(path)
148
150
149 @web.authenticated
151 @web.authenticated
150 @json_errors
152 @json_errors
@@ -168,7 +170,7 b' class ContentsHandler(IPythonHandler):'
168 else:
170 else:
169 self._upload(model, path)
171 self._upload(model, path)
170 else:
172 else:
171 self._create_empty_file(path)
173 self._new(path)
172
174
173 @web.authenticated
175 @web.authenticated
174 @json_errors
176 @json_errors
@@ -119,7 +119,7 b' class ContentsManager(LoggingConfigurable):'
119 raise NotImplementedError('must be implemented in a subclass')
119 raise NotImplementedError('must be implemented in a subclass')
120
120
121 def exists(self, path):
121 def exists(self, path):
122 """Does a file or directory exist at the given name and path?
122 """Does a file or directory exist at the given path?
123
123
124 Like os.path.exists
124 Like os.path.exists
125
125
@@ -181,7 +181,11 b' class ContentsManager(LoggingConfigurable):'
181 return "Serving contents"
181 return "Serving contents"
182
182
183 def get_kernel_path(self, path, model=None):
183 def get_kernel_path(self, path, model=None):
184 """ Return the path to start kernel in """
184 """Return the API path for the kernel
185
186 KernelManagers can turn this value into a filesystem path,
187 or ignore it altogether.
188 """
185 return path
189 return path
186
190
187 def increment_filename(self, filename, path=''):
191 def increment_filename(self, filename, path=''):
@@ -204,7 +208,7 b' class ContentsManager(LoggingConfigurable):'
204 for i in itertools.count():
208 for i in itertools.count():
205 name = u'{basename}{i}{ext}'.format(basename=basename, i=i,
209 name = u'{basename}{i}{ext}'.format(basename=basename, i=i,
206 ext=ext)
210 ext=ext)
207 if not self.file_exists(u'{}/{}'.format(path, name)):
211 if not self.exists(u'{}/{}'.format(path, name)):
208 break
212 break
209 return name
213 return name
210
214
@@ -218,22 +222,35 b' class ContentsManager(LoggingConfigurable):'
218 )
222 )
219 return model
223 return model
220
224
221 def new(self, model=None, path='', ext='.ipynb'):
225 def new(self, model=None, path='', ext=''):
222 """Create a new file or directory and return its model with no content."""
226 """Create a new file or directory and return its model with no content.
227
228 If path is a directory, a new untitled file/directory is created in path.
229 Otherwise, a new file/directory is created exactly at path.
230 """
223 path = path.strip('/')
231 path = path.strip('/')
224 if model is None:
232 if model is None:
225 model = {}
233 model = {}
226 else:
234 else:
227 model.pop('path', None)
235 model.pop('path', None)
228 if 'content' not in model and model.get('type', None) != 'directory':
236
229 if ext == '.ipynb':
237 if ext and ext != '.ipynb':
238 model.setdefault('type', 'file')
239 else:
240 model.setdefault('type', 'notebook')
241
242 # no content, not a directory, so fill out new-file model
243 if 'content' not in model and model['type'] != 'directory':
244 if model['type'] == 'notebook':
245 ext = '.ipynb'
230 model['content'] = new_notebook()
246 model['content'] = new_notebook()
231 model['type'] = 'notebook'
232 model['format'] = 'json'
247 model['format'] = 'json'
233 else:
248 else:
234 model['content'] = ''
249 model['content'] = ''
235 model['type'] = 'file'
250 model['type'] = 'file'
236 model['format'] = 'text'
251 model['format'] = 'text'
252
253 # if path is a directory, create an untitled file or directory
237 if self.dir_exists(path):
254 if self.dir_exists(path):
238 if model['type'] == 'directory':
255 if model['type'] == 'directory':
239 untitled = self.untitled_directory
256 untitled = self.untitled_directory
@@ -252,10 +269,10 b' class ContentsManager(LoggingConfigurable):'
252 def copy(self, from_path, to_path=None):
269 def copy(self, from_path, to_path=None):
253 """Copy an existing file and return its new model.
270 """Copy an existing file and return its new model.
254
271
255 If to_name not specified, increment `from_name-Copy#.ext`.
272 If to_path not specified, it will be the parent directory of from_path.
273 If to_path is a directory, filename will increment `from_path-Copy#.ext`.
256
274
257 copy_from can be a full path to a file,
275 from_path must be a full path to a file.
258 or just a base name. If a base name, `path` is used.
259 """
276 """
260 path = from_path.strip('/')
277 path = from_path.strip('/')
261 if '/' in path:
278 if '/' in path:
@@ -325,7 +342,7 b' class ContentsManager(LoggingConfigurable):'
325 nb : dict
342 nb : dict
326 The notebook object (in current nbformat)
343 The notebook object (in current nbformat)
327 path : string
344 path : string
328 The notebook's directory (for logging)
345 The notebook's path (for logging)
329 """
346 """
330 trusted = self.notary.check_signature(nb)
347 trusted = self.notary.check_signature(nb)
331 if not trusted:
348 if not trusted:
@@ -55,6 +55,9 b' class API(object):'
55 body = json.dumps({'ext': ext})
55 body = json.dumps({'ext': ext})
56 return self._req('POST', path, body)
56 return self._req('POST', path, body)
57
57
58 def mkdir_untitled(self, path='/'):
59 return self._req('POST', path, json.dumps({'type': 'directory'}))
60
58 def copy(self, copy_from, path='/'):
61 def copy(self, copy_from, path='/'):
59 body = json.dumps({'copy_from':copy_from})
62 body = json.dumps({'copy_from':copy_from})
60 return self._req('POST', path, body)
63 return self._req('POST', path, body)
@@ -65,6 +68,9 b' class API(object):'
65 def upload(self, path, body):
68 def upload(self, path, body):
66 return self._req('PUT', path, body)
69 return self._req('PUT', path, body)
67
70
71 def mkdir_untitled(self, path='/'):
72 return self._req('POST', path, json.dumps({'type': 'directory'}))
73
68 def mkdir(self, path='/'):
74 def mkdir(self, path='/'):
69 return self._req('PUT', path, json.dumps({'type': 'directory'}))
75 return self._req('PUT', path, json.dumps({'type': 'directory'}))
70
76
@@ -193,8 +199,11 b' class APITest(NotebookTestBase):'
193 self.assertEqual(nbnames, expected)
199 self.assertEqual(nbnames, expected)
194
200
195 def test_list_dirs(self):
201 def test_list_dirs(self):
202 print(self.api.list().json())
196 dirs = dirs_only(self.api.list().json())
203 dirs = dirs_only(self.api.list().json())
197 dir_names = {normalize('NFC', d['name']) for d in dirs}
204 dir_names = {normalize('NFC', d['name']) for d in dirs}
205 print(dir_names)
206 print(self.top_level_dirs)
198 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
207 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
199
208
200 def test_list_nonexistant_dir(self):
209 def test_list_nonexistant_dir(self):
@@ -293,6 +302,18 b' class APITest(NotebookTestBase):'
293 resp = self.api.upload(path, body=json.dumps(nbmodel))
302 resp = self.api.upload(path, body=json.dumps(nbmodel))
294 self._check_created(resp, path)
303 self._check_created(resp, path)
295
304
305 def test_mkdir_untitled(self):
306 resp = self.api.mkdir_untitled(path=u'å b')
307 self._check_created(resp, u'å b/Untitled Folder0', type='directory')
308
309 # Second time
310 resp = self.api.mkdir_untitled(path=u'å b')
311 self._check_created(resp, u'å b/Untitled Folder1', type='directory')
312
313 # And two directories down
314 resp = self.api.mkdir_untitled(path='foo/bar')
315 self._check_created(resp, 'foo/bar/Untitled Folder0', type='directory')
316
296 def test_mkdir(self):
317 def test_mkdir(self):
297 path = u'å b/New ∂ir'
318 path = u'å b/New ∂ir'
298 resp = self.api.mkdir(path)
319 resp = self.api.mkdir(path)
@@ -90,9 +90,9 b' define(['
90 this.element.find('#new_notebook').click(function () {
90 this.element.find('#new_notebook').click(function () {
91 // Create a new notebook in the same path as the current
91 // Create a new notebook in the same path as the current
92 // notebook's path.
92 // notebook's path.
93 var parent = utils.url_path_split(this.notebook_path)[0];
93 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
94 that.contents.new(parent, {
94 that.contents.new(parent, {
95 ext: ".ipynb",
95 type: "notebook",
96 extra_settings: {async: false}, // So we can open a new window afterwards
96 extra_settings: {async: false}, // So we can open a new window afterwards
97 success: function (data) {
97 success: function (data) {
98 window.open(
98 window.open(
@@ -98,9 +98,13 b' define(['
98 * @param {String} path The path to create the new notebook at
98 * @param {String} path The path to create the new notebook at
99 * @param {Object} options:
99 * @param {Object} options:
100 * ext: file extension to use
100 * ext: file extension to use
101 * type: model type to create ('notebook', 'file', or 'directory')
101 */
102 */
102 Contents.prototype.new = function(path, options) {
103 Contents.prototype.new = function(path, options) {
103 var data = JSON.stringify({ext: options.ext || ".ipynb"});
104 var data = JSON.stringify({
105 ext: options.ext,
106 type: options.type
107 });
104
108
105 var settings = {
109 var settings = {
106 processData : false,
110 processData : false,
@@ -65,7 +65,7 b' require(['
65
65
66 $('#new_notebook').click(function (e) {
66 $('#new_notebook').click(function (e) {
67 contents.new(common_options.notebook_path, {
67 contents.new(common_options.notebook_path, {
68 ext: ".ipynb",
68 type: "notebook",
69 extra_settings: {async: false}, // So we can open a new window afterwards
69 extra_settings: {async: false}, // So we can open a new window afterwards
70 success: function (data) {
70 success: function (data) {
71 window.open(
71 window.open(
@@ -37,9 +37,12 b' class TreeHandler(IPythonHandler):'
37 path = path.strip('/')
37 path = path.strip('/')
38 cm = self.contents_manager
38 cm = self.contents_manager
39 if cm.file_exists(path):
39 if cm.file_exists(path):
40 # is a notebook, redirect to notebook handler
40 # it's not a directory, we have redirecting to do
41 model = cm.get(path, content=False)
42 # redirect to /api/notebooks if it's a notebook, otherwise /api/files
43 service = 'notebooks' if model['type'] == 'notebook' else 'files'
41 url = url_escape(url_path_join(
44 url = url_escape(url_path_join(
42 self.base_url, 'notebooks', path,
45 self.base_url, service, path,
43 ))
46 ))
44 self.log.debug("Redirecting %s to %s", self.request.path, url)
47 self.log.debug("Redirecting %s to %s", self.request.path, url)
45 self.redirect(url)
48 self.redirect(url)
General Comments 0
You need to be logged in to leave comments. Login now