##// END OF EJS Templates
Fix typo in comment
Fix typo in comment

File last commit:

r6322:7a08a2b2
r6658:dcd96120
Show More
notebookmanager.py
275 lines | 10.5 KiB | text/x-python | PythonLexer
Brian E. Granger
More review changes....
r4609 """A notebook manager that uses the local file system for storage.
Authors:
* Brian Granger
"""
Brian E. Granger
Massive work on the notebook document format....
r4484 #-----------------------------------------------------------------------------
Brian E. Granger
More review changes....
r4609 # Copyright (C) 2008-2011 The IPython Development Team
Brian E. Granger
Massive work on the notebook document format....
r4484 #
# Distributed under the terms of the BSD License. The full license is in
Brian E. Granger
More review changes....
r4609 # the file COPYING, distributed as part of this software.
Brian E. Granger
Massive work on the notebook document format....
r4484 #-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
import datetime
Thomas Kluyver
Save notebook as script using unicode file handle....
r6030 import io
Brian E. Granger
Massive work on the notebook document format....
r4484 import os
import uuid
Stefan van der Walt
Move glob to global level import.
r4624 import glob
Brian E. Granger
Massive work on the notebook document format....
r4484
from tornado import web
Brian E. Granger
Adding kernel/notebook associations.
r4494 from IPython.config.configurable import LoggingConfigurable
Brian E. Granger
Massive work on the notebook document format....
r4484 from IPython.nbformat import current
MinRK
allow saving notebook.py next to notebook.ipynb...
r5653 from IPython.utils.traitlets import Unicode, List, Dict, Bool
Brian E. Granger
Massive work on the notebook document format....
r4484
#-----------------------------------------------------------------------------
Fernando Perez
Add --script flag as shorthand for the script autosave notebook option.
r5758 # Classes
#-----------------------------------------------------------------------------
Brian E. Granger
Massive work on the notebook document format....
r4484
Brian E. Granger
Adding kernel/notebook associations.
r4494 class NotebookManager(LoggingConfigurable):
Brian E. Granger
Massive work on the notebook document format....
r4484
MinRK
use os.getcwdu in NotebookManager...
r6322 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
Brian E. Granger
Minor fixes to config system for notebook.
r4515 The directory to use for notebooks.
""")
MinRK
allow saving notebook.py next to notebook.ipynb...
r5653
save_script = Bool(False, config=True,
Fernando Perez
Fix typo in help string
r5760 help="""Automatically create a Python script when saving the notebook.
MinRK
allow saving notebook.py next to notebook.ipynb...
r5653
Fernando Perez
Add --script flag as shorthand for the script autosave notebook option.
r5758 For easier use of import, %run and %loadpy 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.
MinRK
allow saving notebook.py next to notebook.ipynb...
r5653 """
)
Brian E. Granger
Massive work on the notebook document format....
r4484 filename_ext = Unicode(u'.ipynb')
Brian E. Granger
Making JSON the default .ipynb format.
r4633 allowed_formats = List([u'json',u'py'])
Brian E. Granger
Massive work on the notebook document format....
r4484
# Map notebook_ids to notebook names
mapping = Dict()
# Map notebook names to notebook_ids
rev_mapping = Dict()
def list_notebooks(self):
"""List all notebooks in the notebook dir.
This returns a list of dicts of the form::
dict(notebook_id=notebook,name=name)
"""
Stefan van der Walt
Allow period characters in notebook names.
r4623 names = glob.glob(os.path.join(self.notebook_dir,
'*' + self.filename_ext))
names = [os.path.splitext(os.path.basename(name))[0]
for name in names]
Brian E. Granger
Massive work on the notebook document format....
r4484 data = []
for name in names:
if name not in self.rev_mapping:
notebook_id = self.new_notebook_id(name)
else:
notebook_id = self.rev_mapping[name]
data.append(dict(notebook_id=notebook_id,name=name))
Brian E. Granger
Implemented basic notebook browser and fixed numerous bugs.
r4488 data = sorted(data, key=lambda item: item['name'])
Brian E. Granger
Massive work on the notebook document format....
r4484 return data
def new_notebook_id(self, name):
"""Generate a new notebook_id for a name and store its mappings."""
Fernando Perez
Leave commented-out stable url code for reference, with TODO.
r4678 # TODO: the following will give stable urls for notebooks, but unless
# the notebooks are immediately redirected to their new urls when their
# filemname changes, nasty inconsistencies result. So for now it's
# disabled and instead we use a random uuid4() call. But we leave the
# logic here so that we can later reactivate it, whhen the necessary
# url redirection code is written.
#notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
# 'file://'+self.get_path_by_name(name).encode('utf-8')))
Brian E. Granger
Going back to using uuid.uuid4() for notebook ids....
r4674 notebook_id = unicode(uuid.uuid4())
Fernando Perez
Leave commented-out stable url code for reference, with TODO.
r4678
Brian E. Granger
Massive work on the notebook document format....
r4484 self.mapping[notebook_id] = name
self.rev_mapping[name] = notebook_id
return notebook_id
def delete_notebook_id(self, notebook_id):
"""Delete a notebook's id only. This doesn't delete the actual notebook."""
name = self.mapping[notebook_id]
del self.mapping[notebook_id]
del self.rev_mapping[name]
def notebook_exists(self, notebook_id):
"""Does a notebook exist?"""
if notebook_id not in self.mapping:
return False
path = self.get_path_by_name(self.mapping[notebook_id])
Brian E. Granger
More review changes....
r4609 return os.path.isfile(path)
Brian E. Granger
Massive work on the notebook document format....
r4484
def find_path(self, notebook_id):
"""Return a full path to a notebook given its notebook_id."""
try:
name = self.mapping[notebook_id]
except KeyError:
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
Brian E. Granger
Massive work on the notebook document format....
r4484 return self.get_path_by_name(name)
def get_path_by_name(self, name):
"""Return a full path to a notebook given its name."""
filename = name + self.filename_ext
path = os.path.join(self.notebook_dir, filename)
return path
def get_notebook(self, notebook_id, format=u'json'):
"""Get the representation of a notebook in format by notebook_id."""
format = unicode(format)
if format not in self.allowed_formats:
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
Brian E. Granger
Massive work on the notebook document format....
r4484 last_modified, nb = self.get_notebook_object(notebook_id)
MinRK
split likely multiline strings when writing to/from JSON
r5278 kwargs = {}
if format == 'json':
# don't split lines for sending over the wire, because it
# should match the Python in-memory format.
kwargs['split_lines'] = False
data = current.writes(nb, format, **kwargs)
Brian E. Granger
Massive work on the notebook document format....
r4484 name = nb.get('name','notebook')
return last_modified, name, data
def get_notebook_object(self, notebook_id):
"""Get the NotebookNode representation of a notebook by notebook_id."""
path = self.find_path(notebook_id)
if not os.path.isfile(path):
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
Brian E. Granger
Massive work on the notebook document format....
r4484 info = os.stat(path)
last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
Brian E. Granger
Making JSON the default .ipynb format.
r4633 with open(path,'r') as f:
s = f.read()
try:
# v1 and v2 and json in the .ipynb files.
nb = current.reads(s, u'json')
except:
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(500, u'Unreadable JSON notebook.')
Brian E. Granger
Making JSON the default .ipynb format.
r4633 if 'name' not in nb:
nb.name = os.path.split(path)[-1].split(u'.')[0]
Brian E. Granger
Massive work on the notebook document format....
r4484 return last_modified, nb
Brian E. Granger
File upload/import working from notebook browser.
r4491 def save_new_notebook(self, data, name=None, format=u'json'):
"""Save a new notebook and return its notebook_id.
If a name is passed in, it overrides any values in the notebook data
and the value in the data is updated to use that value.
"""
Brian E. Granger
Massive work on the notebook document format....
r4484 if format not in self.allowed_formats:
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
Brian E. Granger
File upload/import working from notebook browser.
r4491
Brian E. Granger
Massive work on the notebook document format....
r4484 try:
Thomas Kluyver
Decode data for saving notebook, allowing saving in Python 3.
r4869 nb = current.reads(data.decode('utf-8'), format)
Brian E. Granger
Massive work on the notebook document format....
r4484 except:
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(400, u'Invalid JSON data')
Brian E. Granger
File upload/import working from notebook browser.
r4491
if name is None:
try:
Brian E. Granger
Implemented metadata for notebook format.
r4637 name = nb.metadata.name
Brian E. Granger
File upload/import working from notebook browser.
r4491 except AttributeError:
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(400, u'Missing notebook name')
Brian E. Granger
Implemented metadata for notebook format.
r4637 nb.metadata.name = name
Brian E. Granger
File upload/import working from notebook browser.
r4491
Brian E. Granger
Massive work on the notebook document format....
r4484 notebook_id = self.new_notebook_id(name)
self.save_notebook_object(notebook_id, nb)
return notebook_id
Brian E. Granger
File upload/import working from notebook browser.
r4491 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
Brian E. Granger
Massive work on the notebook document format....
r4484 """Save an existing notebook by notebook_id."""
if format not in self.allowed_formats:
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
Brian E. Granger
File upload/import working from notebook browser.
r4491
Brian E. Granger
Massive work on the notebook document format....
r4484 try:
Thomas Kluyver
Decode data for saving notebook, allowing saving in Python 3.
r4869 nb = current.reads(data.decode('utf-8'), format)
Brian E. Granger
Massive work on the notebook document format....
r4484 except:
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(400, u'Invalid JSON data')
Brian E. Granger
File upload/import working from notebook browser.
r4491
if name is not None:
Brian E. Granger
Implemented metadata for notebook format.
r4637 nb.metadata.name = name
Brian E. Granger
Massive work on the notebook document format....
r4484 self.save_notebook_object(notebook_id, nb)
def save_notebook_object(self, notebook_id, nb):
"""Save an existing notebook object by notebook_id."""
if notebook_id not in self.mapping:
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
Brian E. Granger
Massive work on the notebook document format....
r4484 old_name = self.mapping[notebook_id]
try:
Brian E. Granger
Implemented metadata for notebook format.
r4637 new_name = nb.metadata.name
Brian E. Granger
Massive work on the notebook document format....
r4484 except AttributeError:
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(400, u'Missing notebook name')
Brian E. Granger
Massive work on the notebook document format....
r4484 path = self.get_path_by_name(new_name)
try:
with open(path,'w') as f:
Brian E. Granger
Making JSON the default .ipynb format.
r4633 current.write(nb, f, u'json')
MinRK
include error in 'Unexpected error' message.
r5709 except Exception as e:
raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
MinRK
allow saving notebook.py next to notebook.ipynb...
r5653 # save .py script as well
if self.save_script:
pypath = os.path.splitext(path)[0] + '.py'
try:
Thomas Kluyver
Specify UTF-8 encoding for saving notebook as script.
r6031 with io.open(pypath,'w', encoding='utf-8') as f:
MinRK
allow saving notebook.py next to notebook.ipynb...
r5653 current.write(nb, f, u'py')
MinRK
include error in 'Unexpected error' message.
r5709 except Exception as e:
raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
MinRK
allow saving notebook.py next to notebook.ipynb...
r5653
Brian E. Granger
Massive work on the notebook document format....
r4484 if old_name != new_name:
old_path = self.get_path_by_name(old_name)
if os.path.isfile(old_path):
os.unlink(old_path)
MinRK
allow saving notebook.py next to notebook.ipynb...
r5653 if self.save_script:
old_pypath = os.path.splitext(old_path)[0] + '.py'
if os.path.isfile(old_pypath):
os.unlink(old_pypath)
Brian E. Granger
Massive work on the notebook document format....
r4484 self.mapping[notebook_id] = new_name
self.rev_mapping[new_name] = notebook_id
def delete_notebook(self, notebook_id):
"""Delete notebook by notebook_id."""
path = self.find_path(notebook_id)
if not os.path.isfile(path):
Brian E. Granger
Adding messages to HTTPError raising....
r4676 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
Brian E. Granger
Massive work on the notebook document format....
r4484 os.unlink(path)
self.delete_notebook_id(notebook_id)
Brian Granger
Finishing work on "Make a Copy" functionality.
r5861 def increment_filename(self, basename):
Brian Granger
Fixing docstring in the notebook manager.
r5877 """Return a non-used filename of the form basename<int>.
This searches through the filenames (basename0, basename1, ...)
until is find one that is not already being used. It is used to
create Untitled and Copy names that are unique.
"""
Brian E. Granger
Massive work on the notebook document format....
r4484 i = 0
while True:
Brian Granger
Finishing work on "Make a Copy" functionality.
r5861 name = u'%s%i' % (basename,i)
Brian E. Granger
Massive work on the notebook document format....
r4484 path = self.get_path_by_name(name)
if not os.path.isfile(path):
break
else:
i = i+1
Brian Granger
Finishing work on "Make a Copy" functionality.
r5861 return path, name
def new_notebook(self):
"""Create a new notebook and return its notebook_id."""
path, name = self.increment_filename('Untitled')
Brian E. Granger
Massive work on the notebook document format....
r4484 notebook_id = self.new_notebook_id(name)
Brian E. Granger
Fixing bug in new metadata implementation.
r4641 metadata = current.new_metadata(name=name)
nb = current.new_notebook(metadata=metadata)
Brian E. Granger
Massive work on the notebook document format....
r4484 with open(path,'w') as f:
Brian E. Granger
Making JSON the default .ipynb format.
r4633 current.write(nb, f, u'json')
Brian E. Granger
Massive work on the notebook document format....
r4484 return notebook_id
Brian Granger
Beginning work on notebook duplication.
r5860 def copy_notebook(self, notebook_id):
Brian Granger
Finishing work on "Make a Copy" functionality.
r5861 """Copy an existing notebook and return its notebook_id."""
Brian Granger
Beginning work on notebook duplication.
r5860 last_mod, nb = self.get_notebook_object(notebook_id)
name = nb.metadata.name + '-Copy'
Brian Granger
Finishing work on "Make a Copy" functionality.
r5861 path, name = self.increment_filename(name)
Brian Granger
Beginning work on notebook duplication.
r5860 nb.metadata.name = name
notebook_id = self.new_notebook_id(name)
self.save_notebook_object(notebook_id, nb)
return notebook_id