mirror of
https://github.com/jupyter/notebook.git
synced 2025-01-06 11:35:24 +08:00
mv services/notebooks services/contents
This commit is contained in:
parent
6d292040b4
commit
b73aa2b9f9
@ -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
|
||||||
|
|
@ -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),
|
||||||
]
|
]
|
||||||
|
|
@ -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
|
@ -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)
|
@ -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, [])
|
||||||
|
|
Loading…
Reference in New Issue
Block a user