diff --git a/IPython/html/services/notebooks/__init__.py b/IPython/html/services/contents/__init__.py
similarity index 100%
rename from IPython/html/services/notebooks/__init__.py
rename to IPython/html/services/contents/__init__.py
diff --git a/IPython/html/services/notebooks/filenbmanager.py b/IPython/html/services/contents/filenbmanager.py
similarity index 100%
rename from IPython/html/services/notebooks/filenbmanager.py
rename to IPython/html/services/contents/filenbmanager.py
index b9bd389..118f02d 100644
--- a/IPython/html/services/notebooks/filenbmanager.py
+++ b/IPython/html/services/contents/filenbmanager.py
@@ -27,10 +27,10 @@ def sort_key(item):
#-----------------------------------------------------------------------------
class FileNotebookManager(NotebookManager):
-
+
save_script = Bool(False, config=True,
help="""Automatically create a Python script when saving the notebook.
-
+
For easier use of import, %run and %load across notebooks, a
.py script will be created next to any
.ipynb on each save. This can also be set with the
@@ -38,7 +38,7 @@ class FileNotebookManager(NotebookManager):
"""
)
notebook_dir = Unicode(getcwd(), config=True)
-
+
def _notebook_dir_changed(self, name, old, new):
"""Do a bit of validation of the notebook dir."""
if not os.path.isabs(new):
@@ -47,19 +47,19 @@ class FileNotebookManager(NotebookManager):
return
if not os.path.exists(new) or not os.path.isdir(new):
raise TraitError("notebook dir %r is not a directory" % new)
-
+
checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
help="""The directory name in which to keep notebook checkpoints
-
+
This is a path relative to the notebook's own directory.
-
+
By default, it is .ipynb_checkpoints
"""
)
-
+
def _copy(self, src, dest):
"""copy src to dest
-
+
like shutil.copy2, but log errors in copystat
"""
shutil.copyfile(src, dest)
@@ -67,7 +67,7 @@ class FileNotebookManager(NotebookManager):
shutil.copystat(src, dest)
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."""
path = path.strip('/')
@@ -80,13 +80,13 @@ class FileNotebookManager(NotebookManager):
def path_exists(self, path):
"""Does the API-style path (directory) actually exist?
-
+
Parameters
----------
path : string
The path to check. This is an API path (`/` separated,
relative to base notebook-dir).
-
+
Returns
-------
exists : bool
@@ -98,18 +98,18 @@ class FileNotebookManager(NotebookManager):
def is_hidden(self, path):
"""Does the API style path correspond to a hidden directory or file?
-
+
Parameters
----------
path : string
The path to check. This is an API path (`/` separated,
relative to base notebook-dir).
-
+
Returns
-------
exists : bool
Whether the path is hidden.
-
+
"""
path = path.strip('/')
os_path = self._get_os_path(path=path)
@@ -204,13 +204,13 @@ class FileNotebookManager(NotebookManager):
def list_notebooks(self, path):
"""Returns a list of dictionaries that are the standard model
for all notebooks in the relative 'path'.
-
+
Parameters
----------
path : str
the URL path that describes the relative path for the
listed notebooks
-
+
Returns
-------
notebooks : list of dicts
@@ -225,7 +225,7 @@ class FileNotebookManager(NotebookManager):
def get_notebook(self, name, path='', content=True):
""" Takes a path and name for a notebook and returns its model
-
+
Parameters
----------
name : str
@@ -233,11 +233,11 @@ class FileNotebookManager(NotebookManager):
path : str
the URL path that describes the relative path for
the notebook
-
+
Returns
-------
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.
"""
path = path.strip('/')
@@ -284,9 +284,9 @@ class FileNotebookManager(NotebookManager):
# Save the notebook file
os_path = self._get_os_path(new_name, new_path)
nb = current.to_notebook_json(model['content'])
-
+
self.check_and_sign(nb, new_name, new_path)
-
+
if 'name' in nb['metadata']:
nb['metadata']['name'] = u''
try:
@@ -325,7 +325,7 @@ class FileNotebookManager(NotebookManager):
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)
-
+
# clear checkpoints
for checkpoint in self.list_checkpoints(name, path):
checkpoint_id = checkpoint['id']
@@ -333,7 +333,7 @@ class FileNotebookManager(NotebookManager):
if os.path.isfile(cp_path):
self.log.debug("Unlinking checkpoint %s", cp_path)
os.unlink(cp_path)
-
+
self.log.debug("Unlinking notebook %s", os_path)
os.unlink(os_path)
@@ -343,7 +343,7 @@ class FileNotebookManager(NotebookManager):
new_path = new_path.strip('/')
if new_name == old_name and new_path == old_path:
return
-
+
new_os_path = self._get_os_path(new_name, new_path)
old_os_path = self._get_os_path(old_name, old_path)
@@ -375,9 +375,9 @@ class FileNotebookManager(NotebookManager):
# 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('/')
@@ -404,9 +404,9 @@ class FileNotebookManager(NotebookManager):
last_modified = last_modified,
)
return info
-
+
# public checkpoint API
-
+
def create_checkpoint(self, name, path=''):
"""Create a checkpoint from the current state of a notebook"""
path = path.strip('/')
@@ -416,13 +416,13 @@ class FileNotebookManager(NotebookManager):
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)
-
+
# 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
-
+
This notebook manager currently only supports one checkpoint per notebook.
"""
path = path.strip('/')
@@ -432,8 +432,8 @@ class FileNotebookManager(NotebookManager):
return []
else:
return [self.get_checkpoint_model(checkpoint_id, name, path)]
-
-
+
+
def restore_checkpoint(self, checkpoint_id, name, path=''):
"""restore a notebook to a checkpointed state"""
path = path.strip('/')
@@ -450,7 +450,7 @@ class FileNotebookManager(NotebookManager):
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"""
path = path.strip('/')
@@ -461,7 +461,7 @@ class FileNotebookManager(NotebookManager):
)
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
diff --git a/IPython/html/services/notebooks/handlers.py b/IPython/html/services/contents/handlers.py
similarity index 100%
rename from IPython/html/services/notebooks/handlers.py
rename to IPython/html/services/contents/handlers.py
index dab6849..5647ce7 100644
--- a/IPython/html/services/notebooks/handlers.py
+++ b/IPython/html/services/contents/handlers.py
@@ -38,7 +38,7 @@ class NotebookHandler(IPythonHandler):
def notebook_location(self, name, path=''):
"""Return the full URL location of a notebook based.
-
+
Parameters
----------
name : unicode
@@ -57,7 +57,7 @@ class NotebookHandler(IPythonHandler):
self.set_header('Location', location)
self.set_header('Last-Modified', model['last_modified'])
self.finish(json.dumps(model, default=date_default))
-
+
@web.authenticated
@json_errors
def get(self, path='', name=None):
@@ -99,10 +99,10 @@ class NotebookHandler(IPythonHandler):
raise web.HTTPError(400, u'JSON body missing')
model = nbm.update_notebook(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.
-
+
Only support copying within the same directory.
"""
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)
self.set_status(201)
self._finish_model(model)
-
+
def _upload_notebook(self, model, path, name=None):
"""Upload a notebook
-
+
If name specified, create it in path/name.
"""
self.log.info(u"Uploading notebook to %s/%s", path, name or '')
if name:
model['name'] = name
-
+
model = self.notebook_manager.create_notebook(model, path)
self.set_status(201)
self._finish_model(model)
-
+
def _create_empty_notebook(self, path, name=None):
"""Create an empty notebook in path
-
+
If name specified, create it in path/name.
"""
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)
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)
@@ -149,26 +149,26 @@ class NotebookHandler(IPythonHandler):
else:
location = False
self._finish_model(model, location)
-
+
@web.authenticated
@json_errors
def post(self, path='', name=None):
"""Create a new notebook in the specified path.
-
+
POST creates new notebooks. The server always decides on the notebook name.
-
+
POST /api/notebooks/path
New untitled notebook in path. If content specified, upload a
notebook, otherwise start empty.
POST /api/notebooks/path?copy=OtherNotebook.ipynb
New copy of OtherNotebook in path
"""
-
+
if name is not None:
raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
-
+
model = self.get_json_body()
-
+
if model is not None:
copy_from = model.get('copy_from')
if copy_from:
@@ -184,10 +184,10 @@ class NotebookHandler(IPythonHandler):
@json_errors
def put(self, path='', name=None):
"""Saves the notebook 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
Save notebook at ``path/Name.ipynb``. Notebook structure is specified
in `content` key of JSON request body. If content is not specified,
@@ -197,7 +197,7 @@ class NotebookHandler(IPythonHandler):
"""
if name is None:
raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
-
+
model = self.get_json_body()
if model:
copy_from = model.get('copy_from')
@@ -223,9 +223,9 @@ class NotebookHandler(IPythonHandler):
class NotebookCheckpointsHandler(IPythonHandler):
-
+
SUPPORTED_METHODS = ('GET', 'POST')
-
+
@web.authenticated
@json_errors
def get(self, path='', name=None):
@@ -234,7 +234,7 @@ class NotebookCheckpointsHandler(IPythonHandler):
checkpoints = nbm.list_checkpoints(name, path)
data = json.dumps(checkpoints, default=date_default)
self.finish(data)
-
+
@web.authenticated
@json_errors
def post(self, path='', name=None):
@@ -250,9 +250,9 @@ class NotebookCheckpointsHandler(IPythonHandler):
class ModifyNotebookCheckpointsHandler(IPythonHandler):
-
+
SUPPORTED_METHODS = ('POST', 'DELETE')
-
+
@web.authenticated
@json_errors
def post(self, path, name, checkpoint_id):
@@ -261,7 +261,7 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
nbm.restore_checkpoint(checkpoint_id, name, path)
self.set_status(204)
self.finish()
-
+
@web.authenticated
@json_errors
def delete(self, path, name, checkpoint_id):
@@ -270,7 +270,7 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
nbm.delete_checkpoint(checkpoint_id, name, path)
self.set_status(204)
self.finish()
-
+
#-----------------------------------------------------------------------------
# URL to handler mappings
#-----------------------------------------------------------------------------
@@ -285,4 +285,3 @@ default_handlers = [
(r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
(r"/api/notebooks%s" % path_regex, NotebookHandler),
]
-
diff --git a/IPython/html/services/notebooks/nbmanager.py b/IPython/html/services/contents/nbmanager.py
similarity index 100%
rename from IPython/html/services/notebooks/nbmanager.py
rename to IPython/html/services/contents/nbmanager.py
index d5b6907..5f8bd97 100644
--- a/IPython/html/services/notebooks/nbmanager.py
+++ b/IPython/html/services/contents/nbmanager.py
@@ -32,11 +32,11 @@ from IPython.utils.traitlets import Instance, Unicode, List
class NotebookManager(LoggingConfigurable):
filename_ext = Unicode(u'.ipynb')
-
+
notary = Instance(sign.NotebookNotary)
def _notary_default(self):
return sign.NotebookNotary(parent=self)
-
+
hide_globs = List(Unicode, [u'__pycache__'], config=True, help="""
Glob patterns to hide in file and directory listings.
""")
@@ -46,14 +46,14 @@ class NotebookManager(LoggingConfigurable):
def path_exists(self, path):
"""Does the API-style path (directory) actually exist?
-
+
Override this method in subclasses.
-
+
Parameters
----------
path : string
The path to check
-
+
Returns
-------
exists : bool
@@ -63,18 +63,18 @@ class NotebookManager(LoggingConfigurable):
def is_hidden(self, path):
"""Does the API style path correspond to a hidden directory or file?
-
+
Parameters
----------
path : string
The path to check. This is an API path (`/` separated,
relative to base notebook-dir).
-
+
Returns
-------
exists : bool
Whether the path is hidden.
-
+
"""
raise NotImplementedError
@@ -104,7 +104,7 @@ class NotebookManager(LoggingConfigurable):
# no longer listed by the notebook web service.
def get_dir_model(self, name, path=''):
"""Get the directory model given a directory name and its API style path.
-
+
The keys in the model should be:
* name
* path
@@ -145,15 +145,15 @@ class NotebookManager(LoggingConfigurable):
def create_checkpoint(self, name, path=''):
"""Create a checkpoint of the current state of a notebook
-
+
Returns a checkpoint_id for the new checkpoint.
"""
raise NotImplementedError("must be implemented in a subclass")
-
+
def list_checkpoints(self, name, path=''):
"""Return a list of checkpoints for a given notebook"""
return []
-
+
def restore_checkpoint(self, checkpoint_id, name, path=''):
"""Restore a notebook from one of its checkpoints"""
raise NotImplementedError("must be implemented in a subclass")
@@ -161,7 +161,7 @@ class NotebookManager(LoggingConfigurable):
def delete_checkpoint(self, checkpoint_id, name, path=''):
"""delete a checkpoint for a notebook"""
raise NotImplementedError("must be implemented in a subclass")
-
+
def info_string(self):
return "Serving notebooks"
@@ -174,7 +174,7 @@ class NotebookManager(LoggingConfigurable):
def increment_filename(self, basename, path=''):
"""Increment a notebook filename without the .ipynb to make it unique.
-
+
Parameters
----------
basename : unicode
@@ -206,14 +206,14 @@ class NotebookManager(LoggingConfigurable):
model['content'] = current.new_notebook(metadata=metadata)
if 'name' not in model:
model['name'] = self.increment_filename('Untitled', path)
-
+
model['path'] = path
model = self.save_notebook(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.
-
+
If to_name not specified, increment `from_name-Copy#.ipynb`.
"""
path = path.strip('/')
@@ -224,13 +224,13 @@ class NotebookManager(LoggingConfigurable):
model['name'] = to_name
model = self.save_notebook(model, to_name, path)
return model
-
+
def log_info(self):
self.log.info(self.info_string())
def trust_notebook(self, name, path=''):
"""Explicitly trust a notebook
-
+
Parameters
----------
name : string
@@ -243,12 +243,12 @@ class NotebookManager(LoggingConfigurable):
self.log.warn("Trusting notebook %s/%s", path, name)
self.notary.mark_cells(nb, True)
self.save_notebook(model, name, path)
-
+
def check_and_sign(self, nb, name, path=''):
"""Check for trusted cells, and sign the notebook.
-
+
Called as a part of saving notebooks.
-
+
Parameters
----------
nb : dict
@@ -262,12 +262,12 @@ class NotebookManager(LoggingConfigurable):
self.notary.sign(nb)
else:
self.log.warn("Saving untrusted notebook %s/%s", path, name)
-
+
def mark_trusted_cells(self, nb, name, path=''):
"""Mark cells as trusted if the notebook signature matches.
-
+
Called as a part of loading notebooks.
-
+
Parameters
----------
nb : dict
diff --git a/IPython/html/services/notebooks/tests/__init__.py b/IPython/html/services/contents/tests/__init__.py
similarity index 100%
rename from IPython/html/services/notebooks/tests/__init__.py
rename to IPython/html/services/contents/tests/__init__.py
diff --git a/IPython/html/services/notebooks/tests/test_nbmanager.py b/IPython/html/services/contents/tests/test_nbmanager.py
similarity index 100%
rename from IPython/html/services/notebooks/tests/test_nbmanager.py
rename to IPython/html/services/contents/tests/test_nbmanager.py
index bc03a87..c4b85b9 100644
--- a/IPython/html/services/notebooks/tests/test_nbmanager.py
+++ b/IPython/html/services/contents/tests/test_nbmanager.py
@@ -55,7 +55,7 @@ class TestFileNotebookManager(TestCase):
path = fm._get_os_path('test.ipynb', '////')
fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
self.assertEqual(path, fs_path)
-
+
def test_checkpoint_subdir(self):
subd = u'sub ∂ir'
cp_name = 'test-cp.ipynb'
@@ -68,10 +68,10 @@ class TestFileNotebookManager(TestCase):
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))
-
+
class TestNotebookManager(TestCase):
-
+
def setUp(self):
self._temp_dir = TemporaryDirectory()
self.td = self._temp_dir.name
@@ -79,10 +79,10 @@ class TestNotebookManager(TestCase):
notebook_dir=self.td,
log=logging.getLogger()
)
-
+
def tearDown(self):
self._temp_dir.cleanup()
-
+
def make_dir(self, abs_path, rel_path):
"""make subdirectory, rel_path is the relative path
to that directory from the location where the server started"""
@@ -91,27 +91,27 @@ class TestNotebookManager(TestCase):
os.makedirs(os_path)
except OSError:
print("Directory already exists: %r" % os_path)
-
+
def add_code_cell(self, nb):
output = current.new_output("display_data", output_javascript="alert('hi');")
cell = current.new_code_cell("print('hi')", outputs=[output])
if not nb.worksheets:
nb.worksheets.append(current.new_worksheet())
nb.worksheets[0].cells.append(cell)
-
+
def new_notebook(self):
nbm = self.notebook_manager
model = nbm.create_notebook()
name = model['name']
path = model['path']
-
+
full_model = nbm.get_notebook(name, path)
nb = full_model['content']
self.add_code_cell(nb)
-
+
nbm.save_notebook(full_model, name, path)
return nb, name, path
-
+
def test_create_notebook(self):
nm = self.notebook_manager
# Test in root directory
@@ -158,7 +158,7 @@ class TestNotebookManager(TestCase):
self.assertIn('content', model2)
self.assertEqual(model2['name'], 'Untitled0.ipynb')
self.assertEqual(model2['path'], sub_dir.strip('/'))
-
+
def test_update_notebook(self):
nm = self.notebook_manager
# Create a notebook
@@ -184,7 +184,7 @@ class TestNotebookManager(TestCase):
model = nm.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)
@@ -193,7 +193,7 @@ class TestNotebookManager(TestCase):
self.assertIn('path', model)
self.assertEqual(model['name'], 'test_in_sub.ipynb')
self.assertEqual(model['path'], sub_dir.strip('/'))
-
+
# Make sure the old name is gone
self.assertRaises(HTTPError, nm.get_notebook, name, path)
@@ -255,50 +255,50 @@ class TestNotebookManager(TestCase):
nm = self.notebook_manager
# Create a notebook
nb, name, path = self.new_notebook()
-
+
# Delete the notebook
nm.delete_notebook(name, path)
-
+
# Check that a 'get' on the deleted notebook raises and error
self.assertRaises(HTTPError, nm.get_notebook, name, path)
-
+
def test_copy_notebook(self):
nm = self.notebook_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)
-
+
# copy with unspecified name
copy = nm.copy_notebook(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)
self.assertEqual(copy2['name'], u'copy 2.ipynb')
-
+
def test_trust_notebook(self):
nbm = self.notebook_manager
nb, name, path = self.new_notebook()
-
+
untrusted = nbm.get_notebook(name, path)['content']
assert not nbm.notary.check_cells(untrusted)
-
+
# print(untrusted)
nbm.trust_notebook(name, path)
trusted = nbm.get_notebook(name, path)['content']
# print(trusted)
assert nbm.notary.check_cells(trusted)
-
+
def test_mark_trusted_cells(self):
nbm = self.notebook_manager
nb, name, path = self.new_notebook()
-
+
nbm.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']
for cell in nb.worksheets[0].cells:
@@ -308,11 +308,11 @@ class TestNotebookManager(TestCase):
def test_check_and_sign(self):
nbm = self.notebook_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)
-
+
nbm.trust_notebook(name, path)
nb = nbm.get_notebook(name, path)['content']
nbm.mark_trusted_cells(nb, name, path)
diff --git a/IPython/html/services/notebooks/tests/test_notebooks_api.py b/IPython/html/services/contents/tests/test_notebooks_api.py
similarity index 100%
rename from IPython/html/services/notebooks/tests/test_notebooks_api.py
rename to IPython/html/services/contents/tests/test_notebooks_api.py
index c8c82e8..74c9a25 100644
--- a/IPython/html/services/notebooks/tests/test_notebooks_api.py
+++ b/IPython/html/services/contents/tests/test_notebooks_api.py
@@ -163,7 +163,7 @@ class APITest(NotebookTestBase):
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())
nbnames = [n['name'] for n in nbs]
expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
@@ -344,4 +344,3 @@ class APITest(NotebookTestBase):
self.assertEqual(r.status_code, 204)
cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
self.assertEqual(cps, [])
-