##// END OF EJS Templates
Merge pull request #4656 from takluyver/nbconvert-service...
Min RK -
r13924:0d046b44 merge
parent child Browse files
Show More
1 NO CONTENT: new file 100644
@@ -0,0 +1,117 b''
1 import io
2 import os
3 import zipfile
4
5 from tornado import web
6
7 from ..base.handlers import IPythonHandler, notebook_path_regex
8 from IPython.nbformat.current import to_notebook_json
9 from IPython.nbconvert.exporters.export import exporter_map
10 from IPython.utils import tz
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
49
50 class NbconvertFileHandler(IPythonHandler):
51
52 SUPPORTED_METHODS = ('GET',)
53
54 @web.authenticated
55 def get(self, format, path='', name=None):
56 exporter = exporter_map[format](config=self.config)
57
58 path = path.strip('/')
59 os_path = self.notebook_manager.get_os_path(name, path)
60 if not os.path.isfile(os_path):
61 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
62
63 info = os.stat(os_path)
64 self.set_header('Last-Modified', tz.utcfromtimestamp(info.st_mtime))
65
66 output, resources = exporter.from_filename(os_path)
67
68 if respond_zip(self, name, output, resources):
69 return
70
71 # Force download if requested
72 if self.get_argument('download', 'false').lower() == 'true':
73 filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
74 self.set_header('Content-Disposition',
75 'attachment; filename="%s"' % filename)
76
77 # MIME type
78 if exporter.output_mimetype:
79 self.set_header('Content-Type',
80 '%s; charset=utf-8' % exporter.output_mimetype)
81
82 self.finish(output)
83
84 class NbconvertPostHandler(IPythonHandler):
85 SUPPORTED_METHODS = ('POST',)
86
87 @web.authenticated
88 def post(self, format):
89 exporter = exporter_map[format](config=self.config)
90
91 model = self.get_json_body()
92 nbnode = to_notebook_json(model['content'])
93
94 output, resources = exporter.from_notebook_node(nbnode)
95
96 if respond_zip(self, nbnode.metadata.name, output, resources):
97 return
98
99 # MIME type
100 if exporter.output_mimetype:
101 self.set_header('Content-Type',
102 '%s; charset=utf-8' % exporter.output_mimetype)
103
104 self.finish(output)
105
106 #-----------------------------------------------------------------------------
107 # URL to handler mappings
108 #-----------------------------------------------------------------------------
109
110 _format_regex = r"(?P<format>\w+)"
111
112
113 default_handlers = [
114 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
115 NbconvertFileHandler),
116 (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
117 ] No newline at end of file
1 NO CONTENT: new file 100644
@@ -0,0 +1,120 b''
1 # coding: utf-8
2 import base64
3 import io
4 import json
5 import os
6 from os.path import join as pjoin
7 import shutil
8
9 import requests
10
11 from IPython.html.utils import url_path_join
12 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
13 from IPython.nbformat.current import (new_notebook, write, new_worksheet,
14 new_heading_cell, new_code_cell,
15 new_output)
16
17 class NbconvertAPI(object):
18 """Wrapper for nbconvert API calls."""
19 def __init__(self, base_url):
20 self.base_url = base_url
21
22 def _req(self, verb, path, body=None, params=None):
23 response = requests.request(verb,
24 url_path_join(self.base_url, 'nbconvert', path),
25 data=body, params=params,
26 )
27 response.raise_for_status()
28 return response
29
30 def from_file(self, format, path, name, download=False):
31 return self._req('GET', url_path_join(format, path, name),
32 params={'download':download})
33
34 def from_post(self, format, nbmodel):
35 body = json.dumps(nbmodel)
36 return self._req('POST', format, body)
37
38 def list_formats(self):
39 return self._req('GET', '')
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
45 class APITest(NotebookTestBase):
46 def setUp(self):
47 nbdir = self.notebook_dir.name
48
49 if not os.path.isdir(pjoin(nbdir, 'foo')):
50 os.mkdir(pjoin(nbdir, 'foo'))
51
52 nb = new_notebook(name='testnb')
53
54 ws = new_worksheet()
55 nb.worksheets = [ws]
56 ws.cells.append(new_heading_cell(u'Created by test ³'))
57 cc1 = new_code_cell(input=u'print(2*6)')
58 cc1.outputs.append(new_output(output_text=u'12'))
59 cc1.outputs.append(new_output(output_png=png_green_pixel, output_type='pyout'))
60 ws.cells.append(cc1)
61
62 with io.open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w',
63 encoding='utf-8') as f:
64 write(nb, f, format='ipynb')
65
66 self.nbconvert_api = NbconvertAPI(self.base_url())
67
68 def tearDown(self):
69 nbdir = self.notebook_dir.name
70
71 for dname in ['foo']:
72 shutil.rmtree(pjoin(nbdir, dname), ignore_errors=True)
73
74 def test_from_file(self):
75 r = self.nbconvert_api.from_file('html', 'foo', 'testnb.ipynb')
76 self.assertEqual(r.status_code, 200)
77 self.assertIn(u'text/html', r.headers['Content-Type'])
78 self.assertIn(u'Created by test', r.text)
79 self.assertIn(u'print', r.text)
80
81 r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb')
82 self.assertIn(u'text/x-python', r.headers['Content-Type'])
83 self.assertIn(u'print(2*6)', r.text)
84
85 def test_from_file_404(self):
86 with assert_http_error(404):
87 self.nbconvert_api.from_file('html', 'foo', 'thisdoesntexist.ipynb')
88
89 def test_from_file_download(self):
90 r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb', download=True)
91 content_disposition = r.headers['Content-Disposition']
92 self.assertIn('attachment', content_disposition)
93 self.assertIn('testnb.py', content_disposition)
94
95 def test_from_file_zip(self):
96 r = self.nbconvert_api.from_file('latex', 'foo', 'testnb.ipynb', download=True)
97 self.assertIn(u'application/zip', r.headers['Content-Type'])
98 self.assertIn(u'.zip', r.headers['Content-Disposition'])
99
100 def test_from_post(self):
101 nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
102 nbmodel = requests.get(nbmodel_url).json()
103
104 r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
105 self.assertEqual(r.status_code, 200)
106 self.assertIn(u'text/html', r.headers['Content-Type'])
107 self.assertIn(u'Created by test', r.text)
108 self.assertIn(u'print', r.text)
109
110 r = self.nbconvert_api.from_post(format='python', nbmodel=nbmodel)
111 self.assertIn(u'text/x-python', r.headers['Content-Type'])
112 self.assertIn(u'print(2*6)', r.text)
113
114 def test_from_post_zip(self):
115 nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
116 nbmodel = requests.get(nbmodel_url).json()
117
118 r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel)
119 self.assertIn(u'application/zip', r.headers['Content-Type'])
120 self.assertIn(u'.zip', r.headers['Content-Disposition'])
1 NO CONTENT: new file 100644
@@ -0,0 +1,23 b''
1 import json
2
3 from tornado import web
4
5 from ...base.handlers import IPythonHandler, json_errors
6 from IPython.nbconvert.exporters.export import exporter_map
7
8 class NbconvertRootHandler(IPythonHandler):
9 SUPPORTED_METHODS = ('GET',)
10
11 @web.authenticated
12 @json_errors
13 def get(self):
14 res = {}
15 for format, exporter in exporter_map.items():
16 res[format] = info = {}
17 info['output_mimetype'] = exporter.output_mimetype
18
19 self.finish(json.dumps(res))
20
21 default_handlers = [
22 (r"/api/nbconvert", NbconvertRootHandler),
23 ] No newline at end of file
1 NO CONTENT: new file 100644
@@ -0,0 +1,31 b''
1 import requests
2
3 from IPython.html.utils import url_path_join
4 from IPython.html.tests.launchnotebook import NotebookTestBase
5
6 class NbconvertAPI(object):
7 """Wrapper for nbconvert API calls."""
8 def __init__(self, base_url):
9 self.base_url = base_url
10
11 def _req(self, verb, path, body=None, params=None):
12 response = requests.request(verb,
13 url_path_join(self.base_url, 'api/nbconvert', path),
14 data=body, params=params,
15 )
16 response.raise_for_status()
17 return response
18
19 def list_formats(self):
20 return self._req('GET', '')
21
22 class APITest(NotebookTestBase):
23 def setUp(self):
24 self.nbconvert_api = NbconvertAPI(self.base_url())
25
26 def test_list_formats(self):
27 formats = self.nbconvert_api.list_formats().json()
28 self.assertIsInstance(formats, dict)
29 self.assertIn('python', formats)
30 self.assertIn('html', formats)
31 self.assertEqual(formats['python']['output_mimetype'], 'text/x-python') No newline at end of file
@@ -0,0 +1,3 b''
1 * Print preview is back in the notebook menus, along with options to
2 download the open notebook in various formats. This is powered by
3 nbconvert.
@@ -355,6 +355,14 b' class TrailingSlashHandler(web.RequestHandler):'
355 355 self.redirect(self.request.uri.rstrip('/'))
356 356
357 357 #-----------------------------------------------------------------------------
358 # URL pattern fragments for re-use
359 #-----------------------------------------------------------------------------
360
361 path_regex = r"(?P<path>(?:/.*)*)"
362 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
363 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
364
365 #-----------------------------------------------------------------------------
358 366 # URL to handler mappings
359 367 #-----------------------------------------------------------------------------
360 368
@@ -20,8 +20,7 b' import os'
20 20 from tornado import web
21 21 HTTPError = web.HTTPError
22 22
23 from ..base.handlers import IPythonHandler
24 from ..services.notebooks.handlers import _notebook_path_regex, _path_regex
23 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
25 24 from ..utils import url_path_join, url_escape
26 25
27 26 #-----------------------------------------------------------------------------
@@ -85,7 +84,7 b' class NotebookRedirectHandler(IPythonHandler):'
85 84
86 85
87 86 default_handlers = [
88 (r"/notebooks%s" % _notebook_path_regex, NotebookHandler),
89 (r"/notebooks%s" % _path_regex, NotebookRedirectHandler),
87 (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
88 (r"/notebooks%s" % path_regex, NotebookRedirectHandler),
90 89 ]
91 90
@@ -192,10 +192,12 b' class NotebookWebApplication(web.Application):'
192 192 handlers.extend(load_handlers('auth.login'))
193 193 handlers.extend(load_handlers('auth.logout'))
194 194 handlers.extend(load_handlers('notebook.handlers'))
195 handlers.extend(load_handlers('nbconvert.handlers'))
195 196 handlers.extend(load_handlers('services.kernels.handlers'))
196 197 handlers.extend(load_handlers('services.notebooks.handlers'))
197 198 handlers.extend(load_handlers('services.clusters.handlers'))
198 199 handlers.extend(load_handlers('services.sessions.handlers'))
200 handlers.extend(load_handlers('services.nbconvert.handlers'))
199 201 handlers.extend([
200 202 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
201 203 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
@@ -178,7 +178,7 b' class FileNotebookManager(NotebookManager):'
178 178 return notebooks
179 179
180 180 def get_notebook_model(self, name, path='', content=True):
181 """ Takes a path and name for a notebook and returns it's model
181 """ Takes a path and name for a notebook and returns its model
182 182
183 183 Parameters
184 184 ----------
@@ -23,7 +23,9 b' from tornado import web'
23 23 from IPython.html.utils import url_path_join, url_escape
24 24 from IPython.utils.jsonutil import date_default
25 25
26 from IPython.html.base.handlers import IPythonHandler, json_errors
26 from IPython.html.base.handlers import (IPythonHandler, json_errors,
27 notebook_path_regex, path_regex,
28 notebook_name_regex)
27 29
28 30 #-----------------------------------------------------------------------------
29 31 # Notebook web service handlers
@@ -264,17 +266,14 b' class ModifyNotebookCheckpointsHandler(IPythonHandler):'
264 266 #-----------------------------------------------------------------------------
265 267
266 268
267 _path_regex = r"(?P<path>(?:/.*)*)"
268 269 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
269 _notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
270 _notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex)
271 270
272 271 default_handlers = [
273 (r"/api/notebooks%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
274 (r"/api/notebooks%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
272 (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
273 (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
275 274 ModifyNotebookCheckpointsHandler),
276 (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler),
277 (r"/api/notebooks%s" % _path_regex, NotebookHandler),
275 (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
276 (r"/api/notebooks%s" % path_regex, NotebookHandler),
278 277 ]
279 278
280 279
@@ -22,7 +22,7 b''
22 22 ["reST", "text/restructuredtext"],
23 23 ["HTML", "text/html"],
24 24 ["Markdown", "text/markdown"],
25 ["Python", "application/x-python"],
25 ["Python", "text/x-python"],
26 26 ["Custom", "dialog"],
27 27
28 28 ],
@@ -87,4 +87,4 b''
87 87 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
88 88 console.log('Raw Cell Format toolbar preset loaded.');
89 89
90 }(IPython)); No newline at end of file
90 }(IPython));
@@ -69,6 +69,22 b' var IPython = (function (IPython) {'
69 69 );
70 70 };
71 71
72 MenuBar.prototype._nbconvert = function (format, download) {
73 download = download || false;
74 var notebook_name = IPython.notebook.get_notebook_name();
75 if (IPython.notebook.dirty) {
76 IPython.notebook.save_notebook({async : false});
77 }
78 var url = utils.url_path_join(
79 this.baseProjectUrl(),
80 'nbconvert',
81 format,
82 this.notebookPath(),
83 notebook_name + '.ipynb'
84 ) + "?download=" + download.toString();
85
86 window.open(url);
87 }
72 88
73 89 MenuBar.prototype.bind_events = function () {
74 90 // File
@@ -102,25 +118,22 b' var IPython = (function (IPython) {'
102 118 window.location.assign(url);
103 119 });
104 120
105 /* FIXME: download-as-py doesn't work right now
106 * We will need nbconvert hooked up to get this back
107
121 this.element.find('#print_preview').click(function () {
122 that._nbconvert('html', false);
123 });
124
108 125 this.element.find('#download_py').click(function () {
109 var notebook_name = IPython.notebook.get_notebook_name();
110 if (IPython.notebook.dirty) {
111 IPython.notebook.save_notebook({async : false});
112 }
113 var url = utils.url_path_join(
114 that.baseProjectUrl(),
115 'api/notebooks',
116 that.notebookPath(),
117 notebook_name + '.ipynb?format=py&download=True'
118 );
119 window.location.assign(url);
126 that._nbconvert('python', true);
120 127 });
121
122 */
123
128
129 this.element.find('#download_html').click(function () {
130 that._nbconvert('html', true);
131 });
132
133 this.element.find('#download_rst').click(function () {
134 that._nbconvert('rst', true);
135 });
136
124 137 this.element.find('#rename_notebook').click(function () {
125 138 IPython.save_widget.rename_notebook();
126 139 });
@@ -77,10 +77,13 b' class="notebook_app"'
77 77 </ul>
78 78 </li>
79 79 <li class="divider"></li>
80 <li id="print_preview"><a href="#">Print Preview</a></li>
80 81 <li class="dropdown-submenu"><a href="#">Download as</a>
81 82 <ul class="dropdown-menu">
82 83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
83 <!-- <li id="download_py"><a href="#">Python (.py)</a></li> -->
84 <li id="download_py"><a href="#">Python (.py)</a></li>
85 <li id="download_html"><a href="#">HTML (.html)</a></li>
86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
84 87 </ul>
85 88 </li>
86 89 <li class="divider"></li>
@@ -1,13 +1,14 b''
1 1 """Base class for notebook tests."""
2 2
3 import os
4 3 import sys
5 4 import time
6 5 import requests
7 6 from contextlib import contextmanager
8 from subprocess import Popen, PIPE
7 from subprocess import Popen, STDOUT
9 8 from unittest import TestCase
10 9
10 import nose
11
11 12 from IPython.utils.tempdir import TemporaryDirectory
12 13
13 14 class NotebookTestBase(TestCase):
@@ -55,10 +56,9 b' class NotebookTestBase(TestCase):'
55 56 '--ipython-dir=%s' % cls.ipython_dir.name,
56 57 '--notebook-dir=%s' % cls.notebook_dir.name,
57 58 ]
58 devnull = open(os.devnull, 'w')
59 59 cls.notebook = Popen(notebook_args,
60 stdout=devnull,
61 stderr=devnull,
60 stdout=nose.iptest_stdstreams_fileno(),
61 stderr=STDOUT,
62 62 )
63 63 cls.wait_until_alive()
64 64
@@ -18,9 +18,8 b' Authors:'
18 18 import os
19 19
20 20 from tornado import web
21 from ..base.handlers import IPythonHandler
21 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
22 22 from ..utils import url_path_join, path2url, url2path, url_escape
23 from ..services.notebooks.handlers import _notebook_path_regex, _path_regex
24 23
25 24 #-----------------------------------------------------------------------------
26 25 # Handlers
@@ -70,8 +69,8 b' class TreeRedirectHandler(IPythonHandler):'
70 69
71 70
72 71 default_handlers = [
73 (r"/tree%s" % _notebook_path_regex, TreeHandler),
74 (r"/tree%s" % _path_regex, TreeHandler),
72 (r"/tree%s" % notebook_path_regex, TreeHandler),
73 (r"/tree%s" % path_regex, TreeHandler),
75 74 (r"/tree", TreeHandler),
76 75 (r"/", TreeRedirectHandler),
77 76 ]
@@ -53,6 +53,11 b' class Exporter(LoggingConfigurable):'
53 53 help="Extension of the file that should be written to disk"
54 54 )
55 55
56 # MIME type of the result file, for HTTP response headers.
57 # This is *not* a traitlet, because we want to be able to access it from
58 # the class, not just on instances.
59 output_mimetype = ''
60
56 61 #Configurability, allows the user to easily add filters and preprocessors.
57 62 preprocessors = List(config=True,
58 63 help="""List of preprocessors, by name or namespace, to enable.""")
@@ -36,11 +36,14 b' class HTMLExporter(TemplateExporter):'
36 36 help="Extension of the file that should be written to disk"
37 37 )
38 38
39 mime_type = Unicode('text/html', config=True,
40 help="MIME type of the result file, for HTTP response headers."
41 )
42
39 43 default_template = Unicode('full', config=True, help="""Flavor of the data
40 44 format to use. I.E. 'full' or 'basic'""")
41 45
42 def _raw_mimetype_default(self):
43 return 'text/html'
46 output_mimetype = 'text/html'
44 47
45 48 @property
46 49 def default_config(self):
@@ -63,8 +63,7 b' class LatexExporter(TemplateExporter):'
63 63 #Extension that the template files use.
64 64 template_extension = Unicode(".tplx", config=True)
65 65
66 def _raw_mimetype_default(self):
67 return 'text/latex'
66 output_mimetype = 'text/latex'
68 67
69 68
70 69 @property
@@ -30,11 +30,10 b' class MarkdownExporter(TemplateExporter):'
30 30 'md', config=True,
31 31 help="Extension of the file that should be written to disk")
32 32
33 def _raw_mimetype_default(self):
34 return 'text/markdown'
33 output_mimetype = 'text/markdown'
35 34
36 35 def _raw_mimetypes_default(self):
37 return ['text/markdown', 'text/html']
36 return ['text/markdown', 'text/html', '']
38 37
39 38 @property
40 39 def default_config(self):
@@ -29,6 +29,4 b' class PythonExporter(TemplateExporter):'
29 29 'py', config=True,
30 30 help="Extension of the file that should be written to disk")
31 31
32 def _raw_mimetype_default(self):
33 return 'application/x-python'
34
32 output_mimetype = 'text/x-python'
@@ -30,9 +30,8 b' class RSTExporter(TemplateExporter):'
30 30 'rst', config=True,
31 31 help="Extension of the file that should be written to disk")
32 32
33 def _raw_mimetype_default(self):
34 return 'text/restructuredtext'
35
33 output_mimetype = 'text/restructuredtext'
34
36 35 @property
37 36 def default_config(self):
38 37 c = Config({'ExtractOutputPreprocessor':{'enabled':True}})
@@ -31,6 +31,8 b' class SlidesExporter(HTMLExporter):'
31 31 help="Extension of the file that should be written to disk"
32 32 )
33 33
34 output_mimetype = 'text/html'
35
34 36 default_template = Unicode('reveal', config=True, help="""Template of the
35 37 data format to use. I.E. 'reveal'""")
36 38
@@ -126,12 +126,11 b' class TemplateExporter(Exporter):'
126 126 help="""Dictionary of filters, by name and namespace, to add to the Jinja
127 127 environment.""")
128 128
129 raw_mimetype = Unicode('')
130 129 raw_mimetypes = List(config=True,
131 130 help="""formats of raw cells to be included in this Exporter's output."""
132 131 )
133 132 def _raw_mimetypes_default(self):
134 return [self.raw_mimetype]
133 return [self.output_mimetype, '']
135 134
136 135
137 136 def __init__(self, config=None, extra_loaders=None, **kw):
@@ -209,7 +208,6 b' class TemplateExporter(Exporter):'
209 208 preprocessors and filters.
210 209 """
211 210 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
212 resources.setdefault('raw_mimetype', self.raw_mimetype)
213 211 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
214 212
215 213 self._load_template()
@@ -23,7 +23,7 b' from ...tests.base import TestsBase'
23 23 #-----------------------------------------------------------------------------
24 24
25 25 all_raw_mimetypes = {
26 'application/x-python',
26 'text/x-python',
27 27 'text/markdown',
28 28 'text/html',
29 29 'text/restructuredtext',
@@ -43,7 +43,7 b''
43 43 {
44 44 "cell_type": "raw",
45 45 "metadata": {
46 "raw_mimetype": "application/x-python"
46 "raw_mimetype": "text/x-python"
47 47 },
48 48 "source": [
49 49 "def bar():\n",
@@ -81,4 +81,4 b''
81 81 "metadata": {}
82 82 }
83 83 ]
84 } No newline at end of file
84 }
@@ -17,7 +17,7 b' 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
@@ -34,6 +34,7 b' class ExtractOutputPreprocessor(Preprocessor):'
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 """
@@ -63,8 +64,8 b' class ExtractOutputPreprocessor(Preprocessor):'
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
@@ -29,6 +29,7 b' class TestExtractOutput(PreprocessorTestsBase):'
29 29 def build_preprocessor(self):
30 30 """Make an instance of a preprocessor"""
31 31 preprocessor = ExtractOutputPreprocessor()
32 preprocessor.extract_output_types = {'text', 'png'}
32 33 preprocessor.enabled = True
33 34 return preprocessor
34 35
@@ -81,7 +81,7 b' consider calling super even if it is a leave block, we might insert more blocks '
81 81 ((*- endblock headingcell -*))
82 82 ((*- elif cell.cell_type in ['raw'] -*))
83 83 ((*- block rawcell scoped -*))
84 ((* if cell.metadata.get('raw_mimetype', resources.get('raw_mimetype')) == resources.get('raw_mimetype') *))
84 ((* if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) *))
85 85 ((( cell.source )))
86 86 ((* endif *))
87 87 ((*- endblock rawcell -*))
@@ -27,7 +27,7 b' it introduces a new 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 %}
@@ -48,4 +48,4 b' it introduces a new line'
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 %}
@@ -77,7 +77,7 b' consider calling super even if it is a leave block, we might insert more blocks '
77 77 {%- endblock headingcell -%}
78 78 {%- elif cell.cell_type in ['raw'] -%}
79 79 {%- block rawcell scoped -%}
80 {% if cell.metadata.get('raw_mimetype', resources.get('raw_mimetype', '')).lower() in resources.get('raw_mimetypes', ['']) %}
80 {% if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %}
81 81 {{ cell.source }}
82 82 {% endif %}
83 83 {%- endblock rawcell -%}
@@ -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