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