add support and tests for uploading and saving regular files

This commit is contained in:
MinRK 2014-06-03 11:35:45 -07:00
parent 3674958b70
commit 8c4069cc00
6 changed files with 147 additions and 47 deletions

View File

@ -196,6 +196,7 @@ class FileContentsManager(ContentsManager):
contents.append(self.get_model(name=name, path=dir_path, content=False))
model['content'] = sorted(contents, key=sort_key)
model['format'] = 'json'
return model
@ -273,12 +274,48 @@ class FileContentsManager(ContentsManager):
model = self._file_model(name, path, content)
return model
def _save_notebook(self, os_path, model, name='', path=''):
# Save the notebook file
nb = current.to_notebook_json(model['content'])
self.check_and_sign(nb, name, path)
if 'name' in nb['metadata']:
nb['metadata']['name'] = u''
with io.open(os_path, 'w', encoding='utf-8') as f:
current.write(nb, f, u'json')
def _save_file(self, os_path, model, name='', path=''):
fmt = model.get('format', None)
if fmt not in {'text', 'base64'}:
raise web.HTTPError(400, "Must specify format of file contents as 'text' or 'base64'")
try:
content = model['content']
if fmt == 'text':
bcontent = content.encode('utf8')
else:
b64_bytes = content.encode('ascii')
bcontent = base64.decodestring(b64_bytes)
except Exception as e:
raise web.HTTPError(400, u'Encoding error saving %s: %s' % (os_path, e))
with io.open(os_path, 'wb') as f:
f.write(bcontent)
def _save_directory(self, os_path, model, name='', path=''):
if not os.path.exists(os_path):
os.mkdir(os_path)
elif not os.path.isdir(os_path):
raise web.HTTPError(400, u'Not a directory: %s' % (os_path))
def save(self, model, name='', path=''):
"""Save the notebook model and return the model with no content."""
"""Save the file model and return the model with no content."""
path = path.strip('/')
if 'content' not in model:
raise web.HTTPError(400, u'No notebook JSON data provided')
raise web.HTTPError(400, u'No file content provided')
if 'type' not in model:
raise web.HTTPError(400, u'No file type provided')
# One checkpoint should always exist
if self.file_exists(name, path) and not self.list_checkpoints(name, path):
@ -290,20 +327,21 @@ class FileContentsManager(ContentsManager):
if path != new_path or name != new_name:
self.rename(name, path, new_name, new_path)
# 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''
self.log.debug("Saving %s", os_path)
try:
self.log.debug("Autosaving notebook %s", os_path)
with io.open(os_path, 'w', encoding='utf-8') as f:
current.write(nb, f, u'json')
if model['type'] == 'notebook':
self._save_notebook(os_path, model, new_name, new_path)
elif model['type'] == 'file':
self._save_file(os_path, model, new_name, new_path)
elif model['type'] == 'directory':
self._save_directory(os_path, model, new_name, new_path)
else:
raise web.HTTPError(400, "Unhandled contents type: %s" % model['type'])
except web.HTTPError:
raise
except Exception as e:
raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
raise web.HTTPError(400, u'Unexpected error while saving file: %s %s' % (os_path, e))
model = self.get_model(new_name, new_path, content=False)
return model

View File

@ -99,20 +99,20 @@ class ContentsHandler(IPythonHandler):
if name:
model['name'] = name
model = self.contents_manager.create_notebook(model, path)
model = self.contents_manager.create_file(model, path)
self.set_status(201)
self._finish_model(model)
def _create_empty_notebook(self, path, name=None):
"""Create an empty notebook in path
def _create_empty_file(self, path, name=None, ext='.ipynb'):
"""Create an empty file in path
If name specified, create it in path/name.
"""
self.log.info(u"Creating new notebook in %s/%s", path, name or '')
self.log.info(u"Creating new file in %s/%s", path, name or '')
model = {}
if name:
model['name'] = name
model = self.contents_manager.create_notebook(model, path=path)
model = self.contents_manager.create_file(model, path=path, ext=ext)
self.set_status(201)
self._finish_model(model)
@ -137,7 +137,8 @@ class ContentsHandler(IPythonHandler):
POST /api/contents/path
New untitled notebook in path. If content specified, upload a
notebook, otherwise start empty.
POST /api/contents/path?copy=OtherNotebook.ipynb
POST /api/contents/path
with body {"copy_from" : "OtherNotebook.ipynb"}
New copy of OtherNotebook in path
"""
@ -156,14 +157,17 @@ class ContentsHandler(IPythonHandler):
if model is not None:
copy_from = model.get('copy_from')
if copy_from:
if model.get('content'):
ext = model.get('ext', '.ipynb')
if model.get('content') is not None:
if copy_from:
raise web.HTTPError(400, "Can't upload and copy at the same time.")
self._upload(model, path)
elif copy_from:
self._copy(copy_from, path)
else:
self._upload(model, path)
self._create_empty_file(path, ext=ext)
else:
self._create_empty_notebook(path)
self._create_empty_file(path)
@web.authenticated
@json_errors
@ -195,7 +199,7 @@ class ContentsHandler(IPythonHandler):
else:
self._upload(model, path, name)
else:
self._create_empty_notebook(path, name)
self._create_empty_file(path, name)
@web.authenticated
@json_errors

View File

@ -155,16 +155,23 @@ class ContentsManager(LoggingConfigurable):
break
return name
def create_notebook(self, model=None, path=''):
def create_file(self, model=None, path='', ext='.ipynb'):
"""Create a new notebook and return its model with no content."""
path = path.strip('/')
if model is None:
model = {}
if 'content' not in model:
metadata = current.new_metadata(name=u'')
model['content'] = current.new_notebook(metadata=metadata)
if ext == '.ipynb':
metadata = current.new_metadata(name=u'')
model['content'] = current.new_notebook(metadata=metadata)
model.setdefault('type', 'notebook')
model.setdefault('format', 'json')
else:
model['content'] = ''
model.setdefault('type', 'file')
model.setdefault('format', 'text')
if 'name' not in model:
model['name'] = self.increment_filename('Untitled.ipynb', path)
model['name'] = self.increment_filename('Untitled' + ext, path)
model['path'] = path
model = self.save(model, model['name'], model['path'])

View File

@ -50,8 +50,11 @@ class API(object):
def read(self, name, path='/'):
return self._req('GET', url_path_join(path, name))
def create_untitled(self, path='/'):
return self._req('POST', path)
def create_untitled(self, path='/', ext=None):
body = None
if ext:
body = json.dumps({'ext': ext})
return self._req('POST', path, body)
def upload_untitled(self, body, path='/'):
return self._req('POST', path, body)
@ -267,26 +270,72 @@ class APITest(NotebookTestBase):
resp = self.api.create_untitled(path='foo/bar')
self._check_nb_created(resp, 'Untitled0.ipynb', 'foo/bar')
def test_create_untitled_txt(self):
resp = self.api.create_untitled(path='foo/bar', ext='.txt')
self._check_nb_created(resp, 'Untitled0.txt', 'foo/bar')
resp = self.api.read(path='foo/bar', name='Untitled0.txt')
model = resp.json()
self.assertEqual(model['type'], 'file')
self.assertEqual(model['format'], 'text')
self.assertEqual(model['content'], '')
def test_upload_untitled(self):
nb = new_notebook(name='Upload test')
nbmodel = {'content': nb}
nbmodel = {'content': nb, 'type': 'notebook'}
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}
nbmodel = {'content': nb, 'type': 'notebook'}
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')
def test_upload_txt(self):
body = u'ünicode téxt'
model = {
'content' : body,
'format' : 'text',
'type' : 'file',
}
resp = self.api.upload(u'Upload tést.txt', path=u'å b',
body=json.dumps(model))
# check roundtrip
resp = self.api.read(path=u'å b', name=u'Upload tést.txt')
model = resp.json()
self.assertEqual(model['type'], 'file')
self.assertEqual(model['format'], 'text')
self.assertEqual(model['content'], body)
def test_upload_b64(self):
body = b'\xFFblob'
b64body = base64.encodestring(body).decode('ascii')
model = {
'content' : b64body,
'format' : 'base64',
'type' : 'file',
}
resp = self.api.upload(u'Upload tést.blob', path=u'å b',
body=json.dumps(model))
# check roundtrip
resp = self.api.read(path=u'å b', name=u'Upload tést.blob')
model = resp.json()
self.assertEqual(model['type'], 'file')
self.assertEqual(model['format'], 'base64')
decoded = base64.decodestring(model['content'].encode('ascii'))
self.assertEqual(decoded, body)
def test_upload_v2(self):
nb = v2.new_notebook()
ws = v2.new_worksheet()
nb.worksheets.append(ws)
ws.cells.append(v2.new_code_cell(input='print("hi")'))
nbmodel = {'content': nb}
nbmodel = {'content': nb, 'type': 'notebook'}
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')
@ -335,7 +384,7 @@ class APITest(NotebookTestBase):
nb.worksheets = [ws]
ws.cells.append(new_heading_cell(u'Created by test ³'))
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
@ -349,7 +398,7 @@ class APITest(NotebookTestBase):
u'Created by test ³')
# Save and rename
nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb}
nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb, 'type': 'notebook'}
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
saved = resp.json()
self.assertEqual(saved['name'], 'a2.ipynb')
@ -375,7 +424,7 @@ class APITest(NotebookTestBase):
hcell = new_heading_cell('Created by test')
ws.cells.append(hcell)
# Save
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
# List checkpoints

View File

@ -101,7 +101,7 @@ class TestContentsManager(TestCase):
def new_notebook(self):
cm = self.contents_manager
model = cm.create_notebook()
model = cm.create_file()
name = model['name']
path = model['path']
@ -112,10 +112,10 @@ class TestContentsManager(TestCase):
cm.save(full_model, name, path)
return nb, name, path
def test_create_notebook(self):
def test_create_file(self):
cm = self.contents_manager
# Test in root directory
model = cm.create_notebook()
model = cm.create_file()
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
@ -125,7 +125,7 @@ class TestContentsManager(TestCase):
# Test in sub-directory
sub_dir = '/foo/'
self.make_dir(cm.root_dir, 'foo')
model = cm.create_notebook(None, sub_dir)
model = cm.create_file(None, sub_dir)
assert isinstance(model, dict)
self.assertIn('name', model)
self.assertIn('path', model)
@ -135,7 +135,7 @@ class TestContentsManager(TestCase):
def test_get(self):
cm = self.contents_manager
# Create a notebook
model = cm.create_notebook()
model = cm.create_file()
name = model['name']
path = model['path']
@ -150,7 +150,7 @@ class TestContentsManager(TestCase):
# Test in sub-directory
sub_dir = '/foo/'
self.make_dir(cm.root_dir, 'foo')
model = cm.create_notebook(None, sub_dir)
model = cm.create_file(None, sub_dir)
model2 = cm.get_model(name, sub_dir)
assert isinstance(model2, dict)
self.assertIn('name', model2)
@ -162,7 +162,7 @@ class TestContentsManager(TestCase):
def test_update(self):
cm = self.contents_manager
# Create a notebook
model = cm.create_notebook()
model = cm.create_file()
name = model['name']
path = model['path']
@ -181,7 +181,7 @@ class TestContentsManager(TestCase):
# Create a directory and notebook in that directory
sub_dir = '/foo/'
self.make_dir(cm.root_dir, 'foo')
model = cm.create_notebook(None, sub_dir)
model = cm.create_file(None, sub_dir)
name = model['name']
path = model['path']
@ -200,7 +200,7 @@ class TestContentsManager(TestCase):
def test_save(self):
cm = self.contents_manager
# Create a notebook
model = cm.create_notebook()
model = cm.create_file()
name = model['name']
path = model['path']
@ -219,7 +219,7 @@ class TestContentsManager(TestCase):
# Create a directory and notebook in that directory
sub_dir = '/foo/'
self.make_dir(cm.root_dir, 'foo')
model = cm.create_notebook(None, sub_dir)
model = cm.create_file(None, sub_dir)
name = model['name']
path = model['path']
model = cm.get_model(name, path)
@ -248,7 +248,7 @@ class TestContentsManager(TestCase):
path = u'å b'
name = u'nb √.ipynb'
os.mkdir(os.path.join(cm.root_dir, path))
orig = cm.create_notebook({'name' : name}, path=path)
orig = cm.create_file({'name' : name}, path=path)
# copy with unspecified name
copy = cm.copy(name, path=path)

View File

@ -1885,6 +1885,8 @@ define([
var model = {};
model.name = this.notebook_name;
model.path = this.notebook_path;
model.type = 'notebook';
model.format = 'json';
model.content = this.toJSON();
model.content.nbformat = this.nbformat;
model.content.nbformat_minor = this.nbformat_minor;