From b77e9cb4ac363ac926dc34dbd71ee478dff43b44 2013-10-17 21:07:49 From: Zachary Sailer Date: 2013-10-17 21:07:49 Subject: [PATCH] manual rebase notebooks web services --- diff --git a/IPython/html/services/notebooks/filenbmanager.py b/IPython/html/services/notebooks/filenbmanager.py index ddb6835..a9f2480 100644 --- a/IPython/html/services/notebooks/filenbmanager.py +++ b/IPython/html/services/notebooks/filenbmanager.py @@ -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. 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) diff --git a/IPython/html/services/notebooks/handlers.py b/IPython/html/services/notebooks/handlers.py index 30d5cad..e56e09d 100644 --- a/IPython/html/services/notebooks/handlers.py +++ b/IPython/html/services/notebooks/handlers.py @@ -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\w+-\w+-\w+-\w+-\w+)" +_notebook_path_regex = r"(?P.+)" _checkpoint_id_regex = r"(?P[\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), ] diff --git a/IPython/html/services/notebooks/nbmanager.py b/IPython/html/services/notebooks/nbmanager.py index 0a9321f..f6702be 100644 --- a/IPython/html/services/notebooks/nbmanager.py +++ b/IPython/html/services/notebooks/nbmanager.py @@ -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" -