Show More
@@ -209,11 +209,15 b' class FileContentsManager(ContentsManager):' | |||||
209 |
|
209 | |||
210 | return model |
|
210 | return model | |
211 |
|
211 | |||
212 | def _file_model(self, path, content=True): |
|
212 | def _file_model(self, path, content=True, format=None): | |
213 | """Build a model for a file |
|
213 | """Build a model for a file | |
214 |
|
214 | |||
215 | if content is requested, include the file contents. |
|
215 | if content is requested, include the file contents. | |
216 | UTF-8 text files will be unicode, binary files will be base64-encoded. |
|
216 | ||
|
217 | format: | |||
|
218 | If 'text', the contents will be decoded as UTF-8. | |||
|
219 | If 'base64', the raw bytes contents will be encoded as base64. | |||
|
220 | If not specified, try to decode as UTF-8, and fall back to base64 | |||
217 | """ |
|
221 | """ | |
218 | model = self._base_model(path) |
|
222 | model = self._base_model(path) | |
219 | model['type'] = 'file' |
|
223 | model['type'] = 'file' | |
@@ -224,13 +228,20 b' class FileContentsManager(ContentsManager):' | |||||
224 | raise web.HTTPError(400, "Cannot get content of non-file %s" % os_path) |
|
228 | raise web.HTTPError(400, "Cannot get content of non-file %s" % os_path) | |
225 | with io.open(os_path, 'rb') as f: |
|
229 | with io.open(os_path, 'rb') as f: | |
226 | bcontent = f.read() |
|
230 | bcontent = f.read() | |
227 | try: |
|
231 | ||
228 | model['content'] = bcontent.decode('utf8') |
|
232 | if format != 'base64': | |
229 | except UnicodeError as e: |
|
233 | try: | |
|
234 | model['content'] = bcontent.decode('utf8') | |||
|
235 | except UnicodeError as e: | |||
|
236 | if format == 'text': | |||
|
237 | raise web.HTTPError(400, "%s is not UTF-8 encoded" % path) | |||
|
238 | else: | |||
|
239 | model['format'] = 'text' | |||
|
240 | ||||
|
241 | if model['content'] is None: | |||
230 | model['content'] = base64.encodestring(bcontent).decode('ascii') |
|
242 | model['content'] = base64.encodestring(bcontent).decode('ascii') | |
231 | model['format'] = 'base64' |
|
243 | model['format'] = 'base64' | |
232 | else: |
|
244 | ||
233 | model['format'] = 'text' |
|
|||
234 | return model |
|
245 | return model | |
235 |
|
246 | |||
236 |
|
247 | |||
@@ -255,7 +266,7 b' class FileContentsManager(ContentsManager):' | |||||
255 | self.validate_notebook_model(model) |
|
266 | self.validate_notebook_model(model) | |
256 | return model |
|
267 | return model | |
257 |
|
268 | |||
258 | def get_model(self, path, content=True, type_=None): |
|
269 | def get_model(self, path, content=True, type_=None, format=None): | |
259 | """ Takes a path for an entity and returns its model |
|
270 | """ Takes a path for an entity and returns its model | |
260 |
|
271 | |||
261 | Parameters |
|
272 | Parameters | |
@@ -267,6 +278,9 b' class FileContentsManager(ContentsManager):' | |||||
267 | type_ : str, optional |
|
278 | type_ : str, optional | |
268 | The requested type - 'file', 'notebook', or 'directory'. |
|
279 | The requested type - 'file', 'notebook', or 'directory'. | |
269 | Will raise HTTPError 406 if the content doesn't match. |
|
280 | Will raise HTTPError 406 if the content doesn't match. | |
|
281 | format : str, optional | |||
|
282 | The requested format for file contents. 'text' or 'base64'. | |||
|
283 | Ignored if this returns a notebook or directory model. | |||
270 |
|
284 | |||
271 | Returns |
|
285 | Returns | |
272 | ------- |
|
286 | ------- | |
@@ -291,7 +305,7 b' class FileContentsManager(ContentsManager):' | |||||
291 | if type_ == 'directory': |
|
305 | if type_ == 'directory': | |
292 | raise web.HTTPError(400, |
|
306 | raise web.HTTPError(400, | |
293 | u'%s is not a directory') |
|
307 | u'%s is not a directory') | |
294 | model = self._file_model(path, content=content) |
|
308 | model = self._file_model(path, content=content, format=format) | |
295 | return model |
|
309 | return model | |
296 |
|
310 | |||
297 | def _save_notebook(self, os_path, model, path=''): |
|
311 | def _save_notebook(self, os_path, model, path=''): |
@@ -62,7 +62,11 b' class ContentsHandler(IPythonHandler):' | |||||
62 | if type_ not in {None, 'directory', 'file', 'notebook'}: |
|
62 | if type_ not in {None, 'directory', 'file', 'notebook'}: | |
63 | raise web.HTTPError(400, u'Type %r is invalid' % type_) |
|
63 | raise web.HTTPError(400, u'Type %r is invalid' % type_) | |
64 |
|
64 | |||
65 | model = self.contents_manager.get_model(path=path, type_=type_) |
|
65 | format = self.get_query_argument('format', default=None)# | |
|
66 | if format not in {None, 'text', 'base64'}: | |||
|
67 | raise web.HTTPError(400, u'Format %r is invalid' % format) | |||
|
68 | ||||
|
69 | model = self.contents_manager.get_model(path=path, type_=type_, format=format) | |||
66 | if model['type'] == 'directory': |
|
70 | if model['type'] == 'directory': | |
67 | # group listing by type, then by name (case-insensitive) |
|
71 | # group listing by type, then by name (case-insensitive) | |
68 | # FIXME: sorting should be done in the frontends |
|
72 | # FIXME: sorting should be done in the frontends |
@@ -135,7 +135,7 b' class ContentsManager(LoggingConfigurable):' | |||||
135 | """ |
|
135 | """ | |
136 | return self.file_exists(path) or self.dir_exists(path) |
|
136 | return self.file_exists(path) or self.dir_exists(path) | |
137 |
|
137 | |||
138 | def get_model(self, path, content=True, type_=None): |
|
138 | def get_model(self, path, content=True, type_=None, format=None): | |
139 | """Get the model of a file or directory with or without content.""" |
|
139 | """Get the model of a file or directory with or without content.""" | |
140 | raise NotImplementedError('must be implemented in a subclass') |
|
140 | raise NotImplementedError('must be implemented in a subclass') | |
141 |
|
141 |
@@ -46,9 +46,14 b' class API(object):' | |||||
46 | def list(self, path='/'): |
|
46 | def list(self, path='/'): | |
47 | return self._req('GET', path) |
|
47 | return self._req('GET', path) | |
48 |
|
48 | |||
49 | def read(self, path, type_=None): |
|
49 | def read(self, path, type_=None, format=None): | |
|
50 | query = [] | |||
50 | if type_ is not None: |
|
51 | if type_ is not None: | |
51 |
|
|
52 | query.append('type=' + type_) | |
|
53 | if format is not None: | |||
|
54 | query.append('format=' + format) | |||
|
55 | if query: | |||
|
56 | path += '?' + '&'.join(query) | |||
52 | return self._req('GET', path) |
|
57 | return self._req('GET', path) | |
53 |
|
58 | |||
54 | def create_untitled(self, path='/', ext='.ipynb'): |
|
59 | def create_untitled(self, path='/', ext='.ipynb'): | |
@@ -245,6 +250,10 b' class APITest(NotebookTestBase):' | |||||
245 | with assert_http_error(404): |
|
250 | with assert_http_error(404): | |
246 | self.api.read('foo/q.txt') |
|
251 | self.api.read('foo/q.txt') | |
247 |
|
252 | |||
|
253 | # Specifying format=text should fail on a non-UTF-8 file | |||
|
254 | with assert_http_error(400): | |||
|
255 | self.api.read('foo/bar/baz.blob', type_='file', format='text') | |||
|
256 | ||||
248 | def test_get_binary_file_contents(self): |
|
257 | def test_get_binary_file_contents(self): | |
249 | for d, name in self.dirs_nbs: |
|
258 | for d, name in self.dirs_nbs: | |
250 | path = url_path_join(d, name + '.blob') |
|
259 | path = url_path_join(d, name + '.blob') |
@@ -165,6 +165,9 b' class TestContentsManager(TestCase):' | |||||
165 | self.assertEqual(nb_as_file['format'], 'text') |
|
165 | self.assertEqual(nb_as_file['format'], 'text') | |
166 | self.assertNotIsInstance(nb_as_file['content'], dict) |
|
166 | self.assertNotIsInstance(nb_as_file['content'], dict) | |
167 |
|
167 | |||
|
168 | nb_as_bin_file = cm.get_model(path, content=True, type_='file', format='base64') | |||
|
169 | self.assertEqual(nb_as_bin_file['format'], 'base64') | |||
|
170 | ||||
168 | # Test in sub-directory |
|
171 | # Test in sub-directory | |
169 | sub_dir = '/foo/' |
|
172 | sub_dir = '/foo/' | |
170 | self.make_dir(cm.root_dir, 'foo') |
|
173 | self.make_dir(cm.root_dir, 'foo') |
General Comments 0
You need to be logged in to leave comments.
Login now