Show More
@@ -0,0 +1,78 b'' | |||||
|
1 | """Python API for composing notebook elements | |||
|
2 | ||||
|
3 | The Python representation of a notebook is a nested structure of | |||
|
4 | dictionary subclasses that support attribute access | |||
|
5 | (IPython.utils.ipstruct.Struct). The functions in this module are merely | |||
|
6 | helpers to build the structs in the right form. | |||
|
7 | """ | |||
|
8 | ||||
|
9 | # Copyright (c) IPython Development Team. | |||
|
10 | # Distributed under the terms of the Modified BSD License. | |||
|
11 | ||||
|
12 | import pprint | |||
|
13 | import uuid | |||
|
14 | ||||
|
15 | from IPython.utils.ipstruct import Struct | |||
|
16 | from IPython.utils.py3compat import cast_unicode, unicode_type | |||
|
17 | ||||
|
18 | from .validator import isvalid, validate | |||
|
19 | from .nbbase import nbformat, nbformat_minor, from_dict, NotebookNode | |||
|
20 | ||||
|
21 | def new_output(output_type, mime_bundle=None, **kwargs): | |||
|
22 | """Create a new output, to go in the ``cell.outputs`` list of a code cell.""" | |||
|
23 | output = NotebookNode(output_type=output_type, **kwargs) | |||
|
24 | if mime_bundle: | |||
|
25 | output.update(mime_bundle) | |||
|
26 | # populate defaults: | |||
|
27 | output.setdefault('metadata', NotebookNode()) | |||
|
28 | if output_type == 'stream': | |||
|
29 | output.setdefault('stream', 'stdout') | |||
|
30 | output.setdefault('text', '') | |||
|
31 | validate(output, output_type) | |||
|
32 | return output | |||
|
33 | ||||
|
34 | def new_code_cell(source='', **kwargs): | |||
|
35 | """Create a new code cell""" | |||
|
36 | cell = NotebookNode(cell_type='code', source=source, **kwargs) | |||
|
37 | cell.setdefault('metadata', NotebookNode()) | |||
|
38 | cell.setdefault('source', '') | |||
|
39 | cell.setdefault('prompt_number', None) | |||
|
40 | cell.setdefault('outputs', []) | |||
|
41 | ||||
|
42 | validate(cell, 'code_cell') | |||
|
43 | return cell | |||
|
44 | ||||
|
45 | def new_markdown_cell(source='', **kwargs): | |||
|
46 | """Create a new markdown cell""" | |||
|
47 | cell = NotebookNode(cell_type='markdown', source=source, **kwargs) | |||
|
48 | cell.setdefault('metadata', NotebookNode()) | |||
|
49 | ||||
|
50 | validate(cell, 'markdown_cell') | |||
|
51 | return cell | |||
|
52 | ||||
|
53 | def new_heading_cell(source='', **kwargs): | |||
|
54 | """Create a new heading cell""" | |||
|
55 | cell = NotebookNode(cell_type='heading', source=source, **kwargs) | |||
|
56 | cell.setdefault('metadata', NotebookNode()) | |||
|
57 | cell.setdefault('level', 1) | |||
|
58 | ||||
|
59 | validate(cell, 'heading_cell') | |||
|
60 | return cell | |||
|
61 | ||||
|
62 | def new_raw_cell(source='', **kwargs): | |||
|
63 | """Create a new raw cell""" | |||
|
64 | cell = NotebookNode(cell_type='raw', source=source, **kwargs) | |||
|
65 | cell.setdefault('metadata', NotebookNode()) | |||
|
66 | ||||
|
67 | validate(cell, 'raw_cell') | |||
|
68 | return cell | |||
|
69 | ||||
|
70 | def new_notebook(**kwargs): | |||
|
71 | """Create a new notebook""" | |||
|
72 | nb = NotebookNode(**kwargs) | |||
|
73 | nb.nbformat = nbformat | |||
|
74 | nb.nbformat_minor = nbformat_minor | |||
|
75 | nb.setdefault('cells', []) | |||
|
76 | nb.setdefault('metadata', NotebookNode()) | |||
|
77 | validate(nb) | |||
|
78 | return nb |
@@ -0,0 +1,105 b'' | |||||
|
1 | # coding: utf-8 | |||
|
2 | """Tests for the Python API for composing notebook elements""" | |||
|
3 | ||||
|
4 | import nose.tools as nt | |||
|
5 | ||||
|
6 | from ..validator import isvalid, validate, ValidationError | |||
|
7 | from ..compose import ( | |||
|
8 | NotebookNode, nbformat, | |||
|
9 | new_code_cell, new_heading_cell, new_markdown_cell, new_notebook, | |||
|
10 | new_output, new_raw_cell, | |||
|
11 | ) | |||
|
12 | ||||
|
13 | def test_empty_notebook(): | |||
|
14 | nb = new_notebook() | |||
|
15 | nt.assert_equal(nb.cells, []) | |||
|
16 | nt.assert_equal(nb.metadata, NotebookNode()) | |||
|
17 | nt.assert_equal(nb.nbformat, nbformat) | |||
|
18 | ||||
|
19 | def test_empty_markdown_cell(): | |||
|
20 | cell = new_markdown_cell() | |||
|
21 | nt.assert_equal(cell.cell_type, 'markdown') | |||
|
22 | nt.assert_equal(cell.source, '') | |||
|
23 | ||||
|
24 | def test_markdown_cell(): | |||
|
25 | cell = new_markdown_cell(u'* Søme markdown') | |||
|
26 | nt.assert_equal(cell.source, u'* Søme markdown') | |||
|
27 | ||||
|
28 | def test_empty_raw_cell(): | |||
|
29 | cell = new_raw_cell() | |||
|
30 | nt.assert_equal(cell.cell_type, u'raw') | |||
|
31 | nt.assert_equal(cell.source, '') | |||
|
32 | ||||
|
33 | def test_raw_cell(): | |||
|
34 | cell = new_raw_cell('hi') | |||
|
35 | nt.assert_equal(cell.source, u'hi') | |||
|
36 | ||||
|
37 | def test_empty_heading_cell(): | |||
|
38 | cell = new_heading_cell() | |||
|
39 | nt.assert_equal(cell.cell_type, u'heading') | |||
|
40 | nt.assert_equal(cell.source, '') | |||
|
41 | nt.assert_equal(cell.level, 1) | |||
|
42 | ||||
|
43 | def test_heading_cell(): | |||
|
44 | cell = new_heading_cell(u'hi', level=2) | |||
|
45 | nt.assert_equal(cell.source, u'hi') | |||
|
46 | nt.assert_equal(cell.level, 2) | |||
|
47 | ||||
|
48 | def test_empty_code_cell(): | |||
|
49 | cell = new_code_cell('hi') | |||
|
50 | nt.assert_equal(cell.cell_type, 'code') | |||
|
51 | nt.assert_equal(cell.source, u'hi') | |||
|
52 | ||||
|
53 | def test_empty_display_data(): | |||
|
54 | output = new_output('display_data') | |||
|
55 | nt.assert_equal(output.output_type, 'display_data') | |||
|
56 | ||||
|
57 | def test_empty_stream(): | |||
|
58 | output = new_output('stream', stream='stdout', text='') | |||
|
59 | nt.assert_equal(output.output_type, 'stream') | |||
|
60 | ||||
|
61 | def test_empty_execute_result(): | |||
|
62 | output = new_output('execute_result', prompt_number=1) | |||
|
63 | nt.assert_equal(output.output_type, 'execute_result') | |||
|
64 | ||||
|
65 | mimebundle = { | |||
|
66 | 'text/plain': "some text", | |||
|
67 | "application/json": { | |||
|
68 | "key": "value" | |||
|
69 | }, | |||
|
70 | "image/svg+xml": 'ABCDEF', | |||
|
71 | "application/octet-stream": 'ABC-123', | |||
|
72 | "application/vnd.foo+bar": "Some other stuff", | |||
|
73 | } | |||
|
74 | ||||
|
75 | def test_display_data(): | |||
|
76 | output = new_output('display_data', mimebundle) | |||
|
77 | for key, expected in mimebundle.items(): | |||
|
78 | nt.assert_equal(output[key], expected) | |||
|
79 | ||||
|
80 | def test_execute_result(): | |||
|
81 | output = new_output('execute_result', mimebundle, prompt_number=10) | |||
|
82 | nt.assert_equal(output.prompt_number, 10) | |||
|
83 | for key, expected in mimebundle.items(): | |||
|
84 | nt.assert_equal(output[key], expected) | |||
|
85 | ||||
|
86 | def test_error(): | |||
|
87 | o = new_output(output_type=u'error', ename=u'NameError', | |||
|
88 | evalue=u'Name not found', traceback=[u'frame 0', u'frame 1', u'frame 2'] | |||
|
89 | ) | |||
|
90 | nt.assert_equal(o.output_type, u'error') | |||
|
91 | nt.assert_equal(o.ename, u'NameError') | |||
|
92 | nt.assert_equal(o.evalue, u'Name not found') | |||
|
93 | nt.assert_equal(o.traceback, [u'frame 0', u'frame 1', u'frame 2']) | |||
|
94 | ||||
|
95 | def test_code_cell_with_outputs(): | |||
|
96 | cell = new_code_cell(prompt_number=10, outputs=[ | |||
|
97 | new_output('display_data', mimebundle), | |||
|
98 | new_output('stream', text='hello'), | |||
|
99 | new_output('execute_result', mimebundle, prompt_number=10), | |||
|
100 | ]) | |||
|
101 | nt.assert_equal(cell.prompt_number, 10) | |||
|
102 | nt.assert_equal(len(cell.outputs), 3) | |||
|
103 | er = cell.outputs[-1] | |||
|
104 | nt.assert_equal(er.prompt_number, 10) | |||
|
105 | nt.assert_equal(er['output_type'], 'execute_result') |
@@ -0,0 +1,126 b'' | |||||
|
1 | """Tests for nbformat validation""" | |||
|
2 | ||||
|
3 | # Copyright (c) IPython Development Team. | |||
|
4 | # Distributed under the terms of the Modified BSD License. | |||
|
5 | ||||
|
6 | import io | |||
|
7 | import os | |||
|
8 | ||||
|
9 | import nose.tools as nt | |||
|
10 | ||||
|
11 | from ..validator import isvalid, validate, ValidationError | |||
|
12 | from ..nbjson import reads | |||
|
13 | from ..compose import ( | |||
|
14 | new_code_cell, new_heading_cell, new_markdown_cell, new_notebook, | |||
|
15 | new_output, new_raw_cell, | |||
|
16 | ) | |||
|
17 | ||||
|
18 | def test_valid_code_cell(): | |||
|
19 | cell = new_code_cell() | |||
|
20 | validate(cell, 'code_cell') | |||
|
21 | ||||
|
22 | def test_invalid_code_cell(): | |||
|
23 | cell = new_code_cell() | |||
|
24 | ||||
|
25 | cell['source'] = 5 | |||
|
26 | with nt.assert_raises(ValidationError): | |||
|
27 | validate(cell, 'code_cell') | |||
|
28 | ||||
|
29 | cell = new_code_cell() | |||
|
30 | del cell['metadata'] | |||
|
31 | ||||
|
32 | with nt.assert_raises(ValidationError): | |||
|
33 | validate(cell, 'code_cell') | |||
|
34 | ||||
|
35 | cell = new_code_cell() | |||
|
36 | del cell['source'] | |||
|
37 | ||||
|
38 | with nt.assert_raises(ValidationError): | |||
|
39 | validate(cell, 'code_cell') | |||
|
40 | ||||
|
41 | cell = new_code_cell() | |||
|
42 | del cell['cell_type'] | |||
|
43 | ||||
|
44 | with nt.assert_raises(ValidationError): | |||
|
45 | validate(cell, 'code_cell') | |||
|
46 | ||||
|
47 | def test_invalid_markdown_cell(): | |||
|
48 | cell = new_markdown_cell() | |||
|
49 | ||||
|
50 | cell['source'] = 5 | |||
|
51 | with nt.assert_raises(ValidationError): | |||
|
52 | validate(cell, 'markdown_cell') | |||
|
53 | ||||
|
54 | cell = new_markdown_cell() | |||
|
55 | del cell['metadata'] | |||
|
56 | ||||
|
57 | with nt.assert_raises(ValidationError): | |||
|
58 | validate(cell, 'markdown_cell') | |||
|
59 | ||||
|
60 | cell = new_markdown_cell() | |||
|
61 | del cell['source'] | |||
|
62 | ||||
|
63 | with nt.assert_raises(ValidationError): | |||
|
64 | validate(cell, 'markdown_cell') | |||
|
65 | ||||
|
66 | cell = new_markdown_cell() | |||
|
67 | del cell['cell_type'] | |||
|
68 | ||||
|
69 | with nt.assert_raises(ValidationError): | |||
|
70 | validate(cell, 'markdown_cell') | |||
|
71 | ||||
|
72 | def test_invalid_heading_cell(): | |||
|
73 | cell = new_heading_cell() | |||
|
74 | ||||
|
75 | cell['source'] = 5 | |||
|
76 | with nt.assert_raises(ValidationError): | |||
|
77 | validate(cell, 'heading_cell') | |||
|
78 | ||||
|
79 | cell = new_heading_cell() | |||
|
80 | del cell['metadata'] | |||
|
81 | ||||
|
82 | with nt.assert_raises(ValidationError): | |||
|
83 | validate(cell, 'heading_cell') | |||
|
84 | ||||
|
85 | cell = new_heading_cell() | |||
|
86 | del cell['source'] | |||
|
87 | ||||
|
88 | with nt.assert_raises(ValidationError): | |||
|
89 | validate(cell, 'heading_cell') | |||
|
90 | ||||
|
91 | cell = new_heading_cell() | |||
|
92 | del cell['cell_type'] | |||
|
93 | ||||
|
94 | with nt.assert_raises(ValidationError): | |||
|
95 | validate(cell, 'heading_cell') | |||
|
96 | ||||
|
97 | def test_invalid_raw_cell(): | |||
|
98 | cell = new_raw_cell() | |||
|
99 | ||||
|
100 | cell['source'] = 5 | |||
|
101 | with nt.assert_raises(ValidationError): | |||
|
102 | validate(cell, 'raw_cell') | |||
|
103 | ||||
|
104 | cell = new_raw_cell() | |||
|
105 | del cell['metadata'] | |||
|
106 | ||||
|
107 | with nt.assert_raises(ValidationError): | |||
|
108 | validate(cell, 'raw_cell') | |||
|
109 | ||||
|
110 | cell = new_raw_cell() | |||
|
111 | del cell['source'] | |||
|
112 | ||||
|
113 | with nt.assert_raises(ValidationError): | |||
|
114 | validate(cell, 'raw_cell') | |||
|
115 | ||||
|
116 | cell = new_raw_cell() | |||
|
117 | del cell['cell_type'] | |||
|
118 | ||||
|
119 | with nt.assert_raises(ValidationError): | |||
|
120 | validate(cell, 'raw_cell') | |||
|
121 | ||||
|
122 | def test_sample_notebook(): | |||
|
123 | here = os.path.dirname(__file__) | |||
|
124 | with io.open(os.path.join(here, "v4-test.ipynb"), encoding='utf-8') as f: | |||
|
125 | nb = reads(f.read()) | |||
|
126 | validate(nb) |
@@ -0,0 +1,155 b'' | |||||
|
1 | { | |||
|
2 | "metadata": { | |||
|
3 | "cell_tags": ["<None>", null], | |||
|
4 | "name": "", | |||
|
5 | "kernel_info": { | |||
|
6 | "name": "python", | |||
|
7 | "language": "python" | |||
|
8 | } | |||
|
9 | }, | |||
|
10 | "nbformat": 4, | |||
|
11 | "nbformat_minor": 0, | |||
|
12 | "cells": [ | |||
|
13 | { | |||
|
14 | "cell_type": "heading", | |||
|
15 | "level": 1, | |||
|
16 | "metadata": {}, | |||
|
17 | "source": [ | |||
|
18 | "nbconvert latex test" | |||
|
19 | ] | |||
|
20 | }, | |||
|
21 | { | |||
|
22 | "cell_type": "markdown", | |||
|
23 | "metadata": {}, | |||
|
24 | "source": [ | |||
|
25 | "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis." | |||
|
26 | ] | |||
|
27 | }, | |||
|
28 | { | |||
|
29 | "cell_type": "heading", | |||
|
30 | "level": 2, | |||
|
31 | "metadata": {}, | |||
|
32 | "source": [ | |||
|
33 | "Printed Using Python" | |||
|
34 | ] | |||
|
35 | }, | |||
|
36 | { | |||
|
37 | "cell_type": "code", | |||
|
38 | "source": [ | |||
|
39 | "print(\"hello\")" | |||
|
40 | ], | |||
|
41 | "metadata": { | |||
|
42 | "collapsed": false, | |||
|
43 | "autoscroll": false | |||
|
44 | }, | |||
|
45 | "outputs": [ | |||
|
46 | { | |||
|
47 | "output_type": "stream", | |||
|
48 | "metadata": {}, | |||
|
49 | "stream": "stdout", | |||
|
50 | "text": [ | |||
|
51 | "hello\n" | |||
|
52 | ] | |||
|
53 | } | |||
|
54 | ], | |||
|
55 | "prompt_number": 1 | |||
|
56 | }, | |||
|
57 | { | |||
|
58 | "cell_type": "heading", | |||
|
59 | "level": 2, | |||
|
60 | "metadata": {}, | |||
|
61 | "source": [ | |||
|
62 | "Pyout" | |||
|
63 | ] | |||
|
64 | }, | |||
|
65 | { | |||
|
66 | "cell_type": "code", | |||
|
67 | "source": [ | |||
|
68 | "from IPython.display import HTML\n", | |||
|
69 | "HTML(\"\"\"\n", | |||
|
70 | "<script>\n", | |||
|
71 | "console.log(\"hello\");\n", | |||
|
72 | "</script>\n", | |||
|
73 | "<b>HTML</b>\n", | |||
|
74 | "\"\"\")" | |||
|
75 | ], | |||
|
76 | "metadata": { | |||
|
77 | "collapsed": false, | |||
|
78 | "autoscroll": false | |||
|
79 | }, | |||
|
80 | "outputs": [ | |||
|
81 | { | |||
|
82 | "text/html": [ | |||
|
83 | "\n", | |||
|
84 | "<script>\n", | |||
|
85 | "console.log(\"hello\");\n", | |||
|
86 | "</script>\n", | |||
|
87 | "<b>HTML</b>\n" | |||
|
88 | ], | |||
|
89 | "metadata": {}, | |||
|
90 | "output_type": "execute_result", | |||
|
91 | "prompt_number": 3, | |||
|
92 | "text/plain": [ | |||
|
93 | "<IPython.core.display.HTML at 0x1112757d0>" | |||
|
94 | ] | |||
|
95 | } | |||
|
96 | ], | |||
|
97 | "prompt_number": 3 | |||
|
98 | }, | |||
|
99 | { | |||
|
100 | "cell_type": "code", | |||
|
101 | "source": [ | |||
|
102 | "%%javascript\n", | |||
|
103 | "console.log(\"hi\");" | |||
|
104 | ], | |||
|
105 | "metadata": { | |||
|
106 | "collapsed": false, | |||
|
107 | "autoscroll": false | |||
|
108 | }, | |||
|
109 | "outputs": [ | |||
|
110 | { | |||
|
111 | "text/javascript": [ | |||
|
112 | "console.log(\"hi\");" | |||
|
113 | ], | |||
|
114 | "metadata": {}, | |||
|
115 | "output_type": "display_data", | |||
|
116 | "text/plain": [ | |||
|
117 | "<IPython.core.display.Javascript at 0x1112b4b50>" | |||
|
118 | ] | |||
|
119 | } | |||
|
120 | ], | |||
|
121 | "prompt_number": 7 | |||
|
122 | }, | |||
|
123 | { | |||
|
124 | "cell_type": "heading", | |||
|
125 | "level": 3, | |||
|
126 | "metadata": {}, | |||
|
127 | "source": [ | |||
|
128 | "Image" | |||
|
129 | ] | |||
|
130 | }, | |||
|
131 | { | |||
|
132 | "cell_type": "code", | |||
|
133 | "source": [ | |||
|
134 | "from IPython.display import Image\n", | |||
|
135 | "Image(\"http://ipython.org/_static/IPy_header.png\")" | |||
|
136 | ], | |||
|
137 | "metadata": { | |||
|
138 | "collapsed": false, | |||
|
139 | "autoscroll": false | |||
|
140 | }, | |||
|
141 | "outputs": [ | |||
|
142 | { | |||
|
143 | "metadata": {}, | |||
|
144 | "output_type": "execute_result", | |||
|
145 | "image/png": "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\n", | |||
|
146 | "prompt_number": 6, | |||
|
147 | "text/plain": [ | |||
|
148 | "<IPython.core.display.Image at 0x111275490>" | |||
|
149 | ] | |||
|
150 | } | |||
|
151 | ], | |||
|
152 | "prompt_number": 6 | |||
|
153 | } | |||
|
154 | ] | |||
|
155 | } |
@@ -1,74 +1,22 b'' | |||||
1 |
"""The main API for the v |
|
1 | """The main API for the v4 notebook format.""" | |
2 |
|
2 | |||
3 | Authors: |
|
3 | # Copyright (c) IPython Development Team. | |
4 |
|
4 | # Distributed under the terms of the Modified BSD License. | ||
5 | * Brian Granger |
|
|||
6 | """ |
|
|||
7 |
|
||||
8 | #----------------------------------------------------------------------------- |
|
|||
9 | # Copyright (C) 2008-2011 The IPython Development Team |
|
|||
10 | # |
|
|||
11 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
12 | # the file COPYING, distributed as part of this software. |
|
|||
13 | #----------------------------------------------------------------------------- |
|
|||
14 |
|
||||
15 | #----------------------------------------------------------------------------- |
|
|||
16 | # Imports |
|
|||
17 | #----------------------------------------------------------------------------- |
|
|||
18 |
|
5 | |||
19 | from .nbbase import ( |
|
6 | from .nbbase import ( | |
20 | NotebookNode, |
|
7 | NotebookNode, | |
21 | new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet, |
|
8 | nbformat, nbformat_minor, nbformat_schema, | |
22 | new_metadata, new_author, new_heading_cell, nbformat, nbformat_minor, |
|
9 | ) | |
23 | nbformat_schema |
|
10 | ||
|
11 | from .compose import ( | |||
|
12 | new_code_cell, new_heading_cell, new_markdown_cell, new_notebook, | |||
|
13 | new_output, | |||
24 | ) |
|
14 | ) | |
25 |
|
15 | |||
26 | from .nbjson import reads as reads_json, writes as writes_json |
|
16 | from .nbjson import reads as reads_json, writes as writes_json | |
27 | from .nbjson import reads as read_json, writes as write_json |
|
17 | from .nbjson import reads as read_json, writes as write_json | |
28 | from .nbjson import to_notebook as to_notebook_json |
|
18 | from .nbjson import to_notebook as to_notebook_json | |
29 |
|
19 | |||
30 | from .nbpy import reads as reads_py, writes as writes_py |
|
|||
31 | from .nbpy import reads as read_py, writes as write_py |
|
|||
32 | from .nbpy import to_notebook as to_notebook_py |
|
|||
33 |
|
||||
34 | from .convert import downgrade, upgrade |
|
20 | from .convert import downgrade, upgrade | |
35 |
|
21 | |||
36 | #----------------------------------------------------------------------------- |
|
|||
37 | # Code |
|
|||
38 | #----------------------------------------------------------------------------- |
|
|||
39 |
|
||||
40 | def parse_filename(fname): |
|
|||
41 | """Parse a notebook filename. |
|
|||
42 |
|
||||
43 | This function takes a notebook filename and returns the notebook |
|
|||
44 | format (json/py) and the notebook name. This logic can be |
|
|||
45 | summarized as follows: |
|
|||
46 |
|
||||
47 | * notebook.ipynb -> (notebook.ipynb, notebook, json) |
|
|||
48 | * notebook.json -> (notebook.json, notebook, json) |
|
|||
49 | * notebook.py -> (notebook.py, notebook, py) |
|
|||
50 | * notebook -> (notebook.ipynb, notebook, json) |
|
|||
51 |
|
||||
52 | Parameters |
|
|||
53 | ---------- |
|
|||
54 | fname : unicode |
|
|||
55 | The notebook filename. The filename can use a specific filename |
|
|||
56 | extention (.ipynb, .json, .py) or none, in which case .ipynb will |
|
|||
57 | be assumed. |
|
|||
58 |
|
22 | |||
59 | Returns |
|
|||
60 | ------- |
|
|||
61 | (fname, name, format) : (unicode, unicode, unicode) |
|
|||
62 | The filename, notebook name and format. |
|
|||
63 | """ |
|
|||
64 | if fname.endswith(u'.ipynb'): |
|
|||
65 | format = u'json' |
|
|||
66 | elif fname.endswith(u'.json'): |
|
|||
67 | format = u'json' |
|
|||
68 | elif fname.endswith(u'.py'): |
|
|||
69 | format = u'py' |
|
|||
70 | else: |
|
|||
71 | fname = fname + u'.ipynb' |
|
|||
72 | format = u'json' |
|
|||
73 | name = fname.split('.')[0] |
|
|||
74 | return fname, name, format |
|
@@ -1,36 +1,20 b'' | |||||
1 |
"""Code for converting notebooks to and from |
|
1 | """Code for converting notebooks to and from v3.""" | |
2 |
|
2 | |||
3 | Authors: |
|
3 | # Copyright (c) IPython Development Team. | |
4 |
|
4 | # Distributed under the terms of the Modified BSD License. | ||
5 | * Brian Granger |
|
|||
6 | * Min RK |
|
|||
7 | * Jonathan Frederic |
|
|||
8 | """ |
|
|||
9 |
|
||||
10 | #----------------------------------------------------------------------------- |
|
|||
11 | # Copyright (C) 2008-2011 The IPython Development Team |
|
|||
12 | # |
|
|||
13 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
14 | # the file COPYING, distributed as part of this software. |
|
|||
15 | #----------------------------------------------------------------------------- |
|
|||
16 |
|
||||
17 | #----------------------------------------------------------------------------- |
|
|||
18 | # Imports |
|
|||
19 | #----------------------------------------------------------------------------- |
|
|||
20 |
|
5 | |||
21 | from .nbbase import ( |
|
6 | from .nbbase import ( | |
22 | new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output, |
|
7 | nbformat, nbformat_minor, | |
23 | nbformat, nbformat_minor |
|
|||
24 | ) |
|
8 | ) | |
25 |
|
9 | |||
26 |
from IPython.nbformat import v |
|
10 | from IPython.nbformat import v3 | |
27 |
|
11 | |||
28 | #----------------------------------------------------------------------------- |
|
12 | #----------------------------------------------------------------------------- | |
29 | # Code |
|
13 | # Code | |
30 | #----------------------------------------------------------------------------- |
|
14 | #----------------------------------------------------------------------------- | |
31 |
|
15 | |||
32 |
def upgrade(nb, from_version= |
|
16 | def upgrade(nb, from_version=3, from_minor=0): | |
33 |
"""Convert a notebook to v |
|
17 | """Convert a notebook to v4. | |
34 |
|
18 | |||
35 | Parameters |
|
19 | Parameters | |
36 | ---------- |
|
20 | ---------- | |
@@ -41,50 +25,135 b' def upgrade(nb, from_version=2, from_minor=0):' | |||||
41 | from_minor : int |
|
25 | from_minor : int | |
42 | The original minor version of the notebook to convert (only relevant for v >= 3). |
|
26 | The original minor version of the notebook to convert (only relevant for v >= 3). | |
43 | """ |
|
27 | """ | |
44 |
if from_version == |
|
28 | if from_version == 3: | |
45 | # Mark the original nbformat so consumers know it has been converted. |
|
29 | # Mark the original nbformat so consumers know it has been converted. | |
46 | nb.nbformat = nbformat |
|
30 | nb.nbformat = nbformat | |
47 | nb.nbformat_minor = nbformat_minor |
|
31 | nb.nbformat_minor = nbformat_minor | |
48 |
|
32 | |||
49 |
nb.orig_nbformat = |
|
33 | nb.orig_nbformat = 3 | |
|
34 | # remove worksheet(s) | |||
|
35 | nb['cells'] = cells = [] | |||
|
36 | # In the unlikely event of multiple worksheets, | |||
|
37 | # they will be flattened | |||
|
38 | for ws in nb.pop('worksheets', []): | |||
|
39 | # upgrade each cell | |||
|
40 | for cell in ws['cells']: | |||
|
41 | cells.append(upgrade_cell(cell)) | |||
|
42 | # upgrade metadata? | |||
|
43 | nb.metadata.pop('name', '') | |||
50 | return nb |
|
44 | return nb | |
51 |
elif from_version == |
|
45 | elif from_version == 4: | |
|
46 | # nothing to do | |||
52 | if from_minor != nbformat_minor: |
|
47 | if from_minor != nbformat_minor: | |
53 | nb.orig_nbformat_minor = from_minor |
|
48 | nb.orig_nbformat_minor = from_minor | |
54 | nb.nbformat_minor = nbformat_minor |
|
49 | nb.nbformat_minor = nbformat_minor | |
55 | return nb |
|
50 | return nb | |
56 | else: |
|
51 | else: | |
57 |
raise ValueError('Cannot convert a notebook directly from v%s to v |
|
52 | raise ValueError('Cannot convert a notebook directly from v%s to v4. ' \ | |
58 | 'Try using the IPython.nbformat.convert module.' % from_version) |
|
53 | 'Try using the IPython.nbformat.convert module.' % from_version) | |
59 |
|
54 | |||
|
55 | def upgrade_cell(cell): | |||
|
56 | """upgrade a cell from v3 to v4 | |||
60 |
|
|
57 | ||
61 | def heading_to_md(cell): |
|
58 | code cell: | |
62 | """turn heading cell into corresponding markdown""" |
|
59 | - remove language metadata | |
63 | cell.cell_type = "markdown" |
|
60 | - cell.input -> cell.source | |
64 | level = cell.pop('level', 1) |
|
61 | - update outputs | |
65 | cell.source = '#'*level + ' ' + cell.source |
|
62 | """ | |
66 |
|
63 | if cell.cell_type == 'code': | ||
67 |
|
64 | cell.metadata.pop('language', '') | ||
68 | def raw_to_md(cell): |
|
65 | cell.source = cell.pop('input') | |
69 | """let raw passthrough as markdown""" |
|
66 | cell.outputs = upgrade_outputs(cell) | |
70 | cell.cell_type = "markdown" |
|
67 | return cell | |
71 |
|
68 | |||
|
69 | def downgrade_cell(cell): | |||
|
70 | if cell.cell_type == 'code': | |||
|
71 | cell.input = cell.pop('source') | |||
|
72 | cell.outputs = downgrade_outputs(cell) | |||
|
73 | return cell | |||
|
74 | ||||
|
75 | _mime_map = { | |||
|
76 | "text" : "text/plain", | |||
|
77 | "html" : "text/html", | |||
|
78 | "svg" : "image/svg+xml", | |||
|
79 | "png" : "image/png", | |||
|
80 | "jpeg" : "image/jpeg", | |||
|
81 | "latex" : "text/latex", | |||
|
82 | "json" : "application/json", | |||
|
83 | "javascript" : "application/javascript", | |||
|
84 | }; | |||
|
85 | ||||
|
86 | def to_mime_key(d): | |||
|
87 | """convert dict with v3 aliases to plain mime-type keys""" | |||
|
88 | for alias, mime in _mime_map.items(): | |||
|
89 | if alias in d: | |||
|
90 | d[mime] = d.pop(alias) | |||
|
91 | return d | |||
|
92 | ||||
|
93 | def from_mime_key(d): | |||
|
94 | """convert dict with mime-type keys to v3 aliases""" | |||
|
95 | for alias, mime in _mime_map.items(): | |||
|
96 | if mime in d: | |||
|
97 | d[alias] = d.pop(mime) | |||
|
98 | return d | |||
|
99 | ||||
|
100 | def upgrade_output(output): | |||
|
101 | """upgrade a single code cell output from v3 to v4 | |||
|
102 | ||||
|
103 | - pyout -> execute_result | |||
|
104 | - pyerr -> error | |||
|
105 | - mime-type keys | |||
|
106 | """ | |||
|
107 | if output['output_type'] == 'pyout': | |||
|
108 | output['output_type'] = 'execute_result' | |||
|
109 | to_mime_key(output) | |||
|
110 | to_mime_key(output.get('metadata', {})) | |||
|
111 | elif output['output_type'] == 'pyerr': | |||
|
112 | output['output_type'] = 'error' | |||
|
113 | elif output['output_type'] == 'display_data': | |||
|
114 | to_mime_key(output) | |||
|
115 | to_mime_key(output.get('metadata', {})) | |||
|
116 | return output | |||
|
117 | ||||
|
118 | def downgrade_output(output): | |||
|
119 | """downgrade a single code cell output to v3 from v4 | |||
|
120 | ||||
|
121 | - pyout <- execute_result | |||
|
122 | - pyerr <- error | |||
|
123 | - un-mime-type keys | |||
|
124 | """ | |||
|
125 | if output['output_type'] == 'execute_result': | |||
|
126 | output['output_type'] = 'pyout' | |||
|
127 | from_mime_key(output) | |||
|
128 | from_mime_key(output.get('metadata', {})) | |||
|
129 | elif output['output_type'] == 'error': | |||
|
130 | output['output_type'] = 'pyerr' | |||
|
131 | elif output['output_type'] == 'display_data': | |||
|
132 | from_mime_key(output) | |||
|
133 | from_mime_key(output.get('metadata', {})) | |||
|
134 | return output | |||
|
135 | ||||
|
136 | def upgrade_outputs(outputs): | |||
|
137 | """upgrade outputs of a code cell from v3 to v4""" | |||
|
138 | return [upgrade_output(op) for op in outputs] | |||
|
139 | ||||
|
140 | def downgrade_outputs(outputs): | |||
|
141 | """downgrade outputs of a code cell to v3 from v4""" | |||
|
142 | return [downgrade_output(op) for op in outputs] | |||
72 |
|
143 | |||
73 | def downgrade(nb): |
|
144 | def downgrade(nb): | |
74 |
"""Convert a v |
|
145 | """Convert a v4 notebook to v3. | |
75 |
|
146 | |||
76 | Parameters |
|
147 | Parameters | |
77 | ---------- |
|
148 | ---------- | |
78 | nb : NotebookNode |
|
149 | nb : NotebookNode | |
79 | The Python representation of the notebook to convert. |
|
150 | The Python representation of the notebook to convert. | |
80 | """ |
|
151 | """ | |
81 |
if nb.nbformat != |
|
152 | if nb.nbformat != 4: | |
82 | return nb |
|
153 | return nb | |
83 |
nb.nbformat = |
|
154 | nb.nbformat = v3.nbformat | |
84 | for ws in nb.worksheets: |
|
155 | nb.nbformat_minor = v3.nbformat_minor | |
85 |
for cell in |
|
156 | cells = [ downgrade_cell(cell) for cell in nb.cells ] | |
86 | if cell.cell_type == 'heading': |
|
157 | nb.worksheets = [v3.new_worksheet(cells=cells)] | |
87 | heading_to_md(cell) |
|
158 | nb.metadata.setdefault('name', '') | |
88 | elif cell.cell_type == 'raw': |
|
|||
89 | raw_to_md(cell) |
|
|||
90 | return nb No newline at end of file |
|
159 | return nb |
@@ -15,14 +15,11 b' import uuid' | |||||
15 | from IPython.utils.ipstruct import Struct |
|
15 | from IPython.utils.ipstruct import Struct | |
16 | from IPython.utils.py3compat import cast_unicode, unicode_type |
|
16 | from IPython.utils.py3compat import cast_unicode, unicode_type | |
17 |
|
17 | |||
18 | #----------------------------------------------------------------------------- |
|
|||
19 | # Code |
|
|||
20 | #----------------------------------------------------------------------------- |
|
|||
21 |
|
18 | |||
22 | # Change this when incrementing the nbformat version |
|
19 | # Change this when incrementing the nbformat version | |
23 |
nbformat = |
|
20 | nbformat = 4 | |
24 | nbformat_minor = 0 |
|
21 | nbformat_minor = 0 | |
25 |
nbformat_schema = 'v |
|
22 | nbformat_schema = 'v4.withref.json' | |
26 |
|
23 | |||
27 | class NotebookNode(Struct): |
|
24 | class NotebookNode(Struct): | |
28 | pass |
|
25 | pass | |
@@ -39,166 +36,3 b' def from_dict(d):' | |||||
39 | else: |
|
36 | else: | |
40 | return d |
|
37 | return d | |
41 |
|
38 | |||
42 |
|
||||
43 | def new_output(output_type, output_text=None, output_png=None, |
|
|||
44 | output_html=None, output_svg=None, output_latex=None, output_json=None, |
|
|||
45 | output_javascript=None, output_jpeg=None, prompt_number=None, |
|
|||
46 | ename=None, evalue=None, traceback=None, stream=None, metadata=None): |
|
|||
47 | """Create a new output, to go in the ``cell.outputs`` list of a code cell. |
|
|||
48 | """ |
|
|||
49 | output = NotebookNode() |
|
|||
50 | output.output_type = unicode_type(output_type) |
|
|||
51 |
|
||||
52 | if metadata is None: |
|
|||
53 | metadata = {} |
|
|||
54 | if not isinstance(metadata, dict): |
|
|||
55 | raise TypeError("metadata must be dict") |
|
|||
56 | output.metadata = metadata |
|
|||
57 |
|
||||
58 | if output_type != 'pyerr': |
|
|||
59 | if output_text is not None: |
|
|||
60 | output.text = cast_unicode(output_text) |
|
|||
61 | if output_png is not None: |
|
|||
62 | output.png = cast_unicode(output_png) |
|
|||
63 | if output_jpeg is not None: |
|
|||
64 | output.jpeg = cast_unicode(output_jpeg) |
|
|||
65 | if output_html is not None: |
|
|||
66 | output.html = cast_unicode(output_html) |
|
|||
67 | if output_svg is not None: |
|
|||
68 | output.svg = cast_unicode(output_svg) |
|
|||
69 | if output_latex is not None: |
|
|||
70 | output.latex = cast_unicode(output_latex) |
|
|||
71 | if output_json is not None: |
|
|||
72 | output.json = cast_unicode(output_json) |
|
|||
73 | if output_javascript is not None: |
|
|||
74 | output.javascript = cast_unicode(output_javascript) |
|
|||
75 |
|
||||
76 | if output_type == u'pyout': |
|
|||
77 | if prompt_number is not None: |
|
|||
78 | output.prompt_number = int(prompt_number) |
|
|||
79 |
|
||||
80 | if output_type == u'pyerr': |
|
|||
81 | if ename is not None: |
|
|||
82 | output.ename = cast_unicode(ename) |
|
|||
83 | if evalue is not None: |
|
|||
84 | output.evalue = cast_unicode(evalue) |
|
|||
85 | if traceback is not None: |
|
|||
86 | output.traceback = [cast_unicode(frame) for frame in list(traceback)] |
|
|||
87 |
|
||||
88 | if output_type == u'stream': |
|
|||
89 | output.stream = 'stdout' if stream is None else cast_unicode(stream) |
|
|||
90 |
|
||||
91 | return output |
|
|||
92 |
|
||||
93 |
|
||||
94 | def new_code_cell(input=None, prompt_number=None, outputs=None, |
|
|||
95 | language=u'python', collapsed=False, metadata=None): |
|
|||
96 | """Create a new code cell with input and output""" |
|
|||
97 | cell = NotebookNode() |
|
|||
98 | cell.cell_type = u'code' |
|
|||
99 | if language is not None: |
|
|||
100 | cell.language = cast_unicode(language) |
|
|||
101 | if input is not None: |
|
|||
102 | cell.input = cast_unicode(input) |
|
|||
103 | if prompt_number is not None: |
|
|||
104 | cell.prompt_number = int(prompt_number) |
|
|||
105 | if outputs is None: |
|
|||
106 | cell.outputs = [] |
|
|||
107 | else: |
|
|||
108 | cell.outputs = outputs |
|
|||
109 | if collapsed is not None: |
|
|||
110 | cell.collapsed = bool(collapsed) |
|
|||
111 | cell.metadata = NotebookNode(metadata or {}) |
|
|||
112 |
|
||||
113 | return cell |
|
|||
114 |
|
||||
115 | def new_text_cell(cell_type, source=None, rendered=None, metadata=None): |
|
|||
116 | """Create a new text cell.""" |
|
|||
117 | cell = NotebookNode() |
|
|||
118 | # VERSIONHACK: plaintext -> raw |
|
|||
119 | # handle never-released plaintext name for raw cells |
|
|||
120 | if cell_type == 'plaintext': |
|
|||
121 | cell_type = 'raw' |
|
|||
122 | if source is not None: |
|
|||
123 | cell.source = cast_unicode(source) |
|
|||
124 | if rendered is not None: |
|
|||
125 | cell.rendered = cast_unicode(rendered) |
|
|||
126 | cell.metadata = NotebookNode(metadata or {}) |
|
|||
127 | cell.cell_type = cell_type |
|
|||
128 | return cell |
|
|||
129 |
|
||||
130 |
|
||||
131 | def new_heading_cell(source=None, rendered=None, level=1, metadata=None): |
|
|||
132 | """Create a new section cell with a given integer level.""" |
|
|||
133 | cell = NotebookNode() |
|
|||
134 | cell.cell_type = u'heading' |
|
|||
135 | if source is not None: |
|
|||
136 | cell.source = cast_unicode(source) |
|
|||
137 | if rendered is not None: |
|
|||
138 | cell.rendered = cast_unicode(rendered) |
|
|||
139 | cell.level = int(level) |
|
|||
140 | cell.metadata = NotebookNode(metadata or {}) |
|
|||
141 | return cell |
|
|||
142 |
|
||||
143 |
|
||||
144 | def new_worksheet(name=None, cells=None, metadata=None): |
|
|||
145 | """Create a worksheet by name with with a list of cells.""" |
|
|||
146 | ws = NotebookNode() |
|
|||
147 | if name is not None: |
|
|||
148 | ws.name = cast_unicode(name) |
|
|||
149 | if cells is None: |
|
|||
150 | ws.cells = [] |
|
|||
151 | else: |
|
|||
152 | ws.cells = list(cells) |
|
|||
153 | ws.metadata = NotebookNode(metadata or {}) |
|
|||
154 | return ws |
|
|||
155 |
|
||||
156 |
|
||||
157 | def new_notebook(name=None, metadata=None, worksheets=None): |
|
|||
158 | """Create a notebook by name, id and a list of worksheets.""" |
|
|||
159 | nb = NotebookNode() |
|
|||
160 | nb.nbformat = nbformat |
|
|||
161 | nb.nbformat_minor = nbformat_minor |
|
|||
162 | if worksheets is None: |
|
|||
163 | nb.worksheets = [] |
|
|||
164 | else: |
|
|||
165 | nb.worksheets = list(worksheets) |
|
|||
166 | if metadata is None: |
|
|||
167 | nb.metadata = new_metadata() |
|
|||
168 | else: |
|
|||
169 | nb.metadata = NotebookNode(metadata) |
|
|||
170 | if name is not None: |
|
|||
171 | nb.metadata.name = cast_unicode(name) |
|
|||
172 | return nb |
|
|||
173 |
|
||||
174 |
|
||||
175 | def new_metadata(name=None, authors=None, license=None, created=None, |
|
|||
176 | modified=None, gistid=None): |
|
|||
177 | """Create a new metadata node.""" |
|
|||
178 | metadata = NotebookNode() |
|
|||
179 | if name is not None: |
|
|||
180 | metadata.name = cast_unicode(name) |
|
|||
181 | if authors is not None: |
|
|||
182 | metadata.authors = list(authors) |
|
|||
183 | if created is not None: |
|
|||
184 | metadata.created = cast_unicode(created) |
|
|||
185 | if modified is not None: |
|
|||
186 | metadata.modified = cast_unicode(modified) |
|
|||
187 | if license is not None: |
|
|||
188 | metadata.license = cast_unicode(license) |
|
|||
189 | if gistid is not None: |
|
|||
190 | metadata.gistid = cast_unicode(gistid) |
|
|||
191 | return metadata |
|
|||
192 |
|
||||
193 | def new_author(name=None, email=None, affiliation=None, url=None): |
|
|||
194 | """Create a new author.""" |
|
|||
195 | author = NotebookNode() |
|
|||
196 | if name is not None: |
|
|||
197 | author.name = cast_unicode(name) |
|
|||
198 | if email is not None: |
|
|||
199 | author.email = cast_unicode(email) |
|
|||
200 | if affiliation is not None: |
|
|||
201 | author.affiliation = cast_unicode(affiliation) |
|
|||
202 | if url is not None: |
|
|||
203 | author.url = cast_unicode(url) |
|
|||
204 | return author |
|
@@ -1,34 +1,18 b'' | |||||
1 | """Read and write notebooks in JSON format. |
|
1 | """Read and write notebooks in JSON format.""" | |
2 |
|
2 | |||
3 | Authors: |
|
3 | # Copyright (c) IPython Development Team. | |
4 |
|
4 | # Distributed under the terms of the Modified BSD License. | ||
5 | * Brian Granger |
|
|||
6 | """ |
|
|||
7 |
|
||||
8 | #----------------------------------------------------------------------------- |
|
|||
9 | # Copyright (C) 2008-2011 The IPython Development Team |
|
|||
10 | # |
|
|||
11 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
12 | # the file COPYING, distributed as part of this software. |
|
|||
13 | #----------------------------------------------------------------------------- |
|
|||
14 |
|
||||
15 | #----------------------------------------------------------------------------- |
|
|||
16 | # Imports |
|
|||
17 | #----------------------------------------------------------------------------- |
|
|||
18 |
|
5 | |||
19 | import copy |
|
6 | import copy | |
20 | import json |
|
7 | import json | |
21 |
|
8 | |||
|
9 | from IPython.utils import py3compat | |||
|
10 | ||||
22 | from .nbbase import from_dict |
|
11 | from .nbbase import from_dict | |
23 | from .rwbase import ( |
|
12 | from .rwbase import ( | |
24 |
NotebookReader, NotebookWriter, |
|
13 | NotebookReader, NotebookWriter, rejoin_lines, split_lines | |
25 | ) |
|
14 | ) | |
26 |
|
15 | |||
27 | from IPython.utils import py3compat |
|
|||
28 |
|
||||
29 | #----------------------------------------------------------------------------- |
|
|||
30 | # Code |
|
|||
31 | #----------------------------------------------------------------------------- |
|
|||
32 |
|
16 | |||
33 | class BytesEncoder(json.JSONEncoder): |
|
17 | class BytesEncoder(json.JSONEncoder): | |
34 | """A JSON encoder that accepts b64 (and other *ascii*) bytestrings.""" |
|
18 | """A JSON encoder that accepts b64 (and other *ascii*) bytestrings.""" |
@@ -1,51 +1,19 b'' | |||||
1 | """Base classes and utilities for readers and writers. |
|
1 | """Base classes and utilities for readers and writers.""" | |
2 |
|
2 | |||
3 | Authors: |
|
3 | # Copyright (c) IPython Development Team. | |
4 |
|
4 | # Distributed under the terms of the Modified BSD License. | ||
5 | * Brian Granger |
|
|||
6 | """ |
|
|||
7 |
|
||||
8 | #----------------------------------------------------------------------------- |
|
|||
9 | # Copyright (C) 2008-2011 The IPython Development Team |
|
|||
10 | # |
|
|||
11 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
12 | # the file COPYING, distributed as part of this software. |
|
|||
13 | #----------------------------------------------------------------------------- |
|
|||
14 |
|
||||
15 | #----------------------------------------------------------------------------- |
|
|||
16 | # Imports |
|
|||
17 | #----------------------------------------------------------------------------- |
|
|||
18 |
|
||||
19 | from base64 import encodestring, decodestring |
|
|||
20 | import pprint |
|
|||
21 |
|
5 | |||
22 | from IPython.utils import py3compat |
|
6 | from IPython.utils import py3compat | |
23 |
from IPython.utils.py3compat import |
|
7 | from IPython.utils.py3compat import unicode_type, string_types | |
24 |
|
||||
25 | #----------------------------------------------------------------------------- |
|
|||
26 | # Code |
|
|||
27 | #----------------------------------------------------------------------------- |
|
|||
28 |
|
||||
29 | def restore_bytes(nb): |
|
|||
30 | """Restore bytes of image data from unicode-only formats. |
|
|||
31 |
|
||||
32 | Base64 encoding is handled elsewhere. Bytes objects in the notebook are |
|
|||
33 | always b64-encoded. We DO NOT encode/decode around file formats. |
|
|||
34 |
|
||||
35 | Note: this is never used |
|
|||
36 | """ |
|
|||
37 | for ws in nb.worksheets: |
|
|||
38 | for cell in ws.cells: |
|
|||
39 | if cell.cell_type == 'code': |
|
|||
40 | for output in cell.outputs: |
|
|||
41 | if 'png' in output: |
|
|||
42 | output.png = str_to_bytes(output.png, 'ascii') |
|
|||
43 | if 'jpeg' in output: |
|
|||
44 | output.jpeg = str_to_bytes(output.jpeg, 'ascii') |
|
|||
45 | return nb |
|
|||
46 |
|
8 | |||
47 | # output keys that are likely to have multiline values |
|
9 | # output keys that are likely to have multiline values | |
48 | _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json'] |
|
10 | _multiline_outputs = { | |
|
11 | 'text/plain', | |||
|
12 | 'text/html', | |||
|
13 | 'image/svg+xml', | |||
|
14 | 'text/latex', | |||
|
15 | 'application/javascript', | |||
|
16 | } | |||
49 |
|
17 | |||
50 |
|
18 | |||
51 | # FIXME: workaround for old splitlines() |
|
19 | # FIXME: workaround for old splitlines() | |
@@ -73,21 +41,15 b' def rejoin_lines(nb):' | |||||
73 |
|
41 | |||
74 | Used when reading JSON files that may have been passed through split_lines. |
|
42 | Used when reading JSON files that may have been passed through split_lines. | |
75 | """ |
|
43 | """ | |
76 |
for |
|
44 | for cell in nb.cells: | |
77 | for cell in ws.cells: |
|
45 | if 'source' in cell and isinstance(cell.source, list): | |
78 | if cell.cell_type == 'code': |
|
46 | cell.source = _join_lines(cell.source) | |
79 | if 'input' in cell and isinstance(cell.input, list): |
|
47 | if cell.cell_type == 'code': | |
80 | cell.input = _join_lines(cell.input) |
|
48 | for output in cell.outputs: | |
81 |
for |
|
49 | for key in _multiline_outputs: | |
82 | for key in _multiline_outputs: |
|
50 | item = output.get(key, None) | |
83 | item = output.get(key, None) |
|
|||
84 | if isinstance(item, list): |
|
|||
85 | output[key] = _join_lines(item) |
|
|||
86 | else: # text, heading cell |
|
|||
87 | for key in ['source', 'rendered']: |
|
|||
88 | item = cell.get(key, None) |
|
|||
89 | if isinstance(item, list): |
|
51 | if isinstance(item, list): | |
90 |
|
|
52 | output[key] = _join_lines(item) | |
91 | return nb |
|
53 | return nb | |
92 |
|
54 | |||
93 |
|
55 | |||
@@ -99,61 +61,17 b' def split_lines(nb):' | |||||
99 |
|
61 | |||
100 | Used when writing JSON files. |
|
62 | Used when writing JSON files. | |
101 | """ |
|
63 | """ | |
102 |
for |
|
64 | for cell in nb.cells: | |
103 | for cell in ws.cells: |
|
65 | source = cell.get('source', None) | |
104 | if cell.cell_type == 'code': |
|
66 | if isinstance(source, string_types): | |
105 | if 'input' in cell and isinstance(cell.input, string_types): |
|
67 | cell['source'] = source.splitlines(True) | |
106 | cell.input = cell.input.splitlines(True) |
|
68 | ||
107 | for output in cell.outputs: |
|
69 | if cell.cell_type == 'code': | |
108 |
|
|
70 | for output in cell.outputs: | |
109 | item = output.get(key, None) |
|
71 | for key in _multiline_outputs: | |
110 | if isinstance(item, string_types): |
|
72 | item = output.get(key, None) | |
111 | output[key] = item.splitlines(True) |
|
|||
112 | else: # text, heading cell |
|
|||
113 | for key in ['source', 'rendered']: |
|
|||
114 | item = cell.get(key, None) |
|
|||
115 | if isinstance(item, string_types): |
|
73 | if isinstance(item, string_types): | |
116 |
|
|
74 | output[key] = item.splitlines(True) | |
117 | return nb |
|
|||
118 |
|
||||
119 | # b64 encode/decode are never actually used, because all bytes objects in |
|
|||
120 | # the notebook are already b64-encoded, and we don't need/want to double-encode |
|
|||
121 |
|
||||
122 | def base64_decode(nb): |
|
|||
123 | """Restore all bytes objects in the notebook from base64-encoded strings. |
|
|||
124 |
|
||||
125 | Note: This is never used |
|
|||
126 | """ |
|
|||
127 | for ws in nb.worksheets: |
|
|||
128 | for cell in ws.cells: |
|
|||
129 | if cell.cell_type == 'code': |
|
|||
130 | for output in cell.outputs: |
|
|||
131 | if 'png' in output: |
|
|||
132 | if isinstance(output.png, unicode_type): |
|
|||
133 | output.png = output.png.encode('ascii') |
|
|||
134 | output.png = decodestring(output.png) |
|
|||
135 | if 'jpeg' in output: |
|
|||
136 | if isinstance(output.jpeg, unicode_type): |
|
|||
137 | output.jpeg = output.jpeg.encode('ascii') |
|
|||
138 | output.jpeg = decodestring(output.jpeg) |
|
|||
139 | return nb |
|
|||
140 |
|
||||
141 |
|
||||
142 | def base64_encode(nb): |
|
|||
143 | """Base64 encode all bytes objects in the notebook. |
|
|||
144 |
|
||||
145 | These will be b64-encoded unicode strings |
|
|||
146 |
|
||||
147 | Note: This is never used |
|
|||
148 | """ |
|
|||
149 | for ws in nb.worksheets: |
|
|||
150 | for cell in ws.cells: |
|
|||
151 | if cell.cell_type == 'code': |
|
|||
152 | for output in cell.outputs: |
|
|||
153 | if 'png' in output: |
|
|||
154 | output.png = encodestring(output.png).decode('ascii') |
|
|||
155 | if 'jpeg' in output: |
|
|||
156 | output.jpeg = encodestring(output.jpeg).decode('ascii') |
|
|||
157 | return nb |
|
75 | return nb | |
158 |
|
76 | |||
159 |
|
77 |
@@ -6,13 +6,7 b' import tempfile' | |||||
6 |
|
6 | |||
7 | pjoin = os.path.join |
|
7 | pjoin = os.path.join | |
8 |
|
8 | |||
9 |
from . |
|
9 | from .nbexamples import nb0 | |
10 | NotebookNode, |
|
|||
11 | new_code_cell, new_text_cell, new_worksheet, new_notebook |
|
|||
12 | ) |
|
|||
13 |
|
||||
14 | from ..nbpy import reads, writes, read, write |
|
|||
15 | from .nbexamples import nb0, nb0_py |
|
|||
16 |
|
10 | |||
17 |
|
11 | |||
18 | def open_utf8(fname, mode): |
|
12 | def open_utf8(fname, mode): |
@@ -3,150 +3,103 b'' | |||||
3 | import os |
|
3 | import os | |
4 | from base64 import encodestring |
|
4 | from base64 import encodestring | |
5 |
|
5 | |||
6 |
from .. |
|
6 | from ..compose import ( | |
7 | NotebookNode, |
|
7 | new_code_cell, new_heading_cell, new_markdown_cell, new_notebook, | |
8 | new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output, |
|
8 | new_output, new_raw_cell | |
9 | new_metadata, new_author, new_heading_cell, nbformat, nbformat_minor |
|
|||
10 | ) |
|
9 | ) | |
11 |
|
10 | |||
12 | # some random base64-encoded *text* |
|
11 | # some random base64-encoded *text* | |
13 | png = encodestring(os.urandom(5)).decode('ascii') |
|
12 | png = encodestring(os.urandom(5)).decode('ascii') | |
14 | jpeg = encodestring(os.urandom(6)).decode('ascii') |
|
13 | jpeg = encodestring(os.urandom(6)).decode('ascii') | |
15 |
|
14 | |||
16 | ws = new_worksheet(name='worksheet1') |
|
15 | cells = [] | |
17 |
|
16 | cells.append(new_markdown_cell( | ||
18 | ws.cells.append(new_text_cell( |
|
|||
19 | u'html', |
|
|||
20 | source='Some NumPy Examples', |
|
17 | source='Some NumPy Examples', | |
21 | rendered='Some NumPy Examples' |
|
|||
22 | )) |
|
18 | )) | |
23 |
|
19 | |||
24 |
|
20 | |||
25 |
|
|
21 | cells.append(new_code_cell( | |
26 |
|
|
22 | source='import numpy', | |
27 | prompt_number=1, |
|
23 | prompt_number=1, | |
28 | collapsed=False |
|
|||
29 | )) |
|
24 | )) | |
30 |
|
25 | |||
31 |
|
|
26 | cells.append(new_markdown_cell( | |
32 | u'markdown', |
|
|||
33 | source='A random array', |
|
27 | source='A random array', | |
34 | rendered='A random array' |
|
|||
35 | )) |
|
28 | )) | |
36 |
|
29 | |||
37 |
|
|
30 | cells.append(new_raw_cell( | |
38 | u'raw', |
|
|||
39 | source='A random array', |
|
31 | source='A random array', | |
40 | )) |
|
32 | )) | |
41 |
|
33 | |||
42 |
|
|
34 | cells.append(new_heading_cell( | |
43 | u'My Heading', |
|
35 | source=u'My Heading', | |
44 | level=2 |
|
36 | level=2, | |
45 | )) |
|
37 | )) | |
46 |
|
38 | |||
47 |
|
|
39 | cells.append(new_code_cell( | |
48 |
|
|
40 | source='a = numpy.random.rand(100)', | |
49 | prompt_number=2, |
|
41 | prompt_number=2, | |
50 | collapsed=True |
|
|||
51 | )) |
|
42 | )) | |
52 |
|
|
43 | cells.append(new_code_cell( | |
53 |
|
|
44 | source='a = 10\nb = 5\n', | |
54 | prompt_number=3, |
|
45 | prompt_number=3, | |
55 | )) |
|
46 | )) | |
56 |
|
|
47 | cells.append(new_code_cell( | |
57 |
|
|
48 | source='a = 10\nb = 5', | |
58 | prompt_number=4, |
|
49 | prompt_number=4, | |
59 | )) |
|
50 | )) | |
60 |
|
51 | |||
61 |
|
|
52 | cells.append(new_code_cell( | |
62 |
|
|
53 | source=u'print "ünîcødé"', | |
63 | prompt_number=3, |
|
54 | prompt_number=3, | |
64 | collapsed=False, |
|
|||
65 | outputs=[new_output( |
|
55 | outputs=[new_output( | |
66 |
output_type=u' |
|
56 | output_type=u'execute_result', | |
67 | output_text=u'<array a>', |
|
57 | mime_bundle={ | |
68 | output_html=u'The HTML rep', |
|
58 | 'text/plain': u'<array a>', | |
69 | output_latex=u'$a$', |
|
59 | 'text/html': u'The HTML rep', | |
70 | output_png=png, |
|
60 | 'text/latex': u'$a$', | |
71 | output_jpeg=jpeg, |
|
61 | 'image/png': png, | |
72 | output_svg=u'<svg>', |
|
62 | 'image/jpeg': jpeg, | |
73 | output_json=u'json data', |
|
63 | 'image/svg+xml': u'<svg>', | |
74 | output_javascript=u'var i=0;', |
|
64 | 'application/json': { | |
|
65 | 'key': 'value' | |||
|
66 | }, | |||
|
67 | 'application/javascript': u'var i=0;' | |||
|
68 | }, | |||
75 | prompt_number=3 |
|
69 | prompt_number=3 | |
76 | ),new_output( |
|
70 | ),new_output( | |
77 | output_type=u'display_data', |
|
71 | output_type=u'display_data', | |
78 | output_text=u'<array a>', |
|
72 | mime_bundle={ | |
79 | output_html=u'The HTML rep', |
|
73 | 'text/plain': u'<array a>', | |
80 | output_latex=u'$a$', |
|
74 | 'text/html': u'The HTML rep', | |
81 | output_png=png, |
|
75 | 'text/latex': u'$a$', | |
82 | output_jpeg=jpeg, |
|
76 | 'image/png': png, | |
83 | output_svg=u'<svg>', |
|
77 | 'image/jpeg': jpeg, | |
84 | output_json=u'json data', |
|
78 | 'image/svg+xml': u'<svg>', | |
85 | output_javascript=u'var i=0;' |
|
79 | 'application/json': { | |
|
80 | 'key': 'value' | |||
|
81 | }, | |||
|
82 | 'application/javascript': u'var i=0;' | |||
|
83 | }, | |||
86 | ),new_output( |
|
84 | ),new_output( | |
87 |
output_type=u' |
|
85 | output_type=u'error', | |
88 | ename=u'NameError', |
|
86 | ename=u'NameError', | |
89 | evalue=u'NameError was here', |
|
87 | evalue=u'NameError was here', | |
90 | traceback=[u'frame 0', u'frame 1', u'frame 2'] |
|
88 | traceback=[u'frame 0', u'frame 1', u'frame 2'] | |
91 | ),new_output( |
|
89 | ),new_output( | |
92 | output_type=u'stream', |
|
90 | output_type=u'stream', | |
93 |
|
|
91 | text='foo\rbar\r\n' | |
94 | ),new_output( |
|
92 | ),new_output( | |
95 | output_type=u'stream', |
|
93 | output_type=u'stream', | |
96 | stream='stderr', |
|
94 | stream='stderr', | |
97 |
|
|
95 | text='\rfoo\rbar\n' | |
98 | )] |
|
96 | )] | |
99 | )) |
|
97 | )) | |
100 |
|
98 | |||
101 | authors = [new_author(name='Bart Simpson',email='bsimpson@fox.com', |
|
99 | nb0 = new_notebook(cells=cells, | |
102 | affiliation=u'Fox',url=u'http://www.fox.com')] |
|
100 | metadata={ | |
103 | md = new_metadata(name=u'My Notebook',license=u'BSD',created=u'8601_goes_here', |
|
101 | 'language': 'python', | |
104 | modified=u'8601_goes_here',gistid=u'21341231',authors=authors) |
|
102 | } | |
105 |
|
||||
106 | nb0 = new_notebook( |
|
|||
107 | worksheets=[ws, new_worksheet(name='worksheet2')], |
|
|||
108 | metadata=md |
|
|||
109 | ) |
|
103 | ) | |
110 |
|
104 | |||
111 | nb0_py = u"""# -*- coding: utf-8 -*- |
|
|||
112 | # <nbformat>%i.%i</nbformat> |
|
|||
113 |
|
||||
114 | # <htmlcell> |
|
|||
115 |
|
||||
116 | # Some NumPy Examples |
|
|||
117 |
|
||||
118 | # <codecell> |
|
|||
119 |
|
||||
120 | import numpy |
|
|||
121 |
|
||||
122 | # <markdowncell> |
|
|||
123 |
|
||||
124 | # A random array |
|
|||
125 |
|
||||
126 | # <rawcell> |
|
|||
127 |
|
||||
128 | # A random array |
|
|||
129 |
|
||||
130 | # <headingcell level=2> |
|
|||
131 |
|
||||
132 | # My Heading |
|
|||
133 |
|
||||
134 | # <codecell> |
|
|||
135 |
|
||||
136 | a = numpy.random.rand(100) |
|
|||
137 |
|
||||
138 | # <codecell> |
|
|||
139 |
|
||||
140 | a = 10 |
|
|||
141 | b = 5 |
|
|||
142 |
|
||||
143 | # <codecell> |
|
|||
144 |
|
||||
145 | a = 10 |
|
|||
146 | b = 5 |
|
|||
147 |
|
||||
148 | # <codecell> |
|
|||
149 |
|
||||
150 | print "ünîcødé" |
|
|||
151 |
|
105 | |||
152 | """ % (nbformat, nbformat_minor) |
|
@@ -1,4 +1,3 b'' | |||||
1 | import pprint |
|
|||
2 |
|
|
1 | from base64 import decodestring | |
3 | from unittest import TestCase |
|
2 | from unittest import TestCase | |
4 |
|
3 | |||
@@ -9,8 +8,6 b' from .nbexamples import nb0' | |||||
9 |
|
8 | |||
10 | from . import formattest |
|
9 | from . import formattest | |
11 |
|
10 | |||
12 | from .nbexamples import nb0 |
|
|||
13 |
|
||||
14 |
|
11 | |||
15 | class TestJSON(formattest.NBFormatTest, TestCase): |
|
12 | class TestJSON(formattest.NBFormatTest, TestCase): | |
16 |
|
13 | |||
@@ -36,13 +33,13 b' class TestJSON(formattest.NBFormatTest, TestCase):' | |||||
36 | s = writes(nb0) |
|
33 | s = writes(nb0) | |
37 | nb1 = nbjson.reads(s) |
|
34 | nb1 = nbjson.reads(s) | |
38 | found_png = False |
|
35 | found_png = False | |
39 |
for cell in nb1. |
|
36 | for cell in nb1.cells: | |
40 | if not 'outputs' in cell: |
|
37 | if not 'outputs' in cell: | |
41 | continue |
|
38 | continue | |
42 | for output in cell.outputs: |
|
39 | for output in cell.outputs: | |
43 | if 'png' in output: |
|
40 | if 'image/png' in output: | |
44 | found_png = True |
|
41 | found_png = True | |
45 | pngdata = output['png'] |
|
42 | pngdata = output['image/png'] | |
46 | self.assertEqual(type(pngdata), unicode_type) |
|
43 | self.assertEqual(type(pngdata), unicode_type) | |
47 | # test that it is valid b64 data |
|
44 | # test that it is valid b64 data | |
48 | b64bytes = pngdata.encode('ascii') |
|
45 | b64bytes = pngdata.encode('ascii') | |
@@ -54,13 +51,13 b' class TestJSON(formattest.NBFormatTest, TestCase):' | |||||
54 | s = writes(nb0) |
|
51 | s = writes(nb0) | |
55 | nb1 = nbjson.reads(s) |
|
52 | nb1 = nbjson.reads(s) | |
56 | found_jpeg = False |
|
53 | found_jpeg = False | |
57 |
for cell in nb1. |
|
54 | for cell in nb1.cells: | |
58 | if not 'outputs' in cell: |
|
55 | if not 'outputs' in cell: | |
59 | continue |
|
56 | continue | |
60 | for output in cell.outputs: |
|
57 | for output in cell.outputs: | |
61 | if 'jpeg' in output: |
|
58 | if 'image/jpeg' in output: | |
62 | found_jpeg = True |
|
59 | found_jpeg = True | |
63 | jpegdata = output['jpeg'] |
|
60 | jpegdata = output['image/jpeg'] | |
64 | self.assertEqual(type(jpegdata), unicode_type) |
|
61 | self.assertEqual(type(jpegdata), unicode_type) | |
65 | # test that it is valid b64 data |
|
62 | # test that it is valid b64 data | |
66 | b64bytes = jpegdata.encode('ascii') |
|
63 | b64bytes = jpegdata.encode('ascii') |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now