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