##// END OF EJS Templates
Review and refactoring of notebooks web service.
Brian E. Granger -
Show More
@@ -1,361 +1,350 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 * Zach Sailer
6 * Zach Sailer
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import datetime
20 import datetime
21 import io
21 import io
22 import os
22 import os
23 import glob
23 import glob
24 import shutil
24 import shutil
25
25
26 from unicodedata import normalize
26 from unicodedata import normalize
27
27
28 from tornado import web
28 from tornado import web
29
29
30 from .nbmanager import NotebookManager
30 from .nbmanager import NotebookManager
31 from IPython.nbformat import current
31 from IPython.nbformat import current
32 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
32 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
33 from IPython.utils import tz
33 from IPython.utils import tz
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Classes
36 # Classes
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 class FileNotebookManager(NotebookManager):
39 class FileNotebookManager(NotebookManager):
40
40
41 save_script = Bool(False, config=True,
41 save_script = Bool(False, config=True,
42 help="""Automatically create a Python script when saving the notebook.
42 help="""Automatically create a Python script when saving the notebook.
43
43
44 For easier use of import, %run and %load across notebooks, a
44 For easier use of import, %run and %load across notebooks, a
45 <notebook-name>.py script will be created next to any
45 <notebook-name>.py script will be created next to any
46 <notebook-name>.ipynb on each save. This can also be set with the
46 <notebook-name>.ipynb on each save. This can also be set with the
47 short `--script` flag.
47 short `--script` flag.
48 """
48 """
49 )
49 )
50
50
51 checkpoint_dir = Unicode(config=True,
51 checkpoint_dir = Unicode(config=True,
52 help="""The location in which to keep notebook checkpoints
52 help="""The location in which to keep notebook checkpoints
53
53
54 By default, it is notebook-dir/.ipynb_checkpoints
54 By default, it is notebook-dir/.ipynb_checkpoints
55 """
55 """
56 )
56 )
57 def _checkpoint_dir_default(self):
57 def _checkpoint_dir_default(self):
58 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
58 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
59
59
60 def _checkpoint_dir_changed(self, name, old, new):
60 def _checkpoint_dir_changed(self, name, old, new):
61 """do a bit of validation of the checkpoint dir"""
61 """do a bit of validation of the checkpoint dir"""
62 if not os.path.isabs(new):
62 if not os.path.isabs(new):
63 # If we receive a non-absolute path, make it absolute.
63 # If we receive a non-absolute path, make it absolute.
64 abs_new = os.path.abspath(new)
64 abs_new = os.path.abspath(new)
65 self.checkpoint_dir = abs_new
65 self.checkpoint_dir = abs_new
66 return
66 return
67 if os.path.exists(new) and not os.path.isdir(new):
67 if os.path.exists(new) and not os.path.isdir(new):
68 raise TraitError("checkpoint dir %r is not a directory" % new)
68 raise TraitError("checkpoint dir %r is not a directory" % new)
69 if not os.path.exists(new):
69 if not os.path.exists(new):
70 self.log.info("Creating checkpoint dir %s", new)
70 self.log.info("Creating checkpoint dir %s", new)
71 try:
71 try:
72 os.mkdir(new)
72 os.mkdir(new)
73 except:
73 except:
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
75
76 filename_ext = Unicode(u'.ipynb')
77
75
78 def get_notebook_names(self, path):
76 def get_notebook_names(self, path='/'):
79 """List all notebook names in the notebook dir."""
77 """List all notebook names in the notebook dir and path."""
80 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
78 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
81 names = [os.path.basename(name)
79 names = [os.path.basename(name)
82 for name in names]
80 for name in names]
83 return names
81 return names
84
82
85 def increment_filename(self, basename, path='/'):
83 def increment_filename(self, basename, path='/'):
86 """Return a non-used filename of the form basename<int>.
84 """Return a non-used filename of the form basename<int>."""
87
88 This searches through the filenames (basename0, basename1, ...)
89 until is find one that is not already being used. It is used to
90 create Untitled and Copy names that are unique.
91 """
92 i = 0
85 i = 0
93 while True:
86 while True:
94 name = u'%s%i.ipynb' % (basename,i)
87 name = u'%s%i.ipynb' % (basename,i)
95 os_path = self.get_os_path(name, path)
88 os_path = self.get_os_path(name, path)
96 if not os.path.isfile(os_path):
89 if not os.path.isfile(os_path):
97 break
90 break
98 else:
91 else:
99 i = i+1
92 i = i+1
100 return name
93 return name
101
94
102 def notebook_exists(self, name, path):
95 def notebook_exists(self, name, path='/'):
103 """Returns a True if the notebook exists. Else, returns False.
96 """Returns a True if the notebook exists. Else, returns False.
104
97
105 Parameters
98 Parameters
106 ----------
99 ----------
107 name : string
100 name : string
108 The name of the notebook you are checking.
101 The name of the notebook you are checking.
109 path : string
102 path : string
110 The relative path to the notebook (with '/' as separator)
103 The relative path to the notebook (with '/' as separator)
111
104
112 Returns
105 Returns
113 -------
106 -------
114 bool
107 bool
115 """
108 """
116 path = self.get_os_path(name, path)
109 path = self.get_os_path(name, path='/')
117 return os.path.isfile(path)
110 return os.path.isfile(path)
118
111
119 def list_notebooks(self, path):
112 def list_notebooks(self, path):
120 """Returns a list of dictionaries that are the standard model
113 """Returns a list of dictionaries that are the standard model
121 for all notebooks in the relative 'path'.
114 for all notebooks in the relative 'path'.
122
115
123 Parameters
116 Parameters
124 ----------
117 ----------
125 path : str
118 path : str
126 the URL path that describes the relative path for the
119 the URL path that describes the relative path for the
127 listed notebooks
120 listed notebooks
128
121
129 Returns
122 Returns
130 -------
123 -------
131 notebooks : list of dicts
124 notebooks : list of dicts
132 a list of the notebook models without 'content'
125 a list of the notebook models without 'content'
133 """
126 """
134 notebook_names = self.get_notebook_names(path)
127 notebook_names = self.get_notebook_names(path)
135 notebooks = []
128 notebooks = []
136 for name in notebook_names:
129 for name in notebook_names:
137 model = self.get_notebook_model(name, path, content=False)
130 model = self.get_notebook_model(name, path, content=False)
138 notebooks.append(model)
131 notebooks.append(model)
139 notebooks = sorted(notebooks, key=lambda item: item['name'])
132 notebooks = sorted(notebooks, key=lambda item: item['name'])
140 return notebooks
133 return notebooks
141
134
142 def get_notebook_model(self, name, path='/', content=True):
135 def get_notebook_model(self, name, path='/', content=True):
143 """ Takes a path and name for a notebook and returns it's model
136 """ Takes a path and name for a notebook and returns it's model
144
137
145 Parameters
138 Parameters
146 ----------
139 ----------
147 name : str
140 name : str
148 the name of the notebook
141 the name of the notebook
149 path : str
142 path : str
150 the URL path that describes the relative path for
143 the URL path that describes the relative path for
151 the notebook
144 the notebook
152
145
153 Returns
146 Returns
154 -------
147 -------
155 model : dict
148 model : dict
156 the notebook model. If contents=True, returns the 'contents'
149 the notebook model. If contents=True, returns the 'contents'
157 dict in the model as well.
150 dict in the model as well.
158 """
151 """
159 os_path = self.get_os_path(name, path)
152 os_path = self.get_os_path(name, path)
160 if not os.path.isfile(os_path):
153 if not os.path.isfile(os_path):
161 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
154 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
162 info = os.stat(os_path)
155 info = os.stat(os_path)
163 last_modified = tz.utcfromtimestamp(info.st_mtime)
156 last_modified = tz.utcfromtimestamp(info.st_mtime)
164 # Create the notebook model.
157 # Create the notebook model.
165 model ={}
158 model ={}
166 model['name'] = name
159 model['name'] = name
167 model['path'] = path
160 model['path'] = path
168 model['last_modified'] = last_modified.ctime()
161 model['last_modified'] = last_modified
169 if content is True:
162 if content is True:
170 with open(os_path,'r') as f:
163 with open(os_path, 'r') as f:
171 s = f.read()
172 try:
164 try:
173 # v1 and v2 and json in the .ipynb files.
165 nb = current.read(f, u'json')
174 nb = current.reads(s, u'json')
166 except Exception as e:
175 except ValueError as e:
167 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
176 raise web.HTTPError(400, u"Unreadable Notebook: %s" % e)
177 model['content'] = nb
168 model['content'] = nb
178 return model
169 return model
179
170
180 def save_notebook_model(self, model, name, path='/'):
171 def save_notebook_model(self, model, name, path='/'):
181 """Save the notebook model and return the model with no content."""
172 """Save the notebook model and return the model with no content."""
182
173
183 if 'content' not in model:
174 if 'content' not in model:
184 raise web.HTTPError(400, u'No notebook JSON data provided')
175 raise web.HTTPError(400, u'No notebook JSON data provided')
185
176
186 new_path = model.get('path', path)
177 new_path = model.get('path', path)
187 new_name = model.get('name', name)
178 new_name = model.get('name', name)
188
179
189 if path != new_path or name != new_name:
180 if path != new_path or name != new_name:
190 self.rename_notebook(name, path, new_name, new_path)
181 self.rename_notebook(name, path, new_name, new_path)
191
182
192 # Save the notebook file
183 # Save the notebook file
193 ospath = self.get_os_path(new_name, new_path)
184 os_path = self.get_os_path(new_name, new_path)
194 nb = model['content']
185 nb = current.to_notebook_json(model['content'])
195 if 'name' in nb['metadata']:
186 if 'name' in nb['metadata']:
196 nb['metadata']['name'] = u''
187 nb['metadata']['name'] = u''
197 try:
188 try:
198 self.log.debug("Autosaving notebook %s", ospath)
189 self.log.debug("Autosaving notebook %s", os_path)
199 with open(ospath,'w') as f:
190 with open(os_path, 'w') as f:
200 current.write(nb, f, u'json')
191 current.write(nb, f, u'json')
201 except Exception as e:
192 except Exception as e:
202 #raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s' % ospath)
193 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
203 raise e
204
194
205 # Save .py script as well
195 # Save .py script as well
206 if self.save_script:
196 if self.save_script:
207 pypath = os.path.splitext(path)[0] + '.py'
197 py_path = os.path.splitext(os_path)[0] + '.py'
208 self.log.debug("Writing script %s", pypath)
198 self.log.debug("Writing script %s", py_path)
209 try:
199 try:
210 with io.open(pypath, 'w', encoding='utf-8') as f:
200 with io.open(py_path, 'w', encoding='utf-8') as f:
211 current.write(model, f, u'py')
201 current.write(model, f, u'py')
212 except Exception as e:
202 except Exception as e:
213 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % pypath)
203 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
214
204
215 model = self.get_notebook_model(name, path, content=False)
205 model = self.get_notebook_model(name, path, content=False)
216 return model
206 return model
217
207
218 def update_notebook_model(self, model, name, path='/'):
208 def update_notebook_model(self, model, name, path='/'):
219 """Update the notebook's path and/or name"""
209 """Update the notebook's path and/or name"""
220 new_name = model.get('name', name)
210 new_name = model.get('name', name)
221 new_path = model.get('path', path)
211 new_path = model.get('path', path)
222 if path != new_path or name != new_name:
212 if path != new_path or name != new_name:
223 self.rename_notebook(name, path, new_name, new_path)
213 self.rename_notebook(name, path, new_name, new_path)
224 model = self.get_notebook_model(new_name, new_path, content=False)
214 model = self.get_notebook_model(new_name, new_path, content=False)
225 return model
215 return model
226
216
227 def delete_notebook_model(self, name, path='/'):
217 def delete_notebook_model(self, name, path='/'):
228 """Delete notebook by name and path."""
218 """Delete notebook by name and path."""
229 nb_path = self.get_os_path(name, path)
219 os_path = self.get_os_path(name, path)
230 if not os.path.isfile(nb_path):
220 if not os.path.isfile(os_path):
231 raise web.HTTPError(404, u'Notebook does not exist: %s' % nb_path)
221 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
232
222
233 # clear checkpoints
223 # clear checkpoints
234 for checkpoint in self.list_checkpoints(name):
224 for checkpoint in self.list_checkpoints(name, path):
235 checkpoint_id = checkpoint['checkpoint_id']
225 checkpoint_id = checkpoint['checkpoint_id']
236 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
226 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
237 self.log.debug(cp_path)
238 if os.path.isfile(cp_path):
227 if os.path.isfile(cp_path):
239 self.log.debug("Unlinking checkpoint %s", cp_path)
228 self.log.debug("Unlinking checkpoint %s", cp_path)
240 os.unlink(cp_path)
229 os.unlink(cp_path)
241
230
242 self.log.debug("Unlinking notebook %s", nb_path)
231 self.log.debug("Unlinking notebook %s", nb_path)
243 os.unlink(nb_path)
232 os.unlink(nb_path)
244
233
245 def rename_notebook(self, old_name, old_path, new_name, new_path):
234 def rename_notebook(self, old_name, old_path, new_name, new_path):
246 """Rename a notebook."""
235 """Rename a notebook."""
247 if new_name == old_name and new_path == old_path:
236 if new_name == old_name and new_path == old_path:
248 return
237 return
249
238
250 new_full_path = self.get_os_path(new_name, new_path)
239 new_os_path = self.get_os_path(new_name, new_path)
251 old_full_path = self.get_os_path(old_name, old_path)
240 old_os_path = self.get_os_path(old_name, old_path)
252
241
253 # Should we proceed with the move?
242 # Should we proceed with the move?
254 if os.path.isfile(new_full_path):
243 if os.path.isfile(new_os_path):
255 raise web.HTTPError(409, u'Notebook with name already exists: ' % new_full_path)
244 raise web.HTTPError(409, u'Notebook with name already exists: ' % new_os_path)
256 if self.save_script:
245 if self.save_script:
257 old_pypath = os.path.splitext(old_full_path)[0] + '.py'
246 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
258 new_pypath = os.path.splitext(new_full_path)[0] + '.py'
247 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
259 if os.path.isfile(new_pypath):
248 if os.path.isfile(new_py_path):
260 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_pypath)
249 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
261
250
262 # Move the notebook file
251 # Move the notebook file
263 try:
252 try:
264 os.rename(old_full_path, new_full_path)
253 os.rename(old_os_path, new_os_path)
265 except:
254 except Exception as e:
266 raise web.HTTPError(400, u'Unknown error renaming notebook: %s' % old_full_path)
255 raise web.HTTPError(400, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
267
256
268 # Move the checkpoints
257 # Move the checkpoints
269 old_checkpoints = self.list_checkpoints(old_name, old_path)
258 old_checkpoints = self.list_checkpoints(old_name, old_path)
270 for cp in old_checkpoints:
259 for cp in old_checkpoints:
271 checkpoint_id = cp['checkpoint_id']
260 checkpoint_id = cp['checkpoint_id']
272 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, path)
261 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, path)
273 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, path)
262 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, path)
274 if os.path.isfile(old_cp_path):
263 if os.path.isfile(old_cp_path):
275 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
264 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
276 os.rename(old_cp_path, new_cp_path)
265 os.rename(old_cp_path, new_cp_path)
277
266
278 # Move the .py script
267 # Move the .py script
279 if self.save_script:
268 if self.save_script:
280 os.rename(old_pypath, new_pypath)
269 os.rename(old_py_path, new_py_path)
281
270
282 # Checkpoint-related utilities
271 # Checkpoint-related utilities
283
272
284 def get_checkpoint_path(self, checkpoint_id, name, path='/'):
273 def get_checkpoint_path(self, checkpoint_id, name, path='/'):
285 """find the path to a checkpoint"""
274 """find the path to a checkpoint"""
286 filename = u"{name}-{checkpoint_id}{ext}".format(
275 filename = u"{name}-{checkpoint_id}{ext}".format(
287 name=name,
276 name=name,
288 checkpoint_id=checkpoint_id,
277 checkpoint_id=checkpoint_id,
289 ext=self.filename_ext,
278 ext=self.filename_ext,
290 )
279 )
291 cp_path = os.path.join(path, self.checkpoint_dir, filename)
280 cp_path = os.path.join(path, self.checkpoint_dir, filename)
292 return cp_path
281 return cp_path
293
282
294 def get_checkpoint_model(self, checkpoint_id, name, path='/'):
283 def get_checkpoint_model(self, checkpoint_id, name, path='/'):
295 """construct the info dict for a given checkpoint"""
284 """construct the info dict for a given checkpoint"""
296 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
285 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
297 stats = os.stat(cp_path)
286 stats = os.stat(cp_path)
298 last_modified = tz.utcfromtimestamp(stats.st_mtime)
287 last_modified = tz.utcfromtimestamp(stats.st_mtime)
299 info = dict(
288 info = dict(
300 checkpoint_id = checkpoint_id,
289 checkpoint_id = checkpoint_id,
301 last_modified = last_modified,
290 last_modified = last_modified,
302 )
291 )
303 return info
292 return info
304
293
305 # public checkpoint API
294 # public checkpoint API
306
295
307 def create_checkpoint(self, name, path='/'):
296 def create_checkpoint(self, name, path='/'):
308 """Create a checkpoint from the current state of a notebook"""
297 """Create a checkpoint from the current state of a notebook"""
309 nb_path = self.get_os_path(name, path)
298 nb_path = self.get_os_path(name, path)
310 # only the one checkpoint ID:
299 # only the one checkpoint ID:
311 checkpoint_id = u"checkpoint"
300 checkpoint_id = u"checkpoint"
312 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
301 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
313 self.log.debug("creating checkpoint for notebook %s", name)
302 self.log.debug("creating checkpoint for notebook %s", name)
314 if not os.path.exists(self.checkpoint_dir):
303 if not os.path.exists(self.checkpoint_dir):
315 os.mkdir(self.checkpoint_dir)
304 os.mkdir(self.checkpoint_dir)
316 shutil.copy2(nb_path, cp_path)
305 shutil.copy2(nb_path, cp_path)
317
306
318 # return the checkpoint info
307 # return the checkpoint info
319 return self.get_checkpoint_model(checkpoint_id, name, path)
308 return self.get_checkpoint_model(checkpoint_id, name, path)
320
309
321 def list_checkpoints(self, name, path='/'):
310 def list_checkpoints(self, name, path='/'):
322 """list the checkpoints for a given notebook
311 """list the checkpoints for a given notebook
323
312
324 This notebook manager currently only supports one checkpoint per notebook.
313 This notebook manager currently only supports one checkpoint per notebook.
325 """
314 """
326 checkpoint_id = "checkpoint"
315 checkpoint_id = "checkpoint"
327 path = self.get_checkpoint_path(checkpoint_id, name, path)
316 path = self.get_checkpoint_path(checkpoint_id, name, path)
328 if not os.path.exists(path):
317 if not os.path.exists(path):
329 return []
318 return []
330 else:
319 else:
331 return [self.get_checkpoint_model(checkpoint_id, name, path)]
320 return [self.get_checkpoint_model(checkpoint_id, name, path)]
332
321
333
322
334 def restore_checkpoint(self, checkpoint_id, name, path='/'):
323 def restore_checkpoint(self, checkpoint_id, name, path='/'):
335 """restore a notebook to a checkpointed state"""
324 """restore a notebook to a checkpointed state"""
336 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
325 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
337 nb_path = self.get_os_path(name, path)
326 nb_path = self.get_os_path(name, path)
338 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
327 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
339 if not os.path.isfile(cp_path):
328 if not os.path.isfile(cp_path):
340 self.log.debug("checkpoint file does not exist: %s", cp_path)
329 self.log.debug("checkpoint file does not exist: %s", cp_path)
341 raise web.HTTPError(404,
330 raise web.HTTPError(404,
342 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
331 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
343 )
332 )
344 # ensure notebook is readable (never restore from an unreadable notebook)
333 # ensure notebook is readable (never restore from an unreadable notebook)
345 with file(cp_path, 'r') as f:
334 with file(cp_path, 'r') as f:
346 nb = current.read(f, u'json')
335 nb = current.read(f, u'json')
347 shutil.copy2(cp_path, nb_path)
336 shutil.copy2(cp_path, nb_path)
348 self.log.debug("copying %s -> %s", cp_path, nb_path)
337 self.log.debug("copying %s -> %s", cp_path, nb_path)
349
338
350 def delete_checkpoint(self, checkpoint_id, name, path='/'):
339 def delete_checkpoint(self, checkpoint_id, name, path='/'):
351 """delete a notebook's checkpoint"""
340 """delete a notebook's checkpoint"""
352 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
341 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
353 if not os.path.isfile(cp_path):
342 if not os.path.isfile(cp_path):
354 raise web.HTTPError(404,
343 raise web.HTTPError(404,
355 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
344 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
356 )
345 )
357 self.log.debug("unlinking %s", cp_path)
346 self.log.debug("unlinking %s", cp_path)
358 os.unlink(cp_path)
347 os.unlink(cp_path)
359
348
360 def info_string(self):
349 def info_string(self):
361 return "Serving notebooks from local directory: %s" % self.notebook_dir
350 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -1,224 +1,224 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 * Zach Sailer
6 * Zach Sailer
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import os
20 import os
21 import uuid
21 import uuid
22 from urllib import quote, unquote
22 from urllib import quote, unquote
23
23
24 from tornado import web
24 from tornado import web
25
25
26 from IPython.html.utils import url_path_join
26 from IPython.html.utils import url_path_join
27 from IPython.config.configurable import LoggingConfigurable
27 from IPython.config.configurable import LoggingConfigurable
28 from IPython.nbformat import current
28 from IPython.nbformat import current
29 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
29 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Classes
32 # Classes
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 class NotebookManager(LoggingConfigurable):
35 class NotebookManager(LoggingConfigurable):
36
36
37 # Todo:
37 # Todo:
38 # The notebook_dir attribute is used to mean a couple of different things:
38 # The notebook_dir attribute is used to mean a couple of different things:
39 # 1. Where the notebooks are stored if FileNotebookManager is used.
39 # 1. Where the notebooks are stored if FileNotebookManager is used.
40 # 2. The cwd of the kernel for a project.
40 # 2. The cwd of the kernel for a project.
41 # Right now we use this attribute in a number of different places and
41 # Right now we use this attribute in a number of different places and
42 # we are going to have to disentangle all of this.
42 # we are going to have to disentangle all of this.
43 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
43 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
44 The directory to use for notebooks.
44 The directory to use for notebooks.
45 """)
45 """)
46
46
47 filename_ext = Unicode(u'.ipynb')
47 filename_ext = Unicode(u'.ipynb')
48
48
49 def named_notebook_path(self, notebook_path):
49 def named_notebook_path(self, notebook_path):
50 """Given notebook_path (*always* a URL path to notebook), returns a
50 """Given notebook_path (*always* a URL path to notebook), returns a
51 (name, path) tuple, where name is a .ipynb file, and path is the
51 (name, path) tuple, where name is a .ipynb file, and path is the
52 URL path that describes the file system path for the file.
52 URL path that describes the file system path for the file.
53 It *always* starts *and* ends with a '/' character.
53 It *always* starts *and* ends with a '/' character.
54
54
55 Parameters
55 Parameters
56 ----------
56 ----------
57 notebook_path : string
57 notebook_path : string
58 A path that may be a .ipynb name or a directory
58 A path that may be a .ipynb name or a directory
59
59
60 Returns
60 Returns
61 -------
61 -------
62 name : string or None
62 name : string or None
63 the filename of the notebook, or None if not a .ipynb extension
63 the filename of the notebook, or None if not a .ipynb extension
64 path : string
64 path : string
65 the path to the directory which contains the notebook
65 the path to the directory which contains the notebook
66 """
66 """
67 names = notebook_path.split('/')
67 names = notebook_path.split('/')
68 names = [n for n in names if n != ''] # remove duplicate splits
68 names = [n for n in names if n != ''] # remove duplicate splits
69
69
70 names = [''] + names
70 names = [''] + names
71
71
72 if names and names[-1].endswith(".ipynb"):
72 if names and names[-1].endswith(".ipynb"):
73 name = names[-1]
73 name = names[-1]
74 path = "/".join(names[:-1]) + '/'
74 path = "/".join(names[:-1]) + '/'
75 else:
75 else:
76 name = None
76 name = None
77 path = "/".join(names) + '/'
77 path = "/".join(names) + '/'
78 return name, path
78 return name, path
79
79
80 def get_os_path(self, fname=None, path='/'):
80 def get_os_path(self, fname=None, path='/'):
81 """Given a notebook name and a URL path, return its file system
81 """Given a notebook name and a URL path, return its file system
82 path.
82 path.
83
83
84 Parameters
84 Parameters
85 ----------
85 ----------
86 fname : string
86 fname : string
87 The name of a notebook file with the .ipynb extension
87 The name of a notebook file with the .ipynb extension
88 path : string
88 path : string
89 The relative URL path (with '/' as separator) to the named
89 The relative URL path (with '/' as separator) to the named
90 notebook.
90 notebook.
91
91
92 Returns
92 Returns
93 -------
93 -------
94 path : string
94 path : string
95 A file system path that combines notebook_dir (location where
95 A file system path that combines notebook_dir (location where
96 server started), the relative path, and the filename with the
96 server started), the relative path, and the filename with the
97 current operating system's url.
97 current operating system's url.
98 """
98 """
99 parts = path.split('/')
99 parts = path.split('/')
100 parts = [p for p in parts if p != ''] # remove duplicate splits
100 parts = [p for p in parts if p != ''] # remove duplicate splits
101 if fname is not None:
101 if fname is not None:
102 parts += [fname]
102 parts += [fname]
103 path = os.path.join(self.notebook_dir, *parts)
103 path = os.path.join(self.notebook_dir, *parts)
104 return path
104 return path
105
105
106 def url_encode(self, path):
106 def url_encode(self, path):
107 """Takes a URL path with special characters and returns
107 """Takes a URL path with special characters and returns
108 the path with all these characters URL encoded"""
108 the path with all these characters URL encoded"""
109 parts = path.split('/')
109 parts = path.split('/')
110 return '/'.join([quote(p) for p in parts])
110 return '/'.join([quote(p) for p in parts])
111
111
112 def url_decode(self, path):
112 def url_decode(self, path):
113 """Takes a URL path with encoded special characters and
113 """Takes a URL path with encoded special characters and
114 returns the URL with special characters decoded"""
114 returns the URL with special characters decoded"""
115 parts = path.split('/')
115 parts = path.split('/')
116 return '/'.join([unquote(p) for p in parts])
116 return '/'.join([unquote(p) for p in parts])
117
117
118 def _notebook_dir_changed(self, name, old, new):
118 def _notebook_dir_changed(self, name, old, new):
119 """Do a bit of validation of the notebook dir."""
119 """Do a bit of validation of the notebook dir."""
120 if not os.path.isabs(new):
120 if not os.path.isabs(new):
121 # If we receive a non-absolute path, make it absolute.
121 # If we receive a non-absolute path, make it absolute.
122 abs_new = os.path.abspath(new)
122 abs_new = os.path.abspath(new)
123 self.notebook_dir = os.path.dirname(abs_new)
123 self.notebook_dir = os.path.dirname(abs_new)
124 return
124 return
125 if os.path.exists(new) and not os.path.isdir(new):
125 if os.path.exists(new) and not os.path.isdir(new):
126 raise TraitError("notebook dir %r is not a directory" % new)
126 raise TraitError("notebook dir %r is not a directory" % new)
127 if not os.path.exists(new):
127 if not os.path.exists(new):
128 self.log.info("Creating notebook dir %s", new)
128 self.log.info("Creating notebook dir %s", new)
129 try:
129 try:
130 os.mkdir(new)
130 os.mkdir(new)
131 except:
131 except:
132 raise TraitError("Couldn't create notebook dir %r" % new)
132 raise TraitError("Couldn't create notebook dir %r" % new)
133
133
134 # Main notebook API
134 # Main notebook API
135
135
136 def increment_filename(self, basename, path='/'):
136 def increment_filename(self, basename, path='/'):
137 """Increment a notebook filename without the .ipynb to make it unique.
137 """Increment a notebook filename without the .ipynb to make it unique.
138
138
139 Parameters
139 Parameters
140 ----------
140 ----------
141 basename : unicode
141 basename : unicode
142 The name of a notebook without the ``.ipynb`` file extension.
142 The name of a notebook without the ``.ipynb`` file extension.
143 path : unicode
143 path : unicode
144 The URL path of the notebooks directory
144 The URL path of the notebooks directory
145 """
145 """
146 return basename
146 return basename
147
147
148 def list_notebooks(self):
148 def list_notebooks(self):
149 """Return a list of notebook dicts without content.
149 """Return a list of notebook dicts without content.
150
150
151 This returns a list of dicts, each of the form::
151 This returns a list of dicts, each of the form::
152
152
153 dict(notebook_id=notebook,name=name)
153 dict(notebook_id=notebook,name=name)
154
154
155 This list of dicts should be sorted by name::
155 This list of dicts should be sorted by name::
156
156
157 data = sorted(data, key=lambda item: item['name'])
157 data = sorted(data, key=lambda item: item['name'])
158 """
158 """
159 raise NotImplementedError('must be implemented in a subclass')
159 raise NotImplementedError('must be implemented in a subclass')
160
160
161 def get_notebook_model(self, name, path='/', content=True):
161 def get_notebook_model(self, name, path='/', content=True):
162 """Get the notebook model with or without content."""
162 """Get the notebook model with or without content."""
163 raise NotImplementedError('must be implemented in a subclass')
163 raise NotImplementedError('must be implemented in a subclass')
164
164
165 def save_notebook_model(self, model, name, path='/'):
165 def save_notebook_model(self, model, name, path='/'):
166 """Save the notebook model and return the model with no content."""
166 """Save the notebook model and return the model with no content."""
167 raise NotImplementedError('must be implemented in a subclass')
167 raise NotImplementedError('must be implemented in a subclass')
168
168
169 def update_notebook_model(self, model, name, path='/'):
169 def update_notebook_model(self, model, name, path='/'):
170 """Update the notebook model and return the model with no content."""
170 """Update the notebook model and return the model with no content."""
171 raise NotImplementedError('must be implemented in a subclass')
171 raise NotImplementedError('must be implemented in a subclass')
172
172
173 def delete_notebook_model(self, name, path):
173 def delete_notebook_model(self, name, path):
174 """Delete notebook by name and path."""
174 """Delete notebook by name and path."""
175 raise NotImplementedError('must be implemented in a subclass')
175 raise NotImplementedError('must be implemented in a subclass')
176
176
177 def create_notebook_model(self, model=None, path='/'):
177 def create_notebook_model(self, model=None, path='/'):
178 """Create a new untitled notebook and return its model with no content."""
178 """Create a new untitled notebook and return its model with no content."""
179 name = self.increment_filename('Untitled', path)
179 name = self.increment_filename('Untitled', path)
180 if model is None:
180 if model is None:
181 model = {}
181 model = {}
182 metadata = current.new_metadata(name=u'')
182 metadata = current.new_metadata(name=u'')
183 nb = current.new_notebook(metadata=metadata)
183 nb = current.new_notebook(metadata=metadata)
184 model['content'] = nb
184 model['content'] = nb
185 model['name'] = name
185 model['name'] = name
186 model['path'] = path
186 model['path'] = path
187 model = self.save_notebook_model(model, name, path)
187 model = self.save_notebook_model(model, name, path)
188 return model
188 return model
189
189
190 def copy_notebook(self, name, path='/', content=False):
190 def copy_notebook(self, name, path='/', content=False):
191 """Copy an existing notebook and return its new model."""
191 """Copy an existing notebook and return its new model."""
192 model = self.get_notebook_model(name, path)
192 model = self.get_notebook_model(name, path)
193 name = os.path.splitext(name)[0] + '-Copy'
193 name = os.path.splitext(name)[0] + '-Copy'
194 name = self.increment_filename(name, path) + self.filename_ext
194 name = self.increment_filename(name, path) + self.filename_ext
195 model['name'] = name
195 model['name'] = name
196 model = self.save_notebook_model(model, name, path, content=content)
196 model = self.save_notebook_model(model, name, path, content=content)
197 return model
197 return model
198
198
199 # Checkpoint-related
199 # Checkpoint-related
200
200
201 def create_checkpoint(self, name, path='/'):
201 def create_checkpoint(self, name, path='/'):
202 """Create a checkpoint of the current state of a notebook
202 """Create a checkpoint of the current state of a notebook
203
203
204 Returns a checkpoint_id for the new checkpoint.
204 Returns a checkpoint_id for the new checkpoint.
205 """
205 """
206 raise NotImplementedError("must be implemented in a subclass")
206 raise NotImplementedError("must be implemented in a subclass")
207
207
208 def list_checkpoints(self, name, path='/'):
208 def list_checkpoints(self, name, path='/'):
209 """Return a list of checkpoints for a given notebook"""
209 """Return a list of checkpoints for a given notebook"""
210 return []
210 return []
211
211
212 def restore_checkpoint(self, checkpoint_id, name, path='/'):
212 def restore_checkpoint(self, checkpoint_id, name, path='/'):
213 """Restore a notebook from one of its checkpoints"""
213 """Restore a notebook from one of its checkpoints"""
214 raise NotImplementedError("must be implemented in a subclass")
214 raise NotImplementedError("must be implemented in a subclass")
215
215
216 def delete_checkpoint(self, checkpoint_id, name, path='/'):
216 def delete_checkpoint(self, checkpoint_id, name, path='/'):
217 """delete a checkpoint for a notebook"""
217 """delete a checkpoint for a notebook"""
218 raise NotImplementedError("must be implemented in a subclass")
218 raise NotImplementedError("must be implemented in a subclass")
219
219
220 def log_info(self):
220 def log_info(self):
221 self.log.info(self.info_string())
221 self.log.info(self.info_string())
222
222
223 def info_string(self):
223 def info_string(self):
224 return "Serving notebooks" No newline at end of file
224 return "Serving notebooks"
@@ -1,226 +1,226 b''
1 """The official API for working with notebooks in the current format version.
1 """The official API for working with notebooks in the current format version.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-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 from __future__ import print_function
19 from __future__ import print_function
20 import json
20 import json
21 from xml.etree import ElementTree as ET
21 from xml.etree import ElementTree as ET
22 import re
22 import re
23
23
24 from IPython.nbformat import v3
24 from IPython.nbformat import v3
25 from IPython.nbformat import v2
25 from IPython.nbformat import v2
26 from IPython.nbformat import v1
26 from IPython.nbformat import v1
27
27
28 from IPython.nbformat.v3 import (
28 from IPython.nbformat.v3 import (
29 NotebookNode,
29 NotebookNode,
30 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
30 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
31 parse_filename, new_metadata, new_author, new_heading_cell, nbformat,
31 parse_filename, new_metadata, new_author, new_heading_cell, nbformat,
32 nbformat_minor,
32 nbformat_minor, to_notebook_json
33 )
33 )
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Code
36 # Code
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 current_nbformat = nbformat
39 current_nbformat = nbformat
40 current_nbformat_minor = nbformat_minor
40 current_nbformat_minor = nbformat_minor
41
41
42
42
43 class NBFormatError(ValueError):
43 class NBFormatError(ValueError):
44 pass
44 pass
45
45
46 class NotJSONError(ValueError):
46 class NotJSONError(ValueError):
47 pass
47 pass
48
48
49
49
50 def parse_json(s, **kwargs):
50 def parse_json(s, **kwargs):
51 """Parse a string into a (nbformat, dict) tuple."""
51 """Parse a string into a (nbformat, dict) tuple."""
52 try:
52 try:
53 d = json.loads(s, **kwargs)
53 d = json.loads(s, **kwargs)
54 except ValueError:
54 except ValueError:
55 raise NotJSONError("Notebook does not appear to be JSON: %r" % s[:16])
55 raise NotJSONError("Notebook does not appear to be JSON: %r" % s[:16])
56 nbf = d.get('nbformat', 1)
56 nbf = d.get('nbformat', 1)
57 nbm = d.get('nbformat_minor', 0)
57 nbm = d.get('nbformat_minor', 0)
58 return nbf, nbm, d
58 return nbf, nbm, d
59
59
60
60
61 def parse_py(s, **kwargs):
61 def parse_py(s, **kwargs):
62 """Parse a string into a (nbformat, string) tuple."""
62 """Parse a string into a (nbformat, string) tuple."""
63 nbf = current_nbformat
63 nbf = current_nbformat
64 nbm = current_nbformat_minor
64 nbm = current_nbformat_minor
65
65
66 pattern = r'# <nbformat>(?P<nbformat>\d+[\.\d+]*)</nbformat>'
66 pattern = r'# <nbformat>(?P<nbformat>\d+[\.\d+]*)</nbformat>'
67 m = re.search(pattern,s)
67 m = re.search(pattern,s)
68 if m is not None:
68 if m is not None:
69 digits = m.group('nbformat').split('.')
69 digits = m.group('nbformat').split('.')
70 nbf = int(digits[0])
70 nbf = int(digits[0])
71 if len(digits) > 1:
71 if len(digits) > 1:
72 nbm = int(digits[1])
72 nbm = int(digits[1])
73
73
74 return nbf, nbm, s
74 return nbf, nbm, s
75
75
76
76
77 def reads_json(s, **kwargs):
77 def reads_json(s, **kwargs):
78 """Read a JSON notebook from a string and return the NotebookNode object."""
78 """Read a JSON notebook from a string and return the NotebookNode object."""
79 nbf, minor, d = parse_json(s, **kwargs)
79 nbf, minor, d = parse_json(s, **kwargs)
80 if nbf == 1:
80 if nbf == 1:
81 nb = v1.to_notebook_json(d, **kwargs)
81 nb = v1.to_notebook_json(d, **kwargs)
82 nb = v3.convert_to_this_nbformat(nb, orig_version=1)
82 nb = v3.convert_to_this_nbformat(nb, orig_version=1)
83 elif nbf == 2:
83 elif nbf == 2:
84 nb = v2.to_notebook_json(d, **kwargs)
84 nb = v2.to_notebook_json(d, **kwargs)
85 nb = v3.convert_to_this_nbformat(nb, orig_version=2)
85 nb = v3.convert_to_this_nbformat(nb, orig_version=2)
86 elif nbf == 3:
86 elif nbf == 3:
87 nb = v3.to_notebook_json(d, **kwargs)
87 nb = v3.to_notebook_json(d, **kwargs)
88 nb = v3.convert_to_this_nbformat(nb, orig_version=3, orig_minor=minor)
88 nb = v3.convert_to_this_nbformat(nb, orig_version=3, orig_minor=minor)
89 else:
89 else:
90 raise NBFormatError('Unsupported JSON nbformat version %s (supported version: %i)' % (nbf, 3))
90 raise NBFormatError('Unsupported JSON nbformat version %s (supported version: %i)' % (nbf, 3))
91 return nb
91 return nb
92
92
93
93
94 def writes_json(nb, **kwargs):
94 def writes_json(nb, **kwargs):
95 return v3.writes_json(nb, **kwargs)
95 return v3.writes_json(nb, **kwargs)
96
96
97
97
98 def reads_py(s, **kwargs):
98 def reads_py(s, **kwargs):
99 """Read a .py notebook from a string and return the NotebookNode object."""
99 """Read a .py notebook from a string and return the NotebookNode object."""
100 nbf, nbm, s = parse_py(s, **kwargs)
100 nbf, nbm, s = parse_py(s, **kwargs)
101 if nbf == 2:
101 if nbf == 2:
102 nb = v2.to_notebook_py(s, **kwargs)
102 nb = v2.to_notebook_py(s, **kwargs)
103 elif nbf == 3:
103 elif nbf == 3:
104 nb = v3.to_notebook_py(s, **kwargs)
104 nb = v3.to_notebook_py(s, **kwargs)
105 else:
105 else:
106 raise NBFormatError('Unsupported PY nbformat version: %i' % nbf)
106 raise NBFormatError('Unsupported PY nbformat version: %i' % nbf)
107 return nb
107 return nb
108
108
109
109
110 def writes_py(nb, **kwargs):
110 def writes_py(nb, **kwargs):
111 return v3.writes_py(nb, **kwargs)
111 return v3.writes_py(nb, **kwargs)
112
112
113
113
114 # High level API
114 # High level API
115
115
116
116
117 def reads(s, format, **kwargs):
117 def reads(s, format, **kwargs):
118 """Read a notebook from a string and return the NotebookNode object.
118 """Read a notebook from a string and return the NotebookNode object.
119
119
120 This function properly handles notebooks of any version. The notebook
120 This function properly handles notebooks of any version. The notebook
121 returned will always be in the current version's format.
121 returned will always be in the current version's format.
122
122
123 Parameters
123 Parameters
124 ----------
124 ----------
125 s : unicode
125 s : unicode
126 The raw unicode string to read the notebook from.
126 The raw unicode string to read the notebook from.
127 format : (u'json', u'ipynb', u'py')
127 format : (u'json', u'ipynb', u'py')
128 The format that the string is in.
128 The format that the string is in.
129
129
130 Returns
130 Returns
131 -------
131 -------
132 nb : NotebookNode
132 nb : NotebookNode
133 The notebook that was read.
133 The notebook that was read.
134 """
134 """
135 format = unicode(format)
135 format = unicode(format)
136 if format == u'json' or format == u'ipynb':
136 if format == u'json' or format == u'ipynb':
137 return reads_json(s, **kwargs)
137 return reads_json(s, **kwargs)
138 elif format == u'py':
138 elif format == u'py':
139 return reads_py(s, **kwargs)
139 return reads_py(s, **kwargs)
140 else:
140 else:
141 raise NBFormatError('Unsupported format: %s' % format)
141 raise NBFormatError('Unsupported format: %s' % format)
142
142
143
143
144 def writes(nb, format, **kwargs):
144 def writes(nb, format, **kwargs):
145 """Write a notebook to a string in a given format in the current nbformat version.
145 """Write a notebook to a string in a given format in the current nbformat version.
146
146
147 This function always writes the notebook in the current nbformat version.
147 This function always writes the notebook in the current nbformat version.
148
148
149 Parameters
149 Parameters
150 ----------
150 ----------
151 nb : NotebookNode
151 nb : NotebookNode
152 The notebook to write.
152 The notebook to write.
153 format : (u'json', u'ipynb', u'py')
153 format : (u'json', u'ipynb', u'py')
154 The format to write the notebook in.
154 The format to write the notebook in.
155
155
156 Returns
156 Returns
157 -------
157 -------
158 s : unicode
158 s : unicode
159 The notebook string.
159 The notebook string.
160 """
160 """
161 format = unicode(format)
161 format = unicode(format)
162 if format == u'json' or format == u'ipynb':
162 if format == u'json' or format == u'ipynb':
163 return writes_json(nb, **kwargs)
163 return writes_json(nb, **kwargs)
164 elif format == u'py':
164 elif format == u'py':
165 return writes_py(nb, **kwargs)
165 return writes_py(nb, **kwargs)
166 else:
166 else:
167 raise NBFormatError('Unsupported format: %s' % format)
167 raise NBFormatError('Unsupported format: %s' % format)
168
168
169
169
170 def read(fp, format, **kwargs):
170 def read(fp, format, **kwargs):
171 """Read a notebook from a file and return the NotebookNode object.
171 """Read a notebook from a file and return the NotebookNode object.
172
172
173 This function properly handles notebooks of any version. The notebook
173 This function properly handles notebooks of any version. The notebook
174 returned will always be in the current version's format.
174 returned will always be in the current version's format.
175
175
176 Parameters
176 Parameters
177 ----------
177 ----------
178 fp : file
178 fp : file
179 Any file-like object with a read method.
179 Any file-like object with a read method.
180 format : (u'json', u'ipynb', u'py')
180 format : (u'json', u'ipynb', u'py')
181 The format that the string is in.
181 The format that the string is in.
182
182
183 Returns
183 Returns
184 -------
184 -------
185 nb : NotebookNode
185 nb : NotebookNode
186 The notebook that was read.
186 The notebook that was read.
187 """
187 """
188 return reads(fp.read(), format, **kwargs)
188 return reads(fp.read(), format, **kwargs)
189
189
190
190
191 def write(nb, fp, format, **kwargs):
191 def write(nb, fp, format, **kwargs):
192 """Write a notebook to a file in a given format in the current nbformat version.
192 """Write a notebook to a file in a given format in the current nbformat version.
193
193
194 This function always writes the notebook in the current nbformat version.
194 This function always writes the notebook in the current nbformat version.
195
195
196 Parameters
196 Parameters
197 ----------
197 ----------
198 nb : NotebookNode
198 nb : NotebookNode
199 The notebook to write.
199 The notebook to write.
200 fp : file
200 fp : file
201 Any file-like object with a write method.
201 Any file-like object with a write method.
202 format : (u'json', u'ipynb', u'py')
202 format : (u'json', u'ipynb', u'py')
203 The format to write the notebook in.
203 The format to write the notebook in.
204
204
205 Returns
205 Returns
206 -------
206 -------
207 s : unicode
207 s : unicode
208 The notebook string.
208 The notebook string.
209 """
209 """
210 return fp.write(writes(nb, format, **kwargs))
210 return fp.write(writes(nb, format, **kwargs))
211
211
212 def _convert_to_metadata():
212 def _convert_to_metadata():
213 """Convert to a notebook having notebook metadata."""
213 """Convert to a notebook having notebook metadata."""
214 import glob
214 import glob
215 for fname in glob.glob('*.ipynb'):
215 for fname in glob.glob('*.ipynb'):
216 print('Converting file:',fname)
216 print('Converting file:',fname)
217 with open(fname,'r') as f:
217 with open(fname,'r') as f:
218 nb = read(f,u'json')
218 nb = read(f,u'json')
219 md = new_metadata()
219 md = new_metadata()
220 if u'name' in nb:
220 if u'name' in nb:
221 md.name = nb.name
221 md.name = nb.name
222 del nb[u'name']
222 del nb[u'name']
223 nb.metadata = md
223 nb.metadata = md
224 with open(fname,'w') as f:
224 with open(fname,'w') as f:
225 write(nb, f, u'json')
225 write(nb, f, u'json')
226
226
General Comments 0
You need to be logged in to leave comments. Login now