nbmanager.py
269 lines
| 9.3 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r8180 | """A base class notebook manager. | ||
Authors: | ||||
* Brian Granger | ||||
Zachary Sailer
|
r13046 | * Zach Sailer | ||
Brian Granger
|
r8180 | """ | ||
#----------------------------------------------------------------------------- | ||||
# 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 | ||||
#----------------------------------------------------------------------------- | ||||
Konrad Hinsen
|
r15292 | import itertools | ||
Brian Granger
|
r8181 | import os | ||
Brian Granger
|
r8180 | |||
from IPython.config.configurable import LoggingConfigurable | ||||
MinRK
|
r14857 | from IPython.nbformat import current, sign | ||
Thomas Kluyver
|
r13447 | from IPython.utils import py3compat | ||
MinRK
|
r14857 | from IPython.utils.traitlets import Instance, Unicode, TraitError | ||
Brian Granger
|
r8180 | |||
#----------------------------------------------------------------------------- | ||||
# Classes | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r8194 | class NotebookManager(LoggingConfigurable): | ||
Brian Granger
|
r8180 | |||
Brian Granger
|
r8181 | # Todo: | ||
# The notebook_dir attribute is used to mean a couple of different things: | ||||
# 1. Where the notebooks are stored if FileNotebookManager is used. | ||||
# 2. The cwd of the kernel for a project. | ||||
# Right now we use this attribute in a number of different places and | ||||
MinRK
|
r10497 | # we are going to have to disentangle all of this. | ||
Thomas Kluyver
|
r13447 | notebook_dir = Unicode(py3compat.getcwd(), config=True, help=""" | ||
Zachary Sailer
|
r12984 | The directory to use for notebooks. | ||
""") | ||||
Paul Ivanov
|
r13026 | |||
Zachary Sailer
|
r13046 | filename_ext = Unicode(u'.ipynb') | ||
MinRK
|
r13067 | |||
MinRK
|
r14857 | notary = Instance(sign.NotebookNotary) | ||
def _notary_default(self): | ||||
return sign.NotebookNotary(parent=self) | ||||
MinRK
|
r14856 | |||
Konrad Hinsen
|
r15293 | # NotebookManager API part 1: methods that must be | ||
# implemented in subclasses. | ||||
MinRK
|
r13067 | def path_exists(self, path): | ||
"""Does the API-style path (directory) actually exist? | ||||
MinRK
|
r13070 | Override this method in subclasses. | ||
MinRK
|
r13067 | |||
Paul Ivanov
|
r13026 | Parameters | ||
---------- | ||||
MinRK
|
r13067 | path : string | ||
The | ||||
Paul Ivanov
|
r13026 | Returns | ||
------- | ||||
MinRK
|
r13067 | exists : bool | ||
Whether the path does indeed exist. | ||||
Paul Ivanov
|
r13026 | """ | ||
MinRK
|
r13070 | raise NotImplementedError | ||
Brian E. Granger
|
r15097 | |||
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 | ||||
Konrad Hinsen
|
r15291 | def notebook_exists(self, name, path=''): | ||
"""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') | ||||
Brian E. Granger
|
r15093 | # TODO: Remove this after we create the contents web service and directories are | ||
# no longer listed by the notebook web service. | ||||
def list_dirs(self, path): | ||||
"""List the directory models for a given API style path.""" | ||||
raise NotImplementedError('must be implemented in a subclass') | ||||
# TODO: Remove this after we create the contents web service and directories are | ||||
# 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 | ||||
* last_modified | ||||
* created | ||||
* type='directory' | ||||
""" | ||||
raise NotImplementedError('must be implemented in a subclass') | ||||
MinRK
|
r13074 | def list_notebooks(self, path=''): | ||
Zachary Sailer
|
r13046 | """Return a list of notebook dicts without content. | ||
Brian Granger
|
r8180 | |||
This returns a list of dicts, each of the form:: | ||||
dict(notebook_id=notebook,name=name) | ||||
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') | ||||
Konrad Hinsen
|
r15290 | def get_notebook(self, name, path='', content=True): | ||
Zachary Sailer
|
r13046 | """Get the notebook model with or without content.""" | ||
Brian Granger
|
r8180 | raise NotImplementedError('must be implemented in a subclass') | ||
Konrad Hinsen
|
r15290 | def save_notebook(self, model, name, path=''): | ||
"""Save the notebook and return the model with no content.""" | ||||
Zachary Sailer
|
r13046 | raise NotImplementedError('must be implemented in a subclass') | ||
Brian Granger
|
r8180 | |||
Konrad Hinsen
|
r15290 | def update_notebook(self, model, name, path=''): | ||
"""Update the notebook and return the model with no content.""" | ||||
Brian Granger
|
r8180 | raise NotImplementedError('must be implemented in a subclass') | ||
Konrad Hinsen
|
r15290 | def delete_notebook(self, name, path=''): | ||
Zachary Sailer
|
r13046 | """Delete notebook by name and path.""" | ||
Brian Granger
|
r8180 | raise NotImplementedError('must be implemented in a subclass') | ||
Konrad Hinsen
|
r15293 | 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") | ||||
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" | ||||
# NotebookManager API part 2: methods that have useable default | ||||
# implementations, but can be overridden in subclasses. | ||||
def increment_filename(self, basename, path=''): | ||||
"""Increment a notebook filename without the .ipynb to make it unique. | ||||
Parameters | ||||
---------- | ||||
basename : unicode | ||||
The name of a notebook without the ``.ipynb`` file 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. | ||||
""" | ||||
path = path.strip('/') | ||||
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): | ||||
break | ||||
return name | ||||
Konrad Hinsen
|
r15290 | def create_notebook(self, model=None, path=''): | ||
MinRK
|
r13245 | """Create a new notebook and return its model with no content.""" | ||
MinRK
|
r13078 | path = path.strip('/') | ||
Zachary Sailer
|
r13046 | if model is None: | ||
model = {} | ||||
MinRK
|
r13074 | if 'content' not in model: | ||
Zachary Sailer
|
r13046 | metadata = current.new_metadata(name=u'') | ||
MinRK
|
r13074 | model['content'] = current.new_notebook(metadata=metadata) | ||
if 'name' not in model: | ||||
model['name'] = self.increment_filename('Untitled', path) | ||||
model['path'] = path | ||||
Konrad Hinsen
|
r15290 | model = self.save_notebook(model, model['name'], model['path']) | ||
Zachary Sailer
|
r13046 | return model | ||
Brian Granger
|
r8180 | |||
MinRK
|
r13123 | 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`. | ||||
""" | ||||
MinRK
|
r13078 | path = path.strip('/') | ||
Konrad Hinsen
|
r15290 | model = self.get_notebook(from_name, path) | ||
MinRK
|
r13123 | if not to_name: | ||
base = os.path.splitext(from_name)[0] + '-Copy' | ||||
to_name = self.increment_filename(base, path) | ||||
model['name'] = to_name | ||||
Konrad Hinsen
|
r15290 | model = self.save_notebook(model, to_name, path) | ||
Zachary Sailer
|
r13046 | return model | ||
MinRK
|
r10497 | |||
Konrad Hinsen
|
r15293 | def log_info(self): | ||
self.log.info(self.info_string()) | ||||
# NotebookManager methods provided for use in subclasses. | ||||
def check_and_sign(self, nb, path, name): | ||||
"""Check for trusted cells, and sign the notebook. | ||||
MinRK
|
r10497 | |||
Konrad Hinsen
|
r15293 | Called as a part of saving notebooks. | ||
MinRK
|
r10497 | """ | ||
Konrad Hinsen
|
r15293 | if self.notary.check_cells(nb): | ||
self.notary.sign(nb) | ||||
else: | ||||
self.log.warn("Saving untrusted notebook %s/%s", path, name) | ||||
MinRK
|
r10497 | |||
Konrad Hinsen
|
r15293 | def mark_trusted_cells(self, nb, path, name): | ||
"""Mark cells as trusted if the notebook signature matches. | ||||
Called as a part of loading notebooks. | ||||
""" | ||||
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) | ||||
MinRK
|
r10497 | |||
Konrad Hinsen
|
r15293 | def _notebook_dir_changed(self, name, old, new): | ||
"""Do a bit of validation of the notebook dir.""" | ||||
if not os.path.isabs(new): | ||||
# If we receive a non-absolute path, make it absolute. | ||||
self.notebook_dir = os.path.abspath(new) | ||||
return | ||||
if os.path.exists(new) and not os.path.isdir(new): | ||||
raise TraitError("notebook dir %r is not a directory" % new) | ||||
if not os.path.exists(new): | ||||
self.log.info("Creating notebook dir %s", new) | ||||
try: | ||||
os.mkdir(new) | ||||
except: | ||||
raise TraitError("Couldn't create notebook dir %r" % new) | ||||
Brian Granger
|
r8181 | |||