Show More
@@ -1,4 +1,6 b'' | |||
|
1 | import io | |
|
1 | 2 | import os |
|
3 | import zipfile | |
|
2 | 4 | |
|
3 | 5 | from tornado import web |
|
4 | 6 | |
@@ -6,12 +8,44 b' from ..base.handlers import IPythonHandler, notebook_path_regex' | |||
|
6 | 8 | from IPython.nbformat.current import to_notebook_json |
|
7 | 9 | from IPython.nbconvert.exporters.export import exporter_map |
|
8 | 10 | from IPython.utils import tz |
|
9 | ||
|
10 | ||
|
11 | def has_resource_files(resources): | |
|
12 | output_files_dir = resources.get('output_files_dir', "") | |
|
13 | return bool(os.path.isdir(output_files_dir) and \ | |
|
14 | os.listdir(output_files_dir)) | |
|
11 | from IPython.utils.py3compat import cast_bytes | |
|
12 | ||
|
13 | import sys | |
|
14 | ||
|
15 | def find_resource_files(output_files_dir): | |
|
16 | files = [] | |
|
17 | for dirpath, dirnames, filenames in os.walk(output_files_dir): | |
|
18 | files.extend([os.path.join(dirpath, f) for f in filenames]) | |
|
19 | return files | |
|
20 | ||
|
21 | def respond_zip(handler, name, output, resources): | |
|
22 | """Zip up the output and resource files and respond with the zip file. | |
|
23 | ||
|
24 | Returns True if it has served a zip file, False if there are no resource | |
|
25 | files, in which case we serve the plain output file. | |
|
26 | """ | |
|
27 | # Check if we have resource files we need to zip | |
|
28 | output_files = resources.get('outputs', None) | |
|
29 | if not output_files: | |
|
30 | return False | |
|
31 | ||
|
32 | # Headers | |
|
33 | zip_filename = os.path.splitext(name)[0] + '.zip' | |
|
34 | handler.set_header('Content-Disposition', | |
|
35 | 'attachment; filename="%s"' % zip_filename) | |
|
36 | handler.set_header('Content-Type', 'application/zip') | |
|
37 | ||
|
38 | # Prepare the zip file | |
|
39 | buffer = io.BytesIO() | |
|
40 | zipf = zipfile.ZipFile(buffer, mode='w', compression=zipfile.ZIP_DEFLATED) | |
|
41 | output_filename = os.path.splitext(name)[0] + '.' + resources['output_extension'] | |
|
42 | zipf.writestr(output_filename, cast_bytes(output, 'utf-8')) | |
|
43 | for filename, data in output_files.items(): | |
|
44 | zipf.writestr(os.path.basename(filename), data) | |
|
45 | zipf.close() | |
|
46 | ||
|
47 | handler.finish(buffer.getvalue()) | |
|
48 | return True | |
|
15 | 49 | |
|
16 | 50 | class NbconvertFileHandler(IPythonHandler): |
|
17 | 51 | |
@@ -29,9 +63,14 b' class NbconvertFileHandler(IPythonHandler):' | |||
|
29 | 63 | info = os.stat(os_path) |
|
30 | 64 | self.set_header('Last-Modified', tz.utcfromtimestamp(info.st_mtime)) |
|
31 | 65 | |
|
66 | output, resources = exporter.from_filename(os_path) | |
|
67 | ||
|
68 | if respond_zip(self, name, output, resources): | |
|
69 | return | |
|
70 | ||
|
32 | 71 | # Force download if requested |
|
33 | 72 | if self.get_argument('download', 'false').lower() == 'true': |
|
34 |
filename = os.path.splitext(name)[0] + '.' + |
|
|
73 | filename = os.path.splitext(name)[0] + '.' + resources['output_extension'] | |
|
35 | 74 | self.set_header('Content-Disposition', |
|
36 | 75 | 'attachment; filename="%s"' % filename) |
|
37 | 76 | |
@@ -40,11 +79,6 b' class NbconvertFileHandler(IPythonHandler):' | |||
|
40 | 79 | self.set_header('Content-Type', |
|
41 | 80 | '%s; charset=utf-8' % exporter.output_mimetype) |
|
42 | 81 | |
|
43 | output, resources = exporter.from_filename(os_path) | |
|
44 | ||
|
45 | # TODO: If there are resources, combine them into a zip file | |
|
46 | assert not has_resource_files(resources) | |
|
47 | ||
|
48 | 82 | self.finish(output) |
|
49 | 83 | |
|
50 | 84 | class NbconvertPostHandler(IPythonHandler): |
@@ -57,16 +91,16 b' class NbconvertPostHandler(IPythonHandler):' | |||
|
57 | 91 | model = self.get_json_body() |
|
58 | 92 | nbnode = to_notebook_json(model['content']) |
|
59 | 93 | |
|
94 | output, resources = exporter.from_notebook_node(nbnode) | |
|
95 | ||
|
96 | if respond_zip(self, nbnode.metadata.name, output, resources): | |
|
97 | return | |
|
98 | ||
|
60 | 99 | # MIME type |
|
61 | 100 | if exporter.output_mimetype: |
|
62 | 101 | self.set_header('Content-Type', |
|
63 | 102 | '%s; charset=utf-8' % exporter.output_mimetype) |
|
64 | 103 | |
|
65 | output, resources = exporter.from_notebook_node(nbnode) | |
|
66 | ||
|
67 | # TODO: If there are resources, combine them into a zip file | |
|
68 | assert not has_resource_files(resources) | |
|
69 | ||
|
70 | 104 | self.finish(output) |
|
71 | 105 | |
|
72 | 106 | #----------------------------------------------------------------------------- |
@@ -10,7 +10,8 b' import requests' | |||
|
10 | 10 | from IPython.html.utils import url_path_join |
|
11 | 11 | from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error |
|
12 | 12 | from IPython.nbformat.current import (new_notebook, write, new_worksheet, |
|
13 |
new_heading_cell, new_code_cell |
|
|
13 | new_heading_cell, new_code_cell, | |
|
14 | new_output) | |
|
14 | 15 | |
|
15 | 16 | class NbconvertAPI(object): |
|
16 | 17 | """Wrapper for nbconvert API calls.""" |
@@ -48,7 +49,9 b' class APITest(NotebookTestBase):' | |||
|
48 | 49 | ws = new_worksheet() |
|
49 | 50 | nb.worksheets = [ws] |
|
50 | 51 | ws.cells.append(new_heading_cell(u'Created by test ³')) |
|
51 |
|
|
|
52 | cc1 = new_code_cell(input=u'print(2*6)') | |
|
53 | cc1.outputs.append(new_output(output_text=u'12')) | |
|
54 | ws.cells.append(cc1) | |
|
52 | 55 | |
|
53 | 56 | with io.open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w', |
|
54 | 57 | encoding='utf-8') as f: |
@@ -83,6 +86,11 b' class APITest(NotebookTestBase):' | |||
|
83 | 86 | self.assertIn('attachment', content_disposition) |
|
84 | 87 | self.assertIn('testnb.py', content_disposition) |
|
85 | 88 | |
|
89 | def test_from_file_zip(self): | |
|
90 | r = self.nbconvert_api.from_file('latex', 'foo', 'testnb.ipynb', download=True) | |
|
91 | self.assertIn(u'application/zip', r.headers['Content-Type']) | |
|
92 | self.assertIn(u'.zip', r.headers['Content-Disposition']) | |
|
93 | ||
|
86 | 94 | def test_from_post(self): |
|
87 | 95 | nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb') |
|
88 | 96 | nbmodel = requests.get(nbmodel_url).json() |
@@ -96,3 +104,11 b' class APITest(NotebookTestBase):' | |||
|
96 | 104 | r = self.nbconvert_api.from_post(format='python', nbmodel=nbmodel) |
|
97 | 105 | self.assertIn(u'text/x-python', r.headers['Content-Type']) |
|
98 | 106 | self.assertIn(u'print(2*6)', r.text) |
|
107 | ||
|
108 | def test_from_post_zip(self): | |
|
109 | nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb') | |
|
110 | nbmodel = requests.get(nbmodel_url).json() | |
|
111 | ||
|
112 | r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel) | |
|
113 | self.assertIn(u'application/zip', r.headers['Content-Type']) | |
|
114 | self.assertIn(u'.zip', r.headers['Content-Disposition']) |
@@ -55,7 +55,8 b' def new_output(output_type=None, output_text=None, output_png=None,' | |||
|
55 | 55 | output_html=None, output_svg=None, output_latex=None, output_json=None, |
|
56 | 56 | output_javascript=None, output_jpeg=None, prompt_number=None, |
|
57 | 57 | ename=None, evalue=None, traceback=None, stream=None, metadata=None): |
|
58 | """Create a new code cell with input and output""" | |
|
58 | """Create a new output, to go in the ``cell.outputs`` list of a code cell. | |
|
59 | """ | |
|
59 | 60 | output = NotebookNode() |
|
60 | 61 | if output_type is not None: |
|
61 | 62 | output.output_type = unicode_type(output_type) |
General Comments 0
You need to be logged in to leave comments.
Login now