Show More
@@ -118,7 +118,12 b' class NotebookManager(LoggingConfigurable):' | |||
|
118 | 118 | if format not in self.allowed_formats: |
|
119 | 119 | raise web.HTTPError(415, u'Invalid notebook format: %s' % format) |
|
120 | 120 | last_modified, nb = self.get_notebook_object(notebook_id) |
|
121 | data = current.writes(nb, format) | |
|
121 | kwargs = {} | |
|
122 | if format == 'json': | |
|
123 | # don't split lines for sending over the wire, because it | |
|
124 | # should match the Python in-memory format. | |
|
125 | kwargs['split_lines'] = False | |
|
126 | data = current.writes(nb, format, **kwargs) | |
|
122 | 127 | name = nb.get('name','notebook') |
|
123 | 128 | return last_modified, name, data |
|
124 | 129 |
@@ -16,10 +16,14 b' Authors:' | |||
|
16 | 16 | # Imports |
|
17 | 17 | #----------------------------------------------------------------------------- |
|
18 | 18 | |
|
19 | from .nbbase import from_dict | |
|
20 | from .rwbase import NotebookReader, NotebookWriter, restore_bytes | |
|
19 | import copy | |
|
21 | 20 | import json |
|
22 | 21 | |
|
22 | from .nbbase import from_dict | |
|
23 | from .rwbase import ( | |
|
24 | NotebookReader, NotebookWriter, restore_bytes, rejoin_lines, split_lines | |
|
25 | ) | |
|
26 | ||
|
23 | 27 | #----------------------------------------------------------------------------- |
|
24 | 28 | # Code |
|
25 | 29 | #----------------------------------------------------------------------------- |
@@ -40,7 +44,7 b' class JSONReader(NotebookReader):' | |||
|
40 | 44 | return nb |
|
41 | 45 | |
|
42 | 46 | def to_notebook(self, d, **kwargs): |
|
43 | return restore_bytes(from_dict(d)) | |
|
47 | return restore_bytes(rejoin_lines(from_dict(d))) | |
|
44 | 48 | |
|
45 | 49 | |
|
46 | 50 | class JSONWriter(NotebookWriter): |
@@ -49,8 +53,10 b' class JSONWriter(NotebookWriter):' | |||
|
49 | 53 | kwargs['cls'] = BytesEncoder |
|
50 | 54 | kwargs['indent'] = 4 |
|
51 | 55 | kwargs['sort_keys'] = True |
|
56 | if kwargs.pop('split_lines', True): | |
|
57 | nb = split_lines(copy.deepcopy(nb)) | |
|
52 | 58 | return json.dumps(nb, **kwargs) |
|
53 | ||
|
59 | ||
|
54 | 60 | |
|
55 | 61 | _reader = JSONReader() |
|
56 | 62 | _writer = JSONWriter() |
@@ -41,6 +41,61 b' def restore_bytes(nb):' | |||
|
41 | 41 | output.jpeg = str_to_bytes(output.jpeg, 'ascii') |
|
42 | 42 | return nb |
|
43 | 43 | |
|
44 | # output keys that are likely to have multiline values | |
|
45 | _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json'] | |
|
46 | ||
|
47 | def rejoin_lines(nb): | |
|
48 | """rejoin multiline text into strings | |
|
49 | ||
|
50 | For reversing effects of ``split_lines(nb)``. | |
|
51 | ||
|
52 | This only rejoins lines that have been split, so if text objects were not split | |
|
53 | they will pass through unchanged. | |
|
54 | ||
|
55 | Used when reading JSON files that may have been passed through split_lines. | |
|
56 | """ | |
|
57 | for ws in nb.worksheets: | |
|
58 | for cell in ws.cells: | |
|
59 | if cell.cell_type == 'code': | |
|
60 | if 'input' in cell and isinstance(cell.input, list): | |
|
61 | cell.input = u'\n'.join(cell.input) | |
|
62 | for output in cell.outputs: | |
|
63 | for key in _multiline_outputs: | |
|
64 | item = output.get(key, None) | |
|
65 | if isinstance(item, list): | |
|
66 | output[key] = u'\n'.join(item) | |
|
67 | else: # text cell | |
|
68 | for key in ['source', 'rendered']: | |
|
69 | item = cell.get(key, None) | |
|
70 | if isinstance(item, list): | |
|
71 | cell[key] = u'\n'.join(item) | |
|
72 | return nb | |
|
73 | ||
|
74 | ||
|
75 | def split_lines(nb): | |
|
76 | """split likely multiline text into lists of strings | |
|
77 | ||
|
78 | For file output more friendly to line-based VCS. ``rejoin_lines(nb)`` will | |
|
79 | reverse the effects of ``split_lines(nb)``. | |
|
80 | ||
|
81 | Used when writing JSON files. | |
|
82 | """ | |
|
83 | for ws in nb.worksheets: | |
|
84 | for cell in ws.cells: | |
|
85 | if cell.cell_type == 'code': | |
|
86 | if 'input' in cell and isinstance(cell.input, basestring): | |
|
87 | cell.input = cell.input.splitlines() | |
|
88 | for output in cell.outputs: | |
|
89 | for key in _multiline_outputs: | |
|
90 | item = output.get(key, None) | |
|
91 | if isinstance(item, basestring): | |
|
92 | output[key] = item.splitlines() | |
|
93 | else: # text cell | |
|
94 | for key in ['source', 'rendered']: | |
|
95 | item = cell.get(key, None) | |
|
96 | if isinstance(item, basestring): | |
|
97 | cell[key] = item.splitlines() | |
|
98 | return nb | |
|
44 | 99 | |
|
45 | 100 | # b64 encode/decode are never actually used, because all bytes objects in |
|
46 | 101 | # the notebook are already b64-encoded, and we don't need/want to double-encode |
@@ -16,6 +16,19 b' class TestJSON(TestCase):' | |||
|
16 | 16 | |
|
17 | 17 | # print s |
|
18 | 18 | self.assertEquals(reads(s),nb0) |
|
19 | ||
|
20 | def test_roundtrip_nosplit(self): | |
|
21 | """Ensure that multiline blobs are still readable""" | |
|
22 | # ensures that notebooks written prior to splitlines change | |
|
23 | # are still readable. | |
|
24 | s = writes(nb0, split_lines=False) | |
|
25 | self.assertEquals(reads(s),nb0) | |
|
26 | ||
|
27 | def test_roundtrip_split(self): | |
|
28 | """Ensure that splitting multiline blocks is safe""" | |
|
29 | # This won't differ from test_roundtrip unless the default changes | |
|
30 | s = writes(nb0, split_lines=True) | |
|
31 | self.assertEquals(reads(s),nb0) | |
|
19 | 32 | |
|
20 | 33 | |
|
21 | 34 |
General Comments 0
You need to be logged in to leave comments.
Login now