diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py
index f0ca195..f63438a 100644
--- a/IPython/html/base/handlers.py
+++ b/IPython/html/base/handlers.py
@@ -355,6 +355,14 @@ class TrailingSlashHandler(web.RequestHandler):
self.redirect(self.request.uri.rstrip('/'))
#-----------------------------------------------------------------------------
+# URL pattern fragments for re-use
+#-----------------------------------------------------------------------------
+
+path_regex = r"(?P(?:/.*)*)"
+notebook_name_regex = r"(?P[^/]+\.ipynb)"
+notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
+
+#-----------------------------------------------------------------------------
# URL to handler mappings
#-----------------------------------------------------------------------------
diff --git a/IPython/html/nbconvert/__init__.py b/IPython/html/nbconvert/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/IPython/html/nbconvert/__init__.py
diff --git a/IPython/html/nbconvert/handlers.py b/IPython/html/nbconvert/handlers.py
new file mode 100644
index 0000000..b7197a7
--- /dev/null
+++ b/IPython/html/nbconvert/handlers.py
@@ -0,0 +1,117 @@
+import io
+import os
+import zipfile
+
+from tornado import web
+
+from ..base.handlers import IPythonHandler, notebook_path_regex
+from IPython.nbformat.current import to_notebook_json
+from IPython.nbconvert.exporters.export import exporter_map
+from IPython.utils import tz
+from IPython.utils.py3compat import cast_bytes
+
+import sys
+
+def find_resource_files(output_files_dir):
+ files = []
+ for dirpath, dirnames, filenames in os.walk(output_files_dir):
+ files.extend([os.path.join(dirpath, f) for f in filenames])
+ return files
+
+def respond_zip(handler, name, output, resources):
+ """Zip up the output and resource files and respond with the zip file.
+
+ Returns True if it has served a zip file, False if there are no resource
+ files, in which case we serve the plain output file.
+ """
+ # Check if we have resource files we need to zip
+ output_files = resources.get('outputs', None)
+ if not output_files:
+ return False
+
+ # Headers
+ zip_filename = os.path.splitext(name)[0] + '.zip'
+ handler.set_header('Content-Disposition',
+ 'attachment; filename="%s"' % zip_filename)
+ handler.set_header('Content-Type', 'application/zip')
+
+ # Prepare the zip file
+ buffer = io.BytesIO()
+ zipf = zipfile.ZipFile(buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
+ output_filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
+ zipf.writestr(output_filename, cast_bytes(output, 'utf-8'))
+ for filename, data in output_files.items():
+ zipf.writestr(os.path.basename(filename), data)
+ zipf.close()
+
+ handler.finish(buffer.getvalue())
+ return True
+
+class NbconvertFileHandler(IPythonHandler):
+
+ SUPPORTED_METHODS = ('GET',)
+
+ @web.authenticated
+ def get(self, format, path='', name=None):
+ exporter = exporter_map[format](config=self.config)
+
+ path = path.strip('/')
+ os_path = self.notebook_manager.get_os_path(name, path)
+ if not os.path.isfile(os_path):
+ raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
+
+ info = os.stat(os_path)
+ self.set_header('Last-Modified', tz.utcfromtimestamp(info.st_mtime))
+
+ output, resources = exporter.from_filename(os_path)
+
+ if respond_zip(self, name, output, resources):
+ return
+
+ # Force download if requested
+ if self.get_argument('download', 'false').lower() == 'true':
+ filename = os.path.splitext(name)[0] + '.' + resources['output_extension']
+ self.set_header('Content-Disposition',
+ 'attachment; filename="%s"' % filename)
+
+ # MIME type
+ if exporter.output_mimetype:
+ self.set_header('Content-Type',
+ '%s; charset=utf-8' % exporter.output_mimetype)
+
+ self.finish(output)
+
+class NbconvertPostHandler(IPythonHandler):
+ SUPPORTED_METHODS = ('POST',)
+
+ @web.authenticated
+ def post(self, format):
+ exporter = exporter_map[format](config=self.config)
+
+ model = self.get_json_body()
+ nbnode = to_notebook_json(model['content'])
+
+ output, resources = exporter.from_notebook_node(nbnode)
+
+ if respond_zip(self, nbnode.metadata.name, output, resources):
+ return
+
+ # MIME type
+ if exporter.output_mimetype:
+ self.set_header('Content-Type',
+ '%s; charset=utf-8' % exporter.output_mimetype)
+
+ self.finish(output)
+
+#-----------------------------------------------------------------------------
+# URL to handler mappings
+#-----------------------------------------------------------------------------
+
+_format_regex = r"(?P\w+)"
+
+
+default_handlers = [
+ (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
+ NbconvertFileHandler),
+ (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
+]
\ No newline at end of file
diff --git a/IPython/html/nbconvert/tests/__init__.py b/IPython/html/nbconvert/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/IPython/html/nbconvert/tests/__init__.py
diff --git a/IPython/html/nbconvert/tests/test_nbconvert_handlers.py b/IPython/html/nbconvert/tests/test_nbconvert_handlers.py
new file mode 100644
index 0000000..16e44fe
--- /dev/null
+++ b/IPython/html/nbconvert/tests/test_nbconvert_handlers.py
@@ -0,0 +1,120 @@
+# coding: utf-8
+import base64
+import io
+import json
+import os
+from os.path import join as pjoin
+import shutil
+
+import requests
+
+from IPython.html.utils import url_path_join
+from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
+from IPython.nbformat.current import (new_notebook, write, new_worksheet,
+ new_heading_cell, new_code_cell,
+ new_output)
+
+class NbconvertAPI(object):
+ """Wrapper for nbconvert API calls."""
+ def __init__(self, base_url):
+ self.base_url = base_url
+
+ def _req(self, verb, path, body=None, params=None):
+ response = requests.request(verb,
+ url_path_join(self.base_url, 'nbconvert', path),
+ data=body, params=params,
+ )
+ response.raise_for_status()
+ return response
+
+ def from_file(self, format, path, name, download=False):
+ return self._req('GET', url_path_join(format, path, name),
+ params={'download':download})
+
+ def from_post(self, format, nbmodel):
+ body = json.dumps(nbmodel)
+ return self._req('POST', format, body)
+
+ def list_formats(self):
+ return self._req('GET', '')
+
+png_green_pixel = base64.encodestring(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00'
+b'\x00\x00\x01\x00\x00x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDAT'
+b'\x08\xd7c\x90\xfb\xcf\x00\x00\x02\\\x01\x1e.~d\x87\x00\x00\x00\x00IEND\xaeB`\x82')
+
+class APITest(NotebookTestBase):
+ def setUp(self):
+ nbdir = self.notebook_dir.name
+
+ if not os.path.isdir(pjoin(nbdir, 'foo')):
+ os.mkdir(pjoin(nbdir, 'foo'))
+
+ nb = new_notebook(name='testnb')
+
+ ws = new_worksheet()
+ nb.worksheets = [ws]
+ ws.cells.append(new_heading_cell(u'Created by test ³'))
+ cc1 = new_code_cell(input=u'print(2*6)')
+ cc1.outputs.append(new_output(output_text=u'12'))
+ cc1.outputs.append(new_output(output_png=png_green_pixel, output_type='pyout'))
+ ws.cells.append(cc1)
+
+ with io.open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w',
+ encoding='utf-8') as f:
+ write(nb, f, format='ipynb')
+
+ self.nbconvert_api = NbconvertAPI(self.base_url())
+
+ def tearDown(self):
+ nbdir = self.notebook_dir.name
+
+ for dname in ['foo']:
+ shutil.rmtree(pjoin(nbdir, dname), ignore_errors=True)
+
+ def test_from_file(self):
+ r = self.nbconvert_api.from_file('html', 'foo', 'testnb.ipynb')
+ self.assertEqual(r.status_code, 200)
+ self.assertIn(u'text/html', r.headers['Content-Type'])
+ self.assertIn(u'Created by test', r.text)
+ self.assertIn(u'print', r.text)
+
+ r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb')
+ self.assertIn(u'text/x-python', r.headers['Content-Type'])
+ self.assertIn(u'print(2*6)', r.text)
+
+ def test_from_file_404(self):
+ with assert_http_error(404):
+ self.nbconvert_api.from_file('html', 'foo', 'thisdoesntexist.ipynb')
+
+ def test_from_file_download(self):
+ r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb', download=True)
+ content_disposition = r.headers['Content-Disposition']
+ self.assertIn('attachment', content_disposition)
+ self.assertIn('testnb.py', content_disposition)
+
+ def test_from_file_zip(self):
+ r = self.nbconvert_api.from_file('latex', 'foo', 'testnb.ipynb', download=True)
+ self.assertIn(u'application/zip', r.headers['Content-Type'])
+ self.assertIn(u'.zip', r.headers['Content-Disposition'])
+
+ def test_from_post(self):
+ nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
+ nbmodel = requests.get(nbmodel_url).json()
+
+ r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
+ self.assertEqual(r.status_code, 200)
+ self.assertIn(u'text/html', r.headers['Content-Type'])
+ self.assertIn(u'Created by test', r.text)
+ self.assertIn(u'print', r.text)
+
+ r = self.nbconvert_api.from_post(format='python', nbmodel=nbmodel)
+ self.assertIn(u'text/x-python', r.headers['Content-Type'])
+ self.assertIn(u'print(2*6)', r.text)
+
+ def test_from_post_zip(self):
+ nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
+ nbmodel = requests.get(nbmodel_url).json()
+
+ r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel)
+ self.assertIn(u'application/zip', r.headers['Content-Type'])
+ self.assertIn(u'.zip', r.headers['Content-Disposition'])
diff --git a/IPython/html/notebook/handlers.py b/IPython/html/notebook/handlers.py
index f3174b7..e442d00 100644
--- a/IPython/html/notebook/handlers.py
+++ b/IPython/html/notebook/handlers.py
@@ -20,8 +20,7 @@ import os
from tornado import web
HTTPError = web.HTTPError
-from ..base.handlers import IPythonHandler
-from ..services.notebooks.handlers import _notebook_path_regex, _path_regex
+from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
from ..utils import url_path_join, url_escape
#-----------------------------------------------------------------------------
@@ -85,7 +84,7 @@ class NotebookRedirectHandler(IPythonHandler):
default_handlers = [
- (r"/notebooks%s" % _notebook_path_regex, NotebookHandler),
- (r"/notebooks%s" % _path_regex, NotebookRedirectHandler),
+ (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
+ (r"/notebooks%s" % path_regex, NotebookRedirectHandler),
]
diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py
index 5c68500..0dc0dc2 100644
--- a/IPython/html/notebookapp.py
+++ b/IPython/html/notebookapp.py
@@ -192,10 +192,12 @@ class NotebookWebApplication(web.Application):
handlers.extend(load_handlers('auth.login'))
handlers.extend(load_handlers('auth.logout'))
handlers.extend(load_handlers('notebook.handlers'))
+ handlers.extend(load_handlers('nbconvert.handlers'))
handlers.extend(load_handlers('services.kernels.handlers'))
handlers.extend(load_handlers('services.notebooks.handlers'))
handlers.extend(load_handlers('services.clusters.handlers'))
handlers.extend(load_handlers('services.sessions.handlers'))
+ handlers.extend(load_handlers('services.nbconvert.handlers'))
handlers.extend([
(r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
(r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
diff --git a/IPython/html/services/nbconvert/__init__.py b/IPython/html/services/nbconvert/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/IPython/html/services/nbconvert/__init__.py
diff --git a/IPython/html/services/nbconvert/handlers.py b/IPython/html/services/nbconvert/handlers.py
new file mode 100644
index 0000000..e2ced71
--- /dev/null
+++ b/IPython/html/services/nbconvert/handlers.py
@@ -0,0 +1,23 @@
+import json
+
+from tornado import web
+
+from ...base.handlers import IPythonHandler, json_errors
+from IPython.nbconvert.exporters.export import exporter_map
+
+class NbconvertRootHandler(IPythonHandler):
+ SUPPORTED_METHODS = ('GET',)
+
+ @web.authenticated
+ @json_errors
+ def get(self):
+ res = {}
+ for format, exporter in exporter_map.items():
+ res[format] = info = {}
+ info['output_mimetype'] = exporter.output_mimetype
+
+ self.finish(json.dumps(res))
+
+default_handlers = [
+ (r"/api/nbconvert", NbconvertRootHandler),
+]
\ No newline at end of file
diff --git a/IPython/html/services/nbconvert/tests/__init__.py b/IPython/html/services/nbconvert/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/IPython/html/services/nbconvert/tests/__init__.py
diff --git a/IPython/html/services/nbconvert/tests/test_nbconvert_api.py b/IPython/html/services/nbconvert/tests/test_nbconvert_api.py
new file mode 100644
index 0000000..291ec74
--- /dev/null
+++ b/IPython/html/services/nbconvert/tests/test_nbconvert_api.py
@@ -0,0 +1,31 @@
+import requests
+
+from IPython.html.utils import url_path_join
+from IPython.html.tests.launchnotebook import NotebookTestBase
+
+class NbconvertAPI(object):
+ """Wrapper for nbconvert API calls."""
+ def __init__(self, base_url):
+ self.base_url = base_url
+
+ def _req(self, verb, path, body=None, params=None):
+ response = requests.request(verb,
+ url_path_join(self.base_url, 'api/nbconvert', path),
+ data=body, params=params,
+ )
+ response.raise_for_status()
+ return response
+
+ def list_formats(self):
+ return self._req('GET', '')
+
+class APITest(NotebookTestBase):
+ def setUp(self):
+ self.nbconvert_api = NbconvertAPI(self.base_url())
+
+ def test_list_formats(self):
+ formats = self.nbconvert_api.list_formats().json()
+ self.assertIsInstance(formats, dict)
+ self.assertIn('python', formats)
+ self.assertIn('html', formats)
+ self.assertEqual(formats['python']['output_mimetype'], 'text/x-python')
\ No newline at end of file
diff --git a/IPython/html/services/notebooks/filenbmanager.py b/IPython/html/services/notebooks/filenbmanager.py
index 3ae54ed..749892f 100644
--- a/IPython/html/services/notebooks/filenbmanager.py
+++ b/IPython/html/services/notebooks/filenbmanager.py
@@ -178,7 +178,7 @@ class FileNotebookManager(NotebookManager):
return notebooks
def get_notebook_model(self, name, path='', content=True):
- """ Takes a path and name for a notebook and returns it's model
+ """ Takes a path and name for a notebook and returns its model
Parameters
----------
diff --git a/IPython/html/services/notebooks/handlers.py b/IPython/html/services/notebooks/handlers.py
index 69d0ac4..27e74d7 100644
--- a/IPython/html/services/notebooks/handlers.py
+++ b/IPython/html/services/notebooks/handlers.py
@@ -23,7 +23,9 @@ from tornado import web
from IPython.html.utils import url_path_join, url_escape
from IPython.utils.jsonutil import date_default
-from IPython.html.base.handlers import IPythonHandler, json_errors
+from IPython.html.base.handlers import (IPythonHandler, json_errors,
+ notebook_path_regex, path_regex,
+ notebook_name_regex)
#-----------------------------------------------------------------------------
# Notebook web service handlers
@@ -264,17 +266,14 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
#-----------------------------------------------------------------------------
-_path_regex = r"(?P(?:/.*)*)"
_checkpoint_id_regex = r"(?P[\w-]+)"
-_notebook_name_regex = r"(?P[^/]+\.ipynb)"
-_notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex)
default_handlers = [
- (r"/api/notebooks%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
- (r"/api/notebooks%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
+ (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
+ (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
ModifyNotebookCheckpointsHandler),
- (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler),
- (r"/api/notebooks%s" % _path_regex, NotebookHandler),
+ (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
+ (r"/api/notebooks%s" % path_regex, NotebookHandler),
]
diff --git a/IPython/html/static/notebook/js/celltoolbarpresets/rawcell.js b/IPython/html/static/notebook/js/celltoolbarpresets/rawcell.js
index 3d51798..75965f0 100644
--- a/IPython/html/static/notebook/js/celltoolbarpresets/rawcell.js
+++ b/IPython/html/static/notebook/js/celltoolbarpresets/rawcell.js
@@ -22,7 +22,7 @@
["reST", "text/restructuredtext"],
["HTML", "text/html"],
["Markdown", "text/markdown"],
- ["Python", "application/x-python"],
+ ["Python", "text/x-python"],
["Custom", "dialog"],
],
@@ -87,4 +87,4 @@
CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
console.log('Raw Cell Format toolbar preset loaded.');
-}(IPython));
\ No newline at end of file
+}(IPython));
diff --git a/IPython/html/static/notebook/js/menubar.js b/IPython/html/static/notebook/js/menubar.js
index 7de2345..b4e8fec 100644
--- a/IPython/html/static/notebook/js/menubar.js
+++ b/IPython/html/static/notebook/js/menubar.js
@@ -69,6 +69,22 @@ var IPython = (function (IPython) {
);
};
+ MenuBar.prototype._nbconvert = function (format, download) {
+ download = download || false;
+ var notebook_name = IPython.notebook.get_notebook_name();
+ if (IPython.notebook.dirty) {
+ IPython.notebook.save_notebook({async : false});
+ }
+ var url = utils.url_path_join(
+ this.baseProjectUrl(),
+ 'nbconvert',
+ format,
+ this.notebookPath(),
+ notebook_name + '.ipynb'
+ ) + "?download=" + download.toString();
+
+ window.open(url);
+ }
MenuBar.prototype.bind_events = function () {
// File
@@ -102,25 +118,22 @@ var IPython = (function (IPython) {
window.location.assign(url);
});
- /* FIXME: download-as-py doesn't work right now
- * We will need nbconvert hooked up to get this back
-
+ this.element.find('#print_preview').click(function () {
+ that._nbconvert('html', false);
+ });
+
this.element.find('#download_py').click(function () {
- var notebook_name = IPython.notebook.get_notebook_name();
- if (IPython.notebook.dirty) {
- IPython.notebook.save_notebook({async : false});
- }
- var url = utils.url_path_join(
- that.baseProjectUrl(),
- 'api/notebooks',
- that.notebookPath(),
- notebook_name + '.ipynb?format=py&download=True'
- );
- window.location.assign(url);
+ that._nbconvert('python', true);
});
-
- */
-
+
+ this.element.find('#download_html').click(function () {
+ that._nbconvert('html', true);
+ });
+
+ this.element.find('#download_rst').click(function () {
+ that._nbconvert('rst', true);
+ });
+
this.element.find('#rename_notebook').click(function () {
IPython.save_widget.rename_notebook();
});
diff --git a/IPython/html/templates/notebook.html b/IPython/html/templates/notebook.html
index ae73841..84e8c37 100644
--- a/IPython/html/templates/notebook.html
+++ b/IPython/html/templates/notebook.html
@@ -77,10 +77,13 @@ class="notebook_app"
+ Print Preview
diff --git a/IPython/html/tests/launchnotebook.py b/IPython/html/tests/launchnotebook.py
index f9a9058..4a78242 100644
--- a/IPython/html/tests/launchnotebook.py
+++ b/IPython/html/tests/launchnotebook.py
@@ -1,13 +1,14 @@
"""Base class for notebook tests."""
-import os
import sys
import time
import requests
from contextlib import contextmanager
-from subprocess import Popen, PIPE
+from subprocess import Popen, STDOUT
from unittest import TestCase
+import nose
+
from IPython.utils.tempdir import TemporaryDirectory
class NotebookTestBase(TestCase):
@@ -55,10 +56,9 @@ class NotebookTestBase(TestCase):
'--ipython-dir=%s' % cls.ipython_dir.name,
'--notebook-dir=%s' % cls.notebook_dir.name,
]
- devnull = open(os.devnull, 'w')
cls.notebook = Popen(notebook_args,
- stdout=devnull,
- stderr=devnull,
+ stdout=nose.iptest_stdstreams_fileno(),
+ stderr=STDOUT,
)
cls.wait_until_alive()
diff --git a/IPython/html/tree/handlers.py b/IPython/html/tree/handlers.py
index 1ec33d5..03820ca 100644
--- a/IPython/html/tree/handlers.py
+++ b/IPython/html/tree/handlers.py
@@ -18,9 +18,8 @@ Authors:
import os
from tornado import web
-from ..base.handlers import IPythonHandler
+from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
from ..utils import url_path_join, path2url, url2path, url_escape
-from ..services.notebooks.handlers import _notebook_path_regex, _path_regex
#-----------------------------------------------------------------------------
# Handlers
@@ -70,8 +69,8 @@ class TreeRedirectHandler(IPythonHandler):
default_handlers = [
- (r"/tree%s" % _notebook_path_regex, TreeHandler),
- (r"/tree%s" % _path_regex, TreeHandler),
+ (r"/tree%s" % notebook_path_regex, TreeHandler),
+ (r"/tree%s" % path_regex, TreeHandler),
(r"/tree", TreeHandler),
(r"/", TreeRedirectHandler),
]
diff --git a/IPython/nbconvert/exporters/exporter.py b/IPython/nbconvert/exporters/exporter.py
index 3a594d0..cc77fc7 100644
--- a/IPython/nbconvert/exporters/exporter.py
+++ b/IPython/nbconvert/exporters/exporter.py
@@ -53,6 +53,11 @@ class Exporter(LoggingConfigurable):
help="Extension of the file that should be written to disk"
)
+ # MIME type of the result file, for HTTP response headers.
+ # This is *not* a traitlet, because we want to be able to access it from
+ # the class, not just on instances.
+ output_mimetype = ''
+
#Configurability, allows the user to easily add filters and preprocessors.
preprocessors = List(config=True,
help="""List of preprocessors, by name or namespace, to enable.""")
diff --git a/IPython/nbconvert/exporters/html.py b/IPython/nbconvert/exporters/html.py
index bb63397..8cb8675 100644
--- a/IPython/nbconvert/exporters/html.py
+++ b/IPython/nbconvert/exporters/html.py
@@ -36,11 +36,14 @@ class HTMLExporter(TemplateExporter):
help="Extension of the file that should be written to disk"
)
+ mime_type = Unicode('text/html', config=True,
+ help="MIME type of the result file, for HTTP response headers."
+ )
+
default_template = Unicode('full', config=True, help="""Flavor of the data
format to use. I.E. 'full' or 'basic'""")
- def _raw_mimetype_default(self):
- return 'text/html'
+ output_mimetype = 'text/html'
@property
def default_config(self):
diff --git a/IPython/nbconvert/exporters/latex.py b/IPython/nbconvert/exporters/latex.py
index a095262..0a94b9d 100644
--- a/IPython/nbconvert/exporters/latex.py
+++ b/IPython/nbconvert/exporters/latex.py
@@ -63,8 +63,7 @@ class LatexExporter(TemplateExporter):
#Extension that the template files use.
template_extension = Unicode(".tplx", config=True)
- def _raw_mimetype_default(self):
- return 'text/latex'
+ output_mimetype = 'text/latex'
@property
diff --git a/IPython/nbconvert/exporters/markdown.py b/IPython/nbconvert/exporters/markdown.py
index fb38539..6a888ae 100644
--- a/IPython/nbconvert/exporters/markdown.py
+++ b/IPython/nbconvert/exporters/markdown.py
@@ -30,11 +30,10 @@ class MarkdownExporter(TemplateExporter):
'md', config=True,
help="Extension of the file that should be written to disk")
- def _raw_mimetype_default(self):
- return 'text/markdown'
+ output_mimetype = 'text/markdown'
def _raw_mimetypes_default(self):
- return ['text/markdown', 'text/html']
+ return ['text/markdown', 'text/html', '']
@property
def default_config(self):
diff --git a/IPython/nbconvert/exporters/python.py b/IPython/nbconvert/exporters/python.py
index 1d13bc3..b5a4293 100644
--- a/IPython/nbconvert/exporters/python.py
+++ b/IPython/nbconvert/exporters/python.py
@@ -29,6 +29,4 @@ class PythonExporter(TemplateExporter):
'py', config=True,
help="Extension of the file that should be written to disk")
- def _raw_mimetype_default(self):
- return 'application/x-python'
-
+ output_mimetype = 'text/x-python'
diff --git a/IPython/nbconvert/exporters/rst.py b/IPython/nbconvert/exporters/rst.py
index 22dfc82..e401c99 100644
--- a/IPython/nbconvert/exporters/rst.py
+++ b/IPython/nbconvert/exporters/rst.py
@@ -30,9 +30,8 @@ class RSTExporter(TemplateExporter):
'rst', config=True,
help="Extension of the file that should be written to disk")
- def _raw_mimetype_default(self):
- return 'text/restructuredtext'
-
+ output_mimetype = 'text/restructuredtext'
+
@property
def default_config(self):
c = Config({'ExtractOutputPreprocessor':{'enabled':True}})
diff --git a/IPython/nbconvert/exporters/slides.py b/IPython/nbconvert/exporters/slides.py
index 6ecb5a9..1a4ebec 100644
--- a/IPython/nbconvert/exporters/slides.py
+++ b/IPython/nbconvert/exporters/slides.py
@@ -31,6 +31,8 @@ class SlidesExporter(HTMLExporter):
help="Extension of the file that should be written to disk"
)
+ output_mimetype = 'text/html'
+
default_template = Unicode('reveal', config=True, help="""Template of the
data format to use. I.E. 'reveal'""")
diff --git a/IPython/nbconvert/exporters/templateexporter.py b/IPython/nbconvert/exporters/templateexporter.py
index a5eed82..e127ab9 100644
--- a/IPython/nbconvert/exporters/templateexporter.py
+++ b/IPython/nbconvert/exporters/templateexporter.py
@@ -126,12 +126,11 @@ class TemplateExporter(Exporter):
help="""Dictionary of filters, by name and namespace, to add to the Jinja
environment.""")
- raw_mimetype = Unicode('')
raw_mimetypes = List(config=True,
help="""formats of raw cells to be included in this Exporter's output."""
)
def _raw_mimetypes_default(self):
- return [self.raw_mimetype]
+ return [self.output_mimetype, '']
def __init__(self, config=None, extra_loaders=None, **kw):
@@ -209,7 +208,6 @@ class TemplateExporter(Exporter):
preprocessors and filters.
"""
nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw)
- resources.setdefault('raw_mimetype', self.raw_mimetype)
resources.setdefault('raw_mimetypes', self.raw_mimetypes)
self._load_template()
diff --git a/IPython/nbconvert/exporters/tests/base.py b/IPython/nbconvert/exporters/tests/base.py
index 9db1caa..ad61321 100644
--- a/IPython/nbconvert/exporters/tests/base.py
+++ b/IPython/nbconvert/exporters/tests/base.py
@@ -23,7 +23,7 @@ from ...tests.base import TestsBase
#-----------------------------------------------------------------------------
all_raw_mimetypes = {
- 'application/x-python',
+ 'text/x-python',
'text/markdown',
'text/html',
'text/restructuredtext',
diff --git a/IPython/nbconvert/exporters/tests/files/rawtest.ipynb b/IPython/nbconvert/exporters/tests/files/rawtest.ipynb
index 667ddbf..6eae33a 100644
--- a/IPython/nbconvert/exporters/tests/files/rawtest.ipynb
+++ b/IPython/nbconvert/exporters/tests/files/rawtest.ipynb
@@ -43,7 +43,7 @@
{
"cell_type": "raw",
"metadata": {
- "raw_mimetype": "application/x-python"
+ "raw_mimetype": "text/x-python"
},
"source": [
"def bar():\n",
@@ -81,4 +81,4 @@
"metadata": {}
}
]
-}
\ No newline at end of file
+}
diff --git a/IPython/nbconvert/preprocessors/extractoutput.py b/IPython/nbconvert/preprocessors/extractoutput.py
index 961fde0..6fc5fee 100755
--- a/IPython/nbconvert/preprocessors/extractoutput.py
+++ b/IPython/nbconvert/preprocessors/extractoutput.py
@@ -17,7 +17,7 @@ import base64
import sys
import os
-from IPython.utils.traitlets import Unicode
+from IPython.utils.traitlets import Unicode, Set
from .base import Preprocessor
from IPython.utils import py3compat
@@ -34,6 +34,7 @@ class ExtractOutputPreprocessor(Preprocessor):
output_filename_template = Unicode(
"{unique_key}_{cell_index}_{index}.{extension}", config=True)
+ extract_output_types = Set({'png', 'jpg', 'svg', 'pdf'}, config=True)
def preprocess_cell(self, cell, resources, cell_index):
"""
@@ -63,8 +64,8 @@ class ExtractOutputPreprocessor(Preprocessor):
#Loop through all of the outputs in the cell
for index, out in enumerate(cell.get('outputs', [])):
- #Get the output in data formats that the template is interested in.
- for out_type in self.display_data_priority:
+ #Get the output in data formats that the template needs extracted
+ for out_type in self.extract_output_types:
if out.hasattr(out_type):
data = out[out_type]
diff --git a/IPython/nbconvert/preprocessors/tests/test_extractoutput.py b/IPython/nbconvert/preprocessors/tests/test_extractoutput.py
index 08bd1f2..7c22fe5 100644
--- a/IPython/nbconvert/preprocessors/tests/test_extractoutput.py
+++ b/IPython/nbconvert/preprocessors/tests/test_extractoutput.py
@@ -29,6 +29,7 @@ class TestExtractOutput(PreprocessorTestsBase):
def build_preprocessor(self):
"""Make an instance of a preprocessor"""
preprocessor = ExtractOutputPreprocessor()
+ preprocessor.extract_output_types = {'text', 'png'}
preprocessor.enabled = True
return preprocessor
diff --git a/IPython/nbconvert/templates/latex/skeleton/null.tplx b/IPython/nbconvert/templates/latex/skeleton/null.tplx
index 5f8909b..2327e84 100644
--- a/IPython/nbconvert/templates/latex/skeleton/null.tplx
+++ b/IPython/nbconvert/templates/latex/skeleton/null.tplx
@@ -81,7 +81,7 @@ consider calling super even if it is a leave block, we might insert more blocks
((*- endblock headingcell -*))
((*- elif cell.cell_type in ['raw'] -*))
((*- block rawcell scoped -*))
- ((* if cell.metadata.get('raw_mimetype', resources.get('raw_mimetype')) == resources.get('raw_mimetype') *))
+ ((* if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) *))
((( cell.source )))
((* endif *))
((*- endblock rawcell -*))
diff --git a/IPython/nbconvert/templates/python.tpl b/IPython/nbconvert/templates/python.tpl
index c863da7..8aa456b 100644
--- a/IPython/nbconvert/templates/python.tpl
+++ b/IPython/nbconvert/templates/python.tpl
@@ -27,7 +27,7 @@ it introduces a new line
{# .... #}
{% block pyout %}
-{{ output.text | indent | comment_lines }}
+{{ output.text or '' | indent | comment_lines }}
{% endblock pyout %}
{% block stream %}
@@ -48,4 +48,4 @@ it introduces a new line
{% block unknowncell scoped %}
unknown type {{ cell.type }}
-{% endblock unknowncell %}
\ No newline at end of file
+{% endblock unknowncell %}
diff --git a/IPython/nbconvert/templates/skeleton/null.tpl b/IPython/nbconvert/templates/skeleton/null.tpl
index aec85f4..9779043 100644
--- a/IPython/nbconvert/templates/skeleton/null.tpl
+++ b/IPython/nbconvert/templates/skeleton/null.tpl
@@ -77,7 +77,7 @@ consider calling super even if it is a leave block, we might insert more blocks
{%- endblock headingcell -%}
{%- elif cell.cell_type in ['raw'] -%}
{%- block rawcell scoped -%}
- {% if cell.metadata.get('raw_mimetype', resources.get('raw_mimetype', '')).lower() in resources.get('raw_mimetypes', ['']) %}
+ {% if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %}
{{ cell.source }}
{% endif %}
{%- endblock rawcell -%}
diff --git a/IPython/nbformat/v3/nbbase.py b/IPython/nbformat/v3/nbbase.py
index bec2447..c35d711 100644
--- a/IPython/nbformat/v3/nbbase.py
+++ b/IPython/nbformat/v3/nbbase.py
@@ -55,7 +55,8 @@ def new_output(output_type=None, output_text=None, output_png=None,
output_html=None, output_svg=None, output_latex=None, output_json=None,
output_javascript=None, output_jpeg=None, prompt_number=None,
ename=None, evalue=None, traceback=None, stream=None, metadata=None):
- """Create a new code cell with input and output"""
+ """Create a new output, to go in the ``cell.outputs`` list of a code cell.
+ """
output = NotebookNode()
if output_type is not None:
output.output_type = unicode_type(output_type)
diff --git a/docs/source/whatsnew/pr/nbconvert-service.rst b/docs/source/whatsnew/pr/nbconvert-service.rst
new file mode 100644
index 0000000..f3213c7
--- /dev/null
+++ b/docs/source/whatsnew/pr/nbconvert-service.rst
@@ -0,0 +1,3 @@
+* Print preview is back in the notebook menus, along with options to
+ download the open notebook in various formats. This is powered by
+ nbconvert.