##// END OF EJS Templates
standard model changes
Zachary Sailer -
Show More
@@ -1,97 +1,102 b''
1 1 """Tornado handlers for the live notebook view.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import os
20 20 from tornado import web
21 21 HTTPError = web.HTTPError
22 22
23 23 from ..base.handlers import IPythonHandler
24 24 from ..utils import url_path_join
25 25 from urllib import quote
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Handlers
29 29 #-----------------------------------------------------------------------------
30 30
31 31
32 32 class NewPathHandler(IPythonHandler):
33 33
34 34 @web.authenticated
35 35 def get(self, notebook_path):
36 36 notebook_name = self.notebook_manager.new_notebook(notebook_path)
37 37 self.redirect(url_path_join(self.base_project_url,"notebooks", notebook_path, notebook_name))
38 38
39 39
40 40 class NewHandler(IPythonHandler):
41 41
42 42 @web.authenticated
43 43 def get(self):
44 44 notebook_name = self.notebook_manager.new_notebook()
45 45 self.redirect(url_path_join(self.base_project_url, "notebooks", notebook_name))
46 46
47 47
48 48 class NamedNotebookHandler(IPythonHandler):
49 49
50 50 @web.authenticated
51 51 def get(self, notebook_path):
52 52 nbm = self.notebook_manager
53 53 name, path = nbm.named_notebook_path(notebook_path)
54 54 if name != None:
55 55 name = quote(name)
56 56 if path == None:
57 57 project = self.project + '/' + name
58 58 else:
59 59 project = self.project + '/' + path +'/'+ name
60 60 if not nbm.notebook_exists(notebook_path):
61 61 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
62 62 self.write(self.render_template('notebook.html',
63 63 project=project,
64 64 notebook_path=path,
65 65 notebook_name=name,
66 66 kill_kernel=False,
67 67 mathjax_url=self.mathjax_url,
68 68 )
69 69 )
70
71 @web.authenticated
72 def post(self, notebook_path):
73 nbm =self.notebook_manager
74 notebook_name = nbm.new_notebook()
70 75
71 76
72 77 class NotebookCopyHandler(IPythonHandler):
73 78
74 79 @web.authenticated
75 80 def get(self, notebook_path=None):
76 81 nbm = self.notebook_manager
77 82 name, path = nbm.named_notebook_path(notebook_path)
78 83 notebook_name = self.notebook_manager.copy_notebook(name, path)
79 84 if path==None:
80 85 self.redirect(url_path_join(self.base_project_url, "notebooks", notebook_name))
81 86 else:
82 87 self.redirect(url_path_join(self.base_project_url, "notebooks", path, notebook_name))
83 88
84 89
85 90 #-----------------------------------------------------------------------------
86 91 # URL to handler mappings
87 92 #-----------------------------------------------------------------------------
88 93
89 94
90 95 _notebook_path_regex = r"(?P<notebook_path>.+)"
91 96
92 97 default_handlers = [
93 98 (r"/notebooks/%s/new" % _notebook_path_regex, NewPathHandler),
94 99 (r"/notebooks/new", NewHandler),
95 100 (r"/notebooks/%s/copy" % _notebook_path_regex, NotebookCopyHandler),
96 101 (r"/notebooks/%s" % _notebook_path_regex, NamedNotebookHandler)
97 102 ]
@@ -1,102 +1,104 b''
1 1 """A base class contents manager.
2 2
3 3 Authors:
4 4
5 5 * Zach Sailer
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import datetime
20 20 import io
21 21 import os
22 22 import glob
23 23 import shutil
24 24 import ast
25 25 import base64
26 26
27 27 from tornado import web
28 28
29 29 from IPython.config.configurable import LoggingConfigurable
30 30 from IPython.nbformat import current
31 31 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
32 32 from IPython.utils import tz
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Classes
36 36 #-----------------------------------------------------------------------------
37 37
38 38 class ContentManager(LoggingConfigurable):
39 39
40 40 content_dir = Unicode(os.getcwdu(), config=True, help="""
41 41 The directory to use for contents.
42 42 """)
43 43
44 44 contents = List()
45 45
46 46 def get_content_names(self, content_path):
47 """List of dicts of files in content_path"""
47 48 names = glob.glob(os.path.join(self.content_dir, content_path,'*'))
48 content_names = list()
49 dir_names = list()
49 contents = list()
50 dirs = list()
51 notebooks = list()
50 52 for name in names:
51 53 if os.path.isdir(name) == True:
52 dir_names.append(os.path.split(name)[1])
53 elif os.path.splitext(os.path.basename(name))[1] != '.ipynb':
54 content_names.append(os.path.split(name)[1])
55 return dir_names, content_names
56
54 dirs.append(os.path.split(name)[1])
55 elif os.path.splitext(name)[1] == '.ipynb':
56 notebooks.append(os.path.split(name)[1])
57 else:
58 contents.append(os.path.split(name)[1])
59 return dirs, notebooks, contents
60
57 61 def list_contents(self, content_path):
58 62 """List all contents in the named path."""
59 dir_names, content_names = self.get_content_names(content_path)
63 dir_names, notebook_names, content_names = self.get_content_names(content_path)
60 64 content_mapping = []
61 65 for name in dir_names:
62 model = self.directory_model(name, content_path)
66 model = self.content_model(name, content_path, type='dir')
63 67 content_mapping.append(model)
64 68 for name in content_names:
65 model = self.content_model(name, content_path)
69 model = self.content_model(name, content_path, type='file')
70 content_mapping.append(model)
71 for name in notebook_names:
72 model = self.content_model(name, content_path, type='notebook')
66 73 content_mapping.append(model)
67 74 return content_mapping
68 75
69 76 def get_path_by_name(self, name, content_path):
70 77 """Return a full path to content"""
71 78 path = os.path.join(self.content_dir, content_path, name)
72 79 return path
73 80
74 def read_content(self, name, content_path):
81 def content_info(self, name, content_path):
82 """Read the content of a named file"""
75 83 file_type = os.path.splitext(os.path.basename(name))[1]
76 #Collect contents of file
77 with open(name, 'rb') as file_content:
78 contents = file_content.read()
79 84 full_path = self.get_path_by_name(name, content_path)
80 85 info = os.stat(full_path)
81 86 size = info.st_size
82 87 last_modified = tz.utcfromtimestamp(info.st_mtime)
83 return last_modified, file_type, contents, size
84
85 def directory_model(self, name, content_path):
86 model = {"name": name,
87 "path": content_path,
88 "type": 'tree'}
89 return model
88 return last_modified, file_type, size
90 89
91 def content_model(self, name, content_path):
92 last_modified, file_type, contents, size = self.read_content(name, content_path)
90 def content_model(self, name, content_path, type=None):
91 """Create a dict standard model for any file (other than notebooks)"""
92 last_modified, file_type, size = self.content_info(name, content_path)
93 93 model = {"name": name,
94 94 "path": content_path,
95 "type": file_type,
95 "type": type,
96 "MIME-type": "",
96 97 "last_modified": last_modified.ctime(),
97 98 "size": size}
98 99 return model
99 100
100 101 def delete_content(self, content_path):
102 """Delete a file"""
101 103 os.unlink(os.path.join(self.content_dir, content_path))
102 104 No newline at end of file
@@ -1,68 +1,68 b''
1 1 """Tornado handlers for the contents web service.
2 2
3 3 Authors:
4 4
5 5 * Zach Sailer
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from tornado import web
20 20
21 21 from zmq.utils import jsonapi
22 22
23 23 from IPython.utils.jsonutil import date_default
24 24
25 25 from ...base.handlers import IPythonHandler, authenticate_unless_readonly
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Contents web service handlers
29 29 #-----------------------------------------------------------------------------
30 30
31 31
32 32 class ContentRootHandler(IPythonHandler):
33 33
34 34 @authenticate_unless_readonly
35 35 def get(self):
36 36 cm = self.content_manager
37 37 contents = cm.list_contents("")
38 38 self.finish(jsonapi.dumps(contents))
39 39
40 40
41 41 class ContentHandler(IPythonHandler):
42 42
43 43 @web.authenticated
44 44 def get(self, content_path):
45 45 cm = self.content_manager
46 46 contents = cm.list_contents(content_path)
47 47 self.finish(jsonapi.dumps(contents))
48 48
49 49 @web.authenticated
50 50 def delete(self, content_path):
51 51 cm = self.content_manager
52 52 cm.delete_content(content_path)
53 53 self.set_status(204)
54 54 self.finish()
55 55
56 56
57 57 #-----------------------------------------------------------------------------
58 58 # URL to handler mappings
59 59 #-----------------------------------------------------------------------------
60 60
61 61 _content_path_regex = r"(?P<content_path>.+)"
62 62
63 63 default_handlers = [
64 64 (r"api/contents/%s" % _content_path_regex, ContentHandler),
65 65 (r"api/contents", ContentRootHandler)
66 66 ]
67 67
68 68
@@ -1,345 +1,345 b''
1 1 """A notebook manager that uses the local file system for storage.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import datetime
20 20 import io
21 21 import os
22 22 import glob
23 23 import shutil
24 24 import ast
25 25
26 26 from unicodedata import normalize
27 27
28 28 from tornado import web
29 29
30 30 from .nbmanager import NotebookManager
31 31 from IPython.nbformat import current
32 32 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
33 33 from IPython.utils import tz
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Classes
37 37 #-----------------------------------------------------------------------------
38 38
39 39 class FileNotebookManager(NotebookManager):
40 40
41 41 save_script = Bool(False, config=True,
42 42 help="""Automatically create a Python script when saving the notebook.
43 43
44 44 For easier use of import, %run and %load across notebooks, a
45 45 <notebook-name>.py script will be created next to any
46 46 <notebook-name>.ipynb on each save. This can also be set with the
47 47 short `--script` flag.
48 48 """
49 49 )
50 50
51 51 checkpoint_dir = Unicode(config=True,
52 52 help="""The location in which to keep notebook checkpoints
53 53
54 54 By default, it is notebook-dir/.ipynb_checkpoints
55 55 """
56 56 )
57 57 def _checkpoint_dir_default(self):
58 58 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
59 59
60 60 def _checkpoint_dir_changed(self, name, old, new):
61 61 """do a bit of validation of the checkpoint dir"""
62 62 if not os.path.isabs(new):
63 63 # If we receive a non-absolute path, make it absolute.
64 64 abs_new = os.path.abspath(new)
65 65 self.checkpoint_dir = abs_new
66 66 return
67 67 if os.path.exists(new) and not os.path.isdir(new):
68 68 raise TraitError("checkpoint dir %r is not a directory" % new)
69 69 if not os.path.exists(new):
70 70 self.log.info("Creating checkpoint dir %s", new)
71 71 try:
72 72 os.mkdir(new)
73 73 except:
74 74 raise TraitError("Couldn't create checkpoint dir %r" % new)
75 75
76 76 filename_ext = Unicode(u'.ipynb')
77 77
78 78
79 79 def get_notebook_names(self, path):
80 80 """List all notebook names in the notebook dir."""
81 81 names = glob.glob(os.path.join(self.notebook_dir, path,
82 82 '*' + self.filename_ext))
83 83 names = [os.path.basename(name)
84 84 for name in names]
85 85 return names
86 86
87 87 def list_notebooks(self, path):
88 88 """List all notebooks in the notebook dir."""
89 89 notebook_names = self.get_notebook_names(path)
90 90 notebook_mapping = []
91 91 for name in notebook_names:
92 model = self.notebook_model(name, path)
92 model = self.notebook_model(name, path, content=False)
93 93 notebook_mapping.append(model)
94 94 return notebook_mapping
95 95
96 96 def change_notebook(self, data, notebook_name, notebook_path=None):
97 97 """Changes notebook"""
98 98 changes = data.keys()
99 99 for change in changes:
100 100 full_path = self.get_path(notebook_name, notebook_path)
101 101 if change == "notebook_name":
102 102 os.rename(full_path,
103 103 self.get_path(data['notebook_name'], notebook_path))
104 104 notebook_name = data['notebook_name']
105 105 if change == "notebook_path":
106 106 new_path = self.get_path(data['notebook_name'], data['notebook_path'])
107 107 stutil.move(full_path, new_path)
108 108 notebook_path = data['notebook_path']
109 109 if change == "content":
110 110 self.save_notebook(data, notebook_name, notebook_path)
111 111 model = self.notebook_model(notebook_name, notebook_path)
112 112 return model
113 113
114 114 def notebook_exists(self, notebook_path):
115 115 """Does a notebook exist?"""
116 116 return os.path.isfile(notebook_path)
117 117
118 118 def get_path(self, notebook_name, notebook_path=None):
119 119 """Return a full path to a notebook given its notebook_name."""
120 120 return self.get_path_by_name(notebook_name, notebook_path)
121 121
122 122 def get_path_by_name(self, name, notebook_path=None):
123 123 """Return a full path to a notebook given its name."""
124 124 filename = name #+ self.filename_ext
125 125 if notebook_path == None:
126 126 path = os.path.join(self.notebook_dir, filename)
127 127 else:
128 128 path = os.path.join(self.notebook_dir, notebook_path, filename)
129 129 return path
130 130
131 131 def read_notebook_object_from_path(self, path):
132 132 """read a notebook object from a path"""
133 133 info = os.stat(path)
134 134 last_modified = tz.utcfromtimestamp(info.st_mtime)
135 135 with open(path,'r') as f:
136 136 s = f.read()
137 137 try:
138 138 # v1 and v2 and json in the .ipynb files.
139 139 nb = current.reads(s, u'json')
140 140 except ValueError as e:
141 141 msg = u"Unreadable Notebook: %s" % e
142 142 raise web.HTTPError(400, msg, reason=msg)
143 143 return last_modified, nb
144 144
145 145 def read_notebook_object(self, notebook_name, notebook_path):
146 146 """Get the Notebook representation of a notebook by notebook_name."""
147 147 path = self.get_path(notebook_name, notebook_path)
148 148 if not os.path.isfile(path):
149 149 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
150 150 last_modified, nb = self.read_notebook_object_from_path(path)
151 151 # Always use the filename as the notebook name.
152 152 # Eventually we will get rid of the notebook name in the metadata
153 153 # but for now, that name is just an empty string. Until the notebooks
154 154 # web service knows about names in URLs we still pass the name
155 155 # back to the web app using the metadata though.
156 156 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
157 157 return last_modified, nb
158 158
159 159 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name= None):
160 160 """Save an existing notebook object by notebook_name."""
161 161 if new_name == None:
162 162 try:
163 163 new_name = normalize('NFC', nb.metadata.name)
164 164 except AttributeError:
165 165 raise web.HTTPError(400, u'Missing notebook name')
166 166
167 167 new_path = notebook_path
168 168 old_name = notebook_name
169 169 old_checkpoints = self.list_checkpoints(old_name)
170 170
171 171 path = self.get_path_by_name(new_name, new_path)
172 172
173 173 # Right before we save the notebook, we write an empty string as the
174 174 # notebook name in the metadata. This is to prepare for removing
175 175 # this attribute entirely post 1.0. The web app still uses the metadata
176 176 # name for now.
177 177 nb.metadata.name = u''
178 178
179 179 try:
180 180 self.log.debug("Autosaving notebook %s", path)
181 181 with open(path,'w') as f:
182 182 current.write(nb, f, u'json')
183 183 except Exception as e:
184 184 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s' % e)
185 185
186 186 # save .py script as well
187 187 if self.save_script:
188 188 pypath = os.path.splitext(path)[0] + '.py'
189 189 self.log.debug("Writing script %s", pypath)
190 190 try:
191 191 with io.open(pypath,'w', encoding='utf-8') as f:
192 192 current.write(nb, f, u'py')
193 193 except Exception as e:
194 194 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
195 195
196 196 if old_name != None:
197 197 # remove old files if the name changed
198 198 if old_name != new_name:
199 199 # remove renamed original, if it exists
200 200 old_path = self.get_path_by_name(old_name, notebook_path)
201 201 if os.path.isfile(old_path):
202 202 self.log.debug("unlinking notebook %s", old_path)
203 203 os.unlink(old_path)
204 204
205 205 # cleanup old script, if it exists
206 206 if self.save_script:
207 207 old_pypath = os.path.splitext(old_path)[0] + '.py'
208 208 if os.path.isfile(old_pypath):
209 209 self.log.debug("unlinking script %s", old_pypath)
210 210 os.unlink(old_pypath)
211 211
212 212 # rename checkpoints to follow file
213 213 for cp in old_checkpoints:
214 214 checkpoint_id = cp['checkpoint_id']
215 215 old_cp_path = self.get_checkpoint_path_by_name(old_name, checkpoint_id)
216 216 new_cp_path = self.get_checkpoint_path_by_name(new_name, checkpoint_id)
217 217 if os.path.isfile(old_cp_path):
218 218 self.log.debug("renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
219 219 os.rename(old_cp_path, new_cp_path)
220 220
221 221 return new_name
222 222
223 223 def delete_notebook(self, notebook_name, notebook_path):
224 224 """Delete notebook by notebook_name."""
225 225 nb_path = self.get_path(notebook_name, notebook_path)
226 226 if not os.path.isfile(nb_path):
227 227 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
228 228
229 229 # clear checkpoints
230 230 for checkpoint in self.list_checkpoints(notebook_name):
231 231 checkpoint_id = checkpoint['checkpoint_id']
232 232 path = self.get_checkpoint_path(notebook_name, checkpoint_id)
233 233 self.log.debug(path)
234 234 if os.path.isfile(path):
235 235 self.log.debug("unlinking checkpoint %s", path)
236 236 os.unlink(path)
237 237
238 238 self.log.debug("unlinking notebook %s", nb_path)
239 239 os.unlink(nb_path)
240 240
241 241 def increment_filename(self, basename, notebook_path=None):
242 242 """Return a non-used filename of the form basename<int>.
243 243
244 244 This searches through the filenames (basename0, basename1, ...)
245 245 until is find one that is not already being used. It is used to
246 246 create Untitled and Copy names that are unique.
247 247 """
248 248 i = 0
249 249 while True:
250 250 name = u'%s%i.ipynb' % (basename,i)
251 251 path = self.get_path_by_name(name, notebook_path)
252 252 if not os.path.isfile(path):
253 253 break
254 254 else:
255 255 i = i+1
256 256 return name
257 257
258 258 # Checkpoint-related utilities
259 259
260 260 def get_checkpoint_path_by_name(self, name, checkpoint_id, notebook_path=None):
261 261 """Return a full path to a notebook checkpoint, given its name and checkpoint id."""
262 262 filename = u"{name}-{checkpoint_id}{ext}".format(
263 263 name=name,
264 264 checkpoint_id=checkpoint_id,
265 265 ext=self.filename_ext,
266 266 )
267 267 if notebook_path ==None:
268 268 path = os.path.join(self.checkpoint_dir, filename)
269 269 else:
270 270 path = os.path.join(notebook_path, self.checkpoint_dir, filename)
271 271 return path
272 272
273 273 def get_checkpoint_path(self, notebook_name, checkpoint_id, notebook_path=None):
274 274 """find the path to a checkpoint"""
275 275 name = notebook_name
276 276 return self.get_checkpoint_path_by_name(name, checkpoint_id, notebook_path)
277 277
278 278 def get_checkpoint_info(self, notebook_name, checkpoint_id, notebook_path=None):
279 279 """construct the info dict for a given checkpoint"""
280 280 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
281 281 stats = os.stat(path)
282 282 last_modified = tz.utcfromtimestamp(stats.st_mtime)
283 283 info = dict(
284 284 checkpoint_id = checkpoint_id,
285 285 last_modified = last_modified,
286 286 )
287 287
288 288 return info
289 289
290 290 # public checkpoint API
291 291
292 292 def create_checkpoint(self, notebook_name, notebook_path=None):
293 293 """Create a checkpoint from the current state of a notebook"""
294 294 nb_path = self.get_path(notebook_name, notebook_path)
295 295 # only the one checkpoint ID:
296 296 checkpoint_id = u"checkpoint"
297 297 cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
298 298 self.log.debug("creating checkpoint for notebook %s", notebook_name)
299 299 if not os.path.exists(self.checkpoint_dir):
300 300 os.mkdir(self.checkpoint_dir)
301 301 shutil.copy2(nb_path, cp_path)
302 302
303 303 # return the checkpoint info
304 304 return self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)
305 305
306 306 def list_checkpoints(self, notebook_name, notebook_path=None):
307 307 """list the checkpoints for a given notebook
308 308
309 309 This notebook manager currently only supports one checkpoint per notebook.
310 310 """
311 311 checkpoint_id = "checkpoint"
312 312 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
313 313 if not os.path.exists(path):
314 314 return []
315 315 else:
316 316 return [self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)]
317 317
318 318
319 319 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
320 320 """restore a notebook to a checkpointed state"""
321 321 self.log.info("restoring Notebook %s from checkpoint %s", notebook_name, checkpoint_id)
322 322 nb_path = self.get_path(notebook_name, notebook_path)
323 323 cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
324 324 if not os.path.isfile(cp_path):
325 325 self.log.debug("checkpoint file does not exist: %s", cp_path)
326 326 raise web.HTTPError(404,
327 327 u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
328 328 )
329 329 # ensure notebook is readable (never restore from an unreadable notebook)
330 330 last_modified, nb = self.read_notebook_object_from_path(cp_path)
331 331 shutil.copy2(cp_path, nb_path)
332 332 self.log.debug("copying %s -> %s", cp_path, nb_path)
333 333
334 334 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
335 335 """delete a notebook's checkpoint"""
336 336 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
337 337 if not os.path.isfile(path):
338 338 raise web.HTTPError(404,
339 339 u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
340 340 )
341 341 self.log.debug("unlinking %s", path)
342 342 os.unlink(path)
343 343
344 344 def info_string(self):
345 345 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -1,247 +1,249 b''
1 1 """A base class notebook manager.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import os
20 20 import uuid
21 21
22 22 from tornado import web
23 23
24 24 from IPython.config.configurable import LoggingConfigurable
25 25 from IPython.nbformat import current
26 26 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Classes
30 30 #-----------------------------------------------------------------------------
31 31
32 32 class NotebookManager(LoggingConfigurable):
33 33
34 34 # Todo:
35 35 # The notebook_dir attribute is used to mean a couple of different things:
36 36 # 1. Where the notebooks are stored if FileNotebookManager is used.
37 37 # 2. The cwd of the kernel for a project.
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 41 The directory to use for notebooks.
42 42 """)
43 43
44 44 def named_notebook_path(self, notebook_path):
45 45
46 46 l = len(notebook_path)
47 47 names = notebook_path.split('/')
48 48 if len(names) > 1:
49 49 name = names[len(names)-1]
50 50 if name[(len(name)-6):(len(name))] == ".ipynb":
51 51 name = name
52 52 path = notebook_path[0:l-len(name)-1]+'/'
53 53 else:
54 54 name = None
55 55 path = notebook_path+'/'
56 56 else:
57 57 name = names[0]
58 58 if name[(len(name)-6):(len(name))] == ".ipynb":
59 59 name = name
60 60 path = None
61 61 else:
62 62 name = None
63 63 path = notebook_path+'/'
64 64 return name, path
65 65
66 66 def _notebook_dir_changed(self, new):
67 67 """do a bit of validation of the notebook dir"""
68 68 if not os.path.isabs(new):
69 69 # If we receive a non-absolute path, make it absolute.
70 70 abs_new = os.path.abspath(new)
71 71 #self.notebook_dir = os.path.dirname(abs_new)
72 72 return
73 73 if os.path.exists(new) and not os.path.isdir(new):
74 74 raise TraitError("notebook dir %r is not a directory" % new)
75 75 if not os.path.exists(new):
76 76 self.log.info("Creating notebook dir %s", new)
77 77 try:
78 78 os.mkdir(new)
79 79 except:
80 80 raise TraitError("Couldn't create notebook dir %r" % new)
81 81
82 82 allowed_formats = List([u'json',u'py'])
83 83
84 84 def add_new_folder(self, path=None):
85 85 new_path = os.path.join(self.notebook_dir, path)
86 86 if not os.path.exists(new_path):
87 87 os.makedirs(new_path)
88 88 else:
89 89 raise web.HTTPError(409, u'Directory already exists or creation permission not allowed.')
90 90
91 91 def load_notebook_names(self, path):
92 92 """Load the notebook names into memory.
93 93
94 94 This should be called once immediately after the notebook manager
95 95 is created to load the existing notebooks into the mapping in
96 96 memory.
97 97 """
98 98 self.list_notebooks(path)
99 99
100 100 def list_notebooks(self):
101 101 """List all notebooks.
102 102
103 103 This returns a list of dicts, each of the form::
104 104
105 105 dict(notebook_id=notebook,name=name)
106 106
107 107 This list of dicts should be sorted by name::
108 108
109 109 data = sorted(data, key=lambda item: item['name'])
110 110 """
111 111 raise NotImplementedError('must be implemented in a subclass')
112 112
113 113
114 114 def notebook_exists(self, notebook_path):
115 115 """Does a notebook exist?"""
116 116
117 def notebook_model(self, notebook_name, notebook_path=None):
117
118 def notebook_model(self, notebook_name, notebook_path=None, content=True):
118 119 """ Creates the standard notebook model """
119 last_modified, content = self.read_notebook_object(notebook_name, notebook_path)
120 last_modified, contents = self.read_notebook_object(notebook_name, notebook_path)
120 121 model = {"notebook_name": notebook_name,
121 122 "notebook_path": notebook_path,
122 "content": content,
123 123 "last_modified": last_modified.ctime()}
124 if content == True:
125 model['content'] = contents
124 126 return model
125 127
126 128 def get_notebook(self, notebook_name, notebook_path=None, format=u'json'):
127 129 """Get the representation of a notebook in format by notebook_name."""
128 130 format = unicode(format)
129 131 if format not in self.allowed_formats:
130 132 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
131 133 kwargs = {}
132 134 last_mod, nb = self.read_notebook_object(notebook_name, notebook_path)
133 135 if format == 'json':
134 136 # don't split lines for sending over the wire, because it
135 137 # should match the Python in-memory format.
136 138 kwargs['split_lines'] = False
137 139 representation = current.writes(nb, format, **kwargs)
138 140 name = nb.metadata.get('name', 'notebook')
139 141 return last_mod, representation, name
140 142
141 143 def read_notebook_object(self, notebook_name, notebook_path):
142 144 """Get the object representation of a notebook by notebook_id."""
143 145 raise NotImplementedError('must be implemented in a subclass')
144 146
145 147 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
146 """Save a new notebook and return its notebook_id.
148 """Save a new notebook and return its name.
147 149
148 150 If a name is passed in, it overrides any values in the notebook data
149 151 and the value in the data is updated to use that value.
150 152 """
151 153 if format not in self.allowed_formats:
152 154 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
153 155
154 156 try:
155 157 nb = current.reads(data.decode('utf-8'), format)
156 158 except:
157 159 raise web.HTTPError(400, u'Invalid JSON data')
158 160
159 161 if name is None:
160 162 try:
161 163 name = nb.metadata.name
162 164 except AttributeError:
163 165 raise web.HTTPError(400, u'Missing notebook name')
164 166 nb.metadata.name = name
165 167
166 168 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
167 169 return notebook_name
168 170
169 171 def save_notebook(self, data, notebook_path=None, name=None, new_name=None, format=u'json'):
170 172 """Save an existing notebook by notebook_name."""
171 173 if format not in self.allowed_formats:
172 174 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
173 175
174 176 try:
175 177 nb = current.reads(data.decode('utf-8'), format)
176 178 except:
177 179 raise web.HTTPError(400, u'Invalid JSON data')
178 180
179 181 if name is not None:
180 182 nb.metadata.name = name
181 183 self.write_notebook_object(nb, name, notebook_path, new_name)
182 184
183 185 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name=None):
184 186 """Write a notebook object and return its notebook_name.
185 187
186 188 If notebook_name is None, this method should create a new notebook_name.
187 189 If notebook_name is not None, this method should check to make sure it
188 190 exists and is valid.
189 191 """
190 192 raise NotImplementedError('must be implemented in a subclass')
191 193
192 194 def delete_notebook(self, notebook_name, notebook_path):
193 195 """Delete notebook by notebook_id."""
194 196 raise NotImplementedError('must be implemented in a subclass')
195 197
196 198 def increment_filename(self, name):
197 199 """Increment a filename to make it unique.
198 200
199 201 This exists for notebook stores that must have unique names. When a notebook
200 202 is created or copied this method constructs a unique filename, typically
201 203 by appending an integer to the name.
202 204 """
203 205 return name
204 206
205 207 def new_notebook(self, notebook_path=None):
206 208 """Create a new notebook and return its notebook_id."""
207 209 name = self.increment_filename('Untitled', notebook_path)
208 210 metadata = current.new_metadata(name=name)
209 211 nb = current.new_notebook(metadata=metadata)
210 212 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
211 213 return notebook_name
212 214
213 215 def copy_notebook(self, name, path):
214 216 """Copy an existing notebook and return its notebook_id."""
215 217 last_mod, nb = self.read_notebook_object(name, path)
216 218 name = nb.metadata.name + '-Copy'
217 219 name = self.increment_filename(name, path)
218 220 nb.metadata.name = name
219 221 notebook_name = self.write_notebook_object(nb, notebook_path = path)
220 222 return notebook_name
221 223
222 224 # Checkpoint-related
223 225
224 226 def create_checkpoint(self, notebook_name, notebook_path=None):
225 227 """Create a checkpoint of the current state of a notebook
226 228
227 229 Returns a checkpoint_id for the new checkpoint.
228 230 """
229 231 raise NotImplementedError("must be implemented in a subclass")
230 232
231 233 def list_checkpoints(self, notebook_name, notebook_path=None):
232 234 """Return a list of checkpoints for a given notebook"""
233 235 return []
234 236
235 237 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
236 238 """Restore a notebook from one of its checkpoints"""
237 239 raise NotImplementedError("must be implemented in a subclass")
238 240
239 241 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
240 242 """delete a checkpoint for a notebook"""
241 243 raise NotImplementedError("must be implemented in a subclass")
242 244
243 245 def log_info(self):
244 246 self.log.info(self.info_string())
245 247
246 248 def info_string(self):
247 249 return "Serving notebooks"
@@ -1,104 +1,104 b''
1 """Tornado handlers for the notebooks web service.
1 """Tornado handlers for the sessions web service.
2 2
3 3 Authors:
4 4
5 5 * Zach Sailer
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from tornado import web
20 20
21 21 from zmq.utils import jsonapi
22 22
23 23 from IPython.utils.jsonutil import date_default
24 24 from ...base.handlers import IPythonHandler, authenticate_unless_readonly
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Session web service handlers
28 28 #-----------------------------------------------------------------------------
29 29
30 30
31 31
32 32 class SessionRootHandler(IPythonHandler):
33 33
34 34 @authenticate_unless_readonly
35 35 def get(self):
36 36 sm = self.session_manager
37 37 nbm = self.notebook_manager
38 38 km = self.kernel_manager
39 39 sessions = sm.list_sessions()
40 40 self.finish(jsonapi.dumps(sessions))
41 41
42 42 @web.authenticated
43 43 def post(self):
44 44 sm = self.session_manager
45 45 nbm = self.notebook_manager
46 46 km = self.kernel_manager
47 47 notebook_path = self.get_argument('notebook_path', default=None)
48 48 notebook_name, path = nbm.named_notebook_path(notebook_path)
49 49 session_id, model = sm.get_session(notebook_name, path)
50 50 if model == None:
51 51 kernel_id = km.start_kernel()
52 52 kernel = km.kernel_model(kernel_id, self.ws_url)
53 53 model = sm.session_model(session_id, notebook_name, path, kernel)
54 54 self.finish(jsonapi.dumps(model))
55 55
56 56 class SessionHandler(IPythonHandler):
57 57
58 58 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
59 59
60 60 @authenticate_unless_readonly
61 61 def get(self, session_id):
62 62 sm = self.session_manager
63 63 model = sm.get_session_from_id(session_id)
64 64 self.finish(jsonapi.dumps(model))
65 65
66 66 @web.authenticated
67 67 def patch(self, session_id):
68 68 sm = self.session_manager
69 69 nbm = self.notebook_manager
70 70 km = self.kernel_manager
71 71 notebook_path = self.request.body
72 72 notebook_name, path = nbm.named_notebook_path(notebook_path)
73 73 kernel_id = sm.get_kernel_from_session(session_id)
74 74 kernel = km.kernel_model(kernel_id, self.ws_url)
75 75 sm.delete_mapping_for_session(session_id)
76 76 model = sm.session_model(session_id, notebook_name, path, kernel)
77 77 self.finish(jsonapi.dumps(model))
78 78
79 79 @web.authenticated
80 80 def delete(self, session_id):
81 81 sm = self.session_manager
82 82 nbm = self.notebook_manager
83 83 km = self.kernel_manager
84 84 kernel_id = sm.get_kernel_from_session(session_id)
85 85 km.shutdown_kernel(kernel_id)
86 86 sm.delete_mapping_for_session(session_id)
87 87 self.set_status(204)
88 88 self.finish()
89 89
90 90
91 91 #-----------------------------------------------------------------------------
92 92 # URL to handler mappings
93 93 #-----------------------------------------------------------------------------
94 94
95 95 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
96 96
97 97 default_handlers = [
98 98 (r"api/sessions/%s" % _session_id_regex, SessionHandler),
99 99 (r"api/sessions", SessionRootHandler)
100 100 ]
101 101
102 102
103 103
104 104
@@ -1,95 +1,97 b''
1 1 """A base class session manager.
2 2
3 3 Authors:
4 4
5 5 * Zach Sailer
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import os
20 20 import uuid
21 21
22 22 from tornado import web
23 23
24 24 from IPython.config.configurable import LoggingConfigurable
25 25 from IPython.nbformat import current
26 26 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Classes
30 30 #-----------------------------------------------------------------------------
31 31
32 32 class SessionManager(LoggingConfigurable):
33 33
34 34 # Use session_ids to map notebook names to kernel_ids
35 35 sessions = List()
36 36
37 37 def get_session(self, nb_name, nb_path=None):
38 38 """Get an existing session or create a new one"""
39 39 model = None
40 40 for session in self.sessions:
41 41 if session['notebook_name'] == nb_name and session['notebook_path'] == nb_path:
42 42 session_id = session['session_id']
43 43 model = session
44 44 if model != None:
45 45 return session_id, model
46 46 else:
47 47 session_id = unicode(uuid.uuid4())
48 48 return session_id, model
49 49
50 50 def session_model(self, session_id, notebook_name=None, notebook_path=None, kernel=None):
51 51 """ Create a session that links notebooks with kernels """
52 52 model = dict(session_id=session_id,
53 53 notebook_name=notebook_name,
54 54 notebook_path=notebook_path,
55 55 kernel=kernel)
56 if notebook_path == None:
57 model['notebook_path']=""
56 58 self.sessions.append(model)
57 59 return model
58 60
59 61 def list_sessions(self):
60 62 """List all sessions and their information"""
61 63 return self.sessions
62 64
63 65 def set_kernel_for_sessions(self, session_id, kernel_id):
64 66 """Maps the kernel_ids to the session_id in session_mapping"""
65 67 for session in self.sessions:
66 68 if session['session_id'] == session_id:
67 69 session['kernel_id'] = kernel_id
68 70 return self.sessions
69 71
70 72 def delete_mapping_for_session(self, session_id):
71 73 """Delete the session from session_mapping with the given session_id"""
72 74 i = 0
73 75 for session in self.sessions:
74 76 if session['session_id'] == session_id:
75 77 del self.sessions[i]
76 78 i = i + 1
77 79 return self.sessions
78 80
79 81 def get_session_from_id(self, session_id):
80 82 for session in self.sessions:
81 83 if session['session_id'] == session_id:
82 84 return session
83 85
84 86 def get_notebook_from_session(self, session_id):
85 87 """Returns the notebook_path for the given session_id"""
86 88 for session in self.sessions:
87 89 if session['session_id'] == session_id:
88 90 return session['notebook_name']
89 91
90 92 def get_kernel_from_session(self, session_id):
91 93 """Returns the kernel_id for the given session_id"""
92 94 for session in self.sessions:
93 95 if session['session_id'] == session_id:
94 96 return session['kernel']['kernel_id']
95 97
General Comments 0
You need to be logged in to leave comments. Login now