mirror of
https://github.com/jupyter/notebook.git
synced 2025-02-05 12:19:58 +08:00
add support and tests for uploading and saving regular files
This commit is contained in:
parent
3674958b70
commit
8c4069cc00
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'])
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user