rename notebooks service to contents service

minimal functional changes, committing because tests are passing.
MinRK 12 years ago
parent b73aa2b9f9
commit 6cce477e07

@ -1,4 +1,4 @@
"""Base Tornado handlers for the notebook."""
"""Base Tornado handlers for the notebook server."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
@ -141,8 +141,8 @@ class IPythonHandler(AuthenticatedHandler):
return self.settings['kernel_manager']
@property
def notebook_manager(self):
return self.settings['notebook_manager']
def contents_manager(self):
return self.settings['contents_manager']
@property
def cluster_manager(self):
@ -158,7 +158,7 @@ class IPythonHandler(AuthenticatedHandler):
@property
def project_dir(self):
return self.notebook_manager.notebook_dir
return getattr(self.contents_manager, 'root_dir', '/')
#---------------------------------------------------------------
# CORS

@ -73,7 +73,7 @@ class NbconvertFileHandler(IPythonHandler):
exporter = get_exporter(format, config=self.config, log=self.log)
path = path.strip('/')
model = self.notebook_manager.get_notebook(name=name, path=path)
model = self.contents_manager.get(name=name, path=path)
self.set_header('Last-Modified', model['last_modified'])

@ -106,7 +106,7 @@ class APITest(NotebookTestBase):
@onlyif_cmds_exist('pandoc')
def test_from_post(self):
nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
nbmodel = requests.get(nbmodel_url).json()
r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
@ -121,7 +121,7 @@ class APITest(NotebookTestBase):
@onlyif_cmds_exist('pandoc')
def test_from_post_zip(self):
nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
nbmodel = requests.get(nbmodel_url).json()
r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel)

@ -35,12 +35,12 @@ class NotebookHandler(IPythonHandler):
"""get renders the notebook template if a name is given, or
redirects to the '/files/' handler if the name is not given."""
path = path.strip('/')
nbm = self.notebook_manager
cm = self.contents_manager
if name is None:
raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
# a .ipynb filename was given
if not nbm.notebook_exists(name, path):
if not cm.file_exists(name, path):
raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
name = url_escape(name)
path = url_escape(path)
@ -55,8 +55,8 @@ class NotebookHandler(IPythonHandler):
class NotebookRedirectHandler(IPythonHandler):
def get(self, path=''):
nbm = self.notebook_manager
if nbm.path_exists(path):
cm = self.contents_manager
if cm.path_exists(path):
# it's a *directory*, redirect to /tree
url = url_path_join(self.base_url, 'tree', path)
else:
@ -68,7 +68,7 @@ class NotebookRedirectHandler(IPythonHandler):
# but so is the files handler itself,
# so it should work until both are cleaned up.
parts = path.split('/')
files_path = os.path.join(nbm.notebook_dir, *parts)
files_path = os.path.join(cm.root_dir, *parts)
if not os.path.exists(files_path):
self.log.warn("Deprecated files/ URL: %s", path)
path = path.replace('/files/', '/', 1)

@ -55,8 +55,8 @@ from IPython.html import DEFAULT_STATIC_FILES_PATH
from .base.handlers import Template404
from .log import log_request
from .services.kernels.kernelmanager import MappingKernelManager
from .services.notebooks.nbmanager import NotebookManager
from .services.notebooks.filenbmanager import FileNotebookManager
from .services.contents.manager import ContentsManager
from .services.contents.filemanager import FileContentsManager
from .services.clusters.clustermanager import ClusterManager
from .services.sessions.sessionmanager import SessionManager
@ -121,19 +121,19 @@ def load_handlers(name):
class NotebookWebApplication(web.Application):
def __init__(self, ipython_app, kernel_manager, notebook_manager,
def __init__(self, ipython_app, kernel_manager, contents_manager,
cluster_manager, session_manager, kernel_spec_manager, log,
base_url, settings_overrides, jinja_env_options):
settings = self.init_settings(
ipython_app, kernel_manager, notebook_manager, cluster_manager,
ipython_app, kernel_manager, contents_manager, cluster_manager,
session_manager, kernel_spec_manager, log, base_url,
settings_overrides, jinja_env_options)
handlers = self.init_handlers(settings)
super(NotebookWebApplication, self).__init__(handlers, **settings)
def init_settings(self, ipython_app, kernel_manager, notebook_manager,
def init_settings(self, ipython_app, kernel_manager, contents_manager,
cluster_manager, session_manager, kernel_spec_manager,
log, base_url, settings_overrides,
jinja_env_options=None):
@ -165,7 +165,7 @@ class NotebookWebApplication(web.Application):
# managers
kernel_manager=kernel_manager,
notebook_manager=notebook_manager,
contents_manager=contents_manager,
cluster_manager=cluster_manager,
session_manager=session_manager,
kernel_spec_manager=kernel_spec_manager,
@ -193,18 +193,20 @@ class NotebookWebApplication(web.Application):
handlers.extend(load_handlers('nbconvert.handlers'))
handlers.extend(load_handlers('kernelspecs.handlers'))
handlers.extend(load_handlers('services.kernels.handlers'))
handlers.extend(load_handlers('services.notebooks.handlers'))
handlers.extend(load_handlers('services.contents.handlers'))
handlers.extend(load_handlers('services.clusters.handlers'))
handlers.extend(load_handlers('services.sessions.handlers'))
handlers.extend(load_handlers('services.nbconvert.handlers'))
handlers.extend(load_handlers('services.kernelspecs.handlers'))
# FIXME: /files/ should be handled by the Contents service when it exists
nbm = settings['notebook_manager']
if hasattr(nbm, 'notebook_dir'):
handlers.extend([
(r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
cm = settings['contents_manager']
if hasattr(cm, 'root_dir'):
handlers.append(
(r"/files/(.*)", AuthenticatedFileHandler, {'path' : cm.root_dir}),
)
handlers.append(
(r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
])
)
# prepend base_url onto the patterns that we match
new_handlers = []
for handler in handlers:
@ -263,11 +265,6 @@ flags['no-mathjax']=(
"""
)
# Add notebook manager flags
flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
'Auto-save a .py script everytime the .ipynb notebook is saved',
'Do not auto-save .py scripts for every notebook'))
aliases = dict(base_aliases)
aliases.update({
@ -302,7 +299,7 @@ class NotebookApp(BaseIPythonApplication):
classes = [
KernelManager, ProfileDir, Session, MappingKernelManager,
NotebookManager, FileNotebookManager, NotebookNotary,
ContentsManager, FileContentsManager, NotebookNotary,
]
flags = Dict(flags)
aliases = Dict(aliases)
@ -557,7 +554,7 @@ class NotebookApp(BaseIPythonApplication):
else:
self.log.info("Using MathJax: %s", new)
notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
config=True,
help='The notebook manager class to use.'
)
@ -621,7 +618,7 @@ class NotebookApp(BaseIPythonApplication):
raise TraitError("No such notebook dir: %r" % new)
# setting App.notebook_dir implies setting notebook and kernel dirs as well
self.config.FileNotebookManager.notebook_dir = new
self.config.FileContentsManager.root_dir = new
self.config.MappingKernelManager.root_dir = new
@ -658,12 +655,12 @@ class NotebookApp(BaseIPythonApplication):
parent=self, log=self.log, kernel_argv=self.kernel_argv,
connection_dir = self.profile_dir.security_dir,
)
kls = import_item(self.notebook_manager_class)
self.notebook_manager = kls(parent=self, log=self.log)
kls = import_item(self.contents_manager_class)
self.contents_manager = kls(parent=self, log=self.log)
kls = import_item(self.session_manager_class)
self.session_manager = kls(parent=self, log=self.log,
kernel_manager=self.kernel_manager,
notebook_manager=self.notebook_manager)
contents_manager=self.contents_manager)
kls = import_item(self.cluster_manager_class)
self.cluster_manager = kls(parent=self, log=self.log)
self.cluster_manager.update_profiles()
@ -688,7 +685,7 @@ class NotebookApp(BaseIPythonApplication):
self.webapp_settings['allow_credentials'] = self.allow_credentials
self.web_app = NotebookWebApplication(
self, self.kernel_manager, self.notebook_manager,
self, self.kernel_manager, self.contents_manager,
self.cluster_manager, self.session_manager, self.kernel_spec_manager,
self.log, self.base_url, self.webapp_settings,
self.jinja_environment_options
@ -838,7 +835,7 @@ class NotebookApp(BaseIPythonApplication):
def notebook_info(self):
"Return the current working directory and the server url information"
info = self.notebook_manager.info_string() + "\n"
info = self.contents_manager.info_string() + "\n"
info += "%d active kernels \n" % len(self.kernel_manager._kernels)
return info + "The IPython Notebook is running at: %s" % self.display_url

@ -1,4 +1,4 @@
"""A notebook manager that uses the local file system for storage."""
"""A contents manager that uses the local file system for storage."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
@ -10,7 +10,7 @@ import shutil
from tornado import web
from .nbmanager import NotebookManager
from .manager import ContentsManager
from IPython.nbformat import current
from IPython.utils.path import ensure_dir_exists
from IPython.utils.traitlets import Unicode, Bool, TraitError
@ -22,31 +22,19 @@ def sort_key(item):
"""Case-insensitive sorting."""
return item['name'].lower()
#-----------------------------------------------------------------------------
# Classes
#-----------------------------------------------------------------------------
class FileNotebookManager(NotebookManager):
class FileContentsManager(ContentsManager):
save_script = Bool(False, config=True,
help="""Automatically create a Python script when saving the notebook.
root_dir = Unicode(getcwd(), config=True)
For easier use of import, %run and %load across notebooks, a
<notebook-name>.py script will be created next to any
<notebook-name>.ipynb on each save. This can also be set with the
short `--script` flag.
"""
)
notebook_dir = Unicode(getcwd(), config=True)
def _notebook_dir_changed(self, name, old, new):
"""Do a bit of validation of the notebook dir."""
def _root_dir_changed(self, name, old, new):
"""Do a bit of validation of the root_dir."""
if not os.path.isabs(new):
# If we receive a non-absolute path, make it absolute.
self.notebook_dir = os.path.abspath(new)
self.root_dir = os.path.abspath(new)
return
if not os.path.exists(new) or not os.path.isdir(new):
raise TraitError("notebook dir %r is not a directory" % new)
raise TraitError("%r is not a directory" % new)
checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
help="""The directory name in which to keep notebook checkpoints
@ -68,14 +56,13 @@ class FileNotebookManager(NotebookManager):
except OSError as e:
self.log.debug("copystat on %s failed", dest, exc_info=True)
def get_notebook_names(self, path=''):
"""List all notebook names in the notebook dir and path."""
def get_names(self, path=''):
"""List all filenames in the path (relative to root_dir)."""
path = path.strip('/')
if not os.path.isdir(self._get_os_path(path=path)):
raise web.HTTPError(404, 'Directory not found: ' + path)
names = glob.glob(self._get_os_path('*'+self.filename_ext, path))
names = [os.path.basename(name)
for name in names]
names = glob.glob(self._get_os_path('*', path))
names = [ os.path.basename(name) for name in names if os.path.isfile(name)]
return names
def path_exists(self, path):
@ -85,7 +72,7 @@ class FileNotebookManager(NotebookManager):
----------
path : string
The path to check. This is an API path (`/` separated,
relative to base notebook-dir).
relative to root_dir).
Returns
-------
@ -103,7 +90,7 @@ class FileNotebookManager(NotebookManager):
----------
path : string
The path to check. This is an API path (`/` separated,
relative to base notebook-dir).
relative to root_dir).
Returns
-------
@ -113,40 +100,38 @@ class FileNotebookManager(NotebookManager):
"""
path = path.strip('/')
os_path = self._get_os_path(path=path)
return is_hidden(os_path, self.notebook_dir)
return is_hidden(os_path, self.root_dir)
def _get_os_path(self, name=None, path=''):
"""Given a notebook name and a URL path, return its file system
"""Given a filename and a URL path, return its file system
path.
Parameters
----------
name : string
The name of a notebook file with the .ipynb extension
A filename
path : string
The relative URL path (with '/' as separator) to the named
notebook.
file.
Returns
-------
path : string
A file system path that combines notebook_dir (location where
server started), the relative path, and the filename with the
current operating system's url.
API path to be evaluated relative to root_dir.
"""
if name is not None:
path = path + '/' + name
return to_os_path(path, self.notebook_dir)
return to_os_path(path, self.root_dir)
def notebook_exists(self, name, path=''):
"""Returns a True if the notebook exists. Else, returns False.
def file_exists(self, name, path=''):
"""Returns a True if the file exists, else returns False.
Parameters
----------
name : string
The name of the notebook you are checking.
The name of the file you are checking.
path : string
The relative path to the notebook (with '/' as separator)
The relative path to the file's directory (with '/' as separator)
Returns
-------
@ -164,14 +149,14 @@ class FileNotebookManager(NotebookManager):
os_path = self._get_os_path('', path)
if not os.path.isdir(os_path):
raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
elif is_hidden(os_path, self.notebook_dir):
elif is_hidden(os_path, self.root_dir):
self.log.info("Refusing to serve hidden directory, via 404 Error")
raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
dir_names = os.listdir(os_path)
dirs = []
for name in dir_names:
os_path = self._get_os_path(name, path)
if os.path.isdir(os_path) and not is_hidden(os_path, self.notebook_dir)\
if os.path.isdir(os_path) and not is_hidden(os_path, self.root_dir)\
and self.should_list(name):
try:
model = self.get_dir_model(name, path)
@ -201,7 +186,7 @@ class FileNotebookManager(NotebookManager):
model['type'] = 'directory'
return model
def list_notebooks(self, path):
def list_files(self, path):
"""Returns a list of dictionaries that are the standard model
for all notebooks in the relative 'path'.
@ -217,13 +202,13 @@ class FileNotebookManager(NotebookManager):
a list of the notebook models without 'content'
"""
path = path.strip('/')
notebook_names = self.get_notebook_names(path)
notebooks = [self.get_notebook(name, path, content=False)
for name in notebook_names if self.should_list(name)]
names = self.get_names(path)
notebooks = [self.get(name, path, content=False)
for name in names if self.should_list(name)]
notebooks = sorted(notebooks, key=sort_key)
return notebooks
def get_notebook(self, name, path='', content=True):
def get(self, name, path='', content=True):
""" Takes a path and name for a notebook and returns its model
Parameters
@ -241,7 +226,7 @@ class FileNotebookManager(NotebookManager):
dict in the model as well.
"""
path = path.strip('/')
if not self.notebook_exists(name=name, path=path):
if not self.file_exists(name=name, path=path):
raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
os_path = self._get_os_path(name, path)
info = os.stat(os_path)
@ -264,7 +249,7 @@ class FileNotebookManager(NotebookManager):
model['content'] = nb
return model
def save_notebook(self, model, name='', path=''):
def save(self, model, name='', path=''):
"""Save the notebook model and return the model with no content."""
path = path.strip('/')
@ -272,14 +257,14 @@ class FileNotebookManager(NotebookManager):
raise web.HTTPError(400, u'No notebook JSON data provided')
# One checkpoint should always exist
if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
if self.file_exists(name, path) and not self.list_checkpoints(name, path):
self.create_checkpoint(name, path)
new_path = model.get('path', path).strip('/')
new_name = model.get('name', name)
if path != new_path or name != new_name:
self.rename_notebook(name, path, new_name, new_path)
self.rename(name, path, new_name, new_path)
# Save the notebook file
os_path = self._get_os_path(new_name, new_path)
@ -296,35 +281,25 @@ class FileNotebookManager(NotebookManager):
except Exception as e:
raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
# Save .py script as well
if self.save_script:
py_path = os.path.splitext(os_path)[0] + '.py'
self.log.debug("Writing script %s", py_path)
try:
with io.open(py_path, 'w', encoding='utf-8') as f:
current.write(nb, f, u'py')
except Exception as e:
raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
model = self.get_notebook(new_name, new_path, content=False)
model = self.get(new_name, new_path, content=False)
return model
def update_notebook(self, model, name, path=''):
"""Update the notebook's path and/or name"""
def update(self, model, name, path=''):
"""Update the file's path and/or name"""
path = path.strip('/')
new_name = model.get('name', name)
new_path = model.get('path', path).strip('/')
if path != new_path or name != new_name:
self.rename_notebook(name, path, new_name, new_path)
model = self.get_notebook(new_name, new_path, content=False)
self.rename(name, path, new_name, new_path)
model = self.get(new_name, new_path, content=False)
return model
def delete_notebook(self, name, path=''):
"""Delete notebook by name and path."""
def delete(self, name, path=''):
"""Delete file by name and path."""
path = path.strip('/')
os_path = self._get_os_path(name, 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'File does not exist: %s' % os_path)
# clear checkpoints
for checkpoint in self.list_checkpoints(name, path):
@ -334,11 +309,11 @@ class FileNotebookManager(NotebookManager):
self.log.debug("Unlinking checkpoint %s", cp_path)
os.unlink(cp_path)
self.log.debug("Unlinking notebook %s", os_path)
self.log.debug("Unlinking file %s", os_path)
os.unlink(os_path)
def rename_notebook(self, old_name, old_path, new_name, new_path):
"""Rename a notebook."""
def rename(self, old_name, old_path, new_name, new_path):
"""Rename a file."""
old_path = old_path.strip('/')
new_path = new_path.strip('/')
if new_name == old_name and new_path == old_path:
@ -350,17 +325,12 @@ class FileNotebookManager(NotebookManager):
# Should we proceed with the move?
if os.path.isfile(new_os_path):
raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
if self.save_script:
old_py_path = os.path.splitext(old_os_path)[0] + '.py'
new_py_path = os.path.splitext(new_os_path)[0] + '.py'
if os.path.isfile(new_py_path):
raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
# Move the notebook file
# Move the file
try:
shutil.move(old_os_path, new_os_path)
except Exception as e:
raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_os_path, e))
# Move the checkpoints
old_checkpoints = self.list_checkpoints(old_name, old_path)
@ -372,20 +342,16 @@ class FileNotebookManager(NotebookManager):
self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
shutil.move(old_cp_path, new_cp_path)
# Move the .py script
if self.save_script:
shutil.move(old_py_path, new_py_path)
# Checkpoint-related utilities
def get_checkpoint_path(self, checkpoint_id, name, path=''):
"""find the path to a checkpoint"""
path = path.strip('/')
basename, _ = os.path.splitext(name)
basename, ext = os.path.splitext(name)
filename = u"{name}-{checkpoint_id}{ext}".format(
name=basename,
checkpoint_id=checkpoint_id,
ext=self.filename_ext,
ext=ext,
)
os_path = self._get_os_path(path=path)
cp_dir = os.path.join(os_path, self.checkpoint_dir)
@ -408,22 +374,22 @@ class FileNotebookManager(NotebookManager):
# public checkpoint API
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 file"""
path = path.strip('/')
nb_path = self._get_os_path(name, path)
src_path = self._get_os_path(name, path)
# only the one checkpoint ID:
checkpoint_id = u"checkpoint"
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
self.log.debug("creating checkpoint for notebook %s", name)
self._copy(nb_path, cp_path)
self._copy(src_path, cp_path)
# return the checkpoint info
return self.get_checkpoint_model(checkpoint_id, name, path)
def list_checkpoints(self, name, path=''):
"""list the checkpoints for a given notebook
"""list the checkpoints for a given file
This notebook manager currently only supports one checkpoint per notebook.
This contents manager currently only supports one checkpoint per file.
"""
path = path.strip('/')
checkpoint_id = "checkpoint"
@ -435,36 +401,37 @@ class FileNotebookManager(NotebookManager):
def restore_checkpoint(self, checkpoint_id, name, path=''):
"""restore a notebook to a checkpointed state"""
"""restore a file to a checkpointed state"""
path = path.strip('/')
self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
self.log.info("restoring %s from checkpoint %s", name, checkpoint_id)
nb_path = self._get_os_path(name, path)
cp_path = self.get_checkpoint_path(checkpoint_id, name, 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' % (name, checkpoint_id)
u'checkpoint does not exist: %s-%s' % (name, checkpoint_id)
)
# ensure notebook is readable (never restore from an unreadable notebook)
with io.open(cp_path, 'r', encoding='utf-8') as f:
current.read(f, u'json')
if cp_path.endswith('.ipynb'):
with io.open(cp_path, 'r', encoding='utf-8') as f:
current.read(f, u'json')
self._copy(cp_path, nb_path)
self.log.debug("copying %s -> %s", cp_path, nb_path)
def delete_checkpoint(self, checkpoint_id, name, path=''):
"""delete a notebook's checkpoint"""
"""delete a file's checkpoint"""
path = path.strip('/')
cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
if not os.path.isfile(cp_path):
raise web.HTTPError(404,
u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
u'Checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
)
self.log.debug("unlinking %s", cp_path)
os.unlink(cp_path)
def info_string(self):
return "Serving notebooks from local directory: %s" % self.notebook_dir
return "Serving notebooks from local directory: %s" % self.root_dir
def get_kernel_path(self, name, path='', model=None):
""" Return the path to start kernel in """
return os.path.join(self.notebook_dir, path)
"""Return the initial working dir a kernel associated with a given notebook"""
return os.path.join(self.root_dir, path)

@ -1,20 +1,7 @@
"""Tornado handlers for the notebooks web service.
"""Tornado handlers for the contents web service."""
Authors:
* Brian Granger
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2011 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import json
@ -27,33 +14,29 @@ from IPython.html.base.handlers import (IPythonHandler, json_errors,
notebook_path_regex, path_regex,
notebook_name_regex)
#-----------------------------------------------------------------------------
# Notebook web service handlers
#-----------------------------------------------------------------------------
class NotebookHandler(IPythonHandler):
class ContentsHandler(IPythonHandler):
SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
def notebook_location(self, name, path=''):
"""Return the full URL location of a notebook based.
def location_url(self, name, path=''):
"""Return the full URL location of a file.
Parameters
----------
name : unicode
The base name of the notebook, such as "foo.ipynb".
The base name of the file, such as "foo.ipynb".
path : unicode
The URL path of the notebook.
The API path of the file, such as "foo/bar".
"""
return url_escape(url_path_join(
self.base_url, 'api', 'notebooks', path, name
self.base_url, 'api', 'contents', path, name
))
def _finish_model(self, model, location=True):
"""Finish a JSON request with a model, setting relevant headers, etc."""
if location:
location = self.notebook_location(model['name'], model['path'])
location = self.location_url(model['name'], model['path'])
self.set_header('Location', location)
self.set_header('Last-Modified', model['last_modified'])
self.finish(json.dumps(model, default=date_default))
@ -61,68 +44,68 @@ class NotebookHandler(IPythonHandler):
@web.authenticated
@json_errors
def get(self, path='', name=None):
"""Return a Notebook or list of notebooks.
"""Return a file or list of files.
* GET with path and no notebook name lists notebooks in a directory
* GET with path and notebook name returns notebook JSON
* GET with path and no filename lists files in a directory
* GET with path and filename returns file contents model
"""
nbm = self.notebook_manager
# Check to see if a notebook name was given
cm = self.contents_manager
# Check to see if a filename was given
if name is None:
# TODO: Remove this after we create the contents web service and directories are
# no longer listed by the notebook web service. This should only handle notebooks
# and not directories.
dirs = nbm.list_dirs(path)
notebooks = []
dirs = cm.list_dirs(path)
files = []
index = []
for nb in nbm.list_notebooks(path):
for nb in cm.list_files(path):
if nb['name'].lower() == 'index.ipynb':
index.append(nb)
else:
notebooks.append(nb)
notebooks = index + dirs + notebooks
self.finish(json.dumps(notebooks, default=date_default))
files.append(nb)
files = index + dirs + files
self.finish(json.dumps(files, default=date_default))
return
# get and return notebook representation
model = nbm.get_notebook(name, path)
model = cm.get(name, path)
self._finish_model(model, location=False)
@web.authenticated
@json_errors
def patch(self, path='', name=None):
"""PATCH renames a notebook without re-uploading content."""
nbm = self.notebook_manager
cm = self.contents_manager
if name is None:
raise web.HTTPError(400, u'Notebook name missing')
raise web.HTTPError(400, u'Filename missing')
model = self.get_json_body()
if model is None:
raise web.HTTPError(400, u'JSON body missing')
model = nbm.update_notebook(model, name, path)
model = cm.update(model, name, path)
self._finish_model(model)
def _copy_notebook(self, copy_from, path, copy_to=None):
"""Copy a notebook in path, optionally specifying the new name.
def _copy(self, copy_from, path, copy_to=None):
"""Copy a file in path, optionally specifying the new name.
Only support copying within the same directory.
"""
self.log.info(u"Copying notebook from %s/%s to %s/%s",
self.log.info(u"Copying from %s/%s to %s/%s",
path, copy_from,
path, copy_to or '',
)
model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
model = self.contents_manager.copy(copy_from, copy_to, path)
self.set_status(201)
self._finish_model(model)
def _upload_notebook(self, model, path, name=None):
"""Upload a notebook
def _upload(self, model, path, name=None):
"""Upload a file
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 file to %s/%s", path, name or '')
if name:
model['name'] = name
model = self.notebook_manager.create_notebook(model, path)
model = self.contents_manager.create_notebook(model, path)
self.set_status(201)
self._finish_model(model)
@ -135,14 +118,14 @@ class NotebookHandler(IPythonHandler):
model = {}
if name:
model['name'] = name
model = self.notebook_manager.create_notebook(model, path=path)
model = self.contents_manager.create_notebook(model, path=path)
self.set_status(201)
self._finish_model(model)
def _save_notebook(self, model, path, name):
"""Save an existing notebook."""
self.log.info(u"Saving notebook at %s/%s", path, name)
model = self.notebook_manager.save_notebook(model, name, path)
def _save(self, model, path, name):
"""Save an existing file."""
self.log.info(u"Saving file at %s/%s", path, name)
model = self.contents_manager.save(model, name, path)
if model['path'] != path.strip('/') or model['name'] != name:
# a rename happened, set Location header
location = True
@ -157,10 +140,10 @@ class NotebookHandler(IPythonHandler):
POST creates new notebooks. The server always decides on the notebook name.
POST /api/notebooks/path
POST /api/contents/path
New untitled notebook in path. If content specified, upload a
notebook, otherwise start empty.
POST /api/notebooks/path?copy=OtherNotebook.ipynb
POST /api/contents/path?copy=OtherNotebook.ipynb
New copy of OtherNotebook in path
"""
@ -174,25 +157,25 @@ class NotebookHandler(IPythonHandler):
if copy_from:
if model.get('content'):
raise web.HTTPError(400, "Can't upload and copy at the same time.")
self._copy_notebook(copy_from, path)
self._copy(copy_from, path)
else:
self._upload_notebook(model, path)
self._upload(model, path)
else:
self._create_empty_notebook(path)
@web.authenticated
@json_errors
def put(self, path='', name=None):
"""Saves the notebook in the location specified by name and path.
"""Saves the file in the location specified by name and path.
PUT is very similar to POST, but the requester specifies the name,
whereas with POST, the server picks the name.
PUT /api/notebooks/path/Name.ipynb
PUT /api/contents/path/Name.ipynb
Save notebook at ``path/Name.ipynb``. Notebook structure is specified
in `content` key of JSON request body. If content is not specified,
create a new empty notebook.
PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb
PUT /api/contents/path/Name.ipynb?copy=OtherNotebook.ipynb
Copy OtherNotebook to Name
"""
if name is None:
@ -204,34 +187,34 @@ class NotebookHandler(IPythonHandler):
if copy_from:
if model.get('content'):
raise web.HTTPError(400, "Can't upload and copy at the same time.")
self._copy_notebook(copy_from, path, name)
elif self.notebook_manager.notebook_exists(name, path):
self._save_notebook(model, path, name)
self._copy(copy_from, path, name)
elif self.contents_manager.file_exists(name, path):
self._save(model, path, name)
else:
self._upload_notebook(model, path, name)
self._upload(model, path, name)
else:
self._create_empty_notebook(path, name)
@web.authenticated
@json_errors
def delete(self, path='', name=None):
"""delete the notebook in the given notebook path"""
nbm = self.notebook_manager
nbm.delete_notebook(name, path)
"""delete a file in the given path"""
cm = self.contents_manager
cm.delete(name, path)
self.set_status(204)
self.finish()
class NotebookCheckpointsHandler(IPythonHandler):
class CheckpointsHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET', 'POST')
@web.authenticated
@json_errors
def get(self, path='', name=None):
"""get lists checkpoints for a notebook"""
nbm = self.notebook_manager
checkpoints = nbm.list_checkpoints(name, path)
"""get lists checkpoints for a file"""
cm = self.contents_manager
checkpoints = cm.list_checkpoints(name, path)
data = json.dumps(checkpoints, default=date_default)
self.finish(data)
@ -239,35 +222,35 @@ class NotebookCheckpointsHandler(IPythonHandler):
@json_errors
def post(self, path='', name=None):
"""post creates a new checkpoint"""
nbm = self.notebook_manager
checkpoint = nbm.create_checkpoint(name, path)
cm = self.contents_manager
checkpoint = cm.create_checkpoint(name, path)
data = json.dumps(checkpoint, default=date_default)
location = url_path_join(self.base_url, 'api/notebooks',
location = url_path_join(self.base_url, 'api/contents',
path, name, 'checkpoints', checkpoint['id'])
self.set_header('Location', url_escape(location))
self.set_status(201)
self.finish(data)
class ModifyNotebookCheckpointsHandler(IPythonHandler):
class ModifyCheckpointsHandler(IPythonHandler):
SUPPORTED_METHODS = ('POST', 'DELETE')
@web.authenticated
@json_errors
def post(self, path, name, checkpoint_id):
"""post restores a notebook from a checkpoint"""
nbm = self.notebook_manager
nbm.restore_checkpoint(checkpoint_id, name, path)
"""post restores a file from a checkpoint"""
cm = self.contents_manager
cm.restore_checkpoint(checkpoint_id, name, path)
self.set_status(204)
self.finish()
@web.authenticated
@json_errors
def delete(self, path, name, checkpoint_id):
"""delete clears a checkpoint for a given notebook"""
nbm = self.notebook_manager
nbm.delete_checkpoint(checkpoint_id, name, path)
"""delete clears a checkpoint for a given file"""
cm = self.contents_manager
cm.delete_checkpoint(checkpoint_id, name, path)
self.set_status(204)
self.finish()
@ -279,9 +262,9 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
_checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
default_handlers = [
(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%s" % path_regex, NotebookHandler),
(r"/api/contents%s/checkpoints" % notebook_path_regex, CheckpointsHandler),
(r"/api/contents%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
ModifyCheckpointsHandler),
(r"/api/contents%s" % notebook_path_regex, ContentsHandler),
(r"/api/contents%s" % path_regex, ContentsHandler),
]

@ -1,21 +1,7 @@
"""A base class notebook manager.
"""A base class for contents managers."""
Authors:
* Brian Granger
* Zach Sailer
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2011 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from fnmatch import fnmatch
import itertools
@ -25,13 +11,8 @@ from IPython.config.configurable import LoggingConfigurable
from IPython.nbformat import current, sign
from IPython.utils.traitlets import Instance, Unicode, List
#-----------------------------------------------------------------------------
# Classes
#-----------------------------------------------------------------------------
class NotebookManager(LoggingConfigurable):
filename_ext = Unicode(u'.ipynb')
class ContentsManager(LoggingConfigurable):
notary = Instance(sign.NotebookNotary)
def _notary_default(self):
@ -41,7 +22,7 @@ class NotebookManager(LoggingConfigurable):
Glob patterns to hide in file and directory listings.
""")
# NotebookManager API part 1: methods that must be
# ContentsManager API part 1: methods that must be
# implemented in subclasses.
def path_exists(self, path):
@ -68,7 +49,7 @@ class NotebookManager(LoggingConfigurable):
----------
path : string
The path to check. This is an API path (`/` separated,
relative to base notebook-dir).
relative to root dir).
Returns
-------
@ -78,7 +59,7 @@ class NotebookManager(LoggingConfigurable):
"""
raise NotImplementedError
def notebook_exists(self, name, path=''):
def file_exists(self, name, path=''):
"""Returns a True if the notebook exists. Else, returns False.
Parameters
@ -114,12 +95,10 @@ class NotebookManager(LoggingConfigurable):
"""
raise NotImplementedError('must be implemented in a subclass')
def list_notebooks(self, path=''):
"""Return a list of notebook dicts without content.
This returns a list of dicts, each of the form::
def list_files(self, path=''):
"""Return a list of contents dicts without content.
dict(notebook_id=notebook,name=name)
This returns a list of dicts
This list of dicts should be sorted by name::
@ -127,19 +106,19 @@ class NotebookManager(LoggingConfigurable):
"""
raise NotImplementedError('must be implemented in a subclass')
def get_notebook(self, name, path='', content=True):
def get_model(self, name, path='', content=True):
"""Get the notebook model with or without content."""
raise NotImplementedError('must be implemented in a subclass')
def save_notebook(self, model, name, path=''):
def save(self, model, name, path=''):
"""Save the notebook and return the model with no content."""
raise NotImplementedError('must be implemented in a subclass')
def update_notebook(self, model, name, path=''):
def update(self, model, name, path=''):
"""Update the notebook and return the model with no content."""
raise NotImplementedError('must be implemented in a subclass')
def delete_notebook(self, name, path=''):
def delete(self, name, path=''):
"""Delete notebook by name and path."""
raise NotImplementedError('must be implemented in a subclass')
@ -165,34 +144,34 @@ class NotebookManager(LoggingConfigurable):
def info_string(self):
return "Serving notebooks"
# NotebookManager API part 2: methods that have useable default
# ContentsManager API part 2: methods that have useable default
# implementations, but can be overridden in subclasses.
def get_kernel_path(self, name, path='', model=None):
""" Return the path to start kernel in """
return path
def increment_filename(self, basename, path=''):
"""Increment a notebook filename without the .ipynb to make it unique.
def increment_filename(self, filename, path=''):
"""Increment a filename until it is unique.
Parameters
----------
basename : unicode
The name of a notebook without the ``.ipynb`` file extension.
filename : unicode
The name of a file, including extension
path : unicode
The URL path of the notebooks directory
Returns
-------
name : unicode
A notebook name (with the .ipynb extension) that starts
with basename and does not refer to any existing notebook.
A filename that is unique, based on the input filename.
"""
path = path.strip('/')
basename, ext = os.path.splitext(filename)
for i in itertools.count():
name = u'{basename}{i}{ext}'.format(basename=basename, i=i,
ext=self.filename_ext)
if not self.notebook_exists(name, path):
ext=ext)
if not self.file_exists(name, path):
break
return name
@ -205,24 +184,25 @@ class NotebookManager(LoggingConfigurable):
metadata = current.new_metadata(name=u'')
model['content'] = current.new_notebook(metadata=metadata)
if 'name' not in model:
model['name'] = self.increment_filename('Untitled', path)
model['name'] = self.increment_filename('Untitled.ipynb', path)
model['path'] = path
model = self.save_notebook(model, model['name'], model['path'])
model = self.save(model, model['name'], model['path'])
return model
def copy_notebook(self, from_name, to_name=None, path=''):
"""Copy an existing notebook and return its new model.
def copy(self, from_name, to_name=None, path=''):
"""Copy an existing file and return its new model.
If to_name not specified, increment `from_name-Copy#.ipynb`.
"""
path = path.strip('/')
model = self.get_notebook(from_name, path)
model = self.get(from_name, path)
if not to_name:
base = os.path.splitext(from_name)[0] + '-Copy'
to_name = self.increment_filename(base, path)
base, ext = os.path.splitext(from_name)
copy_name = u'{0}-Copy{1}'.format(base, ext)
to_name = self.increment_filename(copy_name, path)
model['name'] = to_name
model = self.save_notebook(model, to_name, path)
model = self.save(model, to_name, path)
return model
def log_info(self):
@ -238,11 +218,11 @@ class NotebookManager(LoggingConfigurable):
path : string
The notebook's directory
"""
model = self.get_notebook(name, path)
model = self.get(name, path)
nb = model['content']
self.log.warn("Trusting notebook %s/%s", path, name)
self.notary.mark_cells(nb, True)
self.save_notebook(model, name, path)
self.save(model, name, path)
def check_and_sign(self, nb, name, path=''):
"""Check for trusted cells, and sign the notebook.

@ -1,5 +1,5 @@
# coding: utf-8
"""Test the notebooks webservice API."""
"""Test the contents webservice API."""
import io
import json
@ -30,14 +30,14 @@ def dirs_only(nb_list):
return [x for x in nb_list if x['type']=='directory']
class NBAPI(object):
"""Wrapper for notebook API calls."""
class API(object):
"""Wrapper for contents API calls."""
def __init__(self, base_url):
self.base_url = base_url
def _req(self, verb, path, body=None):
response = requests.request(verb,
url_path_join(self.base_url, 'api/notebooks', path),
url_path_join(self.base_url, 'api/contents', path),
data=body,
)
response.raise_for_status()
@ -127,7 +127,7 @@ class APITest(NotebookTestBase):
nb = new_notebook(name=name)
write(nb, f, format='ipynb')
self.nb_api = NBAPI(self.base_url())
self.api = API(self.base_url())
def tearDown(self):
nbdir = self.notebook_dir.name
@ -139,48 +139,48 @@ class APITest(NotebookTestBase):
os.unlink(pjoin(nbdir, 'inroot.ipynb'))
def test_list_notebooks(self):
nbs = notebooks_only(self.nb_api.list().json())
nbs = notebooks_only(self.api.list().json())
self.assertEqual(len(nbs), 1)
self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
nbs = notebooks_only(self.nb_api.list('/Directory with spaces in/').json())
nbs = notebooks_only(self.api.list('/Directory with spaces in/').json())
self.assertEqual(len(nbs), 1)
self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
nbs = notebooks_only(self.nb_api.list(u'/unicodé/').json())
nbs = notebooks_only(self.api.list(u'/unicodé/').json())
self.assertEqual(len(nbs), 1)
self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
self.assertEqual(nbs[0]['path'], u'unicodé')
nbs = notebooks_only(self.nb_api.list('/foo/bar/').json())
nbs = notebooks_only(self.api.list('/foo/bar/').json())
self.assertEqual(len(nbs), 1)
self.assertEqual(nbs[0]['name'], 'baz.ipynb')
self.assertEqual(nbs[0]['path'], 'foo/bar')
nbs = notebooks_only(self.nb_api.list('foo').json())
nbs = notebooks_only(self.api.list('foo').json())
self.assertEqual(len(nbs), 4)
nbnames = { normalize('NFC', n['name']) for n in nbs }
expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb']
expected = { normalize('NFC', name) for name in expected }
self.assertEqual(nbnames, expected)
nbs = notebooks_only(self.nb_api.list('ordering').json())
nbs = notebooks_only(self.api.list('ordering').json())
nbnames = [n['name'] for n in nbs]
expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
self.assertEqual(nbnames, expected)
def test_list_dirs(self):
dirs = dirs_only(self.nb_api.list().json())
dirs = dirs_only(self.api.list().json())
dir_names = {normalize('NFC', d['name']) for d in dirs}
self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
def test_list_nonexistant_dir(self):
with assert_http_error(404):
self.nb_api.list('nonexistant')
self.api.list('nonexistant')
def test_get_contents(self):
for d, name in self.dirs_nbs:
nb = self.nb_api.read('%s.ipynb' % name, d+'/').json()
nb = self.api.read('%s.ipynb' % name, d+'/').json()
self.assertEqual(nb['name'], u'%s.ipynb' % name)
self.assertIn('content', nb)
self.assertIn('metadata', nb['content'])
@ -188,12 +188,12 @@ class APITest(NotebookTestBase):
# Name that doesn't exist - should be a 404
with assert_http_error(404):
self.nb_api.read('q.ipynb', 'foo')
self.api.read('q.ipynb', 'foo')
def _check_nb_created(self, resp, name, path):
self.assertEqual(resp.status_code, 201)
location_header = py3compat.str_to_unicode(resp.headers['Location'])
self.assertEqual(location_header, url_escape(url_path_join(u'/api/notebooks', path, name)))
self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path, name)))
self.assertEqual(resp.json()['name'], name)
assert os.path.isfile(pjoin(
self.notebook_dir.name,
@ -202,28 +202,28 @@ class APITest(NotebookTestBase):
))
def test_create_untitled(self):
resp = self.nb_api.create_untitled(path=u'å b')
resp = self.api.create_untitled(path=u'å b')
self._check_nb_created(resp, 'Untitled0.ipynb', u'å b')
# Second time
resp = self.nb_api.create_untitled(path=u'å b')
resp = self.api.create_untitled(path=u'å b')
self._check_nb_created(resp, 'Untitled1.ipynb', u'å b')
# And two directories down
resp = self.nb_api.create_untitled(path='foo/bar')
resp = self.api.create_untitled(path='foo/bar')
self._check_nb_created(resp, 'Untitled0.ipynb', 'foo/bar')
def test_upload_untitled(self):
nb = new_notebook(name='Upload test')
nbmodel = {'content': nb}
resp = self.nb_api.upload_untitled(path=u'å b',
resp = self.api.upload_untitled(path=u'å b',
body=json.dumps(nbmodel))
self._check_nb_created(resp, 'Untitled0.ipynb', u'å b')
def test_upload(self):
nb = new_notebook(name=u'ignored')
nbmodel = {'content': nb}
resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
body=json.dumps(nbmodel))
self._check_nb_created(resp, u'Upload tést.ipynb', u'å b')
@ -233,48 +233,48 @@ class APITest(NotebookTestBase):
nb.worksheets.append(ws)
ws.cells.append(v2.new_code_cell(input='print("hi")'))
nbmodel = {'content': nb}
resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
body=json.dumps(nbmodel))
self._check_nb_created(resp, u'Upload tést.ipynb', u'å b')
resp = self.nb_api.read(u'Upload tést.ipynb', u'å b')
resp = self.api.read(u'Upload tést.ipynb', u'å b')
data = resp.json()
self.assertEqual(data['content']['nbformat'], current.nbformat)
self.assertEqual(data['content']['orig_nbformat'], 2)
def test_copy_untitled(self):
resp = self.nb_api.copy_untitled(u'ç d.ipynb', path=u'å b')
resp = self.api.copy_untitled(u'ç d.ipynb', path=u'å b')
self._check_nb_created(resp, u'ç d-Copy0.ipynb', u'å b')
def test_copy(self):
resp = self.nb_api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
self._check_nb_created(resp, u'cøpy.ipynb', u'å b')
def test_delete(self):
for d, name in self.dirs_nbs:
resp = self.nb_api.delete('%s.ipynb' % name, d)
resp = self.api.delete('%s.ipynb' % name, d)
self.assertEqual(resp.status_code, 204)
for d in self.dirs + ['/']:
nbs = notebooks_only(self.nb_api.list(d).json())
nbs = notebooks_only(self.api.list(d).json())
self.assertEqual(len(nbs), 0)
def test_rename(self):
resp = self.nb_api.rename('a.ipynb', 'foo', 'z.ipynb')
resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb')
self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
self.assertEqual(resp.json()['name'], 'z.ipynb')
assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
nbs = notebooks_only(self.nb_api.list('foo').json())
nbs = notebooks_only(self.api.list('foo').json())
nbnames = set(n['name'] for n in nbs)
self.assertIn('z.ipynb', nbnames)
self.assertNotIn('a.ipynb', nbnames)
def test_rename_existing(self):
with assert_http_error(409):
self.nb_api.rename('a.ipynb', 'foo', 'b.ipynb')
self.api.rename('a.ipynb', 'foo', 'b.ipynb')
def test_save(self):
resp = self.nb_api.read('a.ipynb', 'foo')
resp = self.api.read('a.ipynb', 'foo')
nbcontent = json.loads(resp.text)['content']
nb = to_notebook_json(nbcontent)
ws = new_worksheet()
@ -282,32 +282,32 @@ class APITest(NotebookTestBase):
ws.cells.append(new_heading_cell(u'Created by test ³'))
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
with io.open(nbfile, 'r', encoding='utf-8') as f:
newnb = read(f, format='ipynb')
self.assertEqual(newnb.worksheets[0].cells[0].source,
u'Created by test ³')
nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
newnb = to_notebook_json(nbcontent)
self.assertEqual(newnb.worksheets[0].cells[0].source,
u'Created by test ³')
# Save and rename
nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb}
resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
saved = resp.json()
self.assertEqual(saved['name'], 'a2.ipynb')
self.assertEqual(saved['path'], 'foo/bar')
assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
with assert_http_error(404):
self.nb_api.read('a.ipynb', 'foo')
self.api.read('a.ipynb', 'foo')
def test_checkpoints(self):
resp = self.nb_api.read('a.ipynb', 'foo')
r = self.nb_api.new_checkpoint('a.ipynb', 'foo')
resp = self.api.read('a.ipynb', 'foo')
r = self.api.new_checkpoint('a.ipynb', 'foo')
self.assertEqual(r.status_code, 201)
cp1 = r.json()
self.assertEqual(set(cp1), {'id', 'last_modified'})
@ -322,25 +322,25 @@ class APITest(NotebookTestBase):
ws.cells.append(hcell)
# Save
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
# List checkpoints
cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
self.assertEqual(cps, [cp1])
nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
nb = to_notebook_json(nbcontent)
self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
# Restore cp1
r = self.nb_api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
r = self.api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
self.assertEqual(r.status_code, 204)
nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
nb = to_notebook_json(nbcontent)
self.assertEqual(nb.worksheets, [])
# Delete cp1
r = self.nb_api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
r = self.api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
self.assertEqual(r.status_code, 204)
cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
self.assertEqual(cps, [])

@ -15,59 +15,59 @@ from IPython.utils.tempdir import TemporaryDirectory
from IPython.utils.traitlets import TraitError
from IPython.html.utils import url_path_join
from ..filenbmanager import FileNotebookManager
from ..nbmanager import NotebookManager
from ..filemanager import FileContentsManager
from ..manager import ContentsManager
class TestFileNotebookManager(TestCase):
class TestFileContentsManager(TestCase):
def test_nb_dir(self):
def test_root_dir(self):
with TemporaryDirectory() as td:
fm = FileNotebookManager(notebook_dir=td)
self.assertEqual(fm.notebook_dir, td)
fm = FileContentsManager(root_dir=td)
self.assertEqual(fm.root_dir, td)
def test_missing_nb_dir(self):
def test_missing_root_dir(self):
with TemporaryDirectory() as td:
nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir)
root = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
self.assertRaises(TraitError, FileContentsManager, root_dir=root)
def test_invalid_nb_dir(self):
def test_invalid_root_dir(self):
with NamedTemporaryFile() as tf:
self.assertRaises(TraitError, FileNotebookManager, notebook_dir=tf.name)
self.assertRaises(TraitError, FileContentsManager, root_dir=tf.name)
def test_get_os_path(self):
# full filesystem path should be returned with correct operating system
# separators.
with TemporaryDirectory() as td:
nbdir = td
fm = FileNotebookManager(notebook_dir=nbdir)
root = td
fm = FileContentsManager(root_dir=root)
path = fm._get_os_path('test.ipynb', '/path/to/notebook/')
rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
fs_path = os.path.join(fm.notebook_dir, *rel_path_list)
fs_path = os.path.join(fm.root_dir, *rel_path_list)
self.assertEqual(path, fs_path)
fm = FileNotebookManager(notebook_dir=nbdir)
fm = FileContentsManager(root_dir=root)
path = fm._get_os_path('test.ipynb')
fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
fs_path = os.path.join(fm.root_dir, 'test.ipynb')
self.assertEqual(path, fs_path)
fm = FileNotebookManager(notebook_dir=nbdir)
fm = FileContentsManager(root_dir=root)
path = fm._get_os_path('test.ipynb', '////')
fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
fs_path = os.path.join(fm.root_dir, 'test.ipynb')
self.assertEqual(path, fs_path)
def test_checkpoint_subdir(self):
subd = u'sub ∂ir'
cp_name = 'test-cp.ipynb'
with TemporaryDirectory() as td:
nbdir = td
root = td
os.mkdir(os.path.join(td, subd))
fm = FileNotebookManager(notebook_dir=nbdir)
fm = FileContentsManager(root_dir=root)
cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/')
cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd)
self.assertNotEqual(cp_dir, cp_subdir)
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_dir, os.path.join(root, fm.checkpoint_dir, cp_name))
self.assertEqual(cp_subdir, os.path.join(root, subd, fm.checkpoint_dir, cp_name))
class TestNotebookManager(TestCase):
@ -75,8 +75,8 @@ class TestNotebookManager(TestCase):
def setUp(self):
self._temp_dir = TemporaryDirectory()
self.td = self._temp_dir.name
self.notebook_manager = FileNotebookManager(
notebook_dir=self.td,
self.contents_manager = FileContentsManager(
root_dir=self.td,
log=logging.getLogger()
)
@ -100,22 +100,22 @@ class TestNotebookManager(TestCase):
nb.worksheets[0].cells.append(cell)
def new_notebook(self):
nbm = self.notebook_manager
model = nbm.create_notebook()
cm = self.contents_manager
model = cm.create_notebook()
name = model['name']
path = model['path']
full_model = nbm.get_notebook(name, path)
full_model = cm.get(name, path)
nb = full_model['content']
self.add_code_cell(nb)
nbm.save_notebook(full_model, name, path)
cm.save(full_model, name, path)
return nb, name, path
def test_create_notebook(self):
nm = self.notebook_manager
cm = self.contents_manager
# Test in root directory
model = nm.create_notebook()
model = cm.create_notebook()
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
@ -124,23 +124,23 @@ class TestNotebookManager(TestCase):
# Test in sub-directory
sub_dir = '/foo/'
self.make_dir(nm.notebook_dir, 'foo')
model = nm.create_notebook(None, sub_dir)
self.make_dir(cm.root_dir, 'foo')
model = cm.create_notebook(None, sub_dir)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
self.assertEqual(model['name'], 'Untitled0.ipynb')
self.assertEqual(model['path'], sub_dir.strip('/'))
def test_get_notebook(self):
nm = self.notebook_manager
def test_get(self):
cm = self.contents_manager
# Create a notebook
model = nm.create_notebook()
model = cm.create_notebook()
name = model['name']
path = model['path']
# Check that we 'get' on the notebook we just created
model2 = nm.get_notebook(name, path)
model2 = cm.get(name, path)
assert isinstance(model2, dict)
self.assertIn('name', model2)
self.assertIn('path', model2)
@ -149,9 +149,9 @@ class TestNotebookManager(TestCase):
# Test in sub-directory
sub_dir = '/foo/'
self.make_dir(nm.notebook_dir, 'foo')
model = nm.create_notebook(None, sub_dir)
model2 = nm.get_notebook(name, sub_dir)
self.make_dir(cm.root_dir, 'foo')
model = cm.create_notebook(None, sub_dir)
model2 = cm.get(name, sub_dir)
assert isinstance(model2, dict)
self.assertIn('name', model2)
self.assertIn('path', model2)
@ -159,35 +159,35 @@ class TestNotebookManager(TestCase):
self.assertEqual(model2['name'], 'Untitled0.ipynb')
self.assertEqual(model2['path'], sub_dir.strip('/'))
def test_update_notebook(self):
nm = self.notebook_manager
def test_update(self):
cm = self.contents_manager
# Create a notebook
model = nm.create_notebook()
model = cm.create_notebook()
name = model['name']
path = model['path']
# Change the name in the model for rename
model['name'] = 'test.ipynb'
model = nm.update_notebook(model, name, path)
model = cm.update(model, name, path)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
self.assertEqual(model['name'], 'test.ipynb')
# Make sure the old name is gone
self.assertRaises(HTTPError, nm.get_notebook, name, path)
self.assertRaises(HTTPError, cm.get, name, path)
# Test in sub-directory
# Create a directory and notebook in that directory
sub_dir = '/foo/'
self.make_dir(nm.notebook_dir, 'foo')
model = nm.create_notebook(None, sub_dir)
self.make_dir(cm.root_dir, 'foo')
model = cm.create_notebook(None, sub_dir)
name = model['name']
path = model['path']
# Change the name in the model for rename
model['name'] = 'test_in_sub.ipynb'
model = nm.update_notebook(model, name, path)
model = cm.update(model, name, path)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
@ -195,20 +195,20 @@ class TestNotebookManager(TestCase):
self.assertEqual(model['path'], sub_dir.strip('/'))
# Make sure the old name is gone
self.assertRaises(HTTPError, nm.get_notebook, name, path)
self.assertRaises(HTTPError, cm.get, name, path)
def test_save_notebook(self):
nm = self.notebook_manager
def test_save(self):
cm = self.contents_manager
# Create a notebook
model = nm.create_notebook()
model = cm.create_notebook()
name = model['name']
path = model['path']
# Get the model with 'content'
full_model = nm.get_notebook(name, path)
full_model = cm.get(name, path)
# Save the notebook
model = nm.save_notebook(full_model, name, path)
model = cm.save(full_model, name, path)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
@ -218,103 +218,84 @@ class TestNotebookManager(TestCase):
# Test in sub-directory
# Create a directory and notebook in that directory
sub_dir = '/foo/'
self.make_dir(nm.notebook_dir, 'foo')
model = nm.create_notebook(None, sub_dir)
self.make_dir(cm.root_dir, 'foo')
model = cm.create_notebook(None, sub_dir)
name = model['name']
path = model['path']
model = nm.get_notebook(name, path)
model = cm.get(name, path)
# Change the name in the model for rename
model = nm.save_notebook(model, name, path)
model = cm.save(model, name, path)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
self.assertEqual(model['name'], 'Untitled0.ipynb')
self.assertEqual(model['path'], sub_dir.strip('/'))
def test_save_notebook_with_script(self):
nm = self.notebook_manager
# Create a notebook
model = nm.create_notebook()
nm.save_script = True
model = nm.create_notebook()
name = model['name']
path = model['path']
# Get the model with 'content'
full_model = nm.get_notebook(name, path)
# Save the notebook
model = nm.save_notebook(full_model, name, path)
# Check that the script was created
py_path = os.path.join(nm.notebook_dir, os.path.splitext(name)[0]+'.py')
assert os.path.exists(py_path), py_path
def test_delete_notebook(self):
nm = self.notebook_manager
def test_delete(self):
cm = self.contents_manager
# Create a notebook
nb, name, path = self.new_notebook()
# Delete the notebook
nm.delete_notebook(name, path)
cm.delete(name, path)
# Check that a 'get' on the deleted notebook raises and error
self.assertRaises(HTTPError, nm.get_notebook, name, path)
self.assertRaises(HTTPError, cm.get, name, path)
def test_copy_notebook(self):
nm = self.notebook_manager
def test_copy(self):
cm = self.contents_manager
path = u'å b'
name = u'nb √.ipynb'
os.mkdir(os.path.join(nm.notebook_dir, path))
orig = nm.create_notebook({'name' : name}, path=path)
os.mkdir(os.path.join(cm.root_dir, path))
orig = cm.create_notebook({'name' : name}, path=path)
# copy with unspecified name
copy = nm.copy_notebook(name, path=path)
copy = cm.copy(name, path=path)
self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb'))
# copy with specified name
copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path)
copy2 = cm.copy(name, u'copy 2.ipynb', path=path)
self.assertEqual(copy2['name'], u'copy 2.ipynb')
def test_trust_notebook(self):
nbm = self.notebook_manager
cm = self.contents_manager
nb, name, path = self.new_notebook()
untrusted = nbm.get_notebook(name, path)['content']
assert not nbm.notary.check_cells(untrusted)
untrusted = cm.get(name, path)['content']
assert not cm.notary.check_cells(untrusted)
# print(untrusted)
nbm.trust_notebook(name, path)
trusted = nbm.get_notebook(name, path)['content']
cm.trust_notebook(name, path)
trusted = cm.get(name, path)['content']
# print(trusted)
assert nbm.notary.check_cells(trusted)
assert cm.notary.check_cells(trusted)
def test_mark_trusted_cells(self):
nbm = self.notebook_manager
cm = self.contents_manager
nb, name, path = self.new_notebook()
nbm.mark_trusted_cells(nb, name, path)
cm.mark_trusted_cells(nb, name, path)
for cell in nb.worksheets[0].cells:
if cell.cell_type == 'code':
assert not cell.trusted
nbm.trust_notebook(name, path)
nb = nbm.get_notebook(name, path)['content']
cm.trust_notebook(name, path)
nb = cm.get(name, path)['content']
for cell in nb.worksheets[0].cells:
if cell.cell_type == 'code':
assert cell.trusted
def test_check_and_sign(self):
nbm = self.notebook_manager
cm = self.contents_manager
nb, name, path = self.new_notebook()
nbm.mark_trusted_cells(nb, name, path)
nbm.check_and_sign(nb, name, path)
assert not nbm.notary.check_signature(nb)
cm.mark_trusted_cells(nb, name, path)
cm.check_and_sign(nb, name, path)
assert not cm.notary.check_signature(nb)
nbm.trust_notebook(name, path)
nb = nbm.get_notebook(name, path)['content']
nbm.mark_trusted_cells(nb, name, path)
nbm.check_and_sign(nb, name, path)
assert nbm.notary.check_signature(nb)
cm.trust_notebook(name, path)
nb = cm.get(name, path)['content']
cm.mark_trusted_cells(nb, name, path)
cm.check_and_sign(nb, name, path)
assert cm.notary.check_signature(nb)

@ -1,20 +1,7 @@
"""Tornado handlers for the sessions web service.
"""Tornado handlers for the sessions web service."""
Authors:
* Zach Sailer
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2013 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import json
@ -24,10 +11,6 @@ from ...base.handlers import IPythonHandler, json_errors
from IPython.utils.jsonutil import date_default
from IPython.html.utils import url_path_join, url_escape
#-----------------------------------------------------------------------------
# Session web service handlers
#-----------------------------------------------------------------------------
class SessionRootHandler(IPythonHandler):
@ -45,6 +28,8 @@ class SessionRootHandler(IPythonHandler):
# Creates a new session
#(unless a session already exists for the named nb)
sm = self.session_manager
cm = self.contents_manager
km = self.kernel_manager
model = self.get_json_body()
if model is None:

@ -32,7 +32,7 @@ from IPython.utils.traitlets import Instance
class SessionManager(LoggingConfigurable):
kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
notebook_manager = Instance('IPython.html.services.notebooks.nbmanager.NotebookManager', args=())
contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=())
# Session database initialized below
_cursor = None
@ -77,7 +77,7 @@ class SessionManager(LoggingConfigurable):
"""Creates a session and returns its model"""
session_id = self.new_session_id()
# allow nbm to specify kernels cwd
kernel_path = self.notebook_manager.get_kernel_path(name=name, path=path)
kernel_path = self.contents_manager.get_kernel_path(name=name, path=path)
kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
kernel_name=kernel_name)
return self.save_session(session_id, name=name, path=path,

@ -1908,7 +1908,7 @@ define([
this.events.trigger('notebook_saving.Notebook');
var url = utils.url_join_encode(
this.base_url,
'api/notebooks',
'api/contents',
this.notebook_path,
this.notebook_name
);
@ -2041,7 +2041,7 @@ define([
};
var url = utils.url_join_encode(
base_url,
'api/notebooks',
'api/contents',
path
);
$.ajax(url,settings);
@ -2070,7 +2070,7 @@ define([
};
var url = utils.url_join_encode(
base_url,
'api/notebooks',
'api/contents',
path
);
$.ajax(url,settings);
@ -2095,7 +2095,7 @@ define([
this.events.trigger('rename_notebook.Notebook', data);
var url = utils.url_join_encode(
this.base_url,
'api/notebooks',
'api/contents',
this.notebook_path,
this.notebook_name
);
@ -2113,7 +2113,7 @@ define([
};
var url = utils.url_join_encode(
this.base_url,
'api/notebooks',
'api/contents',
this.notebook_path,
this.notebook_name
);
@ -2182,7 +2182,7 @@ define([
this.events.trigger('notebook_loading.Notebook');
var url = utils.url_join_encode(
this.base_url,
'api/notebooks',
'api/contents',
this.notebook_path,
this.notebook_name
);
@ -2345,7 +2345,7 @@ define([
Notebook.prototype.list_checkpoints = function () {
var url = utils.url_join_encode(
this.base_url,
'api/notebooks',
'api/contents',
this.notebook_path,
this.notebook_name,
'checkpoints'
@ -2396,7 +2396,7 @@ define([
Notebook.prototype.create_checkpoint = function () {
var url = utils.url_join_encode(
this.base_url,
'api/notebooks',
'api/contents',
this.notebook_path,
this.notebook_name,
'checkpoints'
@ -2485,7 +2485,7 @@ define([
this.events.trigger('notebook_restoring.Notebook', checkpoint);
var url = utils.url_join_encode(
this.base_url,
'api/notebooks',
'api/contents',
this.notebook_path,
this.notebook_name,
'checkpoints',
@ -2533,7 +2533,7 @@ define([
this.events.trigger('notebook_restoring.Notebook', checkpoint);
var url = utils.url_join_encode(
this.base_url,
'api/notebooks',
'api/contents',
this.notebook_path,
this.notebook_name,
'checkpoints',

@ -148,7 +148,7 @@ define([
var url = utils.url_join_encode(
this.base_url,
'api',
'notebooks',
'contents',
this.notebook_path
);
$.ajax(url, settings);
@ -328,7 +328,7 @@ define([
};
var url = utils.url_join_encode(
notebooklist.base_url,
'api/notebooks',
'api/contents',
notebooklist.notebook_path,
nbname
);
@ -375,7 +375,7 @@ define([
var url = utils.url_join_encode(
that.base_url,
'api/notebooks',
'api/contents',
that.notebook_path,
nbname
);
@ -419,7 +419,7 @@ define([
};
var url = utils.url_join_encode(
base_url,
'api/notebooks',
'api/contents',
path
);
$.ajax(url, settings);

@ -33,7 +33,7 @@ class NotebookTestBase(TestCase):
@classmethod
def wait_until_alive(cls):
"""Wait for the server to be alive"""
url = 'http://localhost:%i/api/notebooks' % cls.port
url = 'http://localhost:%i/api/contents' % cls.port
for _ in range(int(MAX_WAITTIME/POLL_INTERVAL)):
try:
requests.get(url)

@ -51,7 +51,7 @@ class TreeHandler(IPythonHandler):
@web.authenticated
def get(self, path='', name=None):
path = path.strip('/')
nbm = self.notebook_manager
cm = self.contents_manager
if name is not None:
# is a notebook, redirect to notebook handler
url = url_escape(url_path_join(
@ -60,10 +60,10 @@ class TreeHandler(IPythonHandler):
self.log.debug("Redirecting %s to %s", self.request.path, url)
self.redirect(url)
else:
if not nbm.path_exists(path=path):
if not cm.path_exists(path=path):
# Directory is hidden or does not exist.
raise web.HTTPError(404)
elif nbm.is_hidden(path):
elif cm.is_hidden(path):
self.log.info("Refusing to serve hidden directory, via 404 Error")
raise web.HTTPError(404)
breadcrumbs = self.generate_breadcrumbs(path)

Loading…
Cancel
Save