##// END OF EJS Templates
Extract output preprocessor only extracts specified formats
Thomas Kluyver -
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 is interested in.
67 #Get the output in data formats that the template needs extracted
67 for out_type in self.display_data_priority:
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