##// END OF EJS Templates
add checkpoint API to FileNBManager
MinRK -
Show More
@@ -1,196 +1,323 b''
1 """A notebook manager that uses the local file system for storage.
1 """A notebook manager that uses the local file system for storage.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 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
24
24 from tornado import web
25 from tornado import web
25
26
26 from .nbmanager import NotebookManager
27 from .nbmanager import NotebookManager
27 from IPython.nbformat import current
28 from IPython.nbformat import current
28 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
29 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
29
30
30 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
31 # Classes
32 # Classes
32 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
33
34
34 class FileNotebookManager(NotebookManager):
35 class FileNotebookManager(NotebookManager):
35
36
36 save_script = Bool(False, config=True,
37 save_script = Bool(False, config=True,
37 help="""Automatically create a Python script when saving the notebook.
38 help="""Automatically create a Python script when saving the notebook.
38
39
39 For easier use of import, %run and %load across notebooks, a
40 For easier use of import, %run and %load across notebooks, a
40 <notebook-name>.py script will be created next to any
41 <notebook-name>.py script will be created next to any
41 <notebook-name>.ipynb on each save. This can also be set with the
42 <notebook-name>.ipynb on each save. This can also be set with the
42 short `--script` flag.
43 short `--script` flag.
43 """
44 """
44 )
45 )
45
46
47 checkpoint_dir = Unicode(config=True,
48 help="""The location in which to keep notebook checkpoints
49
50 By default, it is notebook-dir/.ipynb_checkpoints
51 """
52 )
53 def _checkpoint_dir_default(self):
54 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
55
56 def _checkpoint_dir_changed(self, name, old, new):
57 """do a bit of validation of the checkpoint dir"""
58 if not os.path.isabs(new):
59 # If we receive a non-absolute path, make it absolute.
60 abs_new = os.path.abspath(new)
61 self.checkpoint_dir = abs_new
62 return
63 if os.path.exists(new) and not os.path.isdir(new):
64 raise TraitError("checkpoint dir %r is not a directory" % new)
65 if not os.path.exists(new):
66 self.log.info("Creating checkpoint dir %s", new)
67 try:
68 os.mkdir(new)
69 except:
70 raise TraitError("Couldn't create checkpoint dir %r" % new)
71
46 filename_ext = Unicode(u'.ipynb')
72 filename_ext = Unicode(u'.ipynb')
47
73
48 # Map notebook names to notebook_ids
74 # Map notebook names to notebook_ids
49 rev_mapping = Dict()
75 rev_mapping = Dict()
50
76
51 def get_notebook_names(self):
77 def get_notebook_names(self):
52 """List all notebook names in the notebook dir."""
78 """List all notebook names in the notebook dir."""
53 names = glob.glob(os.path.join(self.notebook_dir,
79 names = glob.glob(os.path.join(self.notebook_dir,
54 '*' + self.filename_ext))
80 '*' + self.filename_ext))
55 names = [os.path.splitext(os.path.basename(name))[0]
81 names = [os.path.splitext(os.path.basename(name))[0]
56 for name in names]
82 for name in names]
57 return names
83 return names
58
84
59 def list_notebooks(self):
85 def list_notebooks(self):
60 """List all notebooks in the notebook dir."""
86 """List all notebooks in the notebook dir."""
61 names = self.get_notebook_names()
87 names = self.get_notebook_names()
62
88
63 data = []
89 data = []
64 for name in names:
90 for name in names:
65 if name not in self.rev_mapping:
91 if name not in self.rev_mapping:
66 notebook_id = self.new_notebook_id(name)
92 notebook_id = self.new_notebook_id(name)
67 else:
93 else:
68 notebook_id = self.rev_mapping[name]
94 notebook_id = self.rev_mapping[name]
69 data.append(dict(notebook_id=notebook_id,name=name))
95 data.append(dict(notebook_id=notebook_id,name=name))
70 data = sorted(data, key=lambda item: item['name'])
96 data = sorted(data, key=lambda item: item['name'])
71 return data
97 return data
72
98
73 def new_notebook_id(self, name):
99 def new_notebook_id(self, name):
74 """Generate a new notebook_id for a name and store its mappings."""
100 """Generate a new notebook_id for a name and store its mappings."""
75 notebook_id = super(FileNotebookManager, self).new_notebook_id(name)
101 notebook_id = super(FileNotebookManager, self).new_notebook_id(name)
76 self.rev_mapping[name] = notebook_id
102 self.rev_mapping[name] = notebook_id
77 return notebook_id
103 return notebook_id
78
104
79 def delete_notebook_id(self, notebook_id):
105 def delete_notebook_id(self, notebook_id):
80 """Delete a notebook's id in the mapping."""
106 """Delete a notebook's id in the mapping."""
81 name = self.mapping[notebook_id]
107 name = self.mapping[notebook_id]
82 super(FileNotebookManager, self).delete_notebook_id(notebook_id)
108 super(FileNotebookManager, self).delete_notebook_id(notebook_id)
83 del self.rev_mapping[name]
109 del self.rev_mapping[name]
84
110
85 def notebook_exists(self, notebook_id):
111 def notebook_exists(self, notebook_id):
86 """Does a notebook exist?"""
112 """Does a notebook exist?"""
87 exists = super(FileNotebookManager, self).notebook_exists(notebook_id)
113 exists = super(FileNotebookManager, self).notebook_exists(notebook_id)
88 if not exists:
114 if not exists:
89 return False
115 return False
90 path = self.get_path_by_name(self.mapping[notebook_id])
116 path = self.get_path_by_name(self.mapping[notebook_id])
91 return os.path.isfile(path)
117 return os.path.isfile(path)
92
118
93 def find_path(self, notebook_id):
119 def get_name(self, notebook_id):
94 """Return a full path to a notebook given its notebook_id."""
120 """get a notebook name, raising 404 if not found"""
95 try:
121 try:
96 name = self.mapping[notebook_id]
122 name = self.mapping[notebook_id]
97 except KeyError:
123 except KeyError:
98 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
124 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
125 return name
126
127 def get_path(self, notebook_id):
128 """Return a full path to a notebook given its notebook_id."""
129 name = self.get_name(notebook_id)
99 return self.get_path_by_name(name)
130 return self.get_path_by_name(name)
100
131
101 def get_path_by_name(self, name):
132 def get_path_by_name(self, name):
102 """Return a full path to a notebook given its name."""
133 """Return a full path to a notebook given its name."""
103 filename = name + self.filename_ext
134 filename = name + self.filename_ext
104 path = os.path.join(self.notebook_dir, filename)
135 path = os.path.join(self.notebook_dir, filename)
105 return path
136 return path
106
137
107 def read_notebook_object(self, notebook_id):
138 def read_notebook_object_from_path(self, path):
108 """Get the NotebookNode representation of a notebook by notebook_id."""
139 """read a notebook object from a path"""
109 path = self.find_path(notebook_id)
110 if not os.path.isfile(path):
111 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
112 info = os.stat(path)
140 info = os.stat(path)
113 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
141 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
114 with open(path,'r') as f:
142 with open(path,'r') as f:
115 s = f.read()
143 s = f.read()
116 try:
144 try:
117 # v1 and v2 and json in the .ipynb files.
145 # v1 and v2 and json in the .ipynb files.
118 nb = current.reads(s, u'json')
146 nb = current.reads(s, u'json')
119 except:
147 except Exception as e:
120 raise web.HTTPError(500, u'Unreadable JSON notebook.')
148 raise web.HTTPError(500, u'Unreadable JSON notebook: %s' % e)
149 return last_modified, nb
150
151 def read_notebook_object(self, notebook_id):
152 """Get the Notebook representation of a notebook by notebook_id."""
153 path = self.get_path(notebook_id)
154 if not os.path.isfile(path):
155 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
156 last_modified, nb = self.read_notebook_object_from_path(path)
121 # Always use the filename as the notebook name.
157 # Always use the filename as the notebook name.
122 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
158 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
123 return last_modified, nb
159 return last_modified, nb
124
160
125 def write_notebook_object(self, nb, notebook_id=None):
161 def write_notebook_object(self, nb, notebook_id=None):
126 """Save an existing notebook object by notebook_id."""
162 """Save an existing notebook object by notebook_id."""
127 try:
163 try:
128 new_name = nb.metadata.name
164 new_name = nb.metadata.name
129 except AttributeError:
165 except AttributeError:
130 raise web.HTTPError(400, u'Missing notebook name')
166 raise web.HTTPError(400, u'Missing notebook name')
131
167
132 if notebook_id is None:
168 if notebook_id is None:
133 notebook_id = self.new_notebook_id(new_name)
169 notebook_id = self.new_notebook_id(new_name)
134
170
135 if notebook_id not in self.mapping:
171 if notebook_id not in self.mapping:
136 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
172 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
137
173
138 old_name = self.mapping[notebook_id]
174 old_name = self.mapping[notebook_id]
175 old_checkpoints = self.list_checkpoints(notebook_id)
176
139 path = self.get_path_by_name(new_name)
177 path = self.get_path_by_name(new_name)
140 try:
178 try:
179 self.log.debug("Writing notebook %s", path)
141 with open(path,'w') as f:
180 with open(path,'w') as f:
142 current.write(nb, f, u'json')
181 current.write(nb, f, u'json')
143 except Exception as e:
182 except Exception as e:
144 raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
183 raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
145
184
146 # save .py script as well
185 # save .py script as well
147 if self.save_script:
186 if self.save_script:
148 pypath = os.path.splitext(path)[0] + '.py'
187 pypath = os.path.splitext(path)[0] + '.py'
188 self.log.debug("Writing script %s", pypath)
149 try:
189 try:
150 with io.open(pypath,'w', encoding='utf-8') as f:
190 with io.open(pypath,'w', encoding='utf-8') as f:
151 current.write(nb, f, u'py')
191 current.write(nb, f, u'py')
152 except Exception as e:
192 except Exception as e:
153 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
193 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
154
194
155 # remove old files if the name changed
195 # remove old files if the name changed
156 if old_name != new_name:
196 if old_name != new_name:
197 # update mapping
198 self.mapping[notebook_id] = new_name
199 self.rev_mapping[new_name] = notebook_id
200 del self.rev_mapping[old_name]
201
202 # remove renamed original, if it exists
157 old_path = self.get_path_by_name(old_name)
203 old_path = self.get_path_by_name(old_name)
158 if os.path.isfile(old_path):
204 if os.path.isfile(old_path):
205 self.log.debug("unlinking %s", old_path)
159 os.unlink(old_path)
206 os.unlink(old_path)
207
208 # cleanup old script, if it exists
160 if self.save_script:
209 if self.save_script:
161 old_pypath = os.path.splitext(old_path)[0] + '.py'
210 old_pypath = os.path.splitext(old_path)[0] + '.py'
162 if os.path.isfile(old_pypath):
211 if os.path.isfile(old_pypath):
212 self.log.debug("unlinking %s", old_pypath)
163 os.unlink(old_pypath)
213 os.unlink(old_pypath)
164 self.mapping[notebook_id] = new_name
214
165 self.rev_mapping[new_name] = notebook_id
215 # rename checkpoints to follow file
166 del self.rev_mapping[old_name]
216 self.log.debug("%s", old_checkpoints)
167
217 for cp in old_checkpoints:
218 old_cp_path = self.get_checkpoint_path_by_name(old_name, cp)
219 new_cp_path = self.get_checkpoint_path_by_name(new_name, cp)
220 if os.path.isfile(old_cp_path):
221 self.log.debug("renaming %s -> %s", old_cp_path, new_cp_path)
222 os.rename(old_cp_path, new_cp_path)
223
168 return notebook_id
224 return notebook_id
169
225
170 def delete_notebook(self, notebook_id):
226 def delete_notebook(self, notebook_id):
171 """Delete notebook by notebook_id."""
227 """Delete notebook by notebook_id."""
172 path = self.find_path(notebook_id)
228 path = self.get_path(notebook_id)
173 if not os.path.isfile(path):
229 if not os.path.isfile(path):
174 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
230 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
231 self.log.debug("unlinking %s", path)
175 os.unlink(path)
232 os.unlink(path)
233
234 # clear checkpoints
235 for checkpoint_id in self.list_checkpoints(notebook_id):
236 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
237 if os.path.isfile(path):
238 self.log.debug("unlinking %s", path)
239 os.unlink(path)
176 self.delete_notebook_id(notebook_id)
240 self.delete_notebook_id(notebook_id)
177
241
178 def increment_filename(self, basename):
242 def increment_filename(self, basename):
179 """Return a non-used filename of the form basename<int>.
243 """Return a non-used filename of the form basename<int>.
180
244
181 This searches through the filenames (basename0, basename1, ...)
245 This searches through the filenames (basename0, basename1, ...)
182 until is find one that is not already being used. It is used to
246 until is find one that is not already being used. It is used to
183 create Untitled and Copy names that are unique.
247 create Untitled and Copy names that are unique.
184 """
248 """
185 i = 0
249 i = 0
186 while True:
250 while True:
187 name = u'%s%i' % (basename,i)
251 name = u'%s%i' % (basename,i)
188 path = self.get_path_by_name(name)
252 path = self.get_path_by_name(name)
189 if not os.path.isfile(path):
253 if not os.path.isfile(path):
190 break
254 break
191 else:
255 else:
192 i = i+1
256 i = i+1
193 return name
257 return name
258
259 # Checkpoint-related utilities
260
261 def get_checkpoint_path_by_name(self, name, checkpoint_id):
262 """Return a full path to a notebook checkpoint, given its name and checkpoint id."""
263 filename = "{name}-{checkpoint_id}{ext}".format(
264 name=name,
265 checkpoint_id=checkpoint_id,
266 ext=self.filename_ext,
267 )
268 path = os.path.join(self.checkpoint_dir, filename)
269 return path
270
271 def get_checkpoint_path(self, notebook_id, checkpoint_id):
272 """find the path to a checkpoint"""
273 name = self.get_name(notebook_id)
274 return self.get_checkpoint_path_by_name(name, checkpoint_id)
275
276 # public checkpoint API
277
278 def create_checkpoint(self, notebook_id):
279 """Create a checkpoint from the current state of a notebook"""
280 nb_path = self.get_path(notebook_id)
281 cp_path = self.get_checkpoint_path(notebook_id, "checkpoint")
282 self.log.debug("creating checkpoint for notebook %s", notebook_id)
283 if not os.path.exists(self.checkpoint_dir):
284 os.mkdir(self.checkpoint_dir)
285 shutil.copy2(nb_path, cp_path)
286
287 def list_checkpoints(self, notebook_id):
288 """list the checkpoints for a given notebook
194
289
290 This notebook manager currently only supports one checkpoint per notebook.
291 """
292 path = self.get_checkpoint_path(notebook_id, "checkpoint")
293 if os.path.exists(path):
294 return ["checkpoint"]
295 else:
296 return []
297
298 def restore_checkpoint(self, notebook_id, checkpoint_id):
299 """restore a notebook to a checkpointed state"""
300 self.log.info("restoring Notebook %s from checkpoint %s", notebook_id, checkpoint_id)
301 nb_path = self.get_path(notebook_id)
302 cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
303 if not os.path.isfile(cp_path):
304 raise web.HTTPError(404,
305 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
306 )
307 # ensure notebook is readable (never restore from an unreadable notebook)
308 last_modified, nb = self.read_notebook_object_from_path(cp_path)
309 shutil.copy2(cp_path, nb_path)
310 self.log.debug("copying %s -> %s", cp_path, nb_path)
311
312 def delete_checkpoint(self, notebook_id, checkpoint_id):
313 """delete a notebook's checkpoint"""
314 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
315 if not os.path.isfile(path):
316 raise web.HTTPError(404,
317 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
318 )
319 self.log.debug("unlinking %s", path)
320 os.unlink(path)
321
195 def info_string(self):
322 def info_string(self):
196 return "Serving notebooks from local directory: %s" % self.notebook_dir
323 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -1,214 +1,235 b''
1 """A base class notebook manager.
1 """A base class notebook manager.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 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 os
19 import os
20 import uuid
20 import uuid
21
21
22 from tornado import web
22 from tornado import web
23
23
24 from IPython.config.configurable import LoggingConfigurable
24 from IPython.config.configurable import LoggingConfigurable
25 from IPython.nbformat import current
25 from IPython.nbformat import current
26 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
26 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Classes
29 # Classes
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 class NotebookManager(LoggingConfigurable):
32 class NotebookManager(LoggingConfigurable):
33
33
34 # Todo:
34 # Todo:
35 # The notebook_dir attribute is used to mean a couple of different things:
35 # The notebook_dir attribute is used to mean a couple of different things:
36 # 1. Where the notebooks are stored if FileNotebookManager is used.
36 # 1. Where the notebooks are stored if FileNotebookManager is used.
37 # 2. The cwd of the kernel for a project.
37 # 2. The cwd of the kernel for a project.
38 # Right now we use this attribute in a number of different places and
38 # Right now we use this attribute in a number of different places and
39 # we are going to have to disentagle all of this.
39 # we are going to have to disentangle all of this.
40 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
40 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
41 The directory to use for notebooks.
41 The directory to use for notebooks.
42 """)
42 """)
43 def _notebook_dir_changed(self, name, old, new):
43 def _notebook_dir_changed(self, name, old, new):
44 """do a bit of validation of the notebook dir"""
44 """do a bit of validation of the notebook dir"""
45 if not os.path.isabs(new):
45 if not os.path.isabs(new):
46 # If we receive a non-absolute path, make it absolute.
46 # If we receive a non-absolute path, make it absolute.
47 abs_new = os.path.abspath(new)
47 abs_new = os.path.abspath(new)
48 self.notebook_dir = abs_new
48 self.notebook_dir = abs_new
49 return
49 return
50 if os.path.exists(new) and not os.path.isdir(new):
50 if os.path.exists(new) and not os.path.isdir(new):
51 raise TraitError("notebook dir %r is not a directory" % new)
51 raise TraitError("notebook dir %r is not a directory" % new)
52 if not os.path.exists(new):
52 if not os.path.exists(new):
53 self.log.info("Creating notebook dir %s", new)
53 self.log.info("Creating notebook dir %s", new)
54 try:
54 try:
55 os.mkdir(new)
55 os.mkdir(new)
56 except:
56 except:
57 raise TraitError("Couldn't create notebook dir %r" % new)
57 raise TraitError("Couldn't create notebook dir %r" % new)
58
58
59 allowed_formats = List([u'json',u'py'])
59 allowed_formats = List([u'json',u'py'])
60
60
61 # Map notebook_ids to notebook names
61 # Map notebook_ids to notebook names
62 mapping = Dict()
62 mapping = Dict()
63
63
64 def load_notebook_names(self):
64 def load_notebook_names(self):
65 """Load the notebook names into memory.
65 """Load the notebook names into memory.
66
66
67 This should be called once immediately after the notebook manager
67 This should be called once immediately after the notebook manager
68 is created to load the existing notebooks into the mapping in
68 is created to load the existing notebooks into the mapping in
69 memory.
69 memory.
70 """
70 """
71 self.list_notebooks()
71 self.list_notebooks()
72
72
73 def list_notebooks(self):
73 def list_notebooks(self):
74 """List all notebooks.
74 """List all notebooks.
75
75
76 This returns a list of dicts, each of the form::
76 This returns a list of dicts, each of the form::
77
77
78 dict(notebook_id=notebook,name=name)
78 dict(notebook_id=notebook,name=name)
79
79
80 This list of dicts should be sorted by name::
80 This list of dicts should be sorted by name::
81
81
82 data = sorted(data, key=lambda item: item['name'])
82 data = sorted(data, key=lambda item: item['name'])
83 """
83 """
84 raise NotImplementedError('must be implemented in a subclass')
84 raise NotImplementedError('must be implemented in a subclass')
85
85
86
86
87 def new_notebook_id(self, name):
87 def new_notebook_id(self, name):
88 """Generate a new notebook_id for a name and store its mapping."""
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
89 # TODO: the following will give stable urls for notebooks, but unless
90 # the notebooks are immediately redirected to their new urls when their
90 # the notebooks are immediately redirected to their new urls when their
91 # filemname changes, nasty inconsistencies result. So for now it's
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
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
93 # logic here so that we can later reactivate it, whhen the necessary
94 # url redirection code is written.
94 # url redirection code is written.
95 #notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
95 #notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
96 # 'file://'+self.get_path_by_name(name).encode('utf-8')))
96 # 'file://'+self.get_path_by_name(name).encode('utf-8')))
97
97
98 notebook_id = unicode(uuid.uuid4())
98 notebook_id = unicode(uuid.uuid4())
99 self.mapping[notebook_id] = name
99 self.mapping[notebook_id] = name
100 return notebook_id
100 return notebook_id
101
101
102 def delete_notebook_id(self, notebook_id):
102 def delete_notebook_id(self, notebook_id):
103 """Delete a notebook's id in the mapping.
103 """Delete a notebook's id in the mapping.
104
104
105 This doesn't delete the actual notebook, only its entry in the mapping.
105 This doesn't delete the actual notebook, only its entry in the mapping.
106 """
106 """
107 del self.mapping[notebook_id]
107 del self.mapping[notebook_id]
108
108
109 def notebook_exists(self, notebook_id):
109 def notebook_exists(self, notebook_id):
110 """Does a notebook exist?"""
110 """Does a notebook exist?"""
111 return notebook_id in self.mapping
111 return notebook_id in self.mapping
112
112
113 def get_notebook(self, notebook_id, format=u'json'):
113 def get_notebook(self, notebook_id, format=u'json'):
114 """Get the representation of a notebook in format by notebook_id."""
114 """Get the representation of a notebook in format by notebook_id."""
115 format = unicode(format)
115 format = unicode(format)
116 if format not in self.allowed_formats:
116 if format not in self.allowed_formats:
117 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
117 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
118 last_modified, nb = self.read_notebook_object(notebook_id)
118 last_modified, nb = self.read_notebook_object(notebook_id)
119 kwargs = {}
119 kwargs = {}
120 if format == 'json':
120 if format == 'json':
121 # don't split lines for sending over the wire, because it
121 # don't split lines for sending over the wire, because it
122 # should match the Python in-memory format.
122 # should match the Python in-memory format.
123 kwargs['split_lines'] = False
123 kwargs['split_lines'] = False
124 data = current.writes(nb, format, **kwargs)
124 data = current.writes(nb, format, **kwargs)
125 name = nb.metadata.get('name','notebook')
125 name = nb.metadata.get('name','notebook')
126 return last_modified, name, data
126 return last_modified, name, data
127
127
128 def read_notebook_object(self, notebook_id):
128 def read_notebook_object(self, notebook_id):
129 """Get the object representation of a notebook by notebook_id."""
129 """Get the object representation of a notebook by notebook_id."""
130 raise NotImplementedError('must be implemented in a subclass')
130 raise NotImplementedError('must be implemented in a subclass')
131
131
132 def save_new_notebook(self, data, name=None, format=u'json'):
132 def save_new_notebook(self, data, name=None, format=u'json'):
133 """Save a new notebook and return its notebook_id.
133 """Save a new notebook and return its notebook_id.
134
134
135 If a name is passed in, it overrides any values in the notebook data
135 If a name is passed in, it overrides any values in the notebook data
136 and the value in the data is updated to use that value.
136 and the value in the data is updated to use that value.
137 """
137 """
138 if format not in self.allowed_formats:
138 if format not in self.allowed_formats:
139 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
139 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
140
140
141 try:
141 try:
142 nb = current.reads(data.decode('utf-8'), format)
142 nb = current.reads(data.decode('utf-8'), format)
143 except:
143 except:
144 raise web.HTTPError(400, u'Invalid JSON data')
144 raise web.HTTPError(400, u'Invalid JSON data')
145
145
146 if name is None:
146 if name is None:
147 try:
147 try:
148 name = nb.metadata.name
148 name = nb.metadata.name
149 except AttributeError:
149 except AttributeError:
150 raise web.HTTPError(400, u'Missing notebook name')
150 raise web.HTTPError(400, u'Missing notebook name')
151 nb.metadata.name = name
151 nb.metadata.name = name
152
152
153 notebook_id = self.write_notebook_object(nb)
153 notebook_id = self.write_notebook_object(nb)
154 return notebook_id
154 return notebook_id
155
155
156 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
156 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
157 """Save an existing notebook by notebook_id."""
157 """Save an existing notebook by notebook_id."""
158 if format not in self.allowed_formats:
158 if format not in self.allowed_formats:
159 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
159 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
160
160
161 try:
161 try:
162 nb = current.reads(data.decode('utf-8'), format)
162 nb = current.reads(data.decode('utf-8'), format)
163 except:
163 except:
164 raise web.HTTPError(400, u'Invalid JSON data')
164 raise web.HTTPError(400, u'Invalid JSON data')
165
165
166 if name is not None:
166 if name is not None:
167 nb.metadata.name = name
167 nb.metadata.name = name
168 self.write_notebook_object(nb, notebook_id)
168 self.write_notebook_object(nb, notebook_id)
169
169
170 def write_notebook_object(self, nb, notebook_id=None):
170 def write_notebook_object(self, nb, notebook_id=None):
171 """Write a notebook object and return its notebook_id.
171 """Write a notebook object and return its notebook_id.
172
172
173 If notebook_id is None, this method should create a new notebook_id.
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
174 If notebook_id is not None, this method should check to make sure it
175 exists and is valid.
175 exists and is valid.
176 """
176 """
177 raise NotImplementedError('must be implemented in a subclass')
177 raise NotImplementedError('must be implemented in a subclass')
178
178
179 def delete_notebook(self, notebook_id):
179 def delete_notebook(self, notebook_id):
180 """Delete notebook by notebook_id."""
180 """Delete notebook by notebook_id."""
181 raise NotImplementedError('must be implemented in a subclass')
181 raise NotImplementedError('must be implemented in a subclass')
182
182
183 def increment_filename(self, name):
183 def increment_filename(self, name):
184 """Increment a filename to make it unique.
184 """Increment a filename to make it unique.
185
185
186 This exists for notebook stores that must have unique names. When a notebook
186 This exists for notebook stores that must have unique names. When a notebook
187 is created or copied this method constructs a unique filename, typically
187 is created or copied this method constructs a unique filename, typically
188 by appending an integer to the name.
188 by appending an integer to the name.
189 """
189 """
190 return name
190 return name
191
191
192 def new_notebook(self):
192 def new_notebook(self):
193 """Create a new notebook and return its notebook_id."""
193 """Create a new notebook and return its notebook_id."""
194 name = self.increment_filename('Untitled')
194 name = self.increment_filename('Untitled')
195 metadata = current.new_metadata(name=name)
195 metadata = current.new_metadata(name=name)
196 nb = current.new_notebook(metadata=metadata)
196 nb = current.new_notebook(metadata=metadata)
197 notebook_id = self.write_notebook_object(nb)
197 notebook_id = self.write_notebook_object(nb)
198 return notebook_id
198 return notebook_id
199
199
200 def copy_notebook(self, notebook_id):
200 def copy_notebook(self, notebook_id):
201 """Copy an existing notebook and return its notebook_id."""
201 """Copy an existing notebook and return its notebook_id."""
202 last_mod, nb = self.read_notebook_object(notebook_id)
202 last_mod, nb = self.read_notebook_object(notebook_id)
203 name = nb.metadata.name + '-Copy'
203 name = nb.metadata.name + '-Copy'
204 name = self.increment_filename(name)
204 name = self.increment_filename(name)
205 nb.metadata.name = name
205 nb.metadata.name = name
206 notebook_id = self.write_notebook_object(nb)
206 notebook_id = self.write_notebook_object(nb)
207 return notebook_id
207 return notebook_id
208
209 # Checkpoint-related
210
211 def create_checkpoint(self, notebook_id):
212 """Create a checkpoint of the current state of a notebook
213
214 Returns a checkpoint_id for the new checkpoint.
215 """
216 raise NotImplementedError("must be implemented in a subclass")
217
218 def list_checkpoints(self, notebook_id):
219 """Return a list of checkpoints for a given notebook"""
220 return []
221
222 def restore_checkpoint(self, notebook_id, checkpoint_id):
223 """Restore a notebook from one of its checkpoints"""
224 raise NotImplementedError("must be implemented in a subclass")
208
225
226 def delete_checkpoint(self, notebook_id, checkpoint_id):
227 """delete a checkpoint for a notebook"""
228 raise NotImplementedError("must be implemented in a subclass")
229
209 def log_info(self):
230 def log_info(self):
210 self.log.info(self.info_string())
231 self.log.info(self.info_string())
211
232
212 def info_string(self):
233 def info_string(self):
213 return "Serving notebooks"
234 return "Serving notebooks"
214
235
General Comments 0
You need to be logged in to leave comments. Login now