handlers.py
148 lines
| 4.7 KiB
| text/x-python
|
PythonLexer
|
r17525 | """Tornado handlers for nbconvert.""" | ||
# Copyright (c) IPython Development Team. | ||||
# Distributed under the terms of the Modified BSD License. | ||||
|
r13919 | import io | ||
|
r13827 | import os | ||
|
r13919 | import zipfile | ||
|
r13827 | |||
from tornado import web | ||||
|
r17533 | from ..base.handlers import ( | ||
IPythonHandler, FilesRedirectHandler, | ||||
notebook_path_regex, path_regex, | ||||
) | ||||
|
r13827 | from IPython.nbformat.current import to_notebook_json | ||
|
r13946 | |||
|
r13919 | from IPython.utils.py3compat import cast_bytes | ||
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 | ||||
|
r13827 | |||
|
r13946 | def get_exporter(format, **kwargs): | ||
"""get an exporter, raising appropriate errors""" | ||||
# if this fails, will raise 500 | ||||
try: | ||||
from IPython.nbconvert.exporters.export import exporter_map | ||||
except ImportError as e: | ||||
raise web.HTTPError(500, "Could not import nbconvert: %s" % e) | ||||
try: | ||||
Exporter = exporter_map[format] | ||||
except KeyError: | ||||
# should this be 400? | ||||
raise web.HTTPError(404, u"No exporter for format: %s" % format) | ||||
|
r14044 | try: | ||
return Exporter(**kwargs) | ||||
except Exception as e: | ||||
raise web.HTTPError(500, "Could not construct Exporter: %s" % e) | ||||
|
r13946 | |||
|
r13827 | class NbconvertFileHandler(IPythonHandler): | ||
SUPPORTED_METHODS = ('GET',) | ||||
@web.authenticated | ||||
def get(self, format, path='', name=None): | ||||
|
r13946 | |||
|
r16266 | exporter = get_exporter(format, config=self.config, log=self.log) | ||
|
r13827 | |||
path = path.strip('/') | ||||
|
r17525 | model = self.contents_manager.get_model(name=name, path=path) | ||
|
r13827 | |||
|
r15421 | self.set_header('Last-Modified', model['last_modified']) | ||
|
r13940 | |||
try: | ||||
|
r15421 | output, resources = exporter.from_notebook_node(model['content']) | ||
|
r13940 | except Exception as e: | ||
raise web.HTTPError(500, "nbconvert failed: %s" % e) | ||||
|
r13919 | |||
if respond_zip(self, name, output, resources): | ||||
return | ||||
|
r13830 | # Force download if requested | ||
|
r13829 | if self.get_argument('download', 'false').lower() == 'true': | ||
|
r13919 | filename = os.path.splitext(name)[0] + '.' + resources['output_extension'] | ||
|
r13829 | self.set_header('Content-Disposition', | ||
|
r13919 | 'attachment; filename="%s"' % filename) | ||
|
r13830 | # MIME type | ||
|
r13832 | if exporter.output_mimetype: | ||
self.set_header('Content-Type', | ||||
'%s; charset=utf-8' % exporter.output_mimetype) | ||||
|
r13830 | |||
|
r13827 | self.finish(output) | ||
class NbconvertPostHandler(IPythonHandler): | ||||
SUPPORTED_METHODS = ('POST',) | ||||
@web.authenticated | ||||
def post(self, format): | ||||
|
r13946 | exporter = get_exporter(format, config=self.config) | ||
|
r13827 | |||
model = self.get_json_body() | ||||
|
r18584 | name = model.get('name', 'notebook.ipynb') | ||
|
r13827 | nbnode = to_notebook_json(model['content']) | ||
|
r13940 | |||
try: | ||||
output, resources = exporter.from_notebook_node(nbnode) | ||||
except Exception as e: | ||||
raise web.HTTPError(500, "nbconvert failed: %s" % e) | ||||
|
r13919 | |||
|
r18584 | if respond_zip(self, name, output, resources): | ||
|
r13919 | return | ||
|
r13830 | # MIME type | ||
|
r13832 | if exporter.output_mimetype: | ||
self.set_header('Content-Type', | ||||
|
r13919 | '%s; charset=utf-8' % exporter.output_mimetype) | ||
|
r13830 | |||
|
r13827 | self.finish(output) | ||
|
r17533 | |||
|
r13827 | #----------------------------------------------------------------------------- | ||
# URL to handler mappings | ||||
#----------------------------------------------------------------------------- | ||||
_format_regex = r"(?P<format>\w+)" | ||||
|
r13916 | |||
|
r13827 | |||
default_handlers = [ | ||||
|
r13916 | (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex), | ||
|
r13827 | NbconvertFileHandler), | ||
(r"/nbconvert/%s" % _format_regex, NbconvertPostHandler), | ||||
|
r17533 | (r"/nbconvert/html%s" % path_regex, FilesRedirectHandler), | ||
|
r16525 | ] | ||