Show More
@@ -1,114 +1,120 b'' | |||
|
1 | 1 | # coding: utf-8 |
|
2 | import base64 | |
|
2 | 3 | import io |
|
3 | 4 | import json |
|
4 | 5 | import os |
|
5 | 6 | from os.path import join as pjoin |
|
6 | 7 | import shutil |
|
7 | 8 | |
|
8 | 9 | import requests |
|
9 | 10 | |
|
10 | 11 | from IPython.html.utils import url_path_join |
|
11 | 12 | from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error |
|
12 | 13 | from IPython.nbformat.current import (new_notebook, write, new_worksheet, |
|
13 | 14 | new_heading_cell, new_code_cell, |
|
14 | 15 | new_output) |
|
15 | 16 | |
|
16 | 17 | class NbconvertAPI(object): |
|
17 | 18 | """Wrapper for nbconvert API calls.""" |
|
18 | 19 | def __init__(self, base_url): |
|
19 | 20 | self.base_url = base_url |
|
20 | 21 | |
|
21 | 22 | def _req(self, verb, path, body=None, params=None): |
|
22 | 23 | response = requests.request(verb, |
|
23 | 24 | url_path_join(self.base_url, 'nbconvert', path), |
|
24 | 25 | data=body, params=params, |
|
25 | 26 | ) |
|
26 | 27 | response.raise_for_status() |
|
27 | 28 | return response |
|
28 | 29 | |
|
29 | 30 | def from_file(self, format, path, name, download=False): |
|
30 | 31 | return self._req('GET', url_path_join(format, path, name), |
|
31 | 32 | params={'download':download}) |
|
32 | 33 | |
|
33 | 34 | def from_post(self, format, nbmodel): |
|
34 | 35 | body = json.dumps(nbmodel) |
|
35 | 36 | return self._req('POST', format, body) |
|
36 | 37 | |
|
37 | 38 | def list_formats(self): |
|
38 | 39 | return self._req('GET', '') |
|
39 | 40 | |
|
41 | png_green_pixel = base64.encodestring(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00' | |
|
42 | b'\x00\x00\x01\x00\x00x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDAT' | |
|
43 | b'\x08\xd7c\x90\xfb\xcf\x00\x00\x02\\\x01\x1e.~d\x87\x00\x00\x00\x00IEND\xaeB`\x82') | |
|
44 | ||
|
40 | 45 | class APITest(NotebookTestBase): |
|
41 | 46 | def setUp(self): |
|
42 | 47 | nbdir = self.notebook_dir.name |
|
43 | 48 | |
|
44 | 49 | if not os.path.isdir(pjoin(nbdir, 'foo')): |
|
45 | 50 | os.mkdir(pjoin(nbdir, 'foo')) |
|
46 | 51 | |
|
47 | 52 | nb = new_notebook(name='testnb') |
|
48 | 53 | |
|
49 | 54 | ws = new_worksheet() |
|
50 | 55 | nb.worksheets = [ws] |
|
51 | 56 | ws.cells.append(new_heading_cell(u'Created by test Β³')) |
|
52 | 57 | cc1 = new_code_cell(input=u'print(2*6)') |
|
53 | 58 | cc1.outputs.append(new_output(output_text=u'12')) |
|
59 | cc1.outputs.append(new_output(output_png=png_green_pixel, output_type='pyout')) | |
|
54 | 60 | ws.cells.append(cc1) |
|
55 | 61 | |
|
56 | 62 | with io.open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w', |
|
57 | 63 | encoding='utf-8') as f: |
|
58 | 64 | write(nb, f, format='ipynb') |
|
59 | 65 | |
|
60 | 66 | self.nbconvert_api = NbconvertAPI(self.base_url()) |
|
61 | 67 | |
|
62 | 68 | def tearDown(self): |
|
63 | 69 | nbdir = self.notebook_dir.name |
|
64 | 70 | |
|
65 | 71 | for dname in ['foo']: |
|
66 | 72 | shutil.rmtree(pjoin(nbdir, dname), ignore_errors=True) |
|
67 | 73 | |
|
68 | 74 | def test_from_file(self): |
|
69 | 75 | r = self.nbconvert_api.from_file('html', 'foo', 'testnb.ipynb') |
|
70 | 76 | self.assertEqual(r.status_code, 200) |
|
71 | 77 | self.assertIn(u'text/html', r.headers['Content-Type']) |
|
72 | 78 | self.assertIn(u'Created by test', r.text) |
|
73 | 79 | self.assertIn(u'print', r.text) |
|
74 | 80 | |
|
75 | 81 | r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb') |
|
76 | 82 | self.assertIn(u'text/x-python', r.headers['Content-Type']) |
|
77 | 83 | self.assertIn(u'print(2*6)', r.text) |
|
78 | 84 | |
|
79 | 85 | def test_from_file_404(self): |
|
80 | 86 | with assert_http_error(404): |
|
81 | 87 | self.nbconvert_api.from_file('html', 'foo', 'thisdoesntexist.ipynb') |
|
82 | 88 | |
|
83 | 89 | def test_from_file_download(self): |
|
84 | 90 | r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb', download=True) |
|
85 | 91 | content_disposition = r.headers['Content-Disposition'] |
|
86 | 92 | self.assertIn('attachment', content_disposition) |
|
87 | 93 | self.assertIn('testnb.py', content_disposition) |
|
88 | 94 | |
|
89 | 95 | def test_from_file_zip(self): |
|
90 | 96 | r = self.nbconvert_api.from_file('latex', 'foo', 'testnb.ipynb', download=True) |
|
91 | 97 | self.assertIn(u'application/zip', r.headers['Content-Type']) |
|
92 | 98 | self.assertIn(u'.zip', r.headers['Content-Disposition']) |
|
93 | 99 | |
|
94 | 100 | def test_from_post(self): |
|
95 | 101 | nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb') |
|
96 | 102 | nbmodel = requests.get(nbmodel_url).json() |
|
97 | 103 | |
|
98 | 104 | r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel) |
|
99 | 105 | self.assertEqual(r.status_code, 200) |
|
100 | 106 | self.assertIn(u'text/html', r.headers['Content-Type']) |
|
101 | 107 | self.assertIn(u'Created by test', r.text) |
|
102 | 108 | self.assertIn(u'print', r.text) |
|
103 | 109 | |
|
104 | 110 | r = self.nbconvert_api.from_post(format='python', nbmodel=nbmodel) |
|
105 | 111 | self.assertIn(u'text/x-python', r.headers['Content-Type']) |
|
106 | 112 | self.assertIn(u'print(2*6)', r.text) |
|
107 | 113 | |
|
108 | 114 | def test_from_post_zip(self): |
|
109 | 115 | nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb') |
|
110 | 116 | nbmodel = requests.get(nbmodel_url).json() |
|
111 | 117 | |
|
112 | 118 | r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel) |
|
113 | 119 | self.assertIn(u'application/zip', r.headers['Content-Type']) |
|
114 | 120 | self.assertIn(u'.zip', r.headers['Content-Disposition']) |
@@ -1,102 +1,103 b'' | |||
|
1 | 1 | """Module containing a preprocessor that extracts all of the outputs from the |
|
2 | 2 | notebook file. The extracted outputs are returned in the 'resources' dictionary. |
|
3 | 3 | """ |
|
4 | 4 | #----------------------------------------------------------------------------- |
|
5 | 5 | # Copyright (c) 2013, the IPython Development Team. |
|
6 | 6 | # |
|
7 | 7 | # Distributed under the terms of the Modified BSD License. |
|
8 | 8 | # |
|
9 | 9 | # The full license is in the file COPYING.txt, distributed with this software. |
|
10 | 10 | #----------------------------------------------------------------------------- |
|
11 | 11 | |
|
12 | 12 | #----------------------------------------------------------------------------- |
|
13 | 13 | # Imports |
|
14 | 14 | #----------------------------------------------------------------------------- |
|
15 | 15 | |
|
16 | 16 | import base64 |
|
17 | 17 | import sys |
|
18 | 18 | import os |
|
19 | 19 | |
|
20 | from IPython.utils.traitlets import Unicode | |
|
20 | from IPython.utils.traitlets import Unicode, Set | |
|
21 | 21 | from .base import Preprocessor |
|
22 | 22 | from IPython.utils import py3compat |
|
23 | 23 | |
|
24 | 24 | #----------------------------------------------------------------------------- |
|
25 | 25 | # Classes |
|
26 | 26 | #----------------------------------------------------------------------------- |
|
27 | 27 | |
|
28 | 28 | class ExtractOutputPreprocessor(Preprocessor): |
|
29 | 29 | """ |
|
30 | 30 | Extracts all of the outputs from the notebook file. The extracted |
|
31 | 31 | outputs are returned in the 'resources' dictionary. |
|
32 | 32 | """ |
|
33 | 33 | |
|
34 | 34 | output_filename_template = Unicode( |
|
35 | 35 | "{unique_key}_{cell_index}_{index}.{extension}", config=True) |
|
36 | 36 | |
|
37 | extract_output_types = Set({'png', 'jpg', 'svg', 'pdf'}, config=True) | |
|
37 | 38 | |
|
38 | 39 | def preprocess_cell(self, cell, resources, cell_index): |
|
39 | 40 | """ |
|
40 | 41 | Apply a transformation on each cell, |
|
41 | 42 | |
|
42 | 43 | Parameters |
|
43 | 44 | ---------- |
|
44 | 45 | cell : NotebookNode cell |
|
45 | 46 | Notebook cell being processed |
|
46 | 47 | resources : dictionary |
|
47 | 48 | Additional resources used in the conversion process. Allows |
|
48 | 49 | preprocessors to pass variables into the Jinja engine. |
|
49 | 50 | cell_index : int |
|
50 | 51 | Index of the cell being processed (see base.py) |
|
51 | 52 | """ |
|
52 | 53 | |
|
53 | 54 | #Get the unique key from the resource dict if it exists. If it does not |
|
54 | 55 | #exist, use 'output' as the default. Also, get files directory if it |
|
55 | 56 | #has been specified |
|
56 | 57 | unique_key = resources.get('unique_key', 'output') |
|
57 | 58 | output_files_dir = resources.get('output_files_dir', None) |
|
58 | 59 | |
|
59 | 60 | #Make sure outputs key exists |
|
60 | 61 | if not isinstance(resources['outputs'], dict): |
|
61 | 62 | resources['outputs'] = {} |
|
62 | 63 | |
|
63 | 64 | #Loop through all of the outputs in the cell |
|
64 | 65 | for index, out in enumerate(cell.get('outputs', [])): |
|
65 | 66 | |
|
66 |
#Get the output in data formats that the template |
|
|
67 |
for out_type in self. |
|
|
67 | #Get the output in data formats that the template needs extracted | |
|
68 | for out_type in self.extract_output_types: | |
|
68 | 69 | if out.hasattr(out_type): |
|
69 | 70 | data = out[out_type] |
|
70 | 71 | |
|
71 | 72 | #Binary files are base64-encoded, SVG is already XML |
|
72 | 73 | if out_type in ('png', 'jpg', 'jpeg', 'pdf'): |
|
73 | 74 | |
|
74 | 75 | # data is b64-encoded as text (str, unicode) |
|
75 | 76 | # decodestring only accepts bytes |
|
76 | 77 | data = py3compat.cast_bytes(data) |
|
77 | 78 | data = base64.decodestring(data) |
|
78 | 79 | elif sys.platform == 'win32': |
|
79 | 80 | data = data.replace('\n', '\r\n').encode("UTF-8") |
|
80 | 81 | else: |
|
81 | 82 | data = data.encode("UTF-8") |
|
82 | 83 | |
|
83 | 84 | #Build an output name |
|
84 | 85 | filename = self.output_filename_template.format( |
|
85 | 86 | unique_key=unique_key, |
|
86 | 87 | cell_index=cell_index, |
|
87 | 88 | index=index, |
|
88 | 89 | extension=out_type) |
|
89 | 90 | |
|
90 | 91 | #On the cell, make the figure available via |
|
91 | 92 | # cell.outputs[i].svg_filename ... etc (svg in example) |
|
92 | 93 | # Where |
|
93 | 94 | # cell.outputs[i].svg contains the data |
|
94 | 95 | if output_files_dir is not None: |
|
95 | 96 | filename = os.path.join(output_files_dir, filename) |
|
96 | 97 | out[out_type + '_filename'] = filename |
|
97 | 98 | |
|
98 | 99 | #In the resources, make the figure available via |
|
99 | 100 | # resources['outputs']['filename'] = data |
|
100 | 101 | resources['outputs'][filename] = data |
|
101 | 102 | |
|
102 | 103 | return cell, resources |
@@ -1,51 +1,51 b'' | |||
|
1 | 1 | {%- extends 'null.tpl' -%} |
|
2 | 2 | |
|
3 | 3 | |
|
4 | 4 | {% block in_prompt %} |
|
5 | 5 | # In[{{ cell.prompt_number if cell.prompt_number else ' ' }}]: |
|
6 | 6 | {% endblock in_prompt %} |
|
7 | 7 | |
|
8 | 8 | {% block output_prompt %} |
|
9 | 9 | # Out[{{ cell.prompt_number }}]: |
|
10 | 10 | {% endblock output_prompt %} |
|
11 | 11 | |
|
12 | 12 | {% block input %} |
|
13 | 13 | {{ cell.input | ipython2python }} |
|
14 | 14 | {% endblock input %} |
|
15 | 15 | |
|
16 | 16 | {# Those Two are for error displaying |
|
17 | 17 | even if the first one seem to do nothing, |
|
18 | 18 | it introduces a new line |
|
19 | 19 | #} |
|
20 | 20 | {% block pyerr %} |
|
21 | 21 | {{ super() }} |
|
22 | 22 | {% endblock pyerr %} |
|
23 | 23 | |
|
24 | 24 | {% block traceback_line %} |
|
25 | 25 | {{ line | indent | strip_ansi }} |
|
26 | 26 | {% endblock traceback_line %} |
|
27 | 27 | {# .... #} |
|
28 | 28 | |
|
29 | 29 | {% block pyout %} |
|
30 | {{ output.text | indent | comment_lines }} | |
|
30 | {{ output.text or '' | indent | comment_lines }} | |
|
31 | 31 | {% endblock pyout %} |
|
32 | 32 | |
|
33 | 33 | {% block stream %} |
|
34 | 34 | {{ output.text | indent | comment_lines }} |
|
35 | 35 | {% endblock stream %} |
|
36 | 36 | |
|
37 | 37 | {% block display_data scoped %} |
|
38 | 38 | # image file: |
|
39 | 39 | {% endblock display_data %} |
|
40 | 40 | |
|
41 | 41 | {% block markdowncell scoped %} |
|
42 | 42 | {{ cell.source | comment_lines }} |
|
43 | 43 | {% endblock markdowncell %} |
|
44 | 44 | |
|
45 | 45 | {% block headingcell scoped %} |
|
46 | 46 | {{ '#' * cell.level }}{{ cell.source | replace('\n', ' ') | comment_lines }} |
|
47 | 47 | {% endblock headingcell %} |
|
48 | 48 | |
|
49 | 49 | {% block unknowncell scoped %} |
|
50 | 50 | unknown type {{ cell.type }} |
|
51 | {% endblock unknowncell %} No newline at end of file | |
|
51 | {% endblock unknowncell %} |
General Comments 0
You need to be logged in to leave comments.
Login now