##// 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
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
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
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
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 self.redirect(self.request.uri.rstrip('/'))
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 # URL to handler mappings
366 # URL to handler mappings
359 #-----------------------------------------------------------------------------
367 #-----------------------------------------------------------------------------
360
368
@@ -20,8 +20,7 b' import os'
20 from tornado import web
20 from tornado import web
21 HTTPError = web.HTTPError
21 HTTPError = web.HTTPError
22
22
23 from ..base.handlers import IPythonHandler
23 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
24 from ..services.notebooks.handlers import _notebook_path_regex, _path_regex
25 from ..utils import url_path_join, url_escape
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 default_handlers = [
86 default_handlers = [
88 (r"/notebooks%s" % _notebook_path_regex, NotebookHandler),
87 (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
89 (r"/notebooks%s" % _path_regex, NotebookRedirectHandler),
88 (r"/notebooks%s" % path_regex, NotebookRedirectHandler),
90 ]
89 ]
91
90
@@ -192,10 +192,12 b' class NotebookWebApplication(web.Application):'
192 handlers.extend(load_handlers('auth.login'))
192 handlers.extend(load_handlers('auth.login'))
193 handlers.extend(load_handlers('auth.logout'))
193 handlers.extend(load_handlers('auth.logout'))
194 handlers.extend(load_handlers('notebook.handlers'))
194 handlers.extend(load_handlers('notebook.handlers'))
195 handlers.extend(load_handlers('nbconvert.handlers'))
195 handlers.extend(load_handlers('services.kernels.handlers'))
196 handlers.extend(load_handlers('services.kernels.handlers'))
196 handlers.extend(load_handlers('services.notebooks.handlers'))
197 handlers.extend(load_handlers('services.notebooks.handlers'))
197 handlers.extend(load_handlers('services.clusters.handlers'))
198 handlers.extend(load_handlers('services.clusters.handlers'))
198 handlers.extend(load_handlers('services.sessions.handlers'))
199 handlers.extend(load_handlers('services.sessions.handlers'))
200 handlers.extend(load_handlers('services.nbconvert.handlers'))
199 handlers.extend([
201 handlers.extend([
200 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
202 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
201 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
203 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
@@ -178,7 +178,7 b' class FileNotebookManager(NotebookManager):'
178 return notebooks
178 return notebooks
179
179
180 def get_notebook_model(self, name, path='', content=True):
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 Parameters
183 Parameters
184 ----------
184 ----------
@@ -23,7 +23,9 b' from tornado import web'
23 from IPython.html.utils import url_path_join, url_escape
23 from IPython.html.utils import url_path_join, url_escape
24 from IPython.utils.jsonutil import date_default
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 # Notebook web service handlers
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 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
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 default_handlers = [
271 default_handlers = [
273 (r"/api/notebooks%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
272 (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
274 (r"/api/notebooks%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
273 (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
275 ModifyNotebookCheckpointsHandler),
274 ModifyNotebookCheckpointsHandler),
276 (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler),
275 (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
277 (r"/api/notebooks%s" % _path_regex, NotebookHandler),
276 (r"/api/notebooks%s" % path_regex, NotebookHandler),
278 ]
277 ]
279
278
280
279
@@ -22,7 +22,7 b''
22 ["reST", "text/restructuredtext"],
22 ["reST", "text/restructuredtext"],
23 ["HTML", "text/html"],
23 ["HTML", "text/html"],
24 ["Markdown", "text/markdown"],
24 ["Markdown", "text/markdown"],
25 ["Python", "application/x-python"],
25 ["Python", "text/x-python"],
26 ["Custom", "dialog"],
26 ["Custom", "dialog"],
27
27
28 ],
28 ],
@@ -87,4 +87,4 b''
87 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
87 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
88 console.log('Raw Cell Format toolbar preset loaded.');
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 MenuBar.prototype.bind_events = function () {
89 MenuBar.prototype.bind_events = function () {
74 // File
90 // File
@@ -102,25 +118,22 b' var IPython = (function (IPython) {'
102 window.location.assign(url);
118 window.location.assign(url);
103 });
119 });
104
120
105 /* FIXME: download-as-py doesn't work right now
121 this.element.find('#print_preview').click(function () {
106 * We will need nbconvert hooked up to get this back
122 that._nbconvert('html', false);
107
123 });
124
108 this.element.find('#download_py').click(function () {
125 this.element.find('#download_py').click(function () {
109 var notebook_name = IPython.notebook.get_notebook_name();
126 that._nbconvert('python', true);
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);
120 });
127 });
121
128
122 */
129 this.element.find('#download_html').click(function () {
123
130 that._nbconvert('html', true);
131 });
132
133 this.element.find('#download_rst').click(function () {
134 that._nbconvert('rst', true);
135 });
136
124 this.element.find('#rename_notebook').click(function () {
137 this.element.find('#rename_notebook').click(function () {
125 IPython.save_widget.rename_notebook();
138 IPython.save_widget.rename_notebook();
126 });
139 });
@@ -77,10 +77,13 b' class="notebook_app"'
77 </ul>
77 </ul>
78 </li>
78 </li>
79 <li class="divider"></li>
79 <li class="divider"></li>
80 <li id="print_preview"><a href="#">Print Preview</a></li>
80 <li class="dropdown-submenu"><a href="#">Download as</a>
81 <li class="dropdown-submenu"><a href="#">Download as</a>
81 <ul class="dropdown-menu">
82 <ul class="dropdown-menu">
82 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
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 </ul>
87 </ul>
85 </li>
88 </li>
86 <li class="divider"></li>
89 <li class="divider"></li>
@@ -1,13 +1,14 b''
1 """Base class for notebook tests."""
1 """Base class for notebook tests."""
2
2
3 import os
4 import sys
3 import sys
5 import time
4 import time
6 import requests
5 import requests
7 from contextlib import contextmanager
6 from contextlib import contextmanager
8 from subprocess import Popen, PIPE
7 from subprocess import Popen, STDOUT
9 from unittest import TestCase
8 from unittest import TestCase
10
9
10 import nose
11
11 from IPython.utils.tempdir import TemporaryDirectory
12 from IPython.utils.tempdir import TemporaryDirectory
12
13
13 class NotebookTestBase(TestCase):
14 class NotebookTestBase(TestCase):
@@ -55,10 +56,9 b' class NotebookTestBase(TestCase):'
55 '--ipython-dir=%s' % cls.ipython_dir.name,
56 '--ipython-dir=%s' % cls.ipython_dir.name,
56 '--notebook-dir=%s' % cls.notebook_dir.name,
57 '--notebook-dir=%s' % cls.notebook_dir.name,
57 ]
58 ]
58 devnull = open(os.devnull, 'w')
59 cls.notebook = Popen(notebook_args,
59 cls.notebook = Popen(notebook_args,
60 stdout=devnull,
60 stdout=nose.iptest_stdstreams_fileno(),
61 stderr=devnull,
61 stderr=STDOUT,
62 )
62 )
63 cls.wait_until_alive()
63 cls.wait_until_alive()
64
64
@@ -18,9 +18,8 b' Authors:'
18 import os
18 import os
19
19
20 from tornado import web
20 from tornado import web
21 from ..base.handlers import IPythonHandler
21 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
22 from ..utils import url_path_join, path2url, url2path, url_escape
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 # Handlers
25 # Handlers
@@ -70,8 +69,8 b' class TreeRedirectHandler(IPythonHandler):'
70
69
71
70
72 default_handlers = [
71 default_handlers = [
73 (r"/tree%s" % _notebook_path_regex, TreeHandler),
72 (r"/tree%s" % notebook_path_regex, TreeHandler),
74 (r"/tree%s" % _path_regex, TreeHandler),
73 (r"/tree%s" % path_regex, TreeHandler),
75 (r"/tree", TreeHandler),
74 (r"/tree", TreeHandler),
76 (r"/", TreeRedirectHandler),
75 (r"/", TreeRedirectHandler),
77 ]
76 ]
@@ -53,6 +53,11 b' class Exporter(LoggingConfigurable):'
53 help="Extension of the file that should be written to disk"
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 #Configurability, allows the user to easily add filters and preprocessors.
61 #Configurability, allows the user to easily add filters and preprocessors.
57 preprocessors = List(config=True,
62 preprocessors = List(config=True,
58 help="""List of preprocessors, by name or namespace, to enable.""")
63 help="""List of preprocessors, by name or namespace, to enable.""")
@@ -36,11 +36,14 b' class HTMLExporter(TemplateExporter):'
36 help="Extension of the file that should be written to disk"
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 default_template = Unicode('full', config=True, help="""Flavor of the data
43 default_template = Unicode('full', config=True, help="""Flavor of the data
40 format to use. I.E. 'full' or 'basic'""")
44 format to use. I.E. 'full' or 'basic'""")
41
45
42 def _raw_mimetype_default(self):
46 output_mimetype = 'text/html'
43 return 'text/html'
44
47
45 @property
48 @property
46 def default_config(self):
49 def default_config(self):
@@ -63,8 +63,7 b' class LatexExporter(TemplateExporter):'
63 #Extension that the template files use.
63 #Extension that the template files use.
64 template_extension = Unicode(".tplx", config=True)
64 template_extension = Unicode(".tplx", config=True)
65
65
66 def _raw_mimetype_default(self):
66 output_mimetype = 'text/latex'
67 return 'text/latex'
68
67
69
68
70 @property
69 @property
@@ -30,11 +30,10 b' class MarkdownExporter(TemplateExporter):'
30 'md', config=True,
30 'md', config=True,
31 help="Extension of the file that should be written to disk")
31 help="Extension of the file that should be written to disk")
32
32
33 def _raw_mimetype_default(self):
33 output_mimetype = 'text/markdown'
34 return 'text/markdown'
35
34
36 def _raw_mimetypes_default(self):
35 def _raw_mimetypes_default(self):
37 return ['text/markdown', 'text/html']
36 return ['text/markdown', 'text/html', '']
38
37
39 @property
38 @property
40 def default_config(self):
39 def default_config(self):
@@ -29,6 +29,4 b' class PythonExporter(TemplateExporter):'
29 'py', config=True,
29 'py', config=True,
30 help="Extension of the file that should be written to disk")
30 help="Extension of the file that should be written to disk")
31
31
32 def _raw_mimetype_default(self):
32 output_mimetype = 'text/x-python'
33 return 'application/x-python'
34
@@ -30,9 +30,8 b' class RSTExporter(TemplateExporter):'
30 'rst', config=True,
30 'rst', config=True,
31 help="Extension of the file that should be written to disk")
31 help="Extension of the file that should be written to disk")
32
32
33 def _raw_mimetype_default(self):
33 output_mimetype = 'text/restructuredtext'
34 return 'text/restructuredtext'
34
35
36 @property
35 @property
37 def default_config(self):
36 def default_config(self):
38 c = Config({'ExtractOutputPreprocessor':{'enabled':True}})
37 c = Config({'ExtractOutputPreprocessor':{'enabled':True}})
@@ -31,6 +31,8 b' class SlidesExporter(HTMLExporter):'
31 help="Extension of the file that should be written to disk"
31 help="Extension of the file that should be written to disk"
32 )
32 )
33
33
34 output_mimetype = 'text/html'
35
34 default_template = Unicode('reveal', config=True, help="""Template of the
36 default_template = Unicode('reveal', config=True, help="""Template of the
35 data format to use. I.E. 'reveal'""")
37 data format to use. I.E. 'reveal'""")
36
38
@@ -126,12 +126,11 b' class TemplateExporter(Exporter):'
126 help="""Dictionary of filters, by name and namespace, to add to the Jinja
126 help="""Dictionary of filters, by name and namespace, to add to the Jinja
127 environment.""")
127 environment.""")
128
128
129 raw_mimetype = Unicode('')
130 raw_mimetypes = List(config=True,
129 raw_mimetypes = List(config=True,
131 help="""formats of raw cells to be included in this Exporter's output."""
130 help="""formats of raw cells to be included in this Exporter's output."""
132 )
131 )
133 def _raw_mimetypes_default(self):
132 def _raw_mimetypes_default(self):
134 return [self.raw_mimetype]
133 return [self.output_mimetype, '']
135
134
136
135
137 def __init__(self, config=None, extra_loaders=None, **kw):
136 def __init__(self, config=None, extra_loaders=None, **kw):
@@ -209,7 +208,6 b' class TemplateExporter(Exporter):'
209 preprocessors and filters.
208 preprocessors and filters.
210 """
209 """
211 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
210 nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
212 resources.setdefault('raw_mimetype', self.raw_mimetype)
213 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
211 resources.setdefault('raw_mimetypes', self.raw_mimetypes)
214
212
215 self._load_template()
213 self._load_template()
@@ -23,7 +23,7 b' from ...tests.base import TestsBase'
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 all_raw_mimetypes = {
25 all_raw_mimetypes = {
26 'application/x-python',
26 'text/x-python',
27 'text/markdown',
27 'text/markdown',
28 'text/html',
28 'text/html',
29 'text/restructuredtext',
29 'text/restructuredtext',
@@ -43,7 +43,7 b''
43 {
43 {
44 "cell_type": "raw",
44 "cell_type": "raw",
45 "metadata": {
45 "metadata": {
46 "raw_mimetype": "application/x-python"
46 "raw_mimetype": "text/x-python"
47 },
47 },
48 "source": [
48 "source": [
49 "def bar():\n",
49 "def bar():\n",
@@ -81,4 +81,4 b''
81 "metadata": {}
81 "metadata": {}
82 }
82 }
83 ]
83 ]
84 } No newline at end of file
84 }
@@ -17,7 +17,7 b' 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
@@ -34,6 +34,7 b' class ExtractOutputPreprocessor(Preprocessor):'
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 """
@@ -63,8 +64,8 b' class ExtractOutputPreprocessor(Preprocessor):'
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
@@ -29,6 +29,7 b' class TestExtractOutput(PreprocessorTestsBase):'
29 def build_preprocessor(self):
29 def build_preprocessor(self):
30 """Make an instance of a preprocessor"""
30 """Make an instance of a preprocessor"""
31 preprocessor = ExtractOutputPreprocessor()
31 preprocessor = ExtractOutputPreprocessor()
32 preprocessor.extract_output_types = {'text', 'png'}
32 preprocessor.enabled = True
33 preprocessor.enabled = True
33 return preprocessor
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 ((*- endblock headingcell -*))
81 ((*- endblock headingcell -*))
82 ((*- elif cell.cell_type in ['raw'] -*))
82 ((*- elif cell.cell_type in ['raw'] -*))
83 ((*- block rawcell scoped -*))
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 ((( cell.source )))
85 ((( cell.source )))
86 ((* endif *))
86 ((* endif *))
87 ((*- endblock rawcell -*))
87 ((*- endblock rawcell -*))
@@ -27,7 +27,7 b' it introduces a new 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 %}
@@ -48,4 +48,4 b' it introduces a new line'
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 %}
@@ -77,7 +77,7 b' consider calling super even if it is a leave block, we might insert more blocks '
77 {%- endblock headingcell -%}
77 {%- endblock headingcell -%}
78 {%- elif cell.cell_type in ['raw'] -%}
78 {%- elif cell.cell_type in ['raw'] -%}
79 {%- block rawcell scoped -%}
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 {{ cell.source }}
81 {{ cell.source }}
82 {% endif %}
82 {% endif %}
83 {%- endblock rawcell -%}
83 {%- endblock rawcell -%}
@@ -55,7 +55,8 b' def new_output(output_type=None, output_text=None, output_png=None,'
55 output_html=None, output_svg=None, output_latex=None, output_json=None,
55 output_html=None, output_svg=None, output_latex=None, output_json=None,
56 output_javascript=None, output_jpeg=None, prompt_number=None,
56 output_javascript=None, output_jpeg=None, prompt_number=None,
57 ename=None, evalue=None, traceback=None, stream=None, metadata=None):
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 output = NotebookNode()
60 output = NotebookNode()
60 if output_type is not None:
61 if output_type is not None:
61 output.output_type = unicode_type(output_type)
62 output.output_type = unicode_type(output_type)
General Comments 0
You need to be logged in to leave comments. Login now