##// END OF EJS Templates
manual rebase notebooks web services
Zachary Sailer -
Show More
@@ -21,6 +21,7 b' import io'
21 21 import os
22 22 import glob
23 23 import shutil
24
24 25 from unicodedata import normalize
25 26
26 27 from tornado import web
@@ -73,68 +74,47 b' class FileNotebookManager(NotebookManager):'
73 74
74 75 filename_ext = Unicode(u'.ipynb')
75 76
76 # Map notebook names to notebook_ids
77 77 rev_mapping = Dict()
78 78
79 def get_notebook_names(self):
79 def get_notebook_names(self, path):
80 80 """List all notebook names in the notebook dir."""
81 names = glob.glob(os.path.join(self.notebook_dir,
81 names = glob.glob(os.path.join(self.notebook_dir, path,
82 82 '*' + self.filename_ext))
83 names = [normalize('NFC', os.path.splitext(os.path.basename(name))[0])
83 #names = [os.path.splitext(os.path.basename(name))[0]
84 names = [os.path.basename(name)
84 85 for name in names]
85 86 return names
86
87 def list_notebooks(self):
87
88 def list_notebooks(self, path):
88 89 """List all notebooks in the notebook dir."""
89 names = self.get_notebook_names()
90 names = self.get_notebook_names(path)
90 91
91 92 data = []
92 for name in names:
93 if name not in self.rev_mapping:
94 notebook_id = self.new_notebook_id(name)
95 else:
96 notebook_id = self.rev_mapping[name]
97 data.append(dict(notebook_id=notebook_id,name=name))
98 data = sorted(data, key=lambda item: item['name'])
99 return data
100
101 def new_notebook_id(self, name):
102 """Generate a new notebook_id for a name and store its mappings."""
103 notebook_id = super(FileNotebookManager, self).new_notebook_id(name)
104 self.rev_mapping[name] = notebook_id
105 return notebook_id
106
107 def delete_notebook_id(self, notebook_id):
108 """Delete a notebook's id in the mapping."""
109 name = self.mapping[notebook_id]
110 super(FileNotebookManager, self).delete_notebook_id(notebook_id)
111 del self.rev_mapping[name]
93 for name in names:
94 data.append(name)
95 #data = sorted(data, key=lambda item: item['name'])
96 return names
112 97
113 def notebook_exists(self, notebook_id):
98 def notebook_exists(self, notebook_name):
114 99 """Does a notebook exist?"""
115 exists = super(FileNotebookManager, self).notebook_exists(notebook_id)
100 exists = super(FileNotebookManager, self).notebook_exists(notebook_name)
116 101 if not exists:
117 102 return False
118 path = self.get_path_by_name(self.mapping[notebook_id])
103 path = self.get_path_by_name(self.mapping[notebook_name])
119 104 return os.path.isfile(path)
120
121 def get_name(self, notebook_id):
122 """get a notebook name, raising 404 if not found"""
123 try:
124 name = self.mapping[notebook_id]
125 except KeyError:
126 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
127 return name
128 105
129 def get_path(self, notebook_id):
130 """Return a full path to a notebook given its notebook_id."""
131 name = self.get_name(notebook_id)
132 return self.get_path_by_name(name)
133 106
134 def get_path_by_name(self, name):
107 def get_path(self, notebook_name, notebook_path=None):
108 """Return a full path to a notebook given its notebook_name."""
109 return self.get_path_by_name(notebook_name, notebook_path)
110
111 def get_path_by_name(self, name, notebook_path=None):
135 112 """Return a full path to a notebook given its name."""
136 filename = name + self.filename_ext
137 path = os.path.join(self.notebook_dir, filename)
113 filename = name #+ self.filename_ext
114 if notebook_path == None:
115 path = os.path.join(self.notebook_dir, filename)
116 else:
117 path = os.path.join(self.notebook_dir, notebook_path, filename)
138 118 return path
139 119
140 120 def read_notebook_object_from_path(self, path):
@@ -151,11 +131,11 b' class FileNotebookManager(NotebookManager):'
151 131 raise web.HTTPError(400, msg, reason=msg)
152 132 return last_modified, nb
153 133
154 def read_notebook_object(self, notebook_id):
155 """Get the Notebook representation of a notebook by notebook_id."""
156 path = self.get_path(notebook_id)
134 def read_notebook_object(self, notebook_name, notebook_path):
135 """Get the Notebook representation of a notebook by notebook_name."""
136 path = self.get_path(notebook_name, notebook_path)
157 137 if not os.path.isfile(path):
158 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
138 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
159 139 last_modified, nb = self.read_notebook_object_from_path(path)
160 140 # Always use the filename as the notebook name.
161 141 # Eventually we will get rid of the notebook name in the metadata
@@ -165,23 +145,20 b' class FileNotebookManager(NotebookManager):'
165 145 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
166 146 return last_modified, nb
167 147
168 def write_notebook_object(self, nb, notebook_id=None):
169 """Save an existing notebook object by notebook_id."""
148 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None):
149 """Save an existing notebook object by notebook_name."""
170 150 try:
171 151 new_name = normalize('NFC', nb.metadata.name)
172 152 except AttributeError:
173 153 raise web.HTTPError(400, u'Missing notebook name')
174
175 if notebook_id is None:
176 notebook_id = self.new_notebook_id(new_name)
177
178 if notebook_id not in self.mapping:
179 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
180
181 old_name = self.mapping[notebook_id]
182 old_checkpoints = self.list_checkpoints(notebook_id)
183 path = self.get_path_by_name(new_name)
184
154
155 new_path = notebook_path
156 old_name = notebook_name
157 # old_name = self.mapping[notebook_name]
158 old_checkpoints = self.list_checkpoints(old_name)
159
160 path = self.get_path_by_name(new_name, new_path)
161
185 162 # Right before we save the notebook, we write an empty string as the
186 163 # notebook name in the metadata. This is to prepare for removing
187 164 # this attribute entirely post 1.0. The web app still uses the metadata
@@ -205,47 +182,43 b' class FileNotebookManager(NotebookManager):'
205 182 except Exception as e:
206 183 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
207 184
208 # remove old files if the name changed
209 if old_name != new_name:
210 # update mapping
211 self.mapping[notebook_id] = new_name
212 self.rev_mapping[new_name] = notebook_id
213 del self.rev_mapping[old_name]
214
215 # remove renamed original, if it exists
216 old_path = self.get_path_by_name(old_name)
217 if os.path.isfile(old_path):
218 self.log.debug("unlinking notebook %s", old_path)
219 os.unlink(old_path)
185 if old_name != None:
186 # remove old files if the name changed
187 if old_name != new_name:
188 # remove renamed original, if it exists
189 old_path = self.get_path_by_name(old_name, notebook_path)
190 if os.path.isfile(old_path):
191 self.log.debug("unlinking notebook %s", old_path)
192 os.unlink(old_path)
220 193
221 # cleanup old script, if it exists
222 if self.save_script:
223 old_pypath = os.path.splitext(old_path)[0] + '.py'
224 if os.path.isfile(old_pypath):
225 self.log.debug("unlinking script %s", old_pypath)
226 os.unlink(old_pypath)
194 # cleanup old script, if it exists
195 if self.save_script:
196 old_pypath = os.path.splitext(old_path)[0] + '.py'
197 if os.path.isfile(old_pypath):
198 self.log.debug("unlinking script %s", old_pypath)
199 os.unlink(old_pypath)
200
201 # rename checkpoints to follow file
202 for cp in old_checkpoints:
203 checkpoint_id = cp['checkpoint_id']
204 old_cp_path = self.get_checkpoint_path_by_name(old_name, checkpoint_id)
205 new_cp_path = self.get_checkpoint_path_by_name(new_name, checkpoint_id)
206 if os.path.isfile(old_cp_path):
207 self.log.debug("renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
208 os.rename(old_cp_path, new_cp_path)
227 209
228 # rename checkpoints to follow file
229 for cp in old_checkpoints:
230 checkpoint_id = cp['checkpoint_id']
231 old_cp_path = self.get_checkpoint_path_by_name(old_name, checkpoint_id)
232 new_cp_path = self.get_checkpoint_path_by_name(new_name, checkpoint_id)
233 if os.path.isfile(old_cp_path):
234 self.log.debug("renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
235 os.rename(old_cp_path, new_cp_path)
210 return new_name
236 211
237 return notebook_id
238
239 def delete_notebook(self, notebook_id):
240 """Delete notebook by notebook_id."""
241 nb_path = self.get_path(notebook_id)
212 def delete_notebook(self, notebook_name, notebook_path):
213 """Delete notebook by notebook_name."""
214 nb_path = self.get_path(notebook_name, notebook_path)
242 215 if not os.path.isfile(nb_path):
243 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
216 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
244 217
245 218 # clear checkpoints
246 for checkpoint in self.list_checkpoints(notebook_id):
219 for checkpoint in self.list_checkpoints(notebook_name):
247 220 checkpoint_id = checkpoint['checkpoint_id']
248 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
221 path = self.get_checkpoint_path(notebook_name, checkpoint_id)
249 222 self.log.debug(path)
250 223 if os.path.isfile(path):
251 224 self.log.debug("unlinking checkpoint %s", path)
@@ -253,9 +226,8 b' class FileNotebookManager(NotebookManager):'
253 226
254 227 self.log.debug("unlinking notebook %s", nb_path)
255 228 os.unlink(nb_path)
256 self.delete_notebook_id(notebook_id)
257 229
258 def increment_filename(self, basename):
230 def increment_filename(self, basename, notebook_path=None):
259 231 """Return a non-used filename of the form basename<int>.
260 232
261 233 This searches through the filenames (basename0, basename1, ...)
@@ -264,8 +236,8 b' class FileNotebookManager(NotebookManager):'
264 236 """
265 237 i = 0
266 238 while True:
267 name = u'%s%i' % (basename,i)
268 path = self.get_path_by_name(name)
239 name = u'%s%i.ipynb' % (basename,i)
240 path = self.get_path_by_name(name, notebook_path)
269 241 if not os.path.isfile(path):
270 242 break
271 243 else:
@@ -274,24 +246,27 b' class FileNotebookManager(NotebookManager):'
274 246
275 247 # Checkpoint-related utilities
276 248
277 def get_checkpoint_path_by_name(self, name, checkpoint_id):
249 def get_checkpoint_path_by_name(self, name, checkpoint_id, notebook_path=None):
278 250 """Return a full path to a notebook checkpoint, given its name and checkpoint id."""
279 251 filename = u"{name}-{checkpoint_id}{ext}".format(
280 252 name=name,
281 253 checkpoint_id=checkpoint_id,
282 254 ext=self.filename_ext,
283 255 )
284 path = os.path.join(self.checkpoint_dir, filename)
256 if notebook_path ==None:
257 path = os.path.join(self.checkpoint_dir, filename)
258 else:
259 path = os.path.join(notebook_path, self.checkpoint_dir, filename)
285 260 return path
286 261
287 def get_checkpoint_path(self, notebook_id, checkpoint_id):
262 def get_checkpoint_path(self, notebook_name, checkpoint_id, notebook_path=None):
288 263 """find the path to a checkpoint"""
289 name = self.get_name(notebook_id)
290 return self.get_checkpoint_path_by_name(name, checkpoint_id)
264 name = notebook_name
265 return self.get_checkpoint_path_by_name(name, checkpoint_id, notebook_path)
291 266
292 def get_checkpoint_info(self, notebook_id, checkpoint_id):
267 def get_checkpoint_info(self, notebook_name, checkpoint_id, notebook_path=None):
293 268 """construct the info dict for a given checkpoint"""
294 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
269 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
295 270 stats = os.stat(path)
296 271 last_modified = tz.utcfromtimestamp(stats.st_mtime)
297 272 info = dict(
@@ -303,54 +278,54 b' class FileNotebookManager(NotebookManager):'
303 278
304 279 # public checkpoint API
305 280
306 def create_checkpoint(self, notebook_id):
281 def create_checkpoint(self, notebook_name, notebook_path=None):
307 282 """Create a checkpoint from the current state of a notebook"""
308 nb_path = self.get_path(notebook_id)
283 nb_path = self.get_path(notebook_name, notebook_path)
309 284 # only the one checkpoint ID:
310 285 checkpoint_id = u"checkpoint"
311 cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
312 self.log.debug("creating checkpoint for notebook %s", notebook_id)
286 cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
287 self.log.debug("creating checkpoint for notebook %s", notebook_name)
313 288 if not os.path.exists(self.checkpoint_dir):
314 289 os.mkdir(self.checkpoint_dir)
315 290 shutil.copy2(nb_path, cp_path)
316 291
317 292 # return the checkpoint info
318 return self.get_checkpoint_info(notebook_id, checkpoint_id)
293 return self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)
319 294
320 def list_checkpoints(self, notebook_id):
295 def list_checkpoints(self, notebook_name, notebook_path=None):
321 296 """list the checkpoints for a given notebook
322 297
323 298 This notebook manager currently only supports one checkpoint per notebook.
324 299 """
325 checkpoint_id = u"checkpoint"
326 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
300 checkpoint_id = "checkpoint"
301 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
327 302 if not os.path.exists(path):
328 303 return []
329 304 else:
330 return [self.get_checkpoint_info(notebook_id, checkpoint_id)]
305 return [self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)]
331 306
332 307
333 def restore_checkpoint(self, notebook_id, checkpoint_id):
308 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
334 309 """restore a notebook to a checkpointed state"""
335 self.log.info("restoring Notebook %s from checkpoint %s", notebook_id, checkpoint_id)
336 nb_path = self.get_path(notebook_id)
337 cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
310 self.log.info("restoring Notebook %s from checkpoint %s", notebook_name, checkpoint_id)
311 nb_path = self.get_path(notebook_name, notebook_path)
312 cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
338 313 if not os.path.isfile(cp_path):
339 314 self.log.debug("checkpoint file does not exist: %s", cp_path)
340 315 raise web.HTTPError(404,
341 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
316 u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
342 317 )
343 318 # ensure notebook is readable (never restore from an unreadable notebook)
344 319 last_modified, nb = self.read_notebook_object_from_path(cp_path)
345 320 shutil.copy2(cp_path, nb_path)
346 321 self.log.debug("copying %s -> %s", cp_path, nb_path)
347 322
348 def delete_checkpoint(self, notebook_id, checkpoint_id):
323 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
349 324 """delete a notebook's checkpoint"""
350 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
325 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
351 326 if not os.path.isfile(path):
352 327 raise web.HTTPError(404,
353 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
328 u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
354 329 )
355 330 self.log.debug("unlinking %s", path)
356 331 os.unlink(path)
@@ -28,29 +28,34 b' from ...base.handlers import IPythonHandler'
28 28 # Notebook web service handlers
29 29 #-----------------------------------------------------------------------------
30 30
31
31 32 class NotebookRootHandler(IPythonHandler):
32 33
33 34 @web.authenticated
34 35 def get(self):
35 36 nbm = self.notebook_manager
36 37 km = self.kernel_manager
37 files = nbm.list_notebooks()
38 for f in files :
39 f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
40 self.finish(jsonapi.dumps(files))
38 notebook_names = nbm.list_notebooks("")
39 notebooks = []
40 for name in notebook_names:
41 model = nbm.notebook_model(name)
42 notebooks.append(model)
43 self.finish(jsonapi.dumps(notebooks))
41 44
42 45 @web.authenticated
43 46 def post(self):
44 47 nbm = self.notebook_manager
45 body = self.request.body.strip()
46 format = self.get_argument('format', default='json')
47 name = self.get_argument('name', default=None)
48 if body:
49 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
50 else:
51 notebook_id = nbm.new_notebook()
52 self.set_header('Location', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
53 self.finish(jsonapi.dumps(notebook_id))
48 notebook_name = nbm.new_notebook()
49 model = nbm.notebook_model(notebook_name)
50 self.set_header('Location', '{0}api/notebooks/{1}'.format(self.base_project_url, notebook_name))
51 self.finish(jsonapi.dumps(model))
52
53
54 class NotebookRootRedirect(IPythonHandler):
55
56 @authenticate_unless_readonly
57 def get(self):
58 self.redirect("/api/notebooks")
54 59
55 60
56 61 class NotebookHandler(IPythonHandler):
@@ -58,32 +63,62 b' class NotebookHandler(IPythonHandler):'
58 63 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
59 64
60 65 @web.authenticated
61 def get(self, notebook_id):
66 def get(self, notebook_path):
62 67 nbm = self.notebook_manager
63 format = self.get_argument('format', default='json')
64 last_mod, name, data = nbm.get_notebook(notebook_id, format)
68 name, path = nbm.named_notebook_path(notebook_path)
65 69
66 if format == u'json':
67 self.set_header('Content-Type', 'application/json')
68 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
69 elif format == u'py':
70 self.set_header('Content-Type', 'application/x-python')
71 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
72 self.set_header('Last-Modified', last_mod)
73 self.finish(data)
70 if name == None:
71 notebook_names = nbm.list_notebooks(path)
72 notebooks = []
73 for name in notebook_names:
74 model = nbm.notebook_model(name,path)
75 notebooks.append(model)
76 self.finish(jsonapi.dumps(notebooks))
77 else:
78 format = self.get_argument('format', default='json')
79 model = nbm.notebook_model(name,path)
80 data, name = nbm.get_notebook(model, format)
81
82 if format == u'json':
83 self.set_header('Content-Type', 'application/json')
84 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
85 elif format == u'py':
86 self.set_header('Content-Type', 'application/x-python')
87 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
88 #self.set_header('Last-Modified', last_mod)
89 self.finish(jsonapi.dumps(model))
74 90
75 91 @web.authenticated
76 def put(self, notebook_id):
92 def put(self, notebook_path):
77 93 nbm = self.notebook_manager
78 format = self.get_argument('format', default='json')
79 name = self.get_argument('name', default=None)
80 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
81 self.set_status(204)
82 self.finish()
94 notebook_name, notebook_path = nbm.named_notebook_path(notebook_path)
95 if notebook_name == None:
96 body = self.request.body.strip()
97 format = self.get_argument('format', default='json')
98 name = self.get_argument('name', default=None)
99 if body:
100 notebook_name = nbm.save_new_notebook(body, notebook_path=notebook_path, name=name, format=format)
101 else:
102 notebook_name = nbm.new_notebook(notebook_path=notebook_path)
103 if path==None:
104 self.set_header('Location', nbm.notebook_dir + '/'+ notebook_name)
105 else:
106 self.set_header('Location', nbm.notebook_dir + '/'+ notebook_path + '/' + notebook_name)
107 model = nbm.notebook_model(notebook_name, notebook_path)
108 self.finish(jsonapi.dumps(model))
109 else:
110 format = self.get_argument('format', default='json')
111 name = self.get_argument('name', default=None)
112 nbm.save_notebook(self.request.body, notebook_path=notebook_path, name=name, format=format)
113 model = nbm.notebook_model(notebook_name, notebook_path)
114 self.set_status(204)
115 self.finish(jsonapi.dumps(model))
83 116
84 117 @web.authenticated
85 def delete(self, notebook_id):
86 self.notebook_manager.delete_notebook(notebook_id)
118 def delete(self, notebook_path):
119 nbm = self.notebook_manager
120 name, path = nbm.named_notebook_path(notebook_path)
121 self.notebook_manager.delete_notebook(name, path)
87 122 self.set_status(204)
88 123 self.finish()
89 124
@@ -93,23 +128,29 b' class NotebookCheckpointsHandler(IPythonHandler):'
93 128 SUPPORTED_METHODS = ('GET', 'POST')
94 129
95 130 @web.authenticated
96 def get(self, notebook_id):
131 def get(self, notebook_path):
97 132 """get lists checkpoints for a notebook"""
98 133 nbm = self.notebook_manager
99 checkpoints = nbm.list_checkpoints(notebook_id)
134 name, path = nbm.named_notebook_path(notebook_path)
135 checkpoints = nbm.list_checkpoints(name, path)
100 136 data = jsonapi.dumps(checkpoints, default=date_default)
101 137 self.finish(data)
102 138
103 139 @web.authenticated
104 def post(self, notebook_id):
140 def post(self, notebook_path):
105 141 """post creates a new checkpoint"""
106 142 nbm = self.notebook_manager
107 checkpoint = nbm.create_checkpoint(notebook_id)
143 name, path = nbm.named_notebook_path(notebook_path)
144 checkpoint = nbm.create_checkpoint(name, path)
108 145 data = jsonapi.dumps(checkpoint, default=date_default)
109 self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
110 self.base_project_url, notebook_id, checkpoint['checkpoint_id']
111 ))
112
146 if path == None:
147 self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
148 self.base_project_url, name, checkpoint['checkpoint_id']
149 ))
150 else:
151 self.set_header('Location', '{0}notebooks/{1}/{2}/checkpoints/{3}'.format(
152 self.base_project_url, path, name, checkpoint['checkpoint_id']
153 ))
113 154 self.finish(data)
114 155
115 156
@@ -118,37 +159,38 b' class ModifyNotebookCheckpointsHandler(IPythonHandler):'
118 159 SUPPORTED_METHODS = ('POST', 'DELETE')
119 160
120 161 @web.authenticated
121 def post(self, notebook_id, checkpoint_id):
162 def post(self, notebook_path, checkpoint_id):
122 163 """post restores a notebook from a checkpoint"""
123 164 nbm = self.notebook_manager
124 nbm.restore_checkpoint(notebook_id, checkpoint_id)
165 name, path = nbm.named_notebook_path(notebook_path)
166 nbm.restore_checkpoint(name, checkpoint_id, path)
125 167 self.set_status(204)
126 168 self.finish()
127 169
128 170 @web.authenticated
129 def delete(self, notebook_id, checkpoint_id):
171 def delete(self, notebook_path, checkpoint_id):
130 172 """delete clears a checkpoint for a given notebook"""
131 173 nbm = self.notebook_manager
132 nbm.delte_checkpoint(notebook_id, checkpoint_id)
174 name, path = nbm.named_notebook_path(notebook_path)
175 nbm.delete_checkpoint(name, checkpoint_id, path)
133 176 self.set_status(204)
134 177 self.finish()
135
136
178
137 179 #-----------------------------------------------------------------------------
138 180 # URL to handler mappings
139 181 #-----------------------------------------------------------------------------
140 182
141 183
142 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
184 _notebook_path_regex = r"(?P<notebook_path>.+)"
143 185 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
144 186
145 187 default_handlers = [
146 (r"/notebooks", NotebookRootHandler),
147 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
148 (r"/notebooks/%s/checkpoints" % _notebook_id_regex, NotebookCheckpointsHandler),
149 (r"/notebooks/%s/checkpoints/%s" % (_notebook_id_regex, _checkpoint_id_regex),
150 ModifyNotebookCheckpointsHandler
151 ),
188 (r"api/notebooks/%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
189 (r"api/notebooks/%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
190 ModifyNotebookCheckpointsHandler),
191 (r"api/notebooks/%s" % _notebook_path_regex, NotebookHandler),
192 (r"api/notebooks/", NotebookRootRedirect),
193 (r"api/notebooks", NotebookRootHandler),
152 194 ]
153 195
154 196
@@ -38,14 +38,37 b' class NotebookManager(LoggingConfigurable):'
38 38 # Right now we use this attribute in a number of different places and
39 39 # we are going to have to disentangle all of this.
40 40 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
41 The directory to use for notebooks.
42 """)
43 def _notebook_dir_changed(self, name, old, new):
41 The directory to use for notebooks.
42 """)
43
44 def named_notebook_path(self, notebook_path):
45
46 l = len(notebook_path)
47 names = notebook_path.split('/')
48 if len(names) > 1:
49 name = names[len(names)-1]
50 if name[(len(name)-6):(len(name))] == ".ipynb":
51 name = name
52 path = notebook_path[0:l-len(name)-1]+'/'
53 else:
54 name = None
55 path = notebook_path+'/'
56 else:
57 name = names[0]
58 if name[(len(name)-6):(len(name))] == ".ipynb":
59 name = name
60 path = None
61 else:
62 name = None
63 path = notebook_path+'/'
64 return name, path
65
66 def _notebook_dir_changed(self, new):
44 67 """do a bit of validation of the notebook dir"""
45 68 if not os.path.isabs(new):
46 69 # If we receive a non-absolute path, make it absolute.
47 70 abs_new = os.path.abspath(new)
48 self.notebook_dir = abs_new
71 #self.notebook_dir = os.path.dirname(abs_new)
49 72 return
50 73 if os.path.exists(new) and not os.path.isdir(new):
51 74 raise TraitError("notebook dir %r is not a directory" % new)
@@ -55,20 +78,18 b' class NotebookManager(LoggingConfigurable):'
55 78 os.mkdir(new)
56 79 except:
57 80 raise TraitError("Couldn't create notebook dir %r" % new)
58
81
59 82 allowed_formats = List([u'json',u'py'])
60 83
61 # Map notebook_ids to notebook names
62 mapping = Dict()
63 84
64 def load_notebook_names(self):
85 def load_notebook_names(self, path):
65 86 """Load the notebook names into memory.
66 87
67 88 This should be called once immediately after the notebook manager
68 89 is created to load the existing notebooks into the mapping in
69 90 memory.
70 91 """
71 self.list_notebooks()
92 self.list_notebooks(path)
72 93
73 94 def list_notebooks(self):
74 95 """List all notebooks.
@@ -84,52 +105,37 b' class NotebookManager(LoggingConfigurable):'
84 105 raise NotImplementedError('must be implemented in a subclass')
85 106
86 107
87 def new_notebook_id(self, name):
88 """Generate a new notebook_id for a name and store its mapping."""
89 # TODO: the following will give stable urls for notebooks, but unless
90 # the notebooks are immediately redirected to their new urls when their
91 # filemname changes, nasty inconsistencies result. So for now it's
92 # disabled and instead we use a random uuid4() call. But we leave the
93 # logic here so that we can later reactivate it, whhen the necessary
94 # url redirection code is written.
95 #notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
96 # 'file://'+self.get_path_by_name(name).encode('utf-8')))
97
98 notebook_id = unicode(uuid.uuid4())
99 self.mapping[notebook_id] = name
100 return notebook_id
101
102 def delete_notebook_id(self, notebook_id):
103 """Delete a notebook's id in the mapping.
104
105 This doesn't delete the actual notebook, only its entry in the mapping.
106 """
107 del self.mapping[notebook_id]
108
109 def notebook_exists(self, notebook_id):
108 def notebook_exists(self, notebook_name):
110 109 """Does a notebook exist?"""
111 return notebook_id in self.mapping
112
113 def get_notebook(self, notebook_id, format=u'json'):
114 """Get the representation of a notebook in format by notebook_id."""
110 return notebook_name in self.mapping
111
112 def notebook_model(self, notebook_name, notebook_path=None):
113 """ Creates the standard notebook model """
114 last_modified, content = self.read_notebook_object(notebook_name, notebook_path)
115 model = {"notebook_name": notebook_name,
116 "notebook_path": notebook_path,
117 "content": content}
118 return model
119
120 def get_notebook(self, body, format=u'json'):
121 """Get the representation of a notebook in format by notebook_name."""
115 122 format = unicode(format)
116 123 if format not in self.allowed_formats:
117 124 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
118 last_modified, nb = self.read_notebook_object(notebook_id)
119 125 kwargs = {}
120 126 if format == 'json':
121 127 # don't split lines for sending over the wire, because it
122 128 # should match the Python in-memory format.
123 129 kwargs['split_lines'] = False
124 data = current.writes(nb, format, **kwargs)
125 name = nb.metadata.get('name','notebook')
126 return last_modified, name, data
130 representation = current.writes(body, format, **kwargs)
131 name = body['content']['metadata']['name']
132 return representation, name
127 133
128 def read_notebook_object(self, notebook_id):
134 def read_notebook_object(self, notebook_name, notebook_path):
129 135 """Get the object representation of a notebook by notebook_id."""
130 136 raise NotImplementedError('must be implemented in a subclass')
131 137
132 def save_new_notebook(self, data, name=None, format=u'json'):
138 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
133 139 """Save a new notebook and return its notebook_id.
134 140
135 141 If a name is passed in, it overrides any values in the notebook data
@@ -150,10 +156,10 b' class NotebookManager(LoggingConfigurable):'
150 156 raise web.HTTPError(400, u'Missing notebook name')
151 157 nb.metadata.name = name
152 158
153 notebook_id = self.write_notebook_object(nb)
154 return notebook_id
159 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
160 return notebook_name
155 161
156 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
162 def save_notebook(self, data, notebook_path=None, name=None, format=u'json'):
157 163 """Save an existing notebook by notebook_id."""
158 164 if format not in self.allowed_formats:
159 165 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
@@ -165,18 +171,18 b' class NotebookManager(LoggingConfigurable):'
165 171
166 172 if name is not None:
167 173 nb.metadata.name = name
168 self.write_notebook_object(nb, notebook_id)
174 self.write_notebook_object(nb, name, notebook_path)
169 175
170 def write_notebook_object(self, nb, notebook_id=None):
171 """Write a notebook object and return its notebook_id.
176 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None):
177 """Write a notebook object and return its notebook_name.
172 178
173 If notebook_id is None, this method should create a new notebook_id.
174 If notebook_id is not None, this method should check to make sure it
179 If notebook_name is None, this method should create a new notebook_name.
180 If notebook_name is not None, this method should check to make sure it
175 181 exists and is valid.
176 182 """
177 183 raise NotImplementedError('must be implemented in a subclass')
178 184
179 def delete_notebook(self, notebook_id):
185 def delete_notebook(self, notebook_name, notebook_path):
180 186 """Delete notebook by notebook_id."""
181 187 raise NotImplementedError('must be implemented in a subclass')
182 188
@@ -189,41 +195,41 b' class NotebookManager(LoggingConfigurable):'
189 195 """
190 196 return name
191 197
192 def new_notebook(self):
198 def new_notebook(self, notebook_path=None):
193 199 """Create a new notebook and return its notebook_id."""
194 name = self.increment_filename('Untitled')
200 name = self.increment_filename('Untitled', notebook_path)
195 201 metadata = current.new_metadata(name=name)
196 202 nb = current.new_notebook(metadata=metadata)
197 notebook_id = self.write_notebook_object(nb)
198 return notebook_id
203 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
204 return notebook_name
199 205
200 def copy_notebook(self, notebook_id):
206 def copy_notebook(self, name, path):
201 207 """Copy an existing notebook and return its notebook_id."""
202 last_mod, nb = self.read_notebook_object(notebook_id)
208 last_mod, nb = self.read_notebook_object(name, path)
203 209 name = nb.metadata.name + '-Copy'
204 name = self.increment_filename(name)
210 name = self.increment_filename(name, path)
205 211 nb.metadata.name = name
206 notebook_id = self.write_notebook_object(nb)
207 return notebook_id
212 notebook_name = self.write_notebook_object(nb, notebook_path = path)
213 return notebook_name
208 214
209 215 # Checkpoint-related
210 216
211 def create_checkpoint(self, notebook_id):
217 def create_checkpoint(self, notebook_name, notebook_path=None):
212 218 """Create a checkpoint of the current state of a notebook
213 219
214 220 Returns a checkpoint_id for the new checkpoint.
215 221 """
216 222 raise NotImplementedError("must be implemented in a subclass")
217 223
218 def list_checkpoints(self, notebook_id):
224 def list_checkpoints(self, notebook_name, notebook_path=None):
219 225 """Return a list of checkpoints for a given notebook"""
220 226 return []
221 227
222 def restore_checkpoint(self, notebook_id, checkpoint_id):
228 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
223 229 """Restore a notebook from one of its checkpoints"""
224 230 raise NotImplementedError("must be implemented in a subclass")
225 231
226 def delete_checkpoint(self, notebook_id, checkpoint_id):
232 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
227 233 """delete a checkpoint for a notebook"""
228 234 raise NotImplementedError("must be implemented in a subclass")
229 235
@@ -232,4 +238,3 b' class NotebookManager(LoggingConfigurable):'
232 238
233 239 def info_string(self):
234 240 return "Serving notebooks"
235
General Comments 0
You need to be logged in to leave comments. Login now