Show More
@@ -1,85 +1,100 b'' | |||
|
1 | import json | |
|
1 | 2 | import os |
|
2 | 3 | |
|
3 | 4 | from tornado import web |
|
4 | 5 | |
|
5 | from ..base.handlers import IPythonHandler | |
|
6 | from ..base.handlers import IPythonHandler, json_errors | |
|
6 | 7 | from IPython.nbformat.current import to_notebook_json |
|
7 | 8 | from IPython.nbconvert.exporters.export import exporter_map |
|
8 | 9 | from IPython.utils import tz |
|
9 | 10 | |
|
10 | 11 | |
|
11 | 12 | def has_resource_files(resources): |
|
12 | 13 | output_files_dir = resources.get('output_files_dir', "") |
|
13 | 14 | return bool(os.path.isdir(output_files_dir) and \ |
|
14 | 15 | os.listdir(output_files_dir)) |
|
15 | 16 | |
|
16 | 17 | class NbconvertFileHandler(IPythonHandler): |
|
17 | 18 | |
|
18 | 19 | SUPPORTED_METHODS = ('GET',) |
|
19 | 20 | |
|
20 | 21 | @web.authenticated |
|
21 | 22 | def get(self, format, path='', name=None): |
|
22 | 23 | exporter = exporter_map[format]() |
|
23 | 24 | |
|
24 | 25 | path = path.strip('/') |
|
25 | 26 | os_path = self.notebook_manager.get_os_path(name, path) |
|
26 | 27 | if not os.path.isfile(os_path): |
|
27 | 28 | raise web.HTTPError(404, u'Notebook does not exist: %s' % name) |
|
28 | 29 | |
|
29 | 30 | info = os.stat(os_path) |
|
30 | 31 | self.set_header('Last-Modified', tz.utcfromtimestamp(info.st_mtime)) |
|
31 | 32 | |
|
32 | 33 | # Force download if requested |
|
33 | 34 | if self.get_argument('download', 'false').lower() == 'true': |
|
34 | 35 | filename = os.path.splitext(name)[0] + '.' + exporter.file_extension |
|
35 | 36 | self.set_header('Content-Disposition', |
|
36 | 37 | 'attachment; filename="%s"' % filename) |
|
37 | 38 | |
|
38 | 39 | # MIME type |
|
39 | 40 | if exporter.output_mimetype: |
|
40 | 41 | self.set_header('Content-Type', |
|
41 | 42 | '%s; charset=utf-8' % exporter.output_mimetype) |
|
42 | 43 | |
|
43 | 44 | output, resources = exporter.from_filename(os_path) |
|
44 | 45 | |
|
45 | 46 | # TODO: If there are resources, combine them into a zip file |
|
46 | 47 | assert not has_resource_files(resources) |
|
47 | 48 | |
|
48 | 49 | self.finish(output) |
|
49 | 50 | |
|
50 | 51 | class NbconvertPostHandler(IPythonHandler): |
|
51 | 52 | SUPPORTED_METHODS = ('POST',) |
|
52 | 53 | |
|
53 | 54 | @web.authenticated |
|
54 | 55 | def post(self, format): |
|
55 | 56 | exporter = exporter_map[format]() |
|
56 | 57 | |
|
57 | 58 | model = self.get_json_body() |
|
58 | 59 | nbnode = to_notebook_json(model['content']) |
|
59 | 60 | |
|
60 | 61 | # MIME type |
|
61 | 62 | if exporter.output_mimetype: |
|
62 | 63 | self.set_header('Content-Type', |
|
63 | 64 | '%s; charset=utf-8' % exporter.output_mimetype) |
|
64 | 65 | |
|
65 | 66 | output, resources = exporter.from_notebook_node(nbnode) |
|
66 | 67 | |
|
67 | 68 | # TODO: If there are resources, combine them into a zip file |
|
68 | 69 | assert not has_resource_files(resources) |
|
69 | 70 | |
|
70 | 71 | self.finish(output) |
|
71 | 72 | |
|
73 | class NbconvertRootHandler(IPythonHandler): | |
|
74 | SUPPORTED_METHODS = ('GET',) | |
|
75 | ||
|
76 | @web.authenticated | |
|
77 | @json_errors | |
|
78 | def get(self): | |
|
79 | res = {} | |
|
80 | for format, exporter in exporter_map.items(): | |
|
81 | res[format] = info = {} | |
|
82 | info['output_mimetype'] = exporter.output_mimetype | |
|
83 | ||
|
84 | self.finish(json.dumps(res)) | |
|
85 | ||
|
72 | 86 | #----------------------------------------------------------------------------- |
|
73 | 87 | # URL to handler mappings |
|
74 | 88 | #----------------------------------------------------------------------------- |
|
75 | 89 | |
|
76 | 90 | _format_regex = r"(?P<format>\w+)" |
|
77 | 91 | _path_regex = r"(?P<path>(?:/.*)*)" |
|
78 | 92 | _notebook_name_regex = r"(?P<name>[^/]+\.ipynb)" |
|
79 | 93 | _notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex) |
|
80 | 94 | |
|
81 | 95 | default_handlers = [ |
|
82 | 96 | (r"/nbconvert/%s%s" % (_format_regex, _notebook_path_regex), |
|
83 | 97 | NbconvertFileHandler), |
|
84 | 98 | (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler), |
|
99 | (r"/nbconvert", NbconvertRootHandler), | |
|
85 | 100 | ] No newline at end of file |
@@ -1,94 +1,104 b'' | |||
|
1 | 1 | import io |
|
2 | 2 | import json |
|
3 | 3 | import os |
|
4 | 4 | from os.path import join as pjoin |
|
5 | 5 | import shutil |
|
6 | 6 | |
|
7 | 7 | import requests |
|
8 | 8 | |
|
9 | 9 | from IPython.html.utils import url_path_join |
|
10 | 10 | from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error |
|
11 | 11 | from IPython.nbformat.current import (new_notebook, write, new_worksheet, |
|
12 | 12 | new_heading_cell, new_code_cell) |
|
13 | 13 | |
|
14 | 14 | class NbconvertAPI(object): |
|
15 | 15 | """Wrapper for nbconvert API calls.""" |
|
16 | 16 | def __init__(self, base_url): |
|
17 | 17 | self.base_url = base_url |
|
18 | 18 | |
|
19 | 19 | def _req(self, verb, path, body=None, params=None): |
|
20 | 20 | response = requests.request(verb, |
|
21 | 21 | url_path_join(self.base_url, 'nbconvert', path), |
|
22 | 22 | data=body, params=params, |
|
23 | 23 | ) |
|
24 | 24 | response.raise_for_status() |
|
25 | 25 | return response |
|
26 | 26 | |
|
27 | 27 | def from_file(self, format, path, name, download=False): |
|
28 | 28 | return self._req('GET', url_path_join(format, path, name), |
|
29 | 29 | params={'download':download}) |
|
30 | 30 | |
|
31 | 31 | def from_post(self, format, nbmodel): |
|
32 | 32 | body = json.dumps(nbmodel) |
|
33 | 33 | return self._req('POST', format, body) |
|
34 | 34 | |
|
35 | def list_formats(self): | |
|
36 | return self._req('GET', '') | |
|
37 | ||
|
35 | 38 | class APITest(NotebookTestBase): |
|
36 | 39 | def setUp(self): |
|
37 | 40 | nbdir = self.notebook_dir.name |
|
38 | 41 | |
|
39 | 42 | if not os.path.isdir(pjoin(nbdir, 'foo')): |
|
40 | 43 | os.mkdir(pjoin(nbdir, 'foo')) |
|
41 | 44 | |
|
42 | 45 | nb = new_notebook(name='testnb') |
|
43 | 46 | |
|
44 | 47 | ws = new_worksheet() |
|
45 | 48 | nb.worksheets = [ws] |
|
46 | 49 | ws.cells.append(new_heading_cell(u'Created by test Β³')) |
|
47 | 50 | ws.cells.append(new_code_cell(input=u'print(2*6)')) |
|
48 | 51 | |
|
49 | 52 | with io.open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w', |
|
50 | 53 | encoding='utf-8') as f: |
|
51 | 54 | write(nb, f, format='ipynb') |
|
52 | 55 | |
|
53 | 56 | self.nbconvert_api = NbconvertAPI(self.base_url()) |
|
54 | 57 | |
|
55 | 58 | def tearDown(self): |
|
56 | 59 | nbdir = self.notebook_dir.name |
|
57 | 60 | |
|
58 | 61 | for dname in ['foo']: |
|
59 | 62 | shutil.rmtree(pjoin(nbdir, dname), ignore_errors=True) |
|
60 | 63 | |
|
61 | 64 | def test_from_file(self): |
|
62 | 65 | r = self.nbconvert_api.from_file('html', 'foo', 'testnb.ipynb') |
|
63 | 66 | self.assertEqual(r.status_code, 200) |
|
64 | 67 | self.assertIn(u'text/html', r.headers['Content-Type']) |
|
65 | 68 | self.assertIn(u'Created by test', r.text) |
|
66 | 69 | self.assertIn(u'print', r.text) |
|
67 | 70 | |
|
68 | 71 | r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb') |
|
69 | 72 | self.assertIn(u'text/x-python', r.headers['Content-Type']) |
|
70 | 73 | self.assertIn(u'print(2*6)', r.text) |
|
71 | 74 | |
|
72 | 75 | def test_from_file_404(self): |
|
73 | 76 | with assert_http_error(404): |
|
74 | 77 | self.nbconvert_api.from_file('html', 'foo', 'thisdoesntexist.ipynb') |
|
75 | 78 | |
|
76 | 79 | def test_from_file_download(self): |
|
77 | 80 | r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb', download=True) |
|
78 | 81 | content_disposition = r.headers['Content-Disposition'] |
|
79 | 82 | self.assertIn('attachment', content_disposition) |
|
80 | 83 | self.assertIn('testnb.py', content_disposition) |
|
81 | 84 | |
|
82 | 85 | def test_from_post(self): |
|
83 | 86 | nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb') |
|
84 | 87 | nbmodel = requests.get(nbmodel_url).json() |
|
85 | 88 | |
|
86 | 89 | r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel) |
|
87 | 90 | self.assertEqual(r.status_code, 200) |
|
88 | 91 | self.assertIn(u'text/html', r.headers['Content-Type']) |
|
89 | 92 | self.assertIn(u'Created by test', r.text) |
|
90 | 93 | self.assertIn(u'print', r.text) |
|
91 | 94 | |
|
92 | 95 | r = self.nbconvert_api.from_post(format='python', nbmodel=nbmodel) |
|
93 | 96 | self.assertIn(u'text/x-python', r.headers['Content-Type']) |
|
94 | self.assertIn(u'print(2*6)', r.text) No newline at end of file | |
|
97 | self.assertIn(u'print(2*6)', r.text) | |
|
98 | ||
|
99 | def test_list_formats(self): | |
|
100 | formats = self.nbconvert_api.list_formats().json() | |
|
101 | self.assertIsInstance(formats, dict) | |
|
102 | self.assertIn('python', formats) | |
|
103 | self.assertIn('html', formats) | |
|
104 | self.assertEqual(formats['python']['output_mimetype'], 'text/x-python') No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now