##// END OF EJS Templates
Add GET /nbconvert to list available formats
Thomas Kluyver -
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