manual rebase notebooks web services

pull/37/head
Zachary Sailer 13 years ago committed by MinRK
parent 09c4ecbb10
commit 98e67bfa22

@ -21,6 +21,7 @@ import io
import os
import glob
import shutil
from unicodedata import normalize
from tornado import web
@ -73,68 +74,47 @@ class FileNotebookManager(NotebookManager):
filename_ext = Unicode(u'.ipynb')
# Map notebook names to notebook_ids
rev_mapping = Dict()
def get_notebook_names(self):
def get_notebook_names(self, path):
"""List all notebook names in the notebook dir."""
names = glob.glob(os.path.join(self.notebook_dir,
names = glob.glob(os.path.join(self.notebook_dir, path,
'*' + self.filename_ext))
names = [normalize('NFC', os.path.splitext(os.path.basename(name))[0])
#names = [os.path.splitext(os.path.basename(name))[0]
names = [os.path.basename(name)
for name in names]
return names
def list_notebooks(self):
def list_notebooks(self, path):
"""List all notebooks in the notebook dir."""
names = self.get_notebook_names()
names = self.get_notebook_names(path)
data = []
for name in names:
if name not in self.rev_mapping:
notebook_id = self.new_notebook_id(name)
else:
notebook_id = self.rev_mapping[name]
data.append(dict(notebook_id=notebook_id,name=name))
data = sorted(data, key=lambda item: item['name'])
return data
def new_notebook_id(self, name):
"""Generate a new notebook_id for a name and store its mappings."""
notebook_id = super(FileNotebookManager, self).new_notebook_id(name)
self.rev_mapping[name] = notebook_id
return notebook_id
def delete_notebook_id(self, notebook_id):
"""Delete a notebook's id in the mapping."""
name = self.mapping[notebook_id]
super(FileNotebookManager, self).delete_notebook_id(notebook_id)
del self.rev_mapping[name]
for name in names:
data.append(name)
#data = sorted(data, key=lambda item: item['name'])
return names
def notebook_exists(self, notebook_id):
def notebook_exists(self, notebook_name):
"""Does a notebook exist?"""
exists = super(FileNotebookManager, self).notebook_exists(notebook_id)
exists = super(FileNotebookManager, self).notebook_exists(notebook_name)
if not exists:
return False
path = self.get_path_by_name(self.mapping[notebook_id])
path = self.get_path_by_name(self.mapping[notebook_name])
return os.path.isfile(path)
def get_name(self, notebook_id):
"""get a notebook name, raising 404 if not found"""
try:
name = self.mapping[notebook_id]
except KeyError:
raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
return name
def get_path(self, notebook_id):
"""Return a full path to a notebook given its notebook_id."""
name = self.get_name(notebook_id)
return self.get_path_by_name(name)
def get_path_by_name(self, name):
def get_path(self, notebook_name, notebook_path=None):
"""Return a full path to a notebook given its notebook_name."""
return self.get_path_by_name(notebook_name, notebook_path)
def get_path_by_name(self, name, notebook_path=None):
"""Return a full path to a notebook given its name."""
filename = name + self.filename_ext
path = os.path.join(self.notebook_dir, filename)
filename = name #+ self.filename_ext
if notebook_path == None:
path = os.path.join(self.notebook_dir, filename)
else:
path = os.path.join(self.notebook_dir, notebook_path, filename)
return path
def read_notebook_object_from_path(self, path):
@ -151,11 +131,11 @@ class FileNotebookManager(NotebookManager):
raise web.HTTPError(400, msg, reason=msg)
return last_modified, nb
def read_notebook_object(self, notebook_id):
"""Get the Notebook representation of a notebook by notebook_id."""
path = self.get_path(notebook_id)
def read_notebook_object(self, notebook_name, notebook_path):
"""Get the Notebook representation of a notebook by notebook_name."""
path = self.get_path(notebook_name, notebook_path)
if not os.path.isfile(path):
raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
last_modified, nb = self.read_notebook_object_from_path(path)
# Always use the filename as the notebook name.
# Eventually we will get rid of the notebook name in the metadata
@ -165,23 +145,20 @@ class FileNotebookManager(NotebookManager):
nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
return last_modified, nb
def write_notebook_object(self, nb, notebook_id=None):
"""Save an existing notebook object by notebook_id."""
def write_notebook_object(self, nb, notebook_name=None, notebook_path=None):
"""Save an existing notebook object by notebook_name."""
try:
new_name = normalize('NFC', nb.metadata.name)
except AttributeError:
raise web.HTTPError(400, u'Missing notebook name')
if notebook_id is None:
notebook_id = self.new_notebook_id(new_name)
if notebook_id not in self.mapping:
raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
old_name = self.mapping[notebook_id]
old_checkpoints = self.list_checkpoints(notebook_id)
path = self.get_path_by_name(new_name)
new_path = notebook_path
old_name = notebook_name
# old_name = self.mapping[notebook_name]
old_checkpoints = self.list_checkpoints(old_name)
path = self.get_path_by_name(new_name, new_path)
# Right before we save the notebook, we write an empty string as the
# notebook name in the metadata. This is to prepare for removing
# this attribute entirely post 1.0. The web app still uses the metadata
@ -205,47 +182,43 @@ class FileNotebookManager(NotebookManager):
except Exception as e:
raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
# remove old files if the name changed
if old_name != new_name:
# update mapping
self.mapping[notebook_id] = new_name
self.rev_mapping[new_name] = notebook_id
del self.rev_mapping[old_name]
# remove renamed original, if it exists
old_path = self.get_path_by_name(old_name)
if os.path.isfile(old_path):
self.log.debug("unlinking notebook %s", old_path)
os.unlink(old_path)
if old_name != None:
# remove old files if the name changed
if old_name != new_name:
# remove renamed original, if it exists
old_path = self.get_path_by_name(old_name, notebook_path)
if os.path.isfile(old_path):
self.log.debug("unlinking notebook %s", old_path)
os.unlink(old_path)
# cleanup old script, if it exists
if self.save_script:
old_pypath = os.path.splitext(old_path)[0] + '.py'
if os.path.isfile(old_pypath):
self.log.debug("unlinking script %s", old_pypath)
os.unlink(old_pypath)
# cleanup old script, if it exists
if self.save_script:
old_pypath = os.path.splitext(old_path)[0] + '.py'
if os.path.isfile(old_pypath):
self.log.debug("unlinking script %s", old_pypath)
os.unlink(old_pypath)
# rename checkpoints to follow file
for cp in old_checkpoints:
checkpoint_id = cp['checkpoint_id']
old_cp_path = self.get_checkpoint_path_by_name(old_name, checkpoint_id)
new_cp_path = self.get_checkpoint_path_by_name(new_name, checkpoint_id)
if os.path.isfile(old_cp_path):
self.log.debug("renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
os.rename(old_cp_path, new_cp_path)
# rename checkpoints to follow file
for cp in old_checkpoints:
checkpoint_id = cp['checkpoint_id']
old_cp_path = self.get_checkpoint_path_by_name(old_name, checkpoint_id)
new_cp_path = self.get_checkpoint_path_by_name(new_name, checkpoint_id)
if os.path.isfile(old_cp_path):
self.log.debug("renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
os.rename(old_cp_path, new_cp_path)
return new_name
return notebook_id
def delete_notebook(self, notebook_id):
"""Delete notebook by notebook_id."""
nb_path = self.get_path(notebook_id)
def delete_notebook(self, notebook_name, notebook_path):
"""Delete notebook by notebook_name."""
nb_path = self.get_path(notebook_name, notebook_path)
if not os.path.isfile(nb_path):
raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
# clear checkpoints
for checkpoint in self.list_checkpoints(notebook_id):
for checkpoint in self.list_checkpoints(notebook_name):
checkpoint_id = checkpoint['checkpoint_id']
path = self.get_checkpoint_path(notebook_id, checkpoint_id)
path = self.get_checkpoint_path(notebook_name, checkpoint_id)
self.log.debug(path)
if os.path.isfile(path):
self.log.debug("unlinking checkpoint %s", path)
@ -253,9 +226,8 @@ class FileNotebookManager(NotebookManager):
self.log.debug("unlinking notebook %s", nb_path)
os.unlink(nb_path)
self.delete_notebook_id(notebook_id)
def increment_filename(self, basename):
def increment_filename(self, basename, notebook_path=None):
"""Return a non-used filename of the form basename<int>.
This searches through the filenames (basename0, basename1, ...)
@ -264,8 +236,8 @@ class FileNotebookManager(NotebookManager):
"""
i = 0
while True:
name = u'%s%i' % (basename,i)
path = self.get_path_by_name(name)
name = u'%s%i.ipynb' % (basename,i)
path = self.get_path_by_name(name, notebook_path)
if not os.path.isfile(path):
break
else:
@ -274,24 +246,27 @@ class FileNotebookManager(NotebookManager):
# Checkpoint-related utilities
def get_checkpoint_path_by_name(self, name, checkpoint_id):
def get_checkpoint_path_by_name(self, name, checkpoint_id, notebook_path=None):
"""Return a full path to a notebook checkpoint, given its name and checkpoint id."""
filename = u"{name}-{checkpoint_id}{ext}".format(
name=name,
checkpoint_id=checkpoint_id,
ext=self.filename_ext,
)
path = os.path.join(self.checkpoint_dir, filename)
if notebook_path ==None:
path = os.path.join(self.checkpoint_dir, filename)
else:
path = os.path.join(notebook_path, self.checkpoint_dir, filename)
return path
def get_checkpoint_path(self, notebook_id, checkpoint_id):
def get_checkpoint_path(self, notebook_name, checkpoint_id, notebook_path=None):
"""find the path to a checkpoint"""
name = self.get_name(notebook_id)
return self.get_checkpoint_path_by_name(name, checkpoint_id)
name = notebook_name
return self.get_checkpoint_path_by_name(name, checkpoint_id, notebook_path)
def get_checkpoint_info(self, notebook_id, checkpoint_id):
def get_checkpoint_info(self, notebook_name, checkpoint_id, notebook_path=None):
"""construct the info dict for a given checkpoint"""
path = self.get_checkpoint_path(notebook_id, checkpoint_id)
path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
stats = os.stat(path)
last_modified = tz.utcfromtimestamp(stats.st_mtime)
info = dict(
@ -303,54 +278,54 @@ class FileNotebookManager(NotebookManager):
# public checkpoint API
def create_checkpoint(self, notebook_id):
def create_checkpoint(self, notebook_name, notebook_path=None):
"""Create a checkpoint from the current state of a notebook"""
nb_path = self.get_path(notebook_id)
nb_path = self.get_path(notebook_name, notebook_path)
# only the one checkpoint ID:
checkpoint_id = u"checkpoint"
cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
self.log.debug("creating checkpoint for notebook %s", notebook_id)
cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
self.log.debug("creating checkpoint for notebook %s", notebook_name)
if not os.path.exists(self.checkpoint_dir):
os.mkdir(self.checkpoint_dir)
shutil.copy2(nb_path, cp_path)
# return the checkpoint info
return self.get_checkpoint_info(notebook_id, checkpoint_id)
return self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)
def list_checkpoints(self, notebook_id):
def list_checkpoints(self, notebook_name, notebook_path=None):
"""list the checkpoints for a given notebook
This notebook manager currently only supports one checkpoint per notebook.
"""
checkpoint_id = u"checkpoint"
path = self.get_checkpoint_path(notebook_id, checkpoint_id)
checkpoint_id = "checkpoint"
path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
if not os.path.exists(path):
return []
else:
return [self.get_checkpoint_info(notebook_id, checkpoint_id)]
return [self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)]
def restore_checkpoint(self, notebook_id, checkpoint_id):
def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
"""restore a notebook to a checkpointed state"""
self.log.info("restoring Notebook %s from checkpoint %s", notebook_id, checkpoint_id)
nb_path = self.get_path(notebook_id)
cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
self.log.info("restoring Notebook %s from checkpoint %s", notebook_name, checkpoint_id)
nb_path = self.get_path(notebook_name, notebook_path)
cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
if not os.path.isfile(cp_path):
self.log.debug("checkpoint file does not exist: %s", cp_path)
raise web.HTTPError(404,
u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
)
# ensure notebook is readable (never restore from an unreadable notebook)
last_modified, nb = self.read_notebook_object_from_path(cp_path)
shutil.copy2(cp_path, nb_path)
self.log.debug("copying %s -> %s", cp_path, nb_path)
def delete_checkpoint(self, notebook_id, checkpoint_id):
def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
"""delete a notebook's checkpoint"""
path = self.get_checkpoint_path(notebook_id, checkpoint_id)
path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
if not os.path.isfile(path):
raise web.HTTPError(404,
u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
)
self.log.debug("unlinking %s", path)
os.unlink(path)

@ -28,29 +28,34 @@ from ...base.handlers import IPythonHandler
# Notebook web service handlers
#-----------------------------------------------------------------------------
class NotebookRootHandler(IPythonHandler):
@web.authenticated
def get(self):
nbm = self.notebook_manager
km = self.kernel_manager
files = nbm.list_notebooks()
for f in files :
f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
self.finish(jsonapi.dumps(files))
notebook_names = nbm.list_notebooks("")
notebooks = []
for name in notebook_names:
model = nbm.notebook_model(name)
notebooks.append(model)
self.finish(jsonapi.dumps(notebooks))
@web.authenticated
def post(self):
nbm = self.notebook_manager
body = self.request.body.strip()
format = self.get_argument('format', default='json')
name = self.get_argument('name', default=None)
if body:
notebook_id = nbm.save_new_notebook(body, name=name, format=format)
else:
notebook_id = nbm.new_notebook()
self.set_header('Location', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
self.finish(jsonapi.dumps(notebook_id))
notebook_name = nbm.new_notebook()
model = nbm.notebook_model(notebook_name)
self.set_header('Location', '{0}api/notebooks/{1}'.format(self.base_project_url, notebook_name))
self.finish(jsonapi.dumps(model))
class NotebookRootRedirect(IPythonHandler):
@authenticate_unless_readonly
def get(self):
self.redirect("/api/notebooks")
class NotebookHandler(IPythonHandler):
@ -58,32 +63,62 @@ class NotebookHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
@web.authenticated
def get(self, notebook_id):
def get(self, notebook_path):
nbm = self.notebook_manager
format = self.get_argument('format', default='json')
last_mod, name, data = nbm.get_notebook(notebook_id, format)
name, path = nbm.named_notebook_path(notebook_path)
if format == u'json':
self.set_header('Content-Type', 'application/json')
self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
elif format == u'py':
self.set_header('Content-Type', 'application/x-python')
self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
self.set_header('Last-Modified', last_mod)
self.finish(data)
if name == None:
notebook_names = nbm.list_notebooks(path)
notebooks = []
for name in notebook_names:
model = nbm.notebook_model(name,path)
notebooks.append(model)
self.finish(jsonapi.dumps(notebooks))
else:
format = self.get_argument('format', default='json')
model = nbm.notebook_model(name,path)
data, name = nbm.get_notebook(model, format)
if format == u'json':
self.set_header('Content-Type', 'application/json')
self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
elif format == u'py':
self.set_header('Content-Type', 'application/x-python')
self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
#self.set_header('Last-Modified', last_mod)
self.finish(jsonapi.dumps(model))
@web.authenticated
def put(self, notebook_id):
def put(self, notebook_path):
nbm = self.notebook_manager
format = self.get_argument('format', default='json')
name = self.get_argument('name', default=None)
nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
self.set_status(204)
self.finish()
notebook_name, notebook_path = nbm.named_notebook_path(notebook_path)
if notebook_name == None:
body = self.request.body.strip()
format = self.get_argument('format', default='json')
name = self.get_argument('name', default=None)
if body:
notebook_name = nbm.save_new_notebook(body, notebook_path=notebook_path, name=name, format=format)
else:
notebook_name = nbm.new_notebook(notebook_path=notebook_path)
if path==None:
self.set_header('Location', nbm.notebook_dir + '/'+ notebook_name)
else:
self.set_header('Location', nbm.notebook_dir + '/'+ notebook_path + '/' + notebook_name)
model = nbm.notebook_model(notebook_name, notebook_path)
self.finish(jsonapi.dumps(model))
else:
format = self.get_argument('format', default='json')
name = self.get_argument('name', default=None)
nbm.save_notebook(self.request.body, notebook_path=notebook_path, name=name, format=format)
model = nbm.notebook_model(notebook_name, notebook_path)
self.set_status(204)
self.finish(jsonapi.dumps(model))
@web.authenticated
def delete(self, notebook_id):
self.notebook_manager.delete_notebook(notebook_id)
def delete(self, notebook_path):
nbm = self.notebook_manager
name, path = nbm.named_notebook_path(notebook_path)
self.notebook_manager.delete_notebook(name, path)
self.set_status(204)
self.finish()
@ -93,23 +128,29 @@ class NotebookCheckpointsHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET', 'POST')
@web.authenticated
def get(self, notebook_id):
def get(self, notebook_path):
"""get lists checkpoints for a notebook"""
nbm = self.notebook_manager
checkpoints = nbm.list_checkpoints(notebook_id)
name, path = nbm.named_notebook_path(notebook_path)
checkpoints = nbm.list_checkpoints(name, path)
data = jsonapi.dumps(checkpoints, default=date_default)
self.finish(data)
@web.authenticated
def post(self, notebook_id):
def post(self, notebook_path):
"""post creates a new checkpoint"""
nbm = self.notebook_manager
checkpoint = nbm.create_checkpoint(notebook_id)
name, path = nbm.named_notebook_path(notebook_path)
checkpoint = nbm.create_checkpoint(name, path)
data = jsonapi.dumps(checkpoint, default=date_default)
self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
self.base_project_url, notebook_id, checkpoint['checkpoint_id']
))
if path == None:
self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
self.base_project_url, name, checkpoint['checkpoint_id']
))
else:
self.set_header('Location', '{0}notebooks/{1}/{2}/checkpoints/{3}'.format(
self.base_project_url, path, name, checkpoint['checkpoint_id']
))
self.finish(data)
@ -118,37 +159,38 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
SUPPORTED_METHODS = ('POST', 'DELETE')
@web.authenticated
def post(self, notebook_id, checkpoint_id):
def post(self, notebook_path, checkpoint_id):
"""post restores a notebook from a checkpoint"""
nbm = self.notebook_manager
nbm.restore_checkpoint(notebook_id, checkpoint_id)
name, path = nbm.named_notebook_path(notebook_path)
nbm.restore_checkpoint(name, checkpoint_id, path)
self.set_status(204)
self.finish()
@web.authenticated
def delete(self, notebook_id, checkpoint_id):
def delete(self, notebook_path, checkpoint_id):
"""delete clears a checkpoint for a given notebook"""
nbm = self.notebook_manager
nbm.delte_checkpoint(notebook_id, checkpoint_id)
name, path = nbm.named_notebook_path(notebook_path)
nbm.delete_checkpoint(name, checkpoint_id, path)
self.set_status(204)
self.finish()
#-----------------------------------------------------------------------------
# URL to handler mappings
#-----------------------------------------------------------------------------
_notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
_notebook_path_regex = r"(?P<notebook_path>.+)"
_checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
default_handlers = [
(r"/notebooks", NotebookRootHandler),
(r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
(r"/notebooks/%s/checkpoints" % _notebook_id_regex, NotebookCheckpointsHandler),
(r"/notebooks/%s/checkpoints/%s" % (_notebook_id_regex, _checkpoint_id_regex),
ModifyNotebookCheckpointsHandler
),
(r"api/notebooks/%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
(r"api/notebooks/%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
ModifyNotebookCheckpointsHandler),
(r"api/notebooks/%s" % _notebook_path_regex, NotebookHandler),
(r"api/notebooks/", NotebookRootRedirect),
(r"api/notebooks", NotebookRootHandler),
]

@ -38,14 +38,37 @@ class NotebookManager(LoggingConfigurable):
# Right now we use this attribute in a number of different places and
# we are going to have to disentangle all of this.
notebook_dir = Unicode(os.getcwdu(), config=True, help="""
The directory to use for notebooks.
""")
def _notebook_dir_changed(self, name, old, new):
The directory to use for notebooks.
""")
def named_notebook_path(self, notebook_path):
l = len(notebook_path)
names = notebook_path.split('/')
if len(names) > 1:
name = names[len(names)-1]
if name[(len(name)-6):(len(name))] == ".ipynb":
name = name
path = notebook_path[0:l-len(name)-1]+'/'
else:
name = None
path = notebook_path+'/'
else:
name = names[0]
if name[(len(name)-6):(len(name))] == ".ipynb":
name = name
path = None
else:
name = None
path = notebook_path+'/'
return name, path
def _notebook_dir_changed(self, new):
"""do a bit of validation of the notebook dir"""
if not os.path.isabs(new):
# If we receive a non-absolute path, make it absolute.
abs_new = os.path.abspath(new)
self.notebook_dir = abs_new
#self.notebook_dir = os.path.dirname(abs_new)
return
if os.path.exists(new) and not os.path.isdir(new):
raise TraitError("notebook dir %r is not a directory" % new)
@ -55,20 +78,18 @@ class NotebookManager(LoggingConfigurable):
os.mkdir(new)
except:
raise TraitError("Couldn't create notebook dir %r" % new)
allowed_formats = List([u'json',u'py'])
# Map notebook_ids to notebook names
mapping = Dict()
def load_notebook_names(self):
def load_notebook_names(self, path):
"""Load the notebook names into memory.
This should be called once immediately after the notebook manager
is created to load the existing notebooks into the mapping in
memory.
"""
self.list_notebooks()
self.list_notebooks(path)
def list_notebooks(self):
"""List all notebooks.
@ -84,52 +105,37 @@ class NotebookManager(LoggingConfigurable):
raise NotImplementedError('must be implemented in a subclass')
def new_notebook_id(self, name):
"""Generate a new notebook_id for a name and store its mapping."""
# TODO: the following will give stable urls for notebooks, but unless
# the notebooks are immediately redirected to their new urls when their
# filemname changes, nasty inconsistencies result. So for now it's
# disabled and instead we use a random uuid4() call. But we leave the
# logic here so that we can later reactivate it, whhen the necessary
# url redirection code is written.
#notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
# 'file://'+self.get_path_by_name(name).encode('utf-8')))
notebook_id = unicode(uuid.uuid4())
self.mapping[notebook_id] = name
return notebook_id
def delete_notebook_id(self, notebook_id):
"""Delete a notebook's id in the mapping.
This doesn't delete the actual notebook, only its entry in the mapping.
"""
del self.mapping[notebook_id]
def notebook_exists(self, notebook_id):
def notebook_exists(self, notebook_name):
"""Does a notebook exist?"""
return notebook_id in self.mapping
def get_notebook(self, notebook_id, format=u'json'):
"""Get the representation of a notebook in format by notebook_id."""
return notebook_name in self.mapping
def notebook_model(self, notebook_name, notebook_path=None):
""" Creates the standard notebook model """
last_modified, content = self.read_notebook_object(notebook_name, notebook_path)
model = {"notebook_name": notebook_name,
"notebook_path": notebook_path,
"content": content}
return model
def get_notebook(self, body, format=u'json'):
"""Get the representation of a notebook in format by notebook_name."""
format = unicode(format)
if format not in self.allowed_formats:
raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
last_modified, nb = self.read_notebook_object(notebook_id)
kwargs = {}
if format == 'json':
# don't split lines for sending over the wire, because it
# should match the Python in-memory format.
kwargs['split_lines'] = False
data = current.writes(nb, format, **kwargs)
name = nb.metadata.get('name','notebook')
return last_modified, name, data
representation = current.writes(body, format, **kwargs)
name = body['content']['metadata']['name']
return representation, name
def read_notebook_object(self, notebook_id):
def read_notebook_object(self, notebook_name, notebook_path):
"""Get the object representation of a notebook by notebook_id."""
raise NotImplementedError('must be implemented in a subclass')
def save_new_notebook(self, data, name=None, format=u'json'):
def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
"""Save a new notebook and return its notebook_id.
If a name is passed in, it overrides any values in the notebook data
@ -150,10 +156,10 @@ class NotebookManager(LoggingConfigurable):
raise web.HTTPError(400, u'Missing notebook name')
nb.metadata.name = name
notebook_id = self.write_notebook_object(nb)
return notebook_id
notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
return notebook_name
def save_notebook(self, notebook_id, data, name=None, format=u'json'):
def save_notebook(self, data, notebook_path=None, name=None, format=u'json'):
"""Save an existing notebook by notebook_id."""
if format not in self.allowed_formats:
raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
@ -165,18 +171,18 @@ class NotebookManager(LoggingConfigurable):
if name is not None:
nb.metadata.name = name
self.write_notebook_object(nb, notebook_id)
self.write_notebook_object(nb, name, notebook_path)
def write_notebook_object(self, nb, notebook_id=None):
"""Write a notebook object and return its notebook_id.
def write_notebook_object(self, nb, notebook_name=None, notebook_path=None):
"""Write a notebook object and return its notebook_name.
If notebook_id is None, this method should create a new notebook_id.
If notebook_id is not None, this method should check to make sure it
If notebook_name is None, this method should create a new notebook_name.
If notebook_name is not None, this method should check to make sure it
exists and is valid.
"""
raise NotImplementedError('must be implemented in a subclass')
def delete_notebook(self, notebook_id):
def delete_notebook(self, notebook_name, notebook_path):
"""Delete notebook by notebook_id."""
raise NotImplementedError('must be implemented in a subclass')
@ -189,41 +195,41 @@ class NotebookManager(LoggingConfigurable):
"""
return name
def new_notebook(self):
def new_notebook(self, notebook_path=None):
"""Create a new notebook and return its notebook_id."""
name = self.increment_filename('Untitled')
name = self.increment_filename('Untitled', notebook_path)
metadata = current.new_metadata(name=name)
nb = current.new_notebook(metadata=metadata)
notebook_id = self.write_notebook_object(nb)
return notebook_id
notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
return notebook_name
def copy_notebook(self, notebook_id):
def copy_notebook(self, name, path):
"""Copy an existing notebook and return its notebook_id."""
last_mod, nb = self.read_notebook_object(notebook_id)
last_mod, nb = self.read_notebook_object(name, path)
name = nb.metadata.name + '-Copy'
name = self.increment_filename(name)
name = self.increment_filename(name, path)
nb.metadata.name = name
notebook_id = self.write_notebook_object(nb)
return notebook_id
notebook_name = self.write_notebook_object(nb, notebook_path = path)
return notebook_name
# Checkpoint-related
def create_checkpoint(self, notebook_id):
def create_checkpoint(self, notebook_name, notebook_path=None):
"""Create a checkpoint of the current state of a notebook
Returns a checkpoint_id for the new checkpoint.
"""
raise NotImplementedError("must be implemented in a subclass")
def list_checkpoints(self, notebook_id):
def list_checkpoints(self, notebook_name, notebook_path=None):
"""Return a list of checkpoints for a given notebook"""
return []
def restore_checkpoint(self, notebook_id, checkpoint_id):
def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
"""Restore a notebook from one of its checkpoints"""
raise NotImplementedError("must be implemented in a subclass")
def delete_checkpoint(self, notebook_id, checkpoint_id):
def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
"""delete a checkpoint for a notebook"""
raise NotImplementedError("must be implemented in a subclass")
@ -232,4 +238,3 @@ class NotebookManager(LoggingConfigurable):
def info_string(self):
return "Serving notebooks"

Loading…
Cancel
Save