Show More
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 |
@@ -0,0 +1,117 b'' | |||||
|
1 | import io | |||
|
2 | import os | |||
|
3 | import zipfile | |||
|
4 | ||||
|
5 | from tornado import web | |||
|
6 | ||||
|
7 | from ..base.handlers import IPythonHandler, notebook_path_regex | |||
|
8 | from IPython.nbformat.current import to_notebook_json | |||
|
9 | from IPython.nbconvert.exporters.export import exporter_map | |||
|
10 | from IPython.utils import tz | |||
|
11 | from IPython.utils.py3compat import cast_bytes | |||
|
12 | ||||
|
13 | import sys | |||
|
14 | ||||
|
15 | def find_resource_files(output_files_dir): | |||
|
16 | files = [] | |||
|
17 | for dirpath, dirnames, filenames in os.walk(output_files_dir): | |||
|
18 | files.extend([os.path.join(dirpath, f) for f in filenames]) | |||
|
19 | return files | |||
|
20 | ||||
|
21 | def respond_zip(handler, name, output, resources): | |||
|
22 | """Zip up the output and resource files and respond with the zip file. | |||
|
23 | ||||
|
24 | Returns True if it has served a zip file, False if there are no resource | |||
|
25 | files, in which case we serve the plain output file. | |||
|
26 | """ | |||
|
27 | # Check if we have resource files we need to zip | |||
|
28 | output_files = resources.get('outputs', None) | |||
|
29 | if not output_files: | |||
|
30 | return False | |||
|
31 | ||||
|
32 | # Headers | |||
|
33 | zip_filename = os.path.splitext(name)[0] + '.zip' | |||
|
34 | handler.set_header('Content-Disposition', | |||
|
35 | 'attachment; filename="%s"' % zip_filename) | |||
|
36 | handler.set_header('Content-Type', 'application/zip') | |||
|
37 | ||||
|
38 | # Prepare the zip file | |||
|
39 | buffer = io.BytesIO() | |||
|
40 | zipf = zipfile.ZipFile(buffer, mode='w', compression=zipfile.ZIP_DEFLATED) | |||
|
41 | output_filename = os.path.splitext(name)[0] + '.' + resources['output_extension'] | |||
|
42 | zipf.writestr(output_filename, cast_bytes(output, 'utf-8')) | |||
|
43 | for filename, data in output_files.items(): | |||
|
44 | zipf.writestr(os.path.basename(filename), data) | |||
|
45 | zipf.close() | |||
|
46 | ||||
|
47 | handler.finish(buffer.getvalue()) | |||
|
48 | return True | |||
|
49 | ||||
|
50 | class NbconvertFileHandler(IPythonHandler): | |||
|
51 | ||||
|
52 | SUPPORTED_METHODS = ('GET',) | |||
|
53 | ||||
|
54 | @web.authenticated | |||
|
55 | def get(self, format, path='', name=None): | |||
|
56 | exporter = exporter_map[format](config=self.config) | |||
|
57 | ||||
|
58 | path = path.strip('/') | |||
|
59 | os_path = self.notebook_manager.get_os_path(name, path) | |||
|
60 | if not os.path.isfile(os_path): | |||
|
61 | raise web.HTTPError(404, u'Notebook does not exist: %s' % name) | |||
|
62 | ||||
|
63 | info = os.stat(os_path) | |||
|
64 | self.set_header('Last-Modified', tz.utcfromtimestamp(info.st_mtime)) | |||
|
65 | ||||
|
66 | output, resources = exporter.from_filename(os_path) | |||
|
67 | ||||
|
68 | if respond_zip(self, name, output, resources): | |||
|
69 | return | |||
|
70 | ||||
|
71 | # Force download if requested | |||
|
72 | if self.get_argument('download', 'false').lower() == 'true': | |||
|
73 | filename = os.path.splitext(name)[0] + '.' + resources['output_extension'] | |||
|
74 | self.set_header('Content-Disposition', | |||
|
75 | 'attachment; filename="%s"' % filename) | |||
|
76 | ||||
|
77 | # MIME type | |||
|
78 | if exporter.output_mimetype: | |||
|
79 | self.set_header('Content-Type', | |||
|
80 | '%s; charset=utf-8' % exporter.output_mimetype) | |||
|
81 | ||||
|
82 | self.finish(output) | |||
|
83 | ||||
|
84 | class NbconvertPostHandler(IPythonHandler): | |||
|
85 | SUPPORTED_METHODS = ('POST',) | |||
|
86 | ||||
|
87 | @web.authenticated | |||
|
88 | def post(self, format): | |||
|
89 | exporter = exporter_map[format](config=self.config) | |||
|
90 | ||||
|
91 | model = self.get_json_body() | |||
|
92 | nbnode = to_notebook_json(model['content']) | |||
|
93 | ||||
|
94 | output, resources = exporter.from_notebook_node(nbnode) | |||
|
95 | ||||
|
96 | if respond_zip(self, nbnode.metadata.name, output, resources): | |||
|
97 | return | |||
|
98 | ||||
|
99 | # MIME type | |||
|
100 | if exporter.output_mimetype: | |||
|
101 | self.set_header('Content-Type', | |||
|
102 | '%s; charset=utf-8' % exporter.output_mimetype) | |||
|
103 | ||||
|
104 | self.finish(output) | |||
|
105 | ||||
|
106 | #----------------------------------------------------------------------------- | |||
|
107 | # URL to handler mappings | |||
|
108 | #----------------------------------------------------------------------------- | |||
|
109 | ||||
|
110 | _format_regex = r"(?P<format>\w+)" | |||
|
111 | ||||
|
112 | ||||
|
113 | default_handlers = [ | |||
|
114 | (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex), | |||
|
115 | NbconvertFileHandler), | |||
|
116 | (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler), | |||
|
117 | ] No newline at end of file |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 |
@@ -0,0 +1,120 b'' | |||||
|
1 | # coding: utf-8 | |||
|
2 | import base64 | |||
|
3 | import io | |||
|
4 | import json | |||
|
5 | import os | |||
|
6 | from os.path import join as pjoin | |||
|
7 | import shutil | |||
|
8 | ||||
|
9 | import requests | |||
|
10 | ||||
|
11 | from IPython.html.utils import url_path_join | |||
|
12 | from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error | |||
|
13 | from IPython.nbformat.current import (new_notebook, write, new_worksheet, | |||
|
14 | new_heading_cell, new_code_cell, | |||
|
15 | new_output) | |||
|
16 | ||||
|
17 | class NbconvertAPI(object): | |||
|
18 | """Wrapper for nbconvert API calls.""" | |||
|
19 | def __init__(self, base_url): | |||
|
20 | self.base_url = base_url | |||
|
21 | ||||
|
22 | def _req(self, verb, path, body=None, params=None): | |||
|
23 | response = requests.request(verb, | |||
|
24 | url_path_join(self.base_url, 'nbconvert', path), | |||
|
25 | data=body, params=params, | |||
|
26 | ) | |||
|
27 | response.raise_for_status() | |||
|
28 | return response | |||
|
29 | ||||
|
30 | def from_file(self, format, path, name, download=False): | |||
|
31 | return self._req('GET', url_path_join(format, path, name), | |||
|
32 | params={'download':download}) | |||
|
33 | ||||
|
34 | def from_post(self, format, nbmodel): | |||
|
35 | body = json.dumps(nbmodel) | |||
|
36 | return self._req('POST', format, body) | |||
|
37 | ||||
|
38 | def list_formats(self): | |||
|
39 | return self._req('GET', '') | |||
|
40 | ||||
|
41 | png_green_pixel = base64.encodestring(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00' | |||
|
42 | b'\x00\x00\x01\x00\x00x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDAT' | |||
|
43 | b'\x08\xd7c\x90\xfb\xcf\x00\x00\x02\\\x01\x1e.~d\x87\x00\x00\x00\x00IEND\xaeB`\x82') | |||
|
44 | ||||
|
45 | class APITest(NotebookTestBase): | |||
|
46 | def setUp(self): | |||
|
47 | nbdir = self.notebook_dir.name | |||
|
48 | ||||
|
49 | if not os.path.isdir(pjoin(nbdir, 'foo')): | |||
|
50 | os.mkdir(pjoin(nbdir, 'foo')) | |||
|
51 | ||||
|
52 | nb = new_notebook(name='testnb') | |||
|
53 | ||||
|
54 | ws = new_worksheet() | |||
|
55 | nb.worksheets = [ws] | |||
|
56 | ws.cells.append(new_heading_cell(u'Created by test ³')) | |||
|
57 | cc1 = new_code_cell(input=u'print(2*6)') | |||
|
58 | cc1.outputs.append(new_output(output_text=u'12')) | |||
|
59 | cc1.outputs.append(new_output(output_png=png_green_pixel, output_type='pyout')) | |||
|
60 | ws.cells.append(cc1) | |||
|
61 | ||||
|
62 | with io.open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w', | |||
|
63 | encoding='utf-8') as f: | |||
|
64 | write(nb, f, format='ipynb') | |||
|
65 | ||||
|
66 | self.nbconvert_api = NbconvertAPI(self.base_url()) | |||
|
67 | ||||
|
68 | def tearDown(self): | |||
|
69 | nbdir = self.notebook_dir.name | |||
|
70 | ||||
|
71 | for dname in ['foo']: | |||
|
72 | shutil.rmtree(pjoin(nbdir, dname), ignore_errors=True) | |||
|
73 | ||||
|
74 | def test_from_file(self): | |||
|
75 | r = self.nbconvert_api.from_file('html', 'foo', 'testnb.ipynb') | |||
|
76 | self.assertEqual(r.status_code, 200) | |||
|
77 | self.assertIn(u'text/html', r.headers['Content-Type']) | |||
|
78 | self.assertIn(u'Created by test', r.text) | |||
|
79 | self.assertIn(u'print', r.text) | |||
|
80 | ||||
|
81 | r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb') | |||
|
82 | self.assertIn(u'text/x-python', r.headers['Content-Type']) | |||
|
83 | self.assertIn(u'print(2*6)', r.text) | |||
|
84 | ||||
|
85 | def test_from_file_404(self): | |||
|
86 | with assert_http_error(404): | |||
|
87 | self.nbconvert_api.from_file('html', 'foo', 'thisdoesntexist.ipynb') | |||
|
88 | ||||
|
89 | def test_from_file_download(self): | |||
|
90 | r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb', download=True) | |||
|
91 | content_disposition = r.headers['Content-Disposition'] | |||
|
92 | self.assertIn('attachment', content_disposition) | |||
|
93 | self.assertIn('testnb.py', content_disposition) | |||
|
94 | ||||
|
95 | def test_from_file_zip(self): | |||
|
96 | r = self.nbconvert_api.from_file('latex', 'foo', 'testnb.ipynb', download=True) | |||
|
97 | self.assertIn(u'application/zip', r.headers['Content-Type']) | |||
|
98 | self.assertIn(u'.zip', r.headers['Content-Disposition']) | |||
|
99 | ||||
|
100 | def test_from_post(self): | |||
|
101 | nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb') | |||
|
102 | nbmodel = requests.get(nbmodel_url).json() | |||
|
103 | ||||
|
104 | r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel) | |||
|
105 | self.assertEqual(r.status_code, 200) | |||
|
106 | self.assertIn(u'text/html', r.headers['Content-Type']) | |||
|
107 | self.assertIn(u'Created by test', r.text) | |||
|
108 | self.assertIn(u'print', r.text) | |||
|
109 | ||||
|
110 | r = self.nbconvert_api.from_post(format='python', nbmodel=nbmodel) | |||
|
111 | self.assertIn(u'text/x-python', r.headers['Content-Type']) | |||
|
112 | self.assertIn(u'print(2*6)', r.text) | |||
|
113 | ||||
|
114 | def test_from_post_zip(self): | |||
|
115 | nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb') | |||
|
116 | nbmodel = requests.get(nbmodel_url).json() | |||
|
117 | ||||
|
118 | r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel) | |||
|
119 | self.assertIn(u'application/zip', r.headers['Content-Type']) | |||
|
120 | self.assertIn(u'.zip', r.headers['Content-Disposition']) |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 |
@@ -0,0 +1,23 b'' | |||||
|
1 | import json | |||
|
2 | ||||
|
3 | from tornado import web | |||
|
4 | ||||
|
5 | from ...base.handlers import IPythonHandler, json_errors | |||
|
6 | from IPython.nbconvert.exporters.export import exporter_map | |||
|
7 | ||||
|
8 | class NbconvertRootHandler(IPythonHandler): | |||
|
9 | SUPPORTED_METHODS = ('GET',) | |||
|
10 | ||||
|
11 | @web.authenticated | |||
|
12 | @json_errors | |||
|
13 | def get(self): | |||
|
14 | res = {} | |||
|
15 | for format, exporter in exporter_map.items(): | |||
|
16 | res[format] = info = {} | |||
|
17 | info['output_mimetype'] = exporter.output_mimetype | |||
|
18 | ||||
|
19 | self.finish(json.dumps(res)) | |||
|
20 | ||||
|
21 | default_handlers = [ | |||
|
22 | (r"/api/nbconvert", NbconvertRootHandler), | |||
|
23 | ] No newline at end of file |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 |
@@ -0,0 +1,31 b'' | |||||
|
1 | import requests | |||
|
2 | ||||
|
3 | from IPython.html.utils import url_path_join | |||
|
4 | from IPython.html.tests.launchnotebook import NotebookTestBase | |||
|
5 | ||||
|
6 | class NbconvertAPI(object): | |||
|
7 | """Wrapper for nbconvert API calls.""" | |||
|
8 | def __init__(self, base_url): | |||
|
9 | self.base_url = base_url | |||
|
10 | ||||
|
11 | def _req(self, verb, path, body=None, params=None): | |||
|
12 | response = requests.request(verb, | |||
|
13 | url_path_join(self.base_url, 'api/nbconvert', path), | |||
|
14 | data=body, params=params, | |||
|
15 | ) | |||
|
16 | response.raise_for_status() | |||
|
17 | return response | |||
|
18 | ||||
|
19 | def list_formats(self): | |||
|
20 | return self._req('GET', '') | |||
|
21 | ||||
|
22 | class APITest(NotebookTestBase): | |||
|
23 | def setUp(self): | |||
|
24 | self.nbconvert_api = NbconvertAPI(self.base_url()) | |||
|
25 | ||||
|
26 | def test_list_formats(self): | |||
|
27 | formats = self.nbconvert_api.list_formats().json() | |||
|
28 | self.assertIsInstance(formats, dict) | |||
|
29 | self.assertIn('python', formats) | |||
|
30 | self.assertIn('html', formats) | |||
|
31 | self.assertEqual(formats['python']['output_mimetype'], 'text/x-python') No newline at end of file |
@@ -0,0 +1,3 b'' | |||||
|
1 | * Print preview is back in the notebook menus, along with options to | |||
|
2 | download the open notebook in various formats. This is powered by | |||
|
3 | nbconvert. |
@@ -355,6 +355,14 b' class TrailingSlashHandler(web.RequestHandler):' | |||||
355 | self.redirect(self.request.uri.rstrip('/')) |
|
355 | self.redirect(self.request.uri.rstrip('/')) | |
356 |
|
356 | |||
357 | #----------------------------------------------------------------------------- |
|
357 | #----------------------------------------------------------------------------- | |
|
358 | # URL pattern fragments for re-use | |||
|
359 | #----------------------------------------------------------------------------- | |||
|
360 | ||||
|
361 | path_regex = r"(?P<path>(?:/.*)*)" | |||
|
362 | notebook_name_regex = r"(?P<name>[^/]+\.ipynb)" | |||
|
363 | notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex) | |||
|
364 | ||||
|
365 | #----------------------------------------------------------------------------- | |||
358 | # URL to handler mappings |
|
366 | # URL to handler mappings | |
359 | #----------------------------------------------------------------------------- |
|
367 | #----------------------------------------------------------------------------- | |
360 |
|
368 |
@@ -20,8 +20,7 b' import os' | |||||
20 | from tornado import web |
|
20 | from tornado import web | |
21 | HTTPError = web.HTTPError |
|
21 | HTTPError = web.HTTPError | |
22 |
|
22 | |||
23 | from ..base.handlers import IPythonHandler |
|
23 | from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex | |
24 | from ..services.notebooks.handlers import _notebook_path_regex, _path_regex |
|
|||
25 | from ..utils import url_path_join, url_escape |
|
24 | from ..utils import url_path_join, url_escape | |
26 |
|
25 | |||
27 | #----------------------------------------------------------------------------- |
|
26 | #----------------------------------------------------------------------------- | |
@@ -85,7 +84,7 b' class NotebookRedirectHandler(IPythonHandler):' | |||||
85 |
|
84 | |||
86 |
|
85 | |||
87 | default_handlers = [ |
|
86 | default_handlers = [ | |
88 |
(r"/notebooks%s" % |
|
87 | (r"/notebooks%s" % notebook_path_regex, NotebookHandler), | |
89 |
(r"/notebooks%s" % |
|
88 | (r"/notebooks%s" % path_regex, NotebookRedirectHandler), | |
90 | ] |
|
89 | ] | |
91 |
|
90 |
@@ -192,10 +192,12 b' class NotebookWebApplication(web.Application):' | |||||
192 | handlers.extend(load_handlers('auth.login')) |
|
192 | handlers.extend(load_handlers('auth.login')) | |
193 | handlers.extend(load_handlers('auth.logout')) |
|
193 | handlers.extend(load_handlers('auth.logout')) | |
194 | handlers.extend(load_handlers('notebook.handlers')) |
|
194 | handlers.extend(load_handlers('notebook.handlers')) | |
|
195 | handlers.extend(load_handlers('nbconvert.handlers')) | |||
195 | handlers.extend(load_handlers('services.kernels.handlers')) |
|
196 | handlers.extend(load_handlers('services.kernels.handlers')) | |
196 | handlers.extend(load_handlers('services.notebooks.handlers')) |
|
197 | handlers.extend(load_handlers('services.notebooks.handlers')) | |
197 | handlers.extend(load_handlers('services.clusters.handlers')) |
|
198 | handlers.extend(load_handlers('services.clusters.handlers')) | |
198 | handlers.extend(load_handlers('services.sessions.handlers')) |
|
199 | handlers.extend(load_handlers('services.sessions.handlers')) | |
|
200 | handlers.extend(load_handlers('services.nbconvert.handlers')) | |||
199 | handlers.extend([ |
|
201 | handlers.extend([ | |
200 | (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}), |
|
202 | (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}), | |
201 | (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}), |
|
203 | (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}), |
@@ -178,7 +178,7 b' class FileNotebookManager(NotebookManager):' | |||||
178 | return notebooks |
|
178 | return notebooks | |
179 |
|
179 | |||
180 | def get_notebook_model(self, name, path='', content=True): |
|
180 | def get_notebook_model(self, name, path='', content=True): | |
181 |
""" Takes a path and name for a notebook and returns it |
|
181 | """ Takes a path and name for a notebook and returns its model | |
182 |
|
182 | |||
183 | Parameters |
|
183 | Parameters | |
184 | ---------- |
|
184 | ---------- |
@@ -23,7 +23,9 b' from tornado import web' | |||||
23 | from IPython.html.utils import url_path_join, url_escape |
|
23 | from IPython.html.utils import url_path_join, url_escape | |
24 | from IPython.utils.jsonutil import date_default |
|
24 | from IPython.utils.jsonutil import date_default | |
25 |
|
25 | |||
26 | from IPython.html.base.handlers import IPythonHandler, json_errors |
|
26 | from IPython.html.base.handlers import (IPythonHandler, json_errors, | |
|
27 | notebook_path_regex, path_regex, | |||
|
28 | notebook_name_regex) | |||
27 |
|
29 | |||
28 | #----------------------------------------------------------------------------- |
|
30 | #----------------------------------------------------------------------------- | |
29 | # Notebook web service handlers |
|
31 | # Notebook web service handlers | |
@@ -264,17 +266,14 b' class ModifyNotebookCheckpointsHandler(IPythonHandler):' | |||||
264 | #----------------------------------------------------------------------------- |
|
266 | #----------------------------------------------------------------------------- | |
265 |
|
267 | |||
266 |
|
268 | |||
267 | _path_regex = r"(?P<path>(?:/.*)*)" |
|
|||
268 | _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)" |
|
269 | _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)" | |
269 | _notebook_name_regex = r"(?P<name>[^/]+\.ipynb)" |
|
|||
270 | _notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex) |
|
|||
271 |
|
270 | |||
272 | default_handlers = [ |
|
271 | default_handlers = [ | |
273 |
(r"/api/notebooks%s/checkpoints" % |
|
272 | (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler), | |
274 |
(r"/api/notebooks%s/checkpoints/%s" % ( |
|
273 | (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex), | |
275 | ModifyNotebookCheckpointsHandler), |
|
274 | ModifyNotebookCheckpointsHandler), | |
276 |
(r"/api/notebooks%s" % |
|
275 | (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler), | |
277 |
(r"/api/notebooks%s" % |
|
276 | (r"/api/notebooks%s" % path_regex, NotebookHandler), | |
278 | ] |
|
277 | ] | |
279 |
|
278 | |||
280 |
|
279 |
@@ -22,7 +22,7 b'' | |||||
22 | ["reST", "text/restructuredtext"], |
|
22 | ["reST", "text/restructuredtext"], | |
23 | ["HTML", "text/html"], |
|
23 | ["HTML", "text/html"], | |
24 | ["Markdown", "text/markdown"], |
|
24 | ["Markdown", "text/markdown"], | |
25 |
["Python", " |
|
25 | ["Python", "text/x-python"], | |
26 | ["Custom", "dialog"], |
|
26 | ["Custom", "dialog"], | |
27 |
|
27 | |||
28 | ], |
|
28 | ], | |
@@ -87,4 +87,4 b'' | |||||
87 | CellToolbar.register_preset('Raw Cell Format', raw_cell_preset); |
|
87 | CellToolbar.register_preset('Raw Cell Format', raw_cell_preset); | |
88 | console.log('Raw Cell Format toolbar preset loaded.'); |
|
88 | console.log('Raw Cell Format toolbar preset loaded.'); | |
89 |
|
89 | |||
90 | }(IPython)); No newline at end of file |
|
90 | }(IPython)); |
@@ -69,6 +69,22 b' var IPython = (function (IPython) {' | |||||
69 | ); |
|
69 | ); | |
70 | }; |
|
70 | }; | |
71 |
|
71 | |||
|
72 | MenuBar.prototype._nbconvert = function (format, download) { | |||
|
73 | download = download || false; | |||
|
74 | var notebook_name = IPython.notebook.get_notebook_name(); | |||
|
75 | if (IPython.notebook.dirty) { | |||
|
76 | IPython.notebook.save_notebook({async : false}); | |||
|
77 | } | |||
|
78 | var url = utils.url_path_join( | |||
|
79 | this.baseProjectUrl(), | |||
|
80 | 'nbconvert', | |||
|
81 | format, | |||
|
82 | this.notebookPath(), | |||
|
83 | notebook_name + '.ipynb' | |||
|
84 | ) + "?download=" + download.toString(); | |||
|
85 | ||||
|
86 | window.open(url); | |||
|
87 | } | |||
72 |
|
88 | |||
73 | MenuBar.prototype.bind_events = function () { |
|
89 | MenuBar.prototype.bind_events = function () { | |
74 | // File |
|
90 | // File | |
@@ -102,25 +118,22 b' var IPython = (function (IPython) {' | |||||
102 | window.location.assign(url); |
|
118 | window.location.assign(url); | |
103 | }); |
|
119 | }); | |
104 |
|
120 | |||
105 | /* FIXME: download-as-py doesn't work right now |
|
121 | this.element.find('#print_preview').click(function () { | |
106 | * We will need nbconvert hooked up to get this back |
|
122 | that._nbconvert('html', false); | |
107 |
|
123 | }); | ||
|
124 | ||||
108 | this.element.find('#download_py').click(function () { |
|
125 | this.element.find('#download_py').click(function () { | |
109 | var notebook_name = IPython.notebook.get_notebook_name(); |
|
126 | that._nbconvert('python', true); | |
110 | if (IPython.notebook.dirty) { |
|
|||
111 | IPython.notebook.save_notebook({async : false}); |
|
|||
112 | } |
|
|||
113 | var url = utils.url_path_join( |
|
|||
114 | that.baseProjectUrl(), |
|
|||
115 | 'api/notebooks', |
|
|||
116 | that.notebookPath(), |
|
|||
117 | notebook_name + '.ipynb?format=py&download=True' |
|
|||
118 | ); |
|
|||
119 | window.location.assign(url); |
|
|||
120 | }); |
|
127 | }); | |
121 |
|
128 | |||
122 | */ |
|
129 | this.element.find('#download_html').click(function () { | |
123 |
|
130 | that._nbconvert('html', true); | ||
|
131 | }); | |||
|
132 | ||||
|
133 | this.element.find('#download_rst').click(function () { | |||
|
134 | that._nbconvert('rst', true); | |||
|
135 | }); | |||
|
136 | ||||
124 | this.element.find('#rename_notebook').click(function () { |
|
137 | this.element.find('#rename_notebook').click(function () { | |
125 | IPython.save_widget.rename_notebook(); |
|
138 | IPython.save_widget.rename_notebook(); | |
126 | }); |
|
139 | }); |
@@ -77,10 +77,13 b' class="notebook_app"' | |||||
77 | </ul> |
|
77 | </ul> | |
78 | </li> |
|
78 | </li> | |
79 | <li class="divider"></li> |
|
79 | <li class="divider"></li> | |
|
80 | <li id="print_preview"><a href="#">Print Preview</a></li> | |||
80 | <li class="dropdown-submenu"><a href="#">Download as</a> |
|
81 | <li class="dropdown-submenu"><a href="#">Download as</a> | |
81 | <ul class="dropdown-menu"> |
|
82 | <ul class="dropdown-menu"> | |
82 | <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li> |
|
83 | <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li> | |
83 |
|
|
84 | <li id="download_py"><a href="#">Python (.py)</a></li> | |
|
85 | <li id="download_html"><a href="#">HTML (.html)</a></li> | |||
|
86 | <li id="download_rst"><a href="#">reST (.rst)</a></li> | |||
84 | </ul> |
|
87 | </ul> | |
85 | </li> |
|
88 | </li> | |
86 | <li class="divider"></li> |
|
89 | <li class="divider"></li> |
@@ -1,13 +1,14 b'' | |||||
1 | """Base class for notebook tests.""" |
|
1 | """Base class for notebook tests.""" | |
2 |
|
2 | |||
3 | import os |
|
|||
4 | import sys |
|
3 | import sys | |
5 | import time |
|
4 | import time | |
6 | import requests |
|
5 | import requests | |
7 | from contextlib import contextmanager |
|
6 | from contextlib import contextmanager | |
8 |
from subprocess import Popen, |
|
7 | from subprocess import Popen, STDOUT | |
9 | from unittest import TestCase |
|
8 | from unittest import TestCase | |
10 |
|
9 | |||
|
10 | import nose | |||
|
11 | ||||
11 | from IPython.utils.tempdir import TemporaryDirectory |
|
12 | from IPython.utils.tempdir import TemporaryDirectory | |
12 |
|
13 | |||
13 | class NotebookTestBase(TestCase): |
|
14 | class NotebookTestBase(TestCase): | |
@@ -55,10 +56,9 b' class NotebookTestBase(TestCase):' | |||||
55 | '--ipython-dir=%s' % cls.ipython_dir.name, |
|
56 | '--ipython-dir=%s' % cls.ipython_dir.name, | |
56 | '--notebook-dir=%s' % cls.notebook_dir.name, |
|
57 | '--notebook-dir=%s' % cls.notebook_dir.name, | |
57 | ] |
|
58 | ] | |
58 | devnull = open(os.devnull, 'w') |
|
|||
59 | cls.notebook = Popen(notebook_args, |
|
59 | cls.notebook = Popen(notebook_args, | |
60 | stdout=devnull, |
|
60 | stdout=nose.iptest_stdstreams_fileno(), | |
61 |
stderr= |
|
61 | stderr=STDOUT, | |
62 | ) |
|
62 | ) | |
63 | cls.wait_until_alive() |
|
63 | cls.wait_until_alive() | |
64 |
|
64 |
@@ -18,9 +18,8 b' Authors:' | |||||
18 | import os |
|
18 | import os | |
19 |
|
19 | |||
20 | from tornado import web |
|
20 | from tornado import web | |
21 | from ..base.handlers import IPythonHandler |
|
21 | from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex | |
22 | from ..utils import url_path_join, path2url, url2path, url_escape |
|
22 | from ..utils import url_path_join, path2url, url2path, url_escape | |
23 | from ..services.notebooks.handlers import _notebook_path_regex, _path_regex |
|
|||
24 |
|
23 | |||
25 | #----------------------------------------------------------------------------- |
|
24 | #----------------------------------------------------------------------------- | |
26 | # Handlers |
|
25 | # Handlers | |
@@ -70,8 +69,8 b' class TreeRedirectHandler(IPythonHandler):' | |||||
70 |
|
69 | |||
71 |
|
70 | |||
72 | default_handlers = [ |
|
71 | default_handlers = [ | |
73 |
(r"/tree%s" % |
|
72 | (r"/tree%s" % notebook_path_regex, TreeHandler), | |
74 |
(r"/tree%s" % |
|
73 | (r"/tree%s" % path_regex, TreeHandler), | |
75 | (r"/tree", TreeHandler), |
|
74 | (r"/tree", TreeHandler), | |
76 | (r"/", TreeRedirectHandler), |
|
75 | (r"/", TreeRedirectHandler), | |
77 | ] |
|
76 | ] |
@@ -53,6 +53,11 b' class Exporter(LoggingConfigurable):' | |||||
53 | help="Extension of the file that should be written to disk" |
|
53 | help="Extension of the file that should be written to disk" | |
54 | ) |
|
54 | ) | |
55 |
|
55 | |||
|
56 | # MIME type of the result file, for HTTP response headers. | |||
|
57 | # This is *not* a traitlet, because we want to be able to access it from | |||
|
58 | # the class, not just on instances. | |||
|
59 | output_mimetype = '' | |||
|
60 | ||||
56 | #Configurability, allows the user to easily add filters and preprocessors. |
|
61 | #Configurability, allows the user to easily add filters and preprocessors. | |
57 | preprocessors = List(config=True, |
|
62 | preprocessors = List(config=True, | |
58 | help="""List of preprocessors, by name or namespace, to enable.""") |
|
63 | help="""List of preprocessors, by name or namespace, to enable.""") |
@@ -36,11 +36,14 b' class HTMLExporter(TemplateExporter):' | |||||
36 | help="Extension of the file that should be written to disk" |
|
36 | help="Extension of the file that should be written to disk" | |
37 | ) |
|
37 | ) | |
38 |
|
38 | |||
|
39 | mime_type = Unicode('text/html', config=True, | |||
|
40 | help="MIME type of the result file, for HTTP response headers." | |||
|
41 | ) | |||
|
42 | ||||
39 | default_template = Unicode('full', config=True, help="""Flavor of the data |
|
43 | default_template = Unicode('full', config=True, help="""Flavor of the data | |
40 | format to use. I.E. 'full' or 'basic'""") |
|
44 | format to use. I.E. 'full' or 'basic'""") | |
41 |
|
45 | |||
42 | def _raw_mimetype_default(self): |
|
46 | output_mimetype = 'text/html' | |
43 | return 'text/html' |
|
|||
44 |
|
47 | |||
45 | @property |
|
48 | @property | |
46 | def default_config(self): |
|
49 | def default_config(self): |
@@ -63,8 +63,7 b' class LatexExporter(TemplateExporter):' | |||||
63 | #Extension that the template files use. |
|
63 | #Extension that the template files use. | |
64 | template_extension = Unicode(".tplx", config=True) |
|
64 | template_extension = Unicode(".tplx", config=True) | |
65 |
|
65 | |||
66 | def _raw_mimetype_default(self): |
|
66 | output_mimetype = 'text/latex' | |
67 | return 'text/latex' |
|
|||
68 |
|
67 | |||
69 |
|
68 | |||
70 | @property |
|
69 | @property |
@@ -30,11 +30,10 b' class MarkdownExporter(TemplateExporter):' | |||||
30 | 'md', config=True, |
|
30 | 'md', config=True, | |
31 | help="Extension of the file that should be written to disk") |
|
31 | help="Extension of the file that should be written to disk") | |
32 |
|
32 | |||
33 | def _raw_mimetype_default(self): |
|
33 | output_mimetype = 'text/markdown' | |
34 | return 'text/markdown' |
|
|||
35 |
|
34 | |||
36 | def _raw_mimetypes_default(self): |
|
35 | def _raw_mimetypes_default(self): | |
37 | return ['text/markdown', 'text/html'] |
|
36 | return ['text/markdown', 'text/html', ''] | |
38 |
|
37 | |||
39 | @property |
|
38 | @property | |
40 | def default_config(self): |
|
39 | def default_config(self): |
@@ -29,6 +29,4 b' class PythonExporter(TemplateExporter):' | |||||
29 | 'py', config=True, |
|
29 | 'py', config=True, | |
30 | help="Extension of the file that should be written to disk") |
|
30 | help="Extension of the file that should be written to disk") | |
31 |
|
31 | |||
32 | def _raw_mimetype_default(self): |
|
32 | output_mimetype = 'text/x-python' | |
33 | return 'application/x-python' |
|
|||
34 |
|
@@ -30,9 +30,8 b' class RSTExporter(TemplateExporter):' | |||||
30 | 'rst', config=True, |
|
30 | 'rst', config=True, | |
31 | help="Extension of the file that should be written to disk") |
|
31 | help="Extension of the file that should be written to disk") | |
32 |
|
32 | |||
33 | def _raw_mimetype_default(self): |
|
33 | output_mimetype = 'text/restructuredtext' | |
34 | return 'text/restructuredtext' |
|
34 | ||
35 |
|
||||
36 | @property |
|
35 | @property | |
37 | def default_config(self): |
|
36 | def default_config(self): | |
38 | c = Config({'ExtractOutputPreprocessor':{'enabled':True}}) |
|
37 | c = Config({'ExtractOutputPreprocessor':{'enabled':True}}) |
@@ -31,6 +31,8 b' class SlidesExporter(HTMLExporter):' | |||||
31 | help="Extension of the file that should be written to disk" |
|
31 | help="Extension of the file that should be written to disk" | |
32 | ) |
|
32 | ) | |
33 |
|
33 | |||
|
34 | output_mimetype = 'text/html' | |||
|
35 | ||||
34 | default_template = Unicode('reveal', config=True, help="""Template of the |
|
36 | default_template = Unicode('reveal', config=True, help="""Template of the | |
35 | data format to use. I.E. 'reveal'""") |
|
37 | data format to use. I.E. 'reveal'""") | |
36 |
|
38 |
@@ -126,12 +126,11 b' class TemplateExporter(Exporter):' | |||||
126 | help="""Dictionary of filters, by name and namespace, to add to the Jinja |
|
126 | help="""Dictionary of filters, by name and namespace, to add to the Jinja | |
127 | environment.""") |
|
127 | environment.""") | |
128 |
|
128 | |||
129 | raw_mimetype = Unicode('') |
|
|||
130 | raw_mimetypes = List(config=True, |
|
129 | raw_mimetypes = List(config=True, | |
131 | help="""formats of raw cells to be included in this Exporter's output.""" |
|
130 | help="""formats of raw cells to be included in this Exporter's output.""" | |
132 | ) |
|
131 | ) | |
133 | def _raw_mimetypes_default(self): |
|
132 | def _raw_mimetypes_default(self): | |
134 |
return [self. |
|
133 | return [self.output_mimetype, ''] | |
135 |
|
134 | |||
136 |
|
135 | |||
137 | def __init__(self, config=None, extra_loaders=None, **kw): |
|
136 | def __init__(self, config=None, extra_loaders=None, **kw): | |
@@ -209,7 +208,6 b' class TemplateExporter(Exporter):' | |||||
209 | preprocessors and filters. |
|
208 | preprocessors and filters. | |
210 | """ |
|
209 | """ | |
211 | nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw) |
|
210 | nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw) | |
212 | resources.setdefault('raw_mimetype', self.raw_mimetype) |
|
|||
213 | resources.setdefault('raw_mimetypes', self.raw_mimetypes) |
|
211 | resources.setdefault('raw_mimetypes', self.raw_mimetypes) | |
214 |
|
212 | |||
215 | self._load_template() |
|
213 | self._load_template() |
@@ -23,7 +23,7 b' from ...tests.base import TestsBase' | |||||
23 | #----------------------------------------------------------------------------- |
|
23 | #----------------------------------------------------------------------------- | |
24 |
|
24 | |||
25 | all_raw_mimetypes = { |
|
25 | all_raw_mimetypes = { | |
26 |
' |
|
26 | 'text/x-python', | |
27 | 'text/markdown', |
|
27 | 'text/markdown', | |
28 | 'text/html', |
|
28 | 'text/html', | |
29 | 'text/restructuredtext', |
|
29 | 'text/restructuredtext', |
@@ -43,7 +43,7 b'' | |||||
43 | { |
|
43 | { | |
44 | "cell_type": "raw", |
|
44 | "cell_type": "raw", | |
45 | "metadata": { |
|
45 | "metadata": { | |
46 |
"raw_mimetype": " |
|
46 | "raw_mimetype": "text/x-python" | |
47 | }, |
|
47 | }, | |
48 | "source": [ |
|
48 | "source": [ | |
49 | "def bar():\n", |
|
49 | "def bar():\n", | |
@@ -81,4 +81,4 b'' | |||||
81 | "metadata": {} |
|
81 | "metadata": {} | |
82 | } |
|
82 | } | |
83 | ] |
|
83 | ] | |
84 | } No newline at end of file |
|
84 | } |
@@ -17,7 +17,7 b' import base64' | |||||
17 | import sys |
|
17 | import sys | |
18 | import os |
|
18 | import os | |
19 |
|
19 | |||
20 | from IPython.utils.traitlets import Unicode |
|
20 | from IPython.utils.traitlets import Unicode, Set | |
21 | from .base import Preprocessor |
|
21 | from .base import Preprocessor | |
22 | from IPython.utils import py3compat |
|
22 | from IPython.utils import py3compat | |
23 |
|
23 | |||
@@ -34,6 +34,7 b' class ExtractOutputPreprocessor(Preprocessor):' | |||||
34 | output_filename_template = Unicode( |
|
34 | output_filename_template = Unicode( | |
35 | "{unique_key}_{cell_index}_{index}.{extension}", config=True) |
|
35 | "{unique_key}_{cell_index}_{index}.{extension}", config=True) | |
36 |
|
36 | |||
|
37 | extract_output_types = Set({'png', 'jpg', 'svg', 'pdf'}, config=True) | |||
37 |
|
38 | |||
38 | def preprocess_cell(self, cell, resources, cell_index): |
|
39 | def preprocess_cell(self, cell, resources, cell_index): | |
39 | """ |
|
40 | """ | |
@@ -63,8 +64,8 b' class ExtractOutputPreprocessor(Preprocessor):' | |||||
63 | #Loop through all of the outputs in the cell |
|
64 | #Loop through all of the outputs in the cell | |
64 | for index, out in enumerate(cell.get('outputs', [])): |
|
65 | for index, out in enumerate(cell.get('outputs', [])): | |
65 |
|
66 | |||
66 |
#Get the output in data formats that the template |
|
67 | #Get the output in data formats that the template needs extracted | |
67 |
for out_type in self. |
|
68 | for out_type in self.extract_output_types: | |
68 | if out.hasattr(out_type): |
|
69 | if out.hasattr(out_type): | |
69 | data = out[out_type] |
|
70 | data = out[out_type] | |
70 |
|
71 |
@@ -29,6 +29,7 b' class TestExtractOutput(PreprocessorTestsBase):' | |||||
29 | def build_preprocessor(self): |
|
29 | def build_preprocessor(self): | |
30 | """Make an instance of a preprocessor""" |
|
30 | """Make an instance of a preprocessor""" | |
31 | preprocessor = ExtractOutputPreprocessor() |
|
31 | preprocessor = ExtractOutputPreprocessor() | |
|
32 | preprocessor.extract_output_types = {'text', 'png'} | |||
32 | preprocessor.enabled = True |
|
33 | preprocessor.enabled = True | |
33 | return preprocessor |
|
34 | return preprocessor | |
34 |
|
35 |
@@ -81,7 +81,7 b' consider calling super even if it is a leave block, we might insert more blocks ' | |||||
81 | ((*- endblock headingcell -*)) |
|
81 | ((*- endblock headingcell -*)) | |
82 | ((*- elif cell.cell_type in ['raw'] -*)) |
|
82 | ((*- elif cell.cell_type in ['raw'] -*)) | |
83 | ((*- block rawcell scoped -*)) |
|
83 | ((*- block rawcell scoped -*)) | |
84 |
((* if cell.metadata.get('raw_mimetype', |
|
84 | ((* if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) *)) | |
85 | ((( cell.source ))) |
|
85 | ((( cell.source ))) | |
86 | ((* endif *)) |
|
86 | ((* endif *)) | |
87 | ((*- endblock rawcell -*)) |
|
87 | ((*- endblock rawcell -*)) |
@@ -27,7 +27,7 b' it introduces a new line' | |||||
27 | {# .... #} |
|
27 | {# .... #} | |
28 |
|
28 | |||
29 | {% block pyout %} |
|
29 | {% block pyout %} | |
30 | {{ output.text | indent | comment_lines }} |
|
30 | {{ output.text or '' | indent | comment_lines }} | |
31 | {% endblock pyout %} |
|
31 | {% endblock pyout %} | |
32 |
|
32 | |||
33 | {% block stream %} |
|
33 | {% block stream %} | |
@@ -48,4 +48,4 b' it introduces a new line' | |||||
48 |
|
48 | |||
49 | {% block unknowncell scoped %} |
|
49 | {% block unknowncell scoped %} | |
50 | unknown type {{ cell.type }} |
|
50 | unknown type {{ cell.type }} | |
51 | {% endblock unknowncell %} No newline at end of file |
|
51 | {% endblock unknowncell %} |
@@ -77,7 +77,7 b' consider calling super even if it is a leave block, we might insert more blocks ' | |||||
77 | {%- endblock headingcell -%} |
|
77 | {%- endblock headingcell -%} | |
78 | {%- elif cell.cell_type in ['raw'] -%} |
|
78 | {%- elif cell.cell_type in ['raw'] -%} | |
79 | {%- block rawcell scoped -%} |
|
79 | {%- block rawcell scoped -%} | |
80 |
{% if cell.metadata.get('raw_mimetype', |
|
80 | {% if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %} | |
81 | {{ cell.source }} |
|
81 | {{ cell.source }} | |
82 | {% endif %} |
|
82 | {% endif %} | |
83 | {%- endblock rawcell -%} |
|
83 | {%- endblock rawcell -%} |
@@ -55,7 +55,8 b' def new_output(output_type=None, output_text=None, output_png=None,' | |||||
55 | output_html=None, output_svg=None, output_latex=None, output_json=None, |
|
55 | output_html=None, output_svg=None, output_latex=None, output_json=None, | |
56 | output_javascript=None, output_jpeg=None, prompt_number=None, |
|
56 | output_javascript=None, output_jpeg=None, prompt_number=None, | |
57 | ename=None, evalue=None, traceback=None, stream=None, metadata=None): |
|
57 | ename=None, evalue=None, traceback=None, stream=None, metadata=None): | |
58 | """Create a new code cell with input and output""" |
|
58 | """Create a new output, to go in the ``cell.outputs`` list of a code cell. | |
|
59 | """ | |||
59 | output = NotebookNode() |
|
60 | output = NotebookNode() | |
60 | if output_type is not None: |
|
61 | if output_type is not None: | |
61 | output.output_type = unicode_type(output_type) |
|
62 | output.output_type = unicode_type(output_type) |
General Comments 0
You need to be logged in to leave comments.
Login now