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