2014-05-31 04:01:21 +08:00
|
|
|
"""A base class for contents managers."""
|
2012-06-27 07:15:42 +08:00
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
# Copyright (c) IPython Development Team.
|
|
|
|
# Distributed under the terms of the Modified BSD License.
|
2012-06-27 07:15:42 +08:00
|
|
|
|
2014-03-01 03:19:54 +08:00
|
|
|
from fnmatch import fnmatch
|
2014-02-11 00:10:31 +08:00
|
|
|
import itertools
|
2012-06-27 07:56:43 +08:00
|
|
|
import os
|
2012-06-27 07:15:42 +08:00
|
|
|
|
|
|
|
from IPython.config.configurable import LoggingConfigurable
|
2014-01-18 09:39:41 +08:00
|
|
|
from IPython.nbformat import current, sign
|
2014-03-01 03:19:54 +08:00
|
|
|
from IPython.utils.traitlets import Instance, Unicode, List
|
2012-06-27 07:15:42 +08:00
|
|
|
|
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
class ContentsManager(LoggingConfigurable):
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-01-18 09:39:41 +08:00
|
|
|
notary = Instance(sign.NotebookNotary)
|
|
|
|
def _notary_default(self):
|
|
|
|
return sign.NotebookNotary(parent=self)
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-03-01 03:19:54 +08:00
|
|
|
hide_globs = List(Unicode, [u'__pycache__'], config=True, help="""
|
|
|
|
Glob patterns to hide in file and directory listings.
|
|
|
|
""")
|
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
# ContentsManager API part 1: methods that must be
|
2014-02-11 00:29:56 +08:00
|
|
|
# implemented in subclasses.
|
|
|
|
|
2013-10-08 03:19:14 +08:00
|
|
|
def path_exists(self, path):
|
|
|
|
"""Does the API-style path (directory) actually exist?
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2013-10-08 03:38:34 +08:00
|
|
|
Override this method in subclasses.
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2013-08-13 13:23:45 +08:00
|
|
|
Parameters
|
|
|
|
----------
|
2013-10-08 03:19:14 +08:00
|
|
|
path : string
|
2014-03-04 08:27:59 +08:00
|
|
|
The path to check
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2013-08-13 13:23:45 +08:00
|
|
|
Returns
|
|
|
|
-------
|
2013-10-08 03:19:14 +08:00
|
|
|
exists : bool
|
|
|
|
Whether the path does indeed exist.
|
2013-08-13 13:23:45 +08:00
|
|
|
"""
|
2013-10-08 03:38:34 +08:00
|
|
|
raise NotImplementedError
|
2014-02-06 05:09:55 +08:00
|
|
|
|
|
|
|
def is_hidden(self, path):
|
|
|
|
"""Does the API style path correspond to a hidden directory or file?
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-06 05:09:55 +08:00
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
path : string
|
|
|
|
The path to check. This is an API path (`/` separated,
|
2014-05-31 04:01:21 +08:00
|
|
|
relative to root dir).
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-06 05:09:55 +08:00
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
exists : bool
|
|
|
|
Whether the path is hidden.
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-06 05:09:55 +08:00
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
def file_exists(self, name, path=''):
|
2014-02-11 00:07:37 +08:00
|
|
|
"""Returns a True if the notebook exists. Else, returns False.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name : string
|
|
|
|
The name of the notebook you are checking.
|
|
|
|
path : string
|
|
|
|
The relative path to the notebook (with '/' as separator)
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
bool
|
|
|
|
"""
|
|
|
|
raise NotImplementedError('must be implemented in a subclass')
|
|
|
|
|
2014-06-03 04:47:11 +08:00
|
|
|
def list(self, path=''):
|
2014-05-31 04:01:21 +08:00
|
|
|
"""Return a list of contents dicts without content.
|
2012-06-27 07:15:42 +08:00
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
This returns a list of dicts
|
2012-06-27 07:15:42 +08:00
|
|
|
|
|
|
|
This list of dicts should be sorted by name::
|
|
|
|
|
|
|
|
data = sorted(data, key=lambda item: item['name'])
|
|
|
|
"""
|
|
|
|
raise NotImplementedError('must be implemented in a subclass')
|
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
def get_model(self, name, path='', content=True):
|
2013-08-27 02:18:34 +08:00
|
|
|
"""Get the notebook model with or without content."""
|
2012-06-27 07:15:42 +08:00
|
|
|
raise NotImplementedError('must be implemented in a subclass')
|
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
def save(self, model, name, path=''):
|
2014-02-11 00:01:39 +08:00
|
|
|
"""Save the notebook and return the model with no content."""
|
2013-08-27 02:18:34 +08:00
|
|
|
raise NotImplementedError('must be implemented in a subclass')
|
2012-06-27 07:15:42 +08:00
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
def update(self, model, name, path=''):
|
2014-02-11 00:01:39 +08:00
|
|
|
"""Update the notebook and return the model with no content."""
|
2012-06-27 07:15:42 +08:00
|
|
|
raise NotImplementedError('must be implemented in a subclass')
|
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
def delete(self, name, path=''):
|
2013-08-27 02:18:34 +08:00
|
|
|
"""Delete notebook by name and path."""
|
2012-06-27 07:15:42 +08:00
|
|
|
raise NotImplementedError('must be implemented in a subclass')
|
|
|
|
|
2014-02-11 00:29:56 +08:00
|
|
|
def create_checkpoint(self, name, path=''):
|
|
|
|
"""Create a checkpoint of the current state of a notebook
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-11 00:29:56 +08:00
|
|
|
Returns a checkpoint_id for the new checkpoint.
|
|
|
|
"""
|
|
|
|
raise NotImplementedError("must be implemented in a subclass")
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-11 00:29:56 +08:00
|
|
|
def list_checkpoints(self, name, path=''):
|
|
|
|
"""Return a list of checkpoints for a given notebook"""
|
|
|
|
return []
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-11 00:29:56 +08:00
|
|
|
def restore_checkpoint(self, checkpoint_id, name, path=''):
|
|
|
|
"""Restore a notebook from one of its checkpoints"""
|
|
|
|
raise NotImplementedError("must be implemented in a subclass")
|
|
|
|
|
|
|
|
def delete_checkpoint(self, checkpoint_id, name, path=''):
|
|
|
|
"""delete a checkpoint for a notebook"""
|
|
|
|
raise NotImplementedError("must be implemented in a subclass")
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-11 00:29:56 +08:00
|
|
|
def info_string(self):
|
|
|
|
return "Serving notebooks"
|
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
# ContentsManager API part 2: methods that have useable default
|
2014-02-11 00:29:56 +08:00
|
|
|
# implementations, but can be overridden in subclasses.
|
|
|
|
|
2014-03-30 05:39:15 +08:00
|
|
|
def get_kernel_path(self, name, path='', model=None):
|
|
|
|
""" Return the path to start kernel in """
|
|
|
|
return path
|
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
def increment_filename(self, filename, path=''):
|
|
|
|
"""Increment a filename until it is unique.
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-11 00:29:56 +08:00
|
|
|
Parameters
|
|
|
|
----------
|
2014-05-31 04:01:21 +08:00
|
|
|
filename : unicode
|
|
|
|
The name of a file, including extension
|
2014-02-11 00:29:56 +08:00
|
|
|
path : unicode
|
|
|
|
The URL path of the notebooks directory
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
name : unicode
|
2014-05-31 04:01:21 +08:00
|
|
|
A filename that is unique, based on the input filename.
|
2014-02-11 00:29:56 +08:00
|
|
|
"""
|
|
|
|
path = path.strip('/')
|
2014-05-31 04:01:21 +08:00
|
|
|
basename, ext = os.path.splitext(filename)
|
2014-02-11 00:29:56 +08:00
|
|
|
for i in itertools.count():
|
|
|
|
name = u'{basename}{i}{ext}'.format(basename=basename, i=i,
|
2014-05-31 04:01:21 +08:00
|
|
|
ext=ext)
|
|
|
|
if not self.file_exists(name, path):
|
2014-02-11 00:29:56 +08:00
|
|
|
break
|
|
|
|
return name
|
|
|
|
|
2014-02-11 00:01:39 +08:00
|
|
|
def create_notebook(self, model=None, path=''):
|
2013-10-24 01:37:49 +08:00
|
|
|
"""Create a new notebook and return its model with no content."""
|
2013-10-09 02:39:03 +08:00
|
|
|
path = path.strip('/')
|
2013-08-27 02:18:34 +08:00
|
|
|
if model is None:
|
|
|
|
model = {}
|
2013-10-08 07:32:38 +08:00
|
|
|
if 'content' not in model:
|
2013-08-27 02:18:34 +08:00
|
|
|
metadata = current.new_metadata(name=u'')
|
2013-10-08 07:32:38 +08:00
|
|
|
model['content'] = current.new_notebook(metadata=metadata)
|
|
|
|
if 'name' not in model:
|
2014-05-31 04:01:21 +08:00
|
|
|
model['name'] = self.increment_filename('Untitled.ipynb', path)
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2013-10-08 07:32:38 +08:00
|
|
|
model['path'] = path
|
2014-05-31 04:01:21 +08:00
|
|
|
model = self.save(model, model['name'], model['path'])
|
2013-08-27 02:18:34 +08:00
|
|
|
return model
|
2012-06-27 07:15:42 +08:00
|
|
|
|
2014-05-31 04:01:21 +08:00
|
|
|
def copy(self, from_name, to_name=None, path=''):
|
|
|
|
"""Copy an existing file and return its new model.
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2013-10-18 10:32:05 +08:00
|
|
|
If to_name not specified, increment `from_name-Copy#.ipynb`.
|
|
|
|
"""
|
2013-10-09 02:39:03 +08:00
|
|
|
path = path.strip('/')
|
2014-06-03 04:47:11 +08:00
|
|
|
model = self.get_model(from_name, path)
|
2013-10-18 10:32:05 +08:00
|
|
|
if not to_name:
|
2014-05-31 04:01:21 +08:00
|
|
|
base, ext = os.path.splitext(from_name)
|
|
|
|
copy_name = u'{0}-Copy{1}'.format(base, ext)
|
|
|
|
to_name = self.increment_filename(copy_name, path)
|
2013-10-18 10:32:05 +08:00
|
|
|
model['name'] = to_name
|
2014-05-31 04:01:21 +08:00
|
|
|
model = self.save(model, to_name, path)
|
2013-08-27 02:18:34 +08:00
|
|
|
return model
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-11 00:29:56 +08:00
|
|
|
def log_info(self):
|
|
|
|
self.log.info(self.info_string())
|
|
|
|
|
2014-02-28 08:33:49 +08:00
|
|
|
def trust_notebook(self, name, path=''):
|
2014-03-04 08:27:59 +08:00
|
|
|
"""Explicitly trust a notebook
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-03-04 08:27:59 +08:00
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name : string
|
|
|
|
The filename of the notebook
|
|
|
|
path : string
|
|
|
|
The notebook's directory
|
2014-02-28 08:33:49 +08:00
|
|
|
"""
|
2014-06-03 04:47:11 +08:00
|
|
|
model = self.get_model(name, path)
|
2014-02-28 08:33:49 +08:00
|
|
|
nb = model['content']
|
|
|
|
self.log.warn("Trusting notebook %s/%s", path, name)
|
|
|
|
self.notary.mark_cells(nb, True)
|
2014-05-31 04:01:21 +08:00
|
|
|
self.save(model, name, path)
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-28 08:33:49 +08:00
|
|
|
def check_and_sign(self, nb, name, path=''):
|
2014-02-11 00:29:56 +08:00
|
|
|
"""Check for trusted cells, and sign the notebook.
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-11 00:29:56 +08:00
|
|
|
Called as a part of saving notebooks.
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-03-04 08:27:59 +08:00
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
nb : dict
|
|
|
|
The notebook structure
|
|
|
|
name : string
|
|
|
|
The filename of the notebook
|
|
|
|
path : string
|
|
|
|
The notebook's directory
|
2013-04-10 04:49:10 +08:00
|
|
|
"""
|
2014-02-11 00:29:56 +08:00
|
|
|
if self.notary.check_cells(nb):
|
|
|
|
self.notary.sign(nb)
|
|
|
|
else:
|
|
|
|
self.log.warn("Saving untrusted notebook %s/%s", path, name)
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-28 08:33:49 +08:00
|
|
|
def mark_trusted_cells(self, nb, name, path=''):
|
2014-02-11 00:29:56 +08:00
|
|
|
"""Mark cells as trusted if the notebook signature matches.
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-02-11 00:29:56 +08:00
|
|
|
Called as a part of loading notebooks.
|
2014-05-31 03:39:11 +08:00
|
|
|
|
2014-03-04 08:27:59 +08:00
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
nb : dict
|
|
|
|
The notebook structure
|
|
|
|
name : string
|
|
|
|
The filename of the notebook
|
|
|
|
path : string
|
|
|
|
The notebook's directory
|
2014-02-11 00:29:56 +08:00
|
|
|
"""
|
|
|
|
trusted = self.notary.check_signature(nb)
|
|
|
|
if not trusted:
|
|
|
|
self.log.warn("Notebook %s/%s is not trusted", path, name)
|
|
|
|
self.notary.mark_cells(nb, trusted)
|
2012-06-27 07:56:43 +08:00
|
|
|
|
2014-03-01 03:19:54 +08:00
|
|
|
def should_list(self, name):
|
|
|
|
"""Should this file/directory name be displayed in a listing?"""
|
|
|
|
return not any(fnmatch(name, glob) for glob in self.hide_globs)
|