Show More
|
1 | 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 |
@@ -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 |
@@ -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 |
@@ -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 | 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 | 366 | # URL to handler mappings |
|
359 | 367 | #----------------------------------------------------------------------------- |
|
360 | 368 |
@@ -20,8 +20,7 b' import os' | |||
|
20 | 20 | from tornado import web |
|
21 | 21 | HTTPError = web.HTTPError |
|
22 | 22 | |
|
23 | from ..base.handlers import IPythonHandler | |
|
24 | from ..services.notebooks.handlers import _notebook_path_regex, _path_regex | |
|
23 | from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex | |
|
25 | 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 | 86 | default_handlers = [ |
|
88 |
(r"/notebooks%s" % |
|
|
89 |
(r"/notebooks%s" % |
|
|
87 | (r"/notebooks%s" % notebook_path_regex, NotebookHandler), | |
|
88 | (r"/notebooks%s" % path_regex, NotebookRedirectHandler), | |
|
90 | 89 | ] |
|
91 | 90 |
@@ -192,10 +192,12 b' class NotebookWebApplication(web.Application):' | |||
|
192 | 192 | handlers.extend(load_handlers('auth.login')) |
|
193 | 193 | handlers.extend(load_handlers('auth.logout')) |
|
194 | 194 | handlers.extend(load_handlers('notebook.handlers')) |
|
195 | handlers.extend(load_handlers('nbconvert.handlers')) | |
|
195 | 196 | handlers.extend(load_handlers('services.kernels.handlers')) |
|
196 | 197 | handlers.extend(load_handlers('services.notebooks.handlers')) |
|
197 | 198 | handlers.extend(load_handlers('services.clusters.handlers')) |
|
198 | 199 | handlers.extend(load_handlers('services.sessions.handlers')) |
|
200 | handlers.extend(load_handlers('services.nbconvert.handlers')) | |
|
199 | 201 | handlers.extend([ |
|
200 | 202 | (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}), |
|
201 | 203 | (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}), |
@@ -178,7 +178,7 b' class FileNotebookManager(NotebookManager):' | |||
|
178 | 178 | return notebooks |
|
179 | 179 | |
|
180 | 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 | 183 | Parameters |
|
184 | 184 | ---------- |
@@ -23,7 +23,9 b' from tornado import web' | |||
|
23 | 23 | from IPython.html.utils import url_path_join, url_escape |
|
24 | 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 | 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 | 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 | 271 | default_handlers = [ |
|
273 |
(r"/api/notebooks%s/checkpoints" % |
|
|
274 |
(r"/api/notebooks%s/checkpoints/%s" % ( |
|
|
272 | (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler), | |
|
273 | (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex), | |
|
275 | 274 | ModifyNotebookCheckpointsHandler), |
|
276 |
(r"/api/notebooks%s" % |
|
|
277 |
(r"/api/notebooks%s" % |
|
|
275 | (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler), | |
|
276 | (r"/api/notebooks%s" % path_regex, NotebookHandler), | |
|
278 | 277 | ] |
|
279 | 278 | |
|
280 | 279 |
@@ -22,7 +22,7 b'' | |||
|
22 | 22 | ["reST", "text/restructuredtext"], |
|
23 | 23 | ["HTML", "text/html"], |
|
24 | 24 | ["Markdown", "text/markdown"], |
|
25 |
["Python", " |
|
|
25 | ["Python", "text/x-python"], | |
|
26 | 26 | ["Custom", "dialog"], |
|
27 | 27 | |
|
28 | 28 | ], |
@@ -87,4 +87,4 b'' | |||
|
87 | 87 | CellToolbar.register_preset('Raw Cell Format', raw_cell_preset); |
|
88 | 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 | 89 | MenuBar.prototype.bind_events = function () { |
|
74 | 90 | // File |
@@ -102,25 +118,22 b' var IPython = (function (IPython) {' | |||
|
102 | 118 | window.location.assign(url); |
|
103 | 119 | }); |
|
104 | 120 | |
|
105 | /* FIXME: download-as-py doesn't work right now | |
|
106 | * We will need nbconvert hooked up to get this back | |
|
107 | ||
|
121 | this.element.find('#print_preview').click(function () { | |
|
122 | that._nbconvert('html', false); | |
|
123 | }); | |
|
124 | ||
|
108 | 125 | this.element.find('#download_py').click(function () { |
|
109 | var notebook_name = IPython.notebook.get_notebook_name(); | |
|
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); | |
|
126 | that._nbconvert('python', true); | |
|
120 | 127 | }); |
|
121 | ||
|
122 | */ | |
|
123 | ||
|
128 | ||
|
129 | this.element.find('#download_html').click(function () { | |
|
130 | that._nbconvert('html', true); | |
|
131 | }); | |
|
132 | ||
|
133 | this.element.find('#download_rst').click(function () { | |
|
134 | that._nbconvert('rst', true); | |
|
135 | }); | |
|
136 | ||
|
124 | 137 | this.element.find('#rename_notebook').click(function () { |
|
125 | 138 | IPython.save_widget.rename_notebook(); |
|
126 | 139 | }); |
@@ -77,10 +77,13 b' class="notebook_app"' | |||
|
77 | 77 | </ul> |
|
78 | 78 | </li> |
|
79 | 79 | <li class="divider"></li> |
|
80 | <li id="print_preview"><a href="#">Print Preview</a></li> | |
|
80 | 81 | <li class="dropdown-submenu"><a href="#">Download as</a> |
|
81 | 82 | <ul class="dropdown-menu"> |
|
82 | 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 | 87 | </ul> |
|
85 | 88 | </li> |
|
86 | 89 | <li class="divider"></li> |
@@ -1,13 +1,14 b'' | |||
|
1 | 1 | """Base class for notebook tests.""" |
|
2 | 2 | |
|
3 | import os | |
|
4 | 3 | import sys |
|
5 | 4 | import time |
|
6 | 5 | import requests |
|
7 | 6 | from contextlib import contextmanager |
|
8 |
from subprocess import Popen, |
|
|
7 | from subprocess import Popen, STDOUT | |
|
9 | 8 | from unittest import TestCase |
|
10 | 9 | |
|
10 | import nose | |
|
11 | ||
|
11 | 12 | from IPython.utils.tempdir import TemporaryDirectory |
|
12 | 13 | |
|
13 | 14 | class NotebookTestBase(TestCase): |
@@ -55,10 +56,9 b' class NotebookTestBase(TestCase):' | |||
|
55 | 56 | '--ipython-dir=%s' % cls.ipython_dir.name, |
|
56 | 57 | '--notebook-dir=%s' % cls.notebook_dir.name, |
|
57 | 58 | ] |
|
58 | devnull = open(os.devnull, 'w') | |
|
59 | 59 | cls.notebook = Popen(notebook_args, |
|
60 | stdout=devnull, | |
|
61 |
stderr= |
|
|
60 | stdout=nose.iptest_stdstreams_fileno(), | |
|
61 | stderr=STDOUT, | |
|
62 | 62 | ) |
|
63 | 63 | cls.wait_until_alive() |
|
64 | 64 |
@@ -18,9 +18,8 b' Authors:' | |||
|
18 | 18 | import os |
|
19 | 19 | |
|
20 | 20 | from tornado import web |
|
21 | from ..base.handlers import IPythonHandler | |
|
21 | from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex | |
|
22 | 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 | 25 | # Handlers |
@@ -70,8 +69,8 b' class TreeRedirectHandler(IPythonHandler):' | |||
|
70 | 69 | |
|
71 | 70 | |
|
72 | 71 | default_handlers = [ |
|
73 |
(r"/tree%s" % |
|
|
74 |
(r"/tree%s" % |
|
|
72 | (r"/tree%s" % notebook_path_regex, TreeHandler), | |
|
73 | (r"/tree%s" % path_regex, TreeHandler), | |
|
75 | 74 | (r"/tree", TreeHandler), |
|
76 | 75 | (r"/", TreeRedirectHandler), |
|
77 | 76 | ] |
@@ -53,6 +53,11 b' class Exporter(LoggingConfigurable):' | |||
|
53 | 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 | 61 | #Configurability, allows the user to easily add filters and preprocessors. |
|
57 | 62 | preprocessors = List(config=True, |
|
58 | 63 | help="""List of preprocessors, by name or namespace, to enable.""") |
@@ -36,11 +36,14 b' class HTMLExporter(TemplateExporter):' | |||
|
36 | 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 | 43 | default_template = Unicode('full', config=True, help="""Flavor of the data |
|
40 | 44 | format to use. I.E. 'full' or 'basic'""") |
|
41 | 45 | |
|
42 | def _raw_mimetype_default(self): | |
|
43 | return 'text/html' | |
|
46 | output_mimetype = 'text/html' | |
|
44 | 47 | |
|
45 | 48 | @property |
|
46 | 49 | def default_config(self): |
@@ -63,8 +63,7 b' class LatexExporter(TemplateExporter):' | |||
|
63 | 63 | #Extension that the template files use. |
|
64 | 64 | template_extension = Unicode(".tplx", config=True) |
|
65 | 65 | |
|
66 | def _raw_mimetype_default(self): | |
|
67 | return 'text/latex' | |
|
66 | output_mimetype = 'text/latex' | |
|
68 | 67 | |
|
69 | 68 | |
|
70 | 69 | @property |
@@ -30,11 +30,10 b' class MarkdownExporter(TemplateExporter):' | |||
|
30 | 30 | 'md', config=True, |
|
31 | 31 | help="Extension of the file that should be written to disk") |
|
32 | 32 | |
|
33 | def _raw_mimetype_default(self): | |
|
34 | return 'text/markdown' | |
|
33 | output_mimetype = 'text/markdown' | |
|
35 | 34 | |
|
36 | 35 | def _raw_mimetypes_default(self): |
|
37 | return ['text/markdown', 'text/html'] | |
|
36 | return ['text/markdown', 'text/html', ''] | |
|
38 | 37 | |
|
39 | 38 | @property |
|
40 | 39 | def default_config(self): |
@@ -29,6 +29,4 b' class PythonExporter(TemplateExporter):' | |||
|
29 | 29 | 'py', config=True, |
|
30 | 30 | help="Extension of the file that should be written to disk") |
|
31 | 31 | |
|
32 | def _raw_mimetype_default(self): | |
|
33 | return 'application/x-python' | |
|
34 | ||
|
32 | output_mimetype = 'text/x-python' |
@@ -30,9 +30,8 b' class RSTExporter(TemplateExporter):' | |||
|
30 | 30 | 'rst', config=True, |
|
31 | 31 | help="Extension of the file that should be written to disk") |
|
32 | 32 | |
|
33 | def _raw_mimetype_default(self): | |
|
34 | return 'text/restructuredtext' | |
|
35 | ||
|
33 | output_mimetype = 'text/restructuredtext' | |
|
34 | ||
|
36 | 35 | @property |
|
37 | 36 | def default_config(self): |
|
38 | 37 | c = Config({'ExtractOutputPreprocessor':{'enabled':True}}) |
@@ -31,6 +31,8 b' class SlidesExporter(HTMLExporter):' | |||
|
31 | 31 | help="Extension of the file that should be written to disk" |
|
32 | 32 | ) |
|
33 | 33 | |
|
34 | output_mimetype = 'text/html' | |
|
35 | ||
|
34 | 36 | default_template = Unicode('reveal', config=True, help="""Template of the |
|
35 | 37 | data format to use. I.E. 'reveal'""") |
|
36 | 38 |
@@ -126,12 +126,11 b' class TemplateExporter(Exporter):' | |||
|
126 | 126 | help="""Dictionary of filters, by name and namespace, to add to the Jinja |
|
127 | 127 | environment.""") |
|
128 | 128 | |
|
129 | raw_mimetype = Unicode('') | |
|
130 | 129 | raw_mimetypes = List(config=True, |
|
131 | 130 | help="""formats of raw cells to be included in this Exporter's output.""" |
|
132 | 131 | ) |
|
133 | 132 | def _raw_mimetypes_default(self): |
|
134 |
return [self. |
|
|
133 | return [self.output_mimetype, ''] | |
|
135 | 134 | |
|
136 | 135 | |
|
137 | 136 | def __init__(self, config=None, extra_loaders=None, **kw): |
@@ -209,7 +208,6 b' class TemplateExporter(Exporter):' | |||
|
209 | 208 | preprocessors and filters. |
|
210 | 209 | """ |
|
211 | 210 | nb_copy, resources = super(TemplateExporter, self).from_notebook_node(nb, resources, **kw) |
|
212 | resources.setdefault('raw_mimetype', self.raw_mimetype) | |
|
213 | 211 | resources.setdefault('raw_mimetypes', self.raw_mimetypes) |
|
214 | 212 | |
|
215 | 213 | self._load_template() |
@@ -23,7 +23,7 b' from ...tests.base import TestsBase' | |||
|
23 | 23 | #----------------------------------------------------------------------------- |
|
24 | 24 | |
|
25 | 25 | all_raw_mimetypes = { |
|
26 |
' |
|
|
26 | 'text/x-python', | |
|
27 | 27 | 'text/markdown', |
|
28 | 28 | 'text/html', |
|
29 | 29 | 'text/restructuredtext', |
@@ -43,7 +43,7 b'' | |||
|
43 | 43 | { |
|
44 | 44 | "cell_type": "raw", |
|
45 | 45 | "metadata": { |
|
46 |
"raw_mimetype": " |
|
|
46 | "raw_mimetype": "text/x-python" | |
|
47 | 47 | }, |
|
48 | 48 | "source": [ |
|
49 | 49 | "def bar():\n", |
@@ -81,4 +81,4 b'' | |||
|
81 | 81 | "metadata": {} |
|
82 | 82 | } |
|
83 | 83 | ] |
|
84 | } No newline at end of file | |
|
84 | } |
@@ -17,7 +17,7 b' import base64' | |||
|
17 | 17 | import sys |
|
18 | 18 | import os |
|
19 | 19 | |
|
20 | from IPython.utils.traitlets import Unicode | |
|
20 | from IPython.utils.traitlets import Unicode, Set | |
|
21 | 21 | from .base import Preprocessor |
|
22 | 22 | from IPython.utils import py3compat |
|
23 | 23 | |
@@ -34,6 +34,7 b' class ExtractOutputPreprocessor(Preprocessor):' | |||
|
34 | 34 | output_filename_template = Unicode( |
|
35 | 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 | 39 | def preprocess_cell(self, cell, resources, cell_index): |
|
39 | 40 | """ |
@@ -63,8 +64,8 b' class ExtractOutputPreprocessor(Preprocessor):' | |||
|
63 | 64 | #Loop through all of the outputs in the cell |
|
64 | 65 | for index, out in enumerate(cell.get('outputs', [])): |
|
65 | 66 | |
|
66 |
#Get the output in data formats that the template |
|
|
67 |
for out_type in self. |
|
|
67 | #Get the output in data formats that the template needs extracted | |
|
68 | for out_type in self.extract_output_types: | |
|
68 | 69 | if out.hasattr(out_type): |
|
69 | 70 | data = out[out_type] |
|
70 | 71 |
@@ -29,6 +29,7 b' class TestExtractOutput(PreprocessorTestsBase):' | |||
|
29 | 29 | def build_preprocessor(self): |
|
30 | 30 | """Make an instance of a preprocessor""" |
|
31 | 31 | preprocessor = ExtractOutputPreprocessor() |
|
32 | preprocessor.extract_output_types = {'text', 'png'} | |
|
32 | 33 | preprocessor.enabled = True |
|
33 | 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 | 81 | ((*- endblock headingcell -*)) |
|
82 | 82 | ((*- elif cell.cell_type in ['raw'] -*)) |
|
83 | 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 | 85 | ((( cell.source ))) |
|
86 | 86 | ((* endif *)) |
|
87 | 87 | ((*- endblock rawcell -*)) |
@@ -27,7 +27,7 b' it introduces a new line' | |||
|
27 | 27 | {# .... #} |
|
28 | 28 | |
|
29 | 29 | {% block pyout %} |
|
30 | {{ output.text | indent | comment_lines }} | |
|
30 | {{ output.text or '' | indent | comment_lines }} | |
|
31 | 31 | {% endblock pyout %} |
|
32 | 32 | |
|
33 | 33 | {% block stream %} |
@@ -48,4 +48,4 b' it introduces a new line' | |||
|
48 | 48 | |
|
49 | 49 | {% block unknowncell scoped %} |
|
50 | 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 | 77 | {%- endblock headingcell -%} |
|
78 | 78 | {%- elif cell.cell_type in ['raw'] -%} |
|
79 | 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 | 81 | {{ cell.source }} |
|
82 | 82 | {% endif %} |
|
83 | 83 | {%- endblock rawcell -%} |
@@ -55,7 +55,8 b' def new_output(output_type=None, output_text=None, output_png=None,' | |||
|
55 | 55 | output_html=None, output_svg=None, output_latex=None, output_json=None, |
|
56 | 56 | output_javascript=None, output_jpeg=None, prompt_number=None, |
|
57 | 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 | 60 | output = NotebookNode() |
|
60 | 61 | if output_type is not None: |
|
61 | 62 | output.output_type = unicode_type(output_type) |
General Comments 0
You need to be logged in to leave comments.
Login now