mv services/notebooks services/contents

This commit is contained in:
MinRK 2014-05-30 12:39:11 -07:00
parent 6d292040b4
commit b73aa2b9f9
5 changed files with 112 additions and 114 deletions

View File

@ -27,10 +27,10 @@ def sort_key(item):
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
class FileNotebookManager(NotebookManager): class FileNotebookManager(NotebookManager):
save_script = Bool(False, config=True, save_script = Bool(False, config=True,
help="""Automatically create a Python script when saving the notebook. help="""Automatically create a Python script when saving the notebook.
For easier use of import, %run and %load across notebooks, a For easier use of import, %run and %load across notebooks, a
<notebook-name>.py script will be created next to any <notebook-name>.py script will be created next to any
<notebook-name>.ipynb on each save. This can also be set with the <notebook-name>.ipynb on each save. This can also be set with the
@ -38,7 +38,7 @@ class FileNotebookManager(NotebookManager):
""" """
) )
notebook_dir = Unicode(getcwd(), config=True) notebook_dir = Unicode(getcwd(), config=True)
def _notebook_dir_changed(self, name, old, new): def _notebook_dir_changed(self, name, old, new):
"""Do a bit of validation of the notebook dir.""" """Do a bit of validation of the notebook dir."""
if not os.path.isabs(new): if not os.path.isabs(new):
@ -47,19 +47,19 @@ class FileNotebookManager(NotebookManager):
return return
if not os.path.exists(new) or not os.path.isdir(new): if not os.path.exists(new) or not os.path.isdir(new):
raise TraitError("notebook dir %r is not a directory" % new) raise TraitError("notebook dir %r is not a directory" % new)
checkpoint_dir = Unicode('.ipynb_checkpoints', config=True, checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
help="""The directory name in which to keep notebook checkpoints help="""The directory name in which to keep notebook checkpoints
This is a path relative to the notebook's own directory. This is a path relative to the notebook's own directory.
By default, it is .ipynb_checkpoints By default, it is .ipynb_checkpoints
""" """
) )
def _copy(self, src, dest): def _copy(self, src, dest):
"""copy src to dest """copy src to dest
like shutil.copy2, but log errors in copystat like shutil.copy2, but log errors in copystat
""" """
shutil.copyfile(src, dest) shutil.copyfile(src, dest)
@ -67,7 +67,7 @@ class FileNotebookManager(NotebookManager):
shutil.copystat(src, dest) shutil.copystat(src, dest)
except OSError as e: except OSError as e:
self.log.debug("copystat on %s failed", dest, exc_info=True) self.log.debug("copystat on %s failed", dest, exc_info=True)
def get_notebook_names(self, path=''): def get_notebook_names(self, path=''):
"""List all notebook names in the notebook dir and path.""" """List all notebook names in the notebook dir and path."""
path = path.strip('/') path = path.strip('/')
@ -80,13 +80,13 @@ class FileNotebookManager(NotebookManager):
def path_exists(self, path): def path_exists(self, path):
"""Does the API-style path (directory) actually exist? """Does the API-style path (directory) actually exist?
Parameters Parameters
---------- ----------
path : string path : string
The path to check. This is an API path (`/` separated, The path to check. This is an API path (`/` separated,
relative to base notebook-dir). relative to base notebook-dir).
Returns Returns
------- -------
exists : bool exists : bool
@ -98,18 +98,18 @@ class FileNotebookManager(NotebookManager):
def is_hidden(self, path): def is_hidden(self, path):
"""Does the API style path correspond to a hidden directory or file? """Does the API style path correspond to a hidden directory or file?
Parameters Parameters
---------- ----------
path : string path : string
The path to check. This is an API path (`/` separated, The path to check. This is an API path (`/` separated,
relative to base notebook-dir). relative to base notebook-dir).
Returns Returns
------- -------
exists : bool exists : bool
Whether the path is hidden. Whether the path is hidden.
""" """
path = path.strip('/') path = path.strip('/')
os_path = self._get_os_path(path=path) os_path = self._get_os_path(path=path)
@ -204,13 +204,13 @@ class FileNotebookManager(NotebookManager):
def list_notebooks(self, path): def list_notebooks(self, path):
"""Returns a list of dictionaries that are the standard model """Returns a list of dictionaries that are the standard model
for all notebooks in the relative 'path'. for all notebooks in the relative 'path'.
Parameters Parameters
---------- ----------
path : str path : str
the URL path that describes the relative path for the the URL path that describes the relative path for the
listed notebooks listed notebooks
Returns Returns
------- -------
notebooks : list of dicts notebooks : list of dicts
@ -225,7 +225,7 @@ class FileNotebookManager(NotebookManager):
def get_notebook(self, name, path='', content=True): def get_notebook(self, name, path='', content=True):
""" Takes a path and name for a notebook and returns its model """ Takes a path and name for a notebook and returns its model
Parameters Parameters
---------- ----------
name : str name : str
@ -233,11 +233,11 @@ class FileNotebookManager(NotebookManager):
path : str path : str
the URL path that describes the relative path for the URL path that describes the relative path for
the notebook the notebook
Returns Returns
------- -------
model : dict model : dict
the notebook model. If contents=True, returns the 'contents' the notebook model. If contents=True, returns the 'contents'
dict in the model as well. dict in the model as well.
""" """
path = path.strip('/') path = path.strip('/')
@ -284,9 +284,9 @@ class FileNotebookManager(NotebookManager):
# Save the notebook file # Save the notebook file
os_path = self._get_os_path(new_name, new_path) os_path = self._get_os_path(new_name, new_path)
nb = current.to_notebook_json(model['content']) nb = current.to_notebook_json(model['content'])
self.check_and_sign(nb, new_name, new_path) self.check_and_sign(nb, new_name, new_path)
if 'name' in nb['metadata']: if 'name' in nb['metadata']:
nb['metadata']['name'] = u'' nb['metadata']['name'] = u''
try: try:
@ -325,7 +325,7 @@ class FileNotebookManager(NotebookManager):
os_path = self._get_os_path(name, path) os_path = self._get_os_path(name, path)
if not os.path.isfile(os_path): if not os.path.isfile(os_path):
raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path) raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
# clear checkpoints # clear checkpoints
for checkpoint in self.list_checkpoints(name, path): for checkpoint in self.list_checkpoints(name, path):
checkpoint_id = checkpoint['id'] checkpoint_id = checkpoint['id']
@ -333,7 +333,7 @@ class FileNotebookManager(NotebookManager):
if os.path.isfile(cp_path): if os.path.isfile(cp_path):
self.log.debug("Unlinking checkpoint %s", cp_path) self.log.debug("Unlinking checkpoint %s", cp_path)
os.unlink(cp_path) os.unlink(cp_path)
self.log.debug("Unlinking notebook %s", os_path) self.log.debug("Unlinking notebook %s", os_path)
os.unlink(os_path) os.unlink(os_path)
@ -343,7 +343,7 @@ class FileNotebookManager(NotebookManager):
new_path = new_path.strip('/') new_path = new_path.strip('/')
if new_name == old_name and new_path == old_path: if new_name == old_name and new_path == old_path:
return return
new_os_path = self._get_os_path(new_name, new_path) new_os_path = self._get_os_path(new_name, new_path)
old_os_path = self._get_os_path(old_name, old_path) old_os_path = self._get_os_path(old_name, old_path)
@ -375,9 +375,9 @@ class FileNotebookManager(NotebookManager):
# Move the .py script # Move the .py script
if self.save_script: if self.save_script:
shutil.move(old_py_path, new_py_path) shutil.move(old_py_path, new_py_path)
# Checkpoint-related utilities # Checkpoint-related utilities
def get_checkpoint_path(self, checkpoint_id, name, path=''): def get_checkpoint_path(self, checkpoint_id, name, path=''):
"""find the path to a checkpoint""" """find the path to a checkpoint"""
path = path.strip('/') path = path.strip('/')
@ -404,9 +404,9 @@ class FileNotebookManager(NotebookManager):
last_modified = last_modified, last_modified = last_modified,
) )
return info return info
# public checkpoint API # public checkpoint API
def create_checkpoint(self, name, path=''): def create_checkpoint(self, name, path=''):
"""Create a checkpoint from the current state of a notebook""" """Create a checkpoint from the current state of a notebook"""
path = path.strip('/') path = path.strip('/')
@ -416,13 +416,13 @@ class FileNotebookManager(NotebookManager):
cp_path = self.get_checkpoint_path(checkpoint_id, name, path) cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
self.log.debug("creating checkpoint for notebook %s", name) self.log.debug("creating checkpoint for notebook %s", name)
self._copy(nb_path, cp_path) self._copy(nb_path, cp_path)
# return the checkpoint info # return the checkpoint info
return self.get_checkpoint_model(checkpoint_id, name, path) return self.get_checkpoint_model(checkpoint_id, name, path)
def list_checkpoints(self, name, path=''): def list_checkpoints(self, name, path=''):
"""list the checkpoints for a given notebook """list the checkpoints for a given notebook
This notebook manager currently only supports one checkpoint per notebook. This notebook manager currently only supports one checkpoint per notebook.
""" """
path = path.strip('/') path = path.strip('/')
@ -432,8 +432,8 @@ class FileNotebookManager(NotebookManager):
return [] return []
else: else:
return [self.get_checkpoint_model(checkpoint_id, name, path)] return [self.get_checkpoint_model(checkpoint_id, name, path)]
def restore_checkpoint(self, checkpoint_id, name, path=''): def restore_checkpoint(self, checkpoint_id, name, path=''):
"""restore a notebook to a checkpointed state""" """restore a notebook to a checkpointed state"""
path = path.strip('/') path = path.strip('/')
@ -450,7 +450,7 @@ class FileNotebookManager(NotebookManager):
current.read(f, u'json') current.read(f, u'json')
self._copy(cp_path, nb_path) self._copy(cp_path, nb_path)
self.log.debug("copying %s -> %s", cp_path, nb_path) self.log.debug("copying %s -> %s", cp_path, nb_path)
def delete_checkpoint(self, checkpoint_id, name, path=''): def delete_checkpoint(self, checkpoint_id, name, path=''):
"""delete a notebook's checkpoint""" """delete a notebook's checkpoint"""
path = path.strip('/') path = path.strip('/')
@ -461,7 +461,7 @@ class FileNotebookManager(NotebookManager):
) )
self.log.debug("unlinking %s", cp_path) self.log.debug("unlinking %s", cp_path)
os.unlink(cp_path) os.unlink(cp_path)
def info_string(self): def info_string(self):
return "Serving notebooks from local directory: %s" % self.notebook_dir return "Serving notebooks from local directory: %s" % self.notebook_dir

View File

@ -38,7 +38,7 @@ class NotebookHandler(IPythonHandler):
def notebook_location(self, name, path=''): def notebook_location(self, name, path=''):
"""Return the full URL location of a notebook based. """Return the full URL location of a notebook based.
Parameters Parameters
---------- ----------
name : unicode name : unicode
@ -57,7 +57,7 @@ class NotebookHandler(IPythonHandler):
self.set_header('Location', location) self.set_header('Location', location)
self.set_header('Last-Modified', model['last_modified']) self.set_header('Last-Modified', model['last_modified'])
self.finish(json.dumps(model, default=date_default)) self.finish(json.dumps(model, default=date_default))
@web.authenticated @web.authenticated
@json_errors @json_errors
def get(self, path='', name=None): def get(self, path='', name=None):
@ -99,10 +99,10 @@ class NotebookHandler(IPythonHandler):
raise web.HTTPError(400, u'JSON body missing') raise web.HTTPError(400, u'JSON body missing')
model = nbm.update_notebook(model, name, path) model = nbm.update_notebook(model, name, path)
self._finish_model(model) self._finish_model(model)
def _copy_notebook(self, copy_from, path, copy_to=None): def _copy_notebook(self, copy_from, path, copy_to=None):
"""Copy a notebook in path, optionally specifying the new name. """Copy a notebook in path, optionally specifying the new name.
Only support copying within the same directory. Only support copying within the same directory.
""" """
self.log.info(u"Copying notebook from %s/%s to %s/%s", self.log.info(u"Copying notebook from %s/%s to %s/%s",
@ -112,23 +112,23 @@ class NotebookHandler(IPythonHandler):
model = self.notebook_manager.copy_notebook(copy_from, copy_to, path) model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
self.set_status(201) self.set_status(201)
self._finish_model(model) self._finish_model(model)
def _upload_notebook(self, model, path, name=None): def _upload_notebook(self, model, path, name=None):
"""Upload a notebook """Upload a notebook
If name specified, create it in path/name. If name specified, create it in path/name.
""" """
self.log.info(u"Uploading notebook to %s/%s", path, name or '') self.log.info(u"Uploading notebook to %s/%s", path, name or '')
if name: if name:
model['name'] = name model['name'] = name
model = self.notebook_manager.create_notebook(model, path) model = self.notebook_manager.create_notebook(model, path)
self.set_status(201) self.set_status(201)
self._finish_model(model) self._finish_model(model)
def _create_empty_notebook(self, path, name=None): def _create_empty_notebook(self, path, name=None):
"""Create an empty notebook in path """Create an empty notebook in path
If name specified, create it in path/name. If name specified, create it in path/name.
""" """
self.log.info(u"Creating new notebook in %s/%s", path, name or '') self.log.info(u"Creating new notebook in %s/%s", path, name or '')
@ -138,7 +138,7 @@ class NotebookHandler(IPythonHandler):
model = self.notebook_manager.create_notebook(model, path=path) model = self.notebook_manager.create_notebook(model, path=path)
self.set_status(201) self.set_status(201)
self._finish_model(model) self._finish_model(model)
def _save_notebook(self, model, path, name): def _save_notebook(self, model, path, name):
"""Save an existing notebook.""" """Save an existing notebook."""
self.log.info(u"Saving notebook at %s/%s", path, name) self.log.info(u"Saving notebook at %s/%s", path, name)
@ -149,26 +149,26 @@ class NotebookHandler(IPythonHandler):
else: else:
location = False location = False
self._finish_model(model, location) self._finish_model(model, location)
@web.authenticated @web.authenticated
@json_errors @json_errors
def post(self, path='', name=None): def post(self, path='', name=None):
"""Create a new notebook in the specified path. """Create a new notebook in the specified path.
POST creates new notebooks. The server always decides on the notebook name. POST creates new notebooks. The server always decides on the notebook name.
POST /api/notebooks/path POST /api/notebooks/path
New untitled notebook in path. If content specified, upload a New untitled notebook in path. If content specified, upload a
notebook, otherwise start empty. notebook, otherwise start empty.
POST /api/notebooks/path?copy=OtherNotebook.ipynb POST /api/notebooks/path?copy=OtherNotebook.ipynb
New copy of OtherNotebook in path New copy of OtherNotebook in path
""" """
if name is not None: if name is not None:
raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.") raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
model = self.get_json_body() model = self.get_json_body()
if model is not None: if model is not None:
copy_from = model.get('copy_from') copy_from = model.get('copy_from')
if copy_from: if copy_from:
@ -184,10 +184,10 @@ class NotebookHandler(IPythonHandler):
@json_errors @json_errors
def put(self, path='', name=None): def put(self, path='', name=None):
"""Saves the notebook in the location specified by name and path. """Saves the notebook in the location specified by name and path.
PUT is very similar to POST, but the requester specifies the name, PUT is very similar to POST, but the requester specifies the name,
whereas with POST, the server picks the name. whereas with POST, the server picks the name.
PUT /api/notebooks/path/Name.ipynb PUT /api/notebooks/path/Name.ipynb
Save notebook at ``path/Name.ipynb``. Notebook structure is specified Save notebook at ``path/Name.ipynb``. Notebook structure is specified
in `content` key of JSON request body. If content is not specified, in `content` key of JSON request body. If content is not specified,
@ -197,7 +197,7 @@ class NotebookHandler(IPythonHandler):
""" """
if name is None: if name is None:
raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.") raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
model = self.get_json_body() model = self.get_json_body()
if model: if model:
copy_from = model.get('copy_from') copy_from = model.get('copy_from')
@ -223,9 +223,9 @@ class NotebookHandler(IPythonHandler):
class NotebookCheckpointsHandler(IPythonHandler): class NotebookCheckpointsHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET', 'POST') SUPPORTED_METHODS = ('GET', 'POST')
@web.authenticated @web.authenticated
@json_errors @json_errors
def get(self, path='', name=None): def get(self, path='', name=None):
@ -234,7 +234,7 @@ class NotebookCheckpointsHandler(IPythonHandler):
checkpoints = nbm.list_checkpoints(name, path) checkpoints = nbm.list_checkpoints(name, path)
data = json.dumps(checkpoints, default=date_default) data = json.dumps(checkpoints, default=date_default)
self.finish(data) self.finish(data)
@web.authenticated @web.authenticated
@json_errors @json_errors
def post(self, path='', name=None): def post(self, path='', name=None):
@ -250,9 +250,9 @@ class NotebookCheckpointsHandler(IPythonHandler):
class ModifyNotebookCheckpointsHandler(IPythonHandler): class ModifyNotebookCheckpointsHandler(IPythonHandler):
SUPPORTED_METHODS = ('POST', 'DELETE') SUPPORTED_METHODS = ('POST', 'DELETE')
@web.authenticated @web.authenticated
@json_errors @json_errors
def post(self, path, name, checkpoint_id): def post(self, path, name, checkpoint_id):
@ -261,7 +261,7 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
nbm.restore_checkpoint(checkpoint_id, name, path) nbm.restore_checkpoint(checkpoint_id, name, path)
self.set_status(204) self.set_status(204)
self.finish() self.finish()
@web.authenticated @web.authenticated
@json_errors @json_errors
def delete(self, path, name, checkpoint_id): def delete(self, path, name, checkpoint_id):
@ -270,7 +270,7 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
nbm.delete_checkpoint(checkpoint_id, name, path) nbm.delete_checkpoint(checkpoint_id, name, path)
self.set_status(204) self.set_status(204)
self.finish() self.finish()
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# URL to handler mappings # URL to handler mappings
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
@ -285,4 +285,3 @@ default_handlers = [
(r"/api/notebooks%s" % notebook_path_regex, NotebookHandler), (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
(r"/api/notebooks%s" % path_regex, NotebookHandler), (r"/api/notebooks%s" % path_regex, NotebookHandler),
] ]

View File

@ -32,11 +32,11 @@ from IPython.utils.traitlets import Instance, Unicode, List
class NotebookManager(LoggingConfigurable): class NotebookManager(LoggingConfigurable):
filename_ext = Unicode(u'.ipynb') filename_ext = Unicode(u'.ipynb')
notary = Instance(sign.NotebookNotary) notary = Instance(sign.NotebookNotary)
def _notary_default(self): def _notary_default(self):
return sign.NotebookNotary(parent=self) return sign.NotebookNotary(parent=self)
hide_globs = List(Unicode, [u'__pycache__'], config=True, help=""" hide_globs = List(Unicode, [u'__pycache__'], config=True, help="""
Glob patterns to hide in file and directory listings. Glob patterns to hide in file and directory listings.
""") """)
@ -46,14 +46,14 @@ class NotebookManager(LoggingConfigurable):
def path_exists(self, path): def path_exists(self, path):
"""Does the API-style path (directory) actually exist? """Does the API-style path (directory) actually exist?
Override this method in subclasses. Override this method in subclasses.
Parameters Parameters
---------- ----------
path : string path : string
The path to check The path to check
Returns Returns
------- -------
exists : bool exists : bool
@ -63,18 +63,18 @@ class NotebookManager(LoggingConfigurable):
def is_hidden(self, path): def is_hidden(self, path):
"""Does the API style path correspond to a hidden directory or file? """Does the API style path correspond to a hidden directory or file?
Parameters Parameters
---------- ----------
path : string path : string
The path to check. This is an API path (`/` separated, The path to check. This is an API path (`/` separated,
relative to base notebook-dir). relative to base notebook-dir).
Returns Returns
------- -------
exists : bool exists : bool
Whether the path is hidden. Whether the path is hidden.
""" """
raise NotImplementedError raise NotImplementedError
@ -104,7 +104,7 @@ class NotebookManager(LoggingConfigurable):
# no longer listed by the notebook web service. # no longer listed by the notebook web service.
def get_dir_model(self, name, path=''): def get_dir_model(self, name, path=''):
"""Get the directory model given a directory name and its API style path. """Get the directory model given a directory name and its API style path.
The keys in the model should be: The keys in the model should be:
* name * name
* path * path
@ -145,15 +145,15 @@ class NotebookManager(LoggingConfigurable):
def create_checkpoint(self, name, path=''): def create_checkpoint(self, name, path=''):
"""Create a checkpoint of the current state of a notebook """Create a checkpoint of the current state of a notebook
Returns a checkpoint_id for the new checkpoint. Returns a checkpoint_id for the new checkpoint.
""" """
raise NotImplementedError("must be implemented in a subclass") raise NotImplementedError("must be implemented in a subclass")
def list_checkpoints(self, name, path=''): def list_checkpoints(self, name, path=''):
"""Return a list of checkpoints for a given notebook""" """Return a list of checkpoints for a given notebook"""
return [] return []
def restore_checkpoint(self, checkpoint_id, name, path=''): def restore_checkpoint(self, checkpoint_id, name, path=''):
"""Restore a notebook from one of its checkpoints""" """Restore a notebook from one of its checkpoints"""
raise NotImplementedError("must be implemented in a subclass") raise NotImplementedError("must be implemented in a subclass")
@ -161,7 +161,7 @@ class NotebookManager(LoggingConfigurable):
def delete_checkpoint(self, checkpoint_id, name, path=''): def delete_checkpoint(self, checkpoint_id, name, path=''):
"""delete a checkpoint for a notebook""" """delete a checkpoint for a notebook"""
raise NotImplementedError("must be implemented in a subclass") raise NotImplementedError("must be implemented in a subclass")
def info_string(self): def info_string(self):
return "Serving notebooks" return "Serving notebooks"
@ -174,7 +174,7 @@ class NotebookManager(LoggingConfigurable):
def increment_filename(self, basename, path=''): def increment_filename(self, basename, path=''):
"""Increment a notebook filename without the .ipynb to make it unique. """Increment a notebook filename without the .ipynb to make it unique.
Parameters Parameters
---------- ----------
basename : unicode basename : unicode
@ -206,14 +206,14 @@ class NotebookManager(LoggingConfigurable):
model['content'] = current.new_notebook(metadata=metadata) model['content'] = current.new_notebook(metadata=metadata)
if 'name' not in model: if 'name' not in model:
model['name'] = self.increment_filename('Untitled', path) model['name'] = self.increment_filename('Untitled', path)
model['path'] = path model['path'] = path
model = self.save_notebook(model, model['name'], model['path']) model = self.save_notebook(model, model['name'], model['path'])
return model return model
def copy_notebook(self, from_name, to_name=None, path=''): def copy_notebook(self, from_name, to_name=None, path=''):
"""Copy an existing notebook and return its new model. """Copy an existing notebook and return its new model.
If to_name not specified, increment `from_name-Copy#.ipynb`. If to_name not specified, increment `from_name-Copy#.ipynb`.
""" """
path = path.strip('/') path = path.strip('/')
@ -224,13 +224,13 @@ class NotebookManager(LoggingConfigurable):
model['name'] = to_name model['name'] = to_name
model = self.save_notebook(model, to_name, path) model = self.save_notebook(model, to_name, path)
return model return model
def log_info(self): def log_info(self):
self.log.info(self.info_string()) self.log.info(self.info_string())
def trust_notebook(self, name, path=''): def trust_notebook(self, name, path=''):
"""Explicitly trust a notebook """Explicitly trust a notebook
Parameters Parameters
---------- ----------
name : string name : string
@ -243,12 +243,12 @@ class NotebookManager(LoggingConfigurable):
self.log.warn("Trusting notebook %s/%s", path, name) self.log.warn("Trusting notebook %s/%s", path, name)
self.notary.mark_cells(nb, True) self.notary.mark_cells(nb, True)
self.save_notebook(model, name, path) self.save_notebook(model, name, path)
def check_and_sign(self, nb, name, path=''): def check_and_sign(self, nb, name, path=''):
"""Check for trusted cells, and sign the notebook. """Check for trusted cells, and sign the notebook.
Called as a part of saving notebooks. Called as a part of saving notebooks.
Parameters Parameters
---------- ----------
nb : dict nb : dict
@ -262,12 +262,12 @@ class NotebookManager(LoggingConfigurable):
self.notary.sign(nb) self.notary.sign(nb)
else: else:
self.log.warn("Saving untrusted notebook %s/%s", path, name) self.log.warn("Saving untrusted notebook %s/%s", path, name)
def mark_trusted_cells(self, nb, name, path=''): def mark_trusted_cells(self, nb, name, path=''):
"""Mark cells as trusted if the notebook signature matches. """Mark cells as trusted if the notebook signature matches.
Called as a part of loading notebooks. Called as a part of loading notebooks.
Parameters Parameters
---------- ----------
nb : dict nb : dict

View File

@ -55,7 +55,7 @@ class TestFileNotebookManager(TestCase):
path = fm._get_os_path('test.ipynb', '////') path = fm._get_os_path('test.ipynb', '////')
fs_path = os.path.join(fm.notebook_dir, 'test.ipynb') fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
self.assertEqual(path, fs_path) self.assertEqual(path, fs_path)
def test_checkpoint_subdir(self): def test_checkpoint_subdir(self):
subd = u'sub ∂ir' subd = u'sub ∂ir'
cp_name = 'test-cp.ipynb' cp_name = 'test-cp.ipynb'
@ -68,10 +68,10 @@ class TestFileNotebookManager(TestCase):
self.assertNotEqual(cp_dir, cp_subdir) self.assertNotEqual(cp_dir, cp_subdir)
self.assertEqual(cp_dir, os.path.join(nbdir, fm.checkpoint_dir, cp_name)) self.assertEqual(cp_dir, os.path.join(nbdir, fm.checkpoint_dir, cp_name))
self.assertEqual(cp_subdir, os.path.join(nbdir, subd, fm.checkpoint_dir, cp_name)) self.assertEqual(cp_subdir, os.path.join(nbdir, subd, fm.checkpoint_dir, cp_name))
class TestNotebookManager(TestCase): class TestNotebookManager(TestCase):
def setUp(self): def setUp(self):
self._temp_dir = TemporaryDirectory() self._temp_dir = TemporaryDirectory()
self.td = self._temp_dir.name self.td = self._temp_dir.name
@ -79,10 +79,10 @@ class TestNotebookManager(TestCase):
notebook_dir=self.td, notebook_dir=self.td,
log=logging.getLogger() log=logging.getLogger()
) )
def tearDown(self): def tearDown(self):
self._temp_dir.cleanup() self._temp_dir.cleanup()
def make_dir(self, abs_path, rel_path): def make_dir(self, abs_path, rel_path):
"""make subdirectory, rel_path is the relative path """make subdirectory, rel_path is the relative path
to that directory from the location where the server started""" to that directory from the location where the server started"""
@ -91,27 +91,27 @@ class TestNotebookManager(TestCase):
os.makedirs(os_path) os.makedirs(os_path)
except OSError: except OSError:
print("Directory already exists: %r" % os_path) print("Directory already exists: %r" % os_path)
def add_code_cell(self, nb): def add_code_cell(self, nb):
output = current.new_output("display_data", output_javascript="alert('hi');") output = current.new_output("display_data", output_javascript="alert('hi');")
cell = current.new_code_cell("print('hi')", outputs=[output]) cell = current.new_code_cell("print('hi')", outputs=[output])
if not nb.worksheets: if not nb.worksheets:
nb.worksheets.append(current.new_worksheet()) nb.worksheets.append(current.new_worksheet())
nb.worksheets[0].cells.append(cell) nb.worksheets[0].cells.append(cell)
def new_notebook(self): def new_notebook(self):
nbm = self.notebook_manager nbm = self.notebook_manager
model = nbm.create_notebook() model = nbm.create_notebook()
name = model['name'] name = model['name']
path = model['path'] path = model['path']
full_model = nbm.get_notebook(name, path) full_model = nbm.get_notebook(name, path)
nb = full_model['content'] nb = full_model['content']
self.add_code_cell(nb) self.add_code_cell(nb)
nbm.save_notebook(full_model, name, path) nbm.save_notebook(full_model, name, path)
return nb, name, path return nb, name, path
def test_create_notebook(self): def test_create_notebook(self):
nm = self.notebook_manager nm = self.notebook_manager
# Test in root directory # Test in root directory
@ -158,7 +158,7 @@ class TestNotebookManager(TestCase):
self.assertIn('content', model2) self.assertIn('content', model2)
self.assertEqual(model2['name'], 'Untitled0.ipynb') self.assertEqual(model2['name'], 'Untitled0.ipynb')
self.assertEqual(model2['path'], sub_dir.strip('/')) self.assertEqual(model2['path'], sub_dir.strip('/'))
def test_update_notebook(self): def test_update_notebook(self):
nm = self.notebook_manager nm = self.notebook_manager
# Create a notebook # Create a notebook
@ -184,7 +184,7 @@ class TestNotebookManager(TestCase):
model = nm.create_notebook(None, sub_dir) model = nm.create_notebook(None, sub_dir)
name = model['name'] name = model['name']
path = model['path'] path = model['path']
# Change the name in the model for rename # Change the name in the model for rename
model['name'] = 'test_in_sub.ipynb' model['name'] = 'test_in_sub.ipynb'
model = nm.update_notebook(model, name, path) model = nm.update_notebook(model, name, path)
@ -193,7 +193,7 @@ class TestNotebookManager(TestCase):
self.assertIn('path', model) self.assertIn('path', model)
self.assertEqual(model['name'], 'test_in_sub.ipynb') self.assertEqual(model['name'], 'test_in_sub.ipynb')
self.assertEqual(model['path'], sub_dir.strip('/')) self.assertEqual(model['path'], sub_dir.strip('/'))
# Make sure the old name is gone # Make sure the old name is gone
self.assertRaises(HTTPError, nm.get_notebook, name, path) self.assertRaises(HTTPError, nm.get_notebook, name, path)
@ -255,50 +255,50 @@ class TestNotebookManager(TestCase):
nm = self.notebook_manager nm = self.notebook_manager
# Create a notebook # Create a notebook
nb, name, path = self.new_notebook() nb, name, path = self.new_notebook()
# Delete the notebook # Delete the notebook
nm.delete_notebook(name, path) nm.delete_notebook(name, path)
# Check that a 'get' on the deleted notebook raises and error # Check that a 'get' on the deleted notebook raises and error
self.assertRaises(HTTPError, nm.get_notebook, name, path) self.assertRaises(HTTPError, nm.get_notebook, name, path)
def test_copy_notebook(self): def test_copy_notebook(self):
nm = self.notebook_manager nm = self.notebook_manager
path = u'å b' path = u'å b'
name = u'nb √.ipynb' name = u'nb √.ipynb'
os.mkdir(os.path.join(nm.notebook_dir, path)) os.mkdir(os.path.join(nm.notebook_dir, path))
orig = nm.create_notebook({'name' : name}, path=path) orig = nm.create_notebook({'name' : name}, path=path)
# copy with unspecified name # copy with unspecified name
copy = nm.copy_notebook(name, path=path) copy = nm.copy_notebook(name, path=path)
self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb')) self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb'))
# copy with specified name # copy with specified name
copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path) copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path)
self.assertEqual(copy2['name'], u'copy 2.ipynb') self.assertEqual(copy2['name'], u'copy 2.ipynb')
def test_trust_notebook(self): def test_trust_notebook(self):
nbm = self.notebook_manager nbm = self.notebook_manager
nb, name, path = self.new_notebook() nb, name, path = self.new_notebook()
untrusted = nbm.get_notebook(name, path)['content'] untrusted = nbm.get_notebook(name, path)['content']
assert not nbm.notary.check_cells(untrusted) assert not nbm.notary.check_cells(untrusted)
# print(untrusted) # print(untrusted)
nbm.trust_notebook(name, path) nbm.trust_notebook(name, path)
trusted = nbm.get_notebook(name, path)['content'] trusted = nbm.get_notebook(name, path)['content']
# print(trusted) # print(trusted)
assert nbm.notary.check_cells(trusted) assert nbm.notary.check_cells(trusted)
def test_mark_trusted_cells(self): def test_mark_trusted_cells(self):
nbm = self.notebook_manager nbm = self.notebook_manager
nb, name, path = self.new_notebook() nb, name, path = self.new_notebook()
nbm.mark_trusted_cells(nb, name, path) nbm.mark_trusted_cells(nb, name, path)
for cell in nb.worksheets[0].cells: for cell in nb.worksheets[0].cells:
if cell.cell_type == 'code': if cell.cell_type == 'code':
assert not cell.trusted assert not cell.trusted
nbm.trust_notebook(name, path) nbm.trust_notebook(name, path)
nb = nbm.get_notebook(name, path)['content'] nb = nbm.get_notebook(name, path)['content']
for cell in nb.worksheets[0].cells: for cell in nb.worksheets[0].cells:
@ -308,11 +308,11 @@ class TestNotebookManager(TestCase):
def test_check_and_sign(self): def test_check_and_sign(self):
nbm = self.notebook_manager nbm = self.notebook_manager
nb, name, path = self.new_notebook() nb, name, path = self.new_notebook()
nbm.mark_trusted_cells(nb, name, path) nbm.mark_trusted_cells(nb, name, path)
nbm.check_and_sign(nb, name, path) nbm.check_and_sign(nb, name, path)
assert not nbm.notary.check_signature(nb) assert not nbm.notary.check_signature(nb)
nbm.trust_notebook(name, path) nbm.trust_notebook(name, path)
nb = nbm.get_notebook(name, path)['content'] nb = nbm.get_notebook(name, path)['content']
nbm.mark_trusted_cells(nb, name, path) nbm.mark_trusted_cells(nb, name, path)

View File

@ -163,7 +163,7 @@ class APITest(NotebookTestBase):
expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb'] expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb']
expected = { normalize('NFC', name) for name in expected } expected = { normalize('NFC', name) for name in expected }
self.assertEqual(nbnames, expected) self.assertEqual(nbnames, expected)
nbs = notebooks_only(self.nb_api.list('ordering').json()) nbs = notebooks_only(self.nb_api.list('ordering').json())
nbnames = [n['name'] for n in nbs] nbnames = [n['name'] for n in nbs]
expected = ['A.ipynb', 'b.ipynb', 'C.ipynb'] expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
@ -344,4 +344,3 @@ class APITest(NotebookTestBase):
self.assertEqual(r.status_code, 204) self.assertEqual(r.status_code, 204)
cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json() cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
self.assertEqual(cps, []) self.assertEqual(cps, [])