diff --git a/IPython/nbformat/v2/__init__.py b/IPython/nbformat/v2/__init__.py new file mode 100644 index 0000000..8e41b05 --- /dev/null +++ b/IPython/nbformat/v2/__init__.py @@ -0,0 +1,78 @@ +"""The main API for the v2 notebook format. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from .nbbase import ( + NotebookNode, + new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet, + new_metadata, new_author +) + +from .nbjson import reads as reads_json, writes as writes_json +from .nbjson import reads as read_json, writes as write_json +from .nbjson import to_notebook as to_notebook_json + +from .nbxml import reads as reads_xml +from .nbxml import reads as read_xml +from .nbxml import to_notebook as to_notebook_xml + +from .nbpy import reads as reads_py, writes as writes_py +from .nbpy import reads as read_py, writes as write_py +from .nbpy import to_notebook as to_notebook_py + +from .convert import convert_to_this_nbformat + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +def parse_filename(fname): + """Parse a notebook filename. + + This function takes a notebook filename and returns the notebook + format (json/py) and the notebook name. This logic can be + summarized as follows: + + * notebook.ipynb -> (notebook.ipynb, notebook, json) + * notebook.json -> (notebook.json, notebook, json) + * notebook.py -> (notebook.py, notebook, py) + * notebook -> (notebook.ipynb, notebook, json) + + Parameters + ---------- + fname : unicode + The notebook filename. The filename can use a specific filename + extention (.ipynb, .json, .py) or none, in which case .ipynb will + be assumed. + + Returns + ------- + (fname, name, format) : (unicode, unicode, unicode) + The filename, notebook name and format. + """ + if fname.endswith(u'.ipynb'): + format = u'json' + elif fname.endswith(u'.json'): + format = u'json' + elif fname.endswith(u'.py'): + format = u'py' + else: + fname = fname + u'.ipynb' + format = u'json' + name = fname.split('.')[0] + return fname, name, format + diff --git a/IPython/nbformat/v2/convert.py b/IPython/nbformat/v2/convert.py new file mode 100644 index 0000000..f5532a4 --- /dev/null +++ b/IPython/nbformat/v2/convert.py @@ -0,0 +1,50 @@ +"""Code for converting notebooks to and from the v2 format. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from .nbbase import ( + new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output +) + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +def convert_to_this_nbformat(nb, orig_version=1): + """Convert a notebook to the v2 format. + + Parameters + ---------- + nb : NotebookNode + The Python representation of the notebook to convert. + orig_version : int + The original version of the notebook to convert. + """ + if orig_version == 1: + newnb = new_notebook() + ws = new_worksheet() + for cell in nb.cells: + if cell.cell_type == u'code': + newcell = new_code_cell(input=cell.get('code'),prompt_number=cell.get('prompt_number')) + elif cell.cell_type == u'text': + newcell = new_text_cell(u'markdown',source=cell.get('text')) + ws.cells.append(newcell) + newnb.worksheets.append(ws) + return newnb + else: + raise ValueError('Cannot convert a notebook from v%s to v2' % orig_version) + diff --git a/IPython/nbformat/v2/nbbase.py b/IPython/nbformat/v2/nbbase.py new file mode 100644 index 0000000..d4ef94e --- /dev/null +++ b/IPython/nbformat/v2/nbbase.py @@ -0,0 +1,179 @@ +"""The basic dict based notebook format. + +The Python representation of a notebook is a nested structure of +dictionary subclasses that support attribute access +(IPython.utils.ipstruct.Struct). The functions in this module are merely +helpers to build the structs in the right form. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import pprint +import uuid + +from IPython.utils.ipstruct import Struct + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +class NotebookNode(Struct): + pass + + +def from_dict(d): + if isinstance(d, dict): + newd = NotebookNode() + for k,v in d.items(): + newd[k] = from_dict(v) + return newd + elif isinstance(d, (tuple, list)): + return [from_dict(i) for i in d] + else: + return d + + +def new_output(output_type=None, output_text=None, output_png=None, + output_html=None, output_svg=None, output_latex=None, output_json=None, + output_javascript=None, output_jpeg=None, prompt_number=None, + etype=None, evalue=None, traceback=None): + """Create a new code cell with input and output""" + output = NotebookNode() + if output_type is not None: + output.output_type = unicode(output_type) + + if output_type != 'pyerr': + if output_text is not None: + output.text = unicode(output_text) + if output_png is not None: + output.png = bytes(output_png) + if output_jpeg is not None: + output.jpeg = bytes(output_jpeg) + if output_html is not None: + output.html = unicode(output_html) + if output_svg is not None: + output.svg = unicode(output_svg) + if output_latex is not None: + output.latex = unicode(output_latex) + if output_json is not None: + output.json = unicode(output_json) + if output_javascript is not None: + output.javascript = unicode(output_javascript) + + if output_type == u'pyout': + if prompt_number is not None: + output.prompt_number = int(prompt_number) + + if output_type == u'pyerr': + if etype is not None: + output.etype = unicode(etype) + if evalue is not None: + output.evalue = unicode(evalue) + if traceback is not None: + output.traceback = [unicode(frame) for frame in list(traceback)] + + return output + + +def new_code_cell(input=None, prompt_number=None, outputs=None, + language=u'python', collapsed=False): + """Create a new code cell with input and output""" + cell = NotebookNode() + cell.cell_type = u'code' + if language is not None: + cell.language = unicode(language) + if input is not None: + cell.input = unicode(input) + if prompt_number is not None: + cell.prompt_number = int(prompt_number) + if outputs is None: + cell.outputs = [] + else: + cell.outputs = outputs + if collapsed is not None: + cell.collapsed = bool(collapsed) + + return cell + +def new_text_cell(cell_type, source=None, rendered=None): + """Create a new text cell.""" + cell = NotebookNode() + if source is not None: + cell.source = unicode(source) + if rendered is not None: + cell.rendered = unicode(rendered) + cell.cell_type = cell_type + return cell + + +def new_worksheet(name=None, cells=None): + """Create a worksheet by name with with a list of cells.""" + ws = NotebookNode() + if name is not None: + ws.name = unicode(name) + if cells is None: + ws.cells = [] + else: + ws.cells = list(cells) + return ws + + +def new_notebook(metadata=None, worksheets=None): + """Create a notebook by name, id and a list of worksheets.""" + nb = NotebookNode() + nb.nbformat = 2 + if worksheets is None: + nb.worksheets = [] + else: + nb.worksheets = list(worksheets) + if metadata is None: + nb.metadata = new_metadata() + else: + nb.metadata = NotebookNode(metadata) + return nb + + +def new_metadata(name=None, authors=None, license=None, created=None, + modified=None, gistid=None): + """Create a new metadata node.""" + metadata = NotebookNode() + if name is not None: + metadata.name = unicode(name) + if authors is not None: + metadata.authors = list(authors) + if created is not None: + metadata.created = unicode(created) + if modified is not None: + metadata.modified = unicode(modified) + if license is not None: + metadata.license = unicode(license) + if gistid is not None: + metadata.gistid = unicode(gistid) + return metadata + +def new_author(name=None, email=None, affiliation=None, url=None): + """Create a new author.""" + author = NotebookNode() + if name is not None: + author.name = unicode(name) + if email is not None: + author.email = unicode(email) + if affiliation is not None: + author.affiliation = unicode(affiliation) + if url is not None: + author.url = unicode(url) + return author + diff --git a/IPython/nbformat/v2/nbjson.py b/IPython/nbformat/v2/nbjson.py new file mode 100644 index 0000000..d60e95f --- /dev/null +++ b/IPython/nbformat/v2/nbjson.py @@ -0,0 +1,69 @@ +"""Read and write notebooks in JSON format. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import copy +import json + +from .nbbase import from_dict +from .rwbase import ( + NotebookReader, NotebookWriter, restore_bytes, rejoin_lines, split_lines +) + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +class BytesEncoder(json.JSONEncoder): + """A JSON encoder that accepts b64 (and other *ascii*) bytestrings.""" + def default(self, obj): + if isinstance(obj, bytes): + return obj.decode('ascii') + return json.JSONEncoder.default(self, obj) + + +class JSONReader(NotebookReader): + + def reads(self, s, **kwargs): + nb = json.loads(s, **kwargs) + nb = self.to_notebook(nb, **kwargs) + return nb + + def to_notebook(self, d, **kwargs): + return restore_bytes(rejoin_lines(from_dict(d))) + + +class JSONWriter(NotebookWriter): + + def writes(self, nb, **kwargs): + kwargs['cls'] = BytesEncoder + kwargs['indent'] = 1 + kwargs['sort_keys'] = True + if kwargs.pop('split_lines', True): + nb = split_lines(copy.deepcopy(nb)) + return json.dumps(nb, **kwargs) + + +_reader = JSONReader() +_writer = JSONWriter() + +reads = _reader.reads +read = _reader.read +to_notebook = _reader.to_notebook +write = _writer.write +writes = _writer.writes + diff --git a/IPython/nbformat/v2/nbpy.py b/IPython/nbformat/v2/nbpy.py new file mode 100644 index 0000000..8d89fc0 --- /dev/null +++ b/IPython/nbformat/v2/nbpy.py @@ -0,0 +1,150 @@ +"""Read and write notebooks as regular .py files. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import re +from .rwbase import NotebookReader, NotebookWriter +from .nbbase import new_code_cell, new_text_cell, new_worksheet, new_notebook + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +_encoding_declaration_re = re.compile(r"^#.*coding[:=]\s*([-\w.]+)") + +class PyReaderError(Exception): + pass + + +class PyReader(NotebookReader): + + def reads(self, s, **kwargs): + return self.to_notebook(s,**kwargs) + + def to_notebook(self, s, **kwargs): + lines = s.splitlines() + cells = [] + cell_lines = [] + state = u'codecell' + for line in lines: + if line.startswith(u'# ') or _encoding_declaration_re.match(line): + pass + elif line.startswith(u'# '): + cell = self.new_cell(state, cell_lines) + if cell is not None: + cells.append(cell) + state = u'codecell' + cell_lines = [] + elif line.startswith(u'# '): + cell = self.new_cell(state, cell_lines) + if cell is not None: + cells.append(cell) + state = u'htmlcell' + cell_lines = [] + elif line.startswith(u'# '): + cell = self.new_cell(state, cell_lines) + if cell is not None: + cells.append(cell) + state = u'markdowncell' + cell_lines = [] + else: + cell_lines.append(line) + if cell_lines and state == u'codecell': + cell = self.new_cell(state, cell_lines) + if cell is not None: + cells.append(cell) + ws = new_worksheet(cells=cells) + nb = new_notebook(worksheets=[ws]) + return nb + + def new_cell(self, state, lines): + if state == u'codecell': + input = u'\n'.join(lines) + input = input.strip(u'\n') + if input: + return new_code_cell(input=input) + elif state == u'htmlcell': + text = self._remove_comments(lines) + if text: + return new_text_cell(u'html',source=text) + elif state == u'markdowncell': + text = self._remove_comments(lines) + if text: + return new_text_cell(u'markdown',source=text) + + def _remove_comments(self, lines): + new_lines = [] + for line in lines: + if line.startswith(u'#'): + new_lines.append(line[2:]) + else: + new_lines.append(line) + text = u'\n'.join(new_lines) + text = text.strip(u'\n') + return text + + def split_lines_into_blocks(self, lines): + if len(lines) == 1: + yield lines[0] + raise StopIteration() + import ast + source = '\n'.join(lines) + code = ast.parse(source) + starts = [x.lineno-1 for x in code.body] + for i in range(len(starts)-1): + yield '\n'.join(lines[starts[i]:starts[i+1]]).strip('\n') + yield '\n'.join(lines[starts[-1]:]).strip('\n') + + +class PyWriter(NotebookWriter): + + def writes(self, nb, **kwargs): + lines = [u'# -*- coding: utf-8 -*-'] + lines.extend([u'# 2','']) + for ws in nb.worksheets: + for cell in ws.cells: + if cell.cell_type == u'code': + input = cell.get(u'input') + if input is not None: + lines.extend([u'# ',u'']) + lines.extend(input.splitlines()) + lines.append(u'') + elif cell.cell_type == u'html': + input = cell.get(u'source') + if input is not None: + lines.extend([u'# ',u'']) + lines.extend([u'# ' + line for line in input.splitlines()]) + lines.append(u'') + elif cell.cell_type == u'markdown': + input = cell.get(u'source') + if input is not None: + lines.extend([u'# ',u'']) + lines.extend([u'# ' + line for line in input.splitlines()]) + lines.append(u'') + lines.append('') + return unicode('\n'.join(lines)) + + +_reader = PyReader() +_writer = PyWriter() + +reads = _reader.reads +read = _reader.read +to_notebook = _reader.to_notebook +write = _writer.write +writes = _writer.writes + diff --git a/IPython/nbformat/v2/nbxml.py b/IPython/nbformat/v2/nbxml.py new file mode 100644 index 0000000..e5f54f5 --- /dev/null +++ b/IPython/nbformat/v2/nbxml.py @@ -0,0 +1,188 @@ +"""Read and write notebook files as XML. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from base64 import encodestring, decodestring +import warnings +from xml.etree import ElementTree as ET + +from .rwbase import NotebookReader, NotebookWriter +from .nbbase import ( + new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output, + new_metadata +) + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +def indent(elem, level=0): + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + +def _get_text(e, tag): + sub_e = e.find(tag) + if sub_e is None: + return None + else: + return sub_e.text + + +def _set_text(nbnode, attr, parent, tag): + if attr in nbnode: + e = ET.SubElement(parent, tag) + e.text = nbnode[attr] + + +def _get_int(e, tag): + sub_e = e.find(tag) + if sub_e is None: + return None + else: + return int(sub_e.text) + + +def _set_int(nbnode, attr, parent, tag): + if attr in nbnode: + e = ET.SubElement(parent, tag) + e.text = unicode(nbnode[attr]) + + +def _get_bool(e, tag): + sub_e = e.find(tag) + if sub_e is None: + return None + else: + return bool(int(sub_e.text)) + + +def _set_bool(nbnode, attr, parent, tag): + if attr in nbnode: + e = ET.SubElement(parent, tag) + if nbnode[attr]: + e.text = u'1' + else: + e.text = u'0' + + +def _get_binary(e, tag): + sub_e = e.find(tag) + if sub_e is None: + return None + else: + return decodestring(sub_e.text) + + +def _set_binary(nbnode, attr, parent, tag): + if attr in nbnode: + e = ET.SubElement(parent, tag) + e.text = encodestring(nbnode[attr]) + + +class XMLReader(NotebookReader): + + def reads(self, s, **kwargs): + root = ET.fromstring(s) + return self.to_notebook(root, **kwargs) + + def to_notebook(self, root, **kwargs): + warnings.warn('The XML notebook format is no longer supported, ' + 'please convert your notebooks to JSON.', DeprecationWarning) + nbname = _get_text(root,u'name') + nbauthor = _get_text(root,u'author') + nbemail = _get_text(root,u'email') + nblicense = _get_text(root,u'license') + nbcreated = _get_text(root,u'created') + nbsaved = _get_text(root,u'saved') + + worksheets = [] + for ws_e in root.find(u'worksheets').getiterator(u'worksheet'): + wsname = _get_text(ws_e,u'name') + cells = [] + for cell_e in ws_e.find(u'cells').getiterator(): + if cell_e.tag == u'codecell': + input = _get_text(cell_e,u'input') + prompt_number = _get_int(cell_e,u'prompt_number') + collapsed = _get_bool(cell_e,u'collapsed') + language = _get_text(cell_e,u'language') + outputs = [] + for output_e in cell_e.find(u'outputs').getiterator(u'output'): + output_type = _get_text(output_e,u'output_type') + output_text = _get_text(output_e,u'text') + output_png = _get_binary(output_e,u'png') + output_jpeg = _get_binary(output_e,u'jpeg') + output_svg = _get_text(output_e,u'svg') + output_html = _get_text(output_e,u'html') + output_latex = _get_text(output_e,u'latex') + output_json = _get_text(output_e,u'json') + output_javascript = _get_text(output_e,u'javascript') + + out_prompt_number = _get_int(output_e,u'prompt_number') + etype = _get_text(output_e,u'etype') + evalue = _get_text(output_e,u'evalue') + traceback = [] + traceback_e = output_e.find(u'traceback') + if traceback_e is not None: + for frame_e in traceback_e.getiterator(u'frame'): + traceback.append(frame_e.text) + if len(traceback) == 0: + traceback = None + output = new_output(output_type=output_type,output_png=output_png, + output_text=output_text, output_svg=output_svg, + output_html=output_html, output_latex=output_latex, + output_json=output_json, output_javascript=output_javascript, + output_jpeg=output_jpeg, prompt_number=out_prompt_number, + etype=etype, evalue=evalue, traceback=traceback + ) + outputs.append(output) + cc = new_code_cell(input=input,prompt_number=prompt_number, + language=language,outputs=outputs,collapsed=collapsed) + cells.append(cc) + if cell_e.tag == u'htmlcell': + source = _get_text(cell_e,u'source') + rendered = _get_text(cell_e,u'rendered') + cells.append(new_text_cell(u'html', source=source, rendered=rendered)) + if cell_e.tag == u'markdowncell': + source = _get_text(cell_e,u'source') + rendered = _get_text(cell_e,u'rendered') + cells.append(new_text_cell(u'markdown', source=source, rendered=rendered)) + ws = new_worksheet(name=wsname,cells=cells) + worksheets.append(ws) + + md = new_metadata(name=nbname) + nb = new_notebook(metadata=md,worksheets=worksheets) + return nb + + +_reader = XMLReader() + +reads = _reader.reads +read = _reader.read +to_notebook = _reader.to_notebook + diff --git a/IPython/nbformat/v2/rwbase.py b/IPython/nbformat/v2/rwbase.py new file mode 100644 index 0000000..39d54b2 --- /dev/null +++ b/IPython/nbformat/v2/rwbase.py @@ -0,0 +1,165 @@ +"""Base classes and utilities for readers and writers. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from base64 import encodestring, decodestring +import pprint + +from IPython.utils.py3compat import str_to_bytes + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +def restore_bytes(nb): + """Restore bytes of image data from unicode-only formats. + + Base64 encoding is handled elsewhere. Bytes objects in the notebook are + always b64-encoded. We DO NOT encode/decode around file formats. + """ + for ws in nb.worksheets: + for cell in ws.cells: + if cell.cell_type == 'code': + for output in cell.outputs: + if 'png' in output: + output.png = str_to_bytes(output.png, 'ascii') + if 'jpeg' in output: + output.jpeg = str_to_bytes(output.jpeg, 'ascii') + return nb + +# output keys that are likely to have multiline values +_multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json'] + +def rejoin_lines(nb): + """rejoin multiline text into strings + + For reversing effects of ``split_lines(nb)``. + + This only rejoins lines that have been split, so if text objects were not split + they will pass through unchanged. + + Used when reading JSON files that may have been passed through split_lines. + """ + for ws in nb.worksheets: + for cell in ws.cells: + if cell.cell_type == 'code': + if 'input' in cell and isinstance(cell.input, list): + cell.input = u'\n'.join(cell.input) + for output in cell.outputs: + for key in _multiline_outputs: + item = output.get(key, None) + if isinstance(item, list): + output[key] = u'\n'.join(item) + else: # text cell + for key in ['source', 'rendered']: + item = cell.get(key, None) + if isinstance(item, list): + cell[key] = u'\n'.join(item) + return nb + + +def split_lines(nb): + """split likely multiline text into lists of strings + + For file output more friendly to line-based VCS. ``rejoin_lines(nb)`` will + reverse the effects of ``split_lines(nb)``. + + Used when writing JSON files. + """ + for ws in nb.worksheets: + for cell in ws.cells: + if cell.cell_type == 'code': + if 'input' in cell and isinstance(cell.input, basestring): + cell.input = cell.input.splitlines() + for output in cell.outputs: + for key in _multiline_outputs: + item = output.get(key, None) + if isinstance(item, basestring): + output[key] = item.splitlines() + else: # text cell + for key in ['source', 'rendered']: + item = cell.get(key, None) + if isinstance(item, basestring): + cell[key] = item.splitlines() + return nb + +# b64 encode/decode are never actually used, because all bytes objects in +# the notebook are already b64-encoded, and we don't need/want to double-encode + +def base64_decode(nb): + """Restore all bytes objects in the notebook from base64-encoded strings. + + Note: This is never used + """ + for ws in nb.worksheets: + for cell in ws.cells: + if cell.cell_type == 'code': + for output in cell.outputs: + if 'png' in output: + if isinstance(output.png, unicode): + output.png = output.png.encode('ascii') + output.png = decodestring(output.png) + if 'jpeg' in output: + if isinstance(output.jpeg, unicode): + output.jpeg = output.jpeg.encode('ascii') + output.jpeg = decodestring(output.jpeg) + return nb + + +def base64_encode(nb): + """Base64 encode all bytes objects in the notebook. + + These will be b64-encoded unicode strings + + Note: This is never used + """ + for ws in nb.worksheets: + for cell in ws.cells: + if cell.cell_type == 'code': + for output in cell.outputs: + if 'png' in output: + output.png = encodestring(output.png).decode('ascii') + if 'jpeg' in output: + output.jpeg = encodestring(output.jpeg).decode('ascii') + return nb + + +class NotebookReader(object): + """A class for reading notebooks.""" + + def reads(self, s, **kwargs): + """Read a notebook from a string.""" + raise NotImplementedError("loads must be implemented in a subclass") + + def read(self, fp, **kwargs): + """Read a notebook from a file like object""" + return self.read(fp.read(), **kwargs) + + +class NotebookWriter(object): + """A class for writing notebooks.""" + + def writes(self, nb, **kwargs): + """Write a notebook to a string.""" + raise NotImplementedError("loads must be implemented in a subclass") + + def write(self, nb, fp, **kwargs): + """Write a notebook to a file like object""" + return fp.write(self.writes(nb,**kwargs)) + + + diff --git a/IPython/nbformat/v2/tests/__init__.py b/IPython/nbformat/v2/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/nbformat/v2/tests/__init__.py diff --git a/IPython/nbformat/v2/tests/nbexamples.py b/IPython/nbformat/v2/tests/nbexamples.py new file mode 100644 index 0000000..3e83e8e --- /dev/null +++ b/IPython/nbformat/v2/tests/nbexamples.py @@ -0,0 +1,109 @@ +import os +from base64 import encodestring + +from ..nbbase import ( + NotebookNode, + new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output, + new_metadata, new_author +) + +# some random base64-encoded *bytes* +png = encodestring(os.urandom(5)) +jpeg = encodestring(os.urandom(6)) + +ws = new_worksheet(name='worksheet1') + +ws.cells.append(new_text_cell( + u'html', + source='Some NumPy Examples', + rendered='Some NumPy Examples' +)) + + +ws.cells.append(new_code_cell( + input='import numpy', + prompt_number=1, + collapsed=False +)) + +ws.cells.append(new_text_cell( + u'markdown', + source='A random array', + rendered='A random array' +)) + +ws.cells.append(new_code_cell( + input='a = numpy.random.rand(100)', + prompt_number=2, + collapsed=True +)) + +ws.cells.append(new_code_cell( + input='print a', + prompt_number=3, + collapsed=False, + outputs=[new_output( + output_type=u'pyout', + output_text=u'', + output_html=u'The HTML rep', + output_latex=u'$a$', + output_png=png, + output_jpeg=jpeg, + output_svg=u'', + output_json=u'json data', + output_javascript=u'var i=0;', + prompt_number=3 + ),new_output( + output_type=u'display_data', + output_text=u'', + output_html=u'The HTML rep', + output_latex=u'$a$', + output_png=png, + output_jpeg=jpeg, + output_svg=u'', + output_json=u'json data', + output_javascript=u'var i=0;' + ),new_output( + output_type=u'pyerr', + etype=u'NameError', + evalue=u'NameError was here', + traceback=[u'frame 0', u'frame 1', u'frame 2'] + )] +)) + +authors = [new_author(name='Bart Simpson',email='bsimpson@fox.com', + affiliation=u'Fox',url=u'http://www.fox.com')] +md = new_metadata(name=u'My Notebook',license=u'BSD',created=u'8601_goes_here', + modified=u'8601_goes_here',gistid=u'21341231',authors=authors) + +nb0 = new_notebook( + worksheets=[ws, new_worksheet(name='worksheet2')], + metadata=md +) + +nb0_py = """# -*- coding: utf-8 -*- +# 2 + +# + +# Some NumPy Examples + +# + +import numpy + +# + +# A random array + +# + +a = numpy.random.rand(100) + +# + +print a + +""" + + diff --git a/IPython/nbformat/v2/tests/test_json.py b/IPython/nbformat/v2/tests/test_json.py new file mode 100644 index 0000000..d6cc5d3 --- /dev/null +++ b/IPython/nbformat/v2/tests/test_json.py @@ -0,0 +1,34 @@ +import pprint +from unittest import TestCase + +from ..nbjson import reads, writes +from .nbexamples import nb0 + + +class TestJSON(TestCase): + + def test_roundtrip(self): + s = writes(nb0) +# print +# print pprint.pformat(nb0,indent=2) +# print +# print pprint.pformat(reads(s),indent=2) +# print +# print s + self.assertEquals(reads(s),nb0) + + def test_roundtrip_nosplit(self): + """Ensure that multiline blobs are still readable""" + # ensures that notebooks written prior to splitlines change + # are still readable. + s = writes(nb0, split_lines=False) + self.assertEquals(reads(s),nb0) + + def test_roundtrip_split(self): + """Ensure that splitting multiline blocks is safe""" + # This won't differ from test_roundtrip unless the default changes + s = writes(nb0, split_lines=True) + self.assertEquals(reads(s),nb0) + + + diff --git a/IPython/nbformat/v2/tests/test_nbbase.py b/IPython/nbformat/v2/tests/test_nbbase.py new file mode 100644 index 0000000..d711bcf --- /dev/null +++ b/IPython/nbformat/v2/tests/test_nbbase.py @@ -0,0 +1,113 @@ +from unittest import TestCase + +from ..nbbase import ( + NotebookNode, + new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output, + new_author, new_metadata +) + +class TestCell(TestCase): + + def test_empty_code_cell(self): + cc = new_code_cell() + self.assertEquals(cc.cell_type,u'code') + self.assertEquals(u'input' not in cc, True) + self.assertEquals(u'prompt_number' not in cc, True) + self.assertEquals(cc.outputs, []) + self.assertEquals(cc.collapsed, False) + + def test_code_cell(self): + cc = new_code_cell(input='a=10', prompt_number=0, collapsed=True) + cc.outputs = [new_output(output_type=u'pyout', + output_svg=u'foo',output_text=u'10',prompt_number=0)] + self.assertEquals(cc.input, u'a=10') + self.assertEquals(cc.prompt_number, 0) + self.assertEquals(cc.language, u'python') + self.assertEquals(cc.outputs[0].svg, u'foo') + self.assertEquals(cc.outputs[0].text, u'10') + self.assertEquals(cc.outputs[0].prompt_number, 0) + self.assertEquals(cc.collapsed, True) + + def test_pyerr(self): + o = new_output(output_type=u'pyerr', etype=u'NameError', + evalue=u'Name not found', traceback=[u'frame 0', u'frame 1', u'frame 2'] + ) + self.assertEquals(o.output_type, u'pyerr') + self.assertEquals(o.etype, u'NameError') + self.assertEquals(o.evalue, u'Name not found') + self.assertEquals(o.traceback, [u'frame 0', u'frame 1', u'frame 2']) + + def test_empty_html_cell(self): + tc = new_text_cell(u'html') + self.assertEquals(tc.cell_type, u'html') + self.assertEquals(u'source' not in tc, True) + self.assertEquals(u'rendered' not in tc, True) + + def test_html_cell(self): + tc = new_text_cell(u'html', 'hi', 'hi') + self.assertEquals(tc.source, u'hi') + self.assertEquals(tc.rendered, u'hi') + + def test_empty_markdown_cell(self): + tc = new_text_cell(u'markdown') + self.assertEquals(tc.cell_type, u'markdown') + self.assertEquals(u'source' not in tc, True) + self.assertEquals(u'rendered' not in tc, True) + + def test_markdown_cell(self): + tc = new_text_cell(u'markdown', 'hi', 'hi') + self.assertEquals(tc.source, u'hi') + self.assertEquals(tc.rendered, u'hi') + + +class TestWorksheet(TestCase): + + def test_empty_worksheet(self): + ws = new_worksheet() + self.assertEquals(ws.cells,[]) + self.assertEquals(u'name' not in ws, True) + + def test_worksheet(self): + cells = [new_code_cell(), new_text_cell(u'html')] + ws = new_worksheet(cells=cells,name=u'foo') + self.assertEquals(ws.cells,cells) + self.assertEquals(ws.name,u'foo') + +class TestNotebook(TestCase): + + def test_empty_notebook(self): + nb = new_notebook() + self.assertEquals(nb.worksheets, []) + self.assertEquals(nb.metadata, NotebookNode()) + self.assertEquals(nb.nbformat,2) + + def test_notebook(self): + worksheets = [new_worksheet(),new_worksheet()] + metadata = new_metadata(name=u'foo') + nb = new_notebook(metadata=metadata,worksheets=worksheets) + self.assertEquals(nb.metadata.name,u'foo') + self.assertEquals(nb.worksheets,worksheets) + self.assertEquals(nb.nbformat,2) + +class TestMetadata(TestCase): + + def test_empty_metadata(self): + md = new_metadata() + self.assertEquals(u'name' not in md, True) + self.assertEquals(u'authors' not in md, True) + self.assertEquals(u'license' not in md, True) + self.assertEquals(u'saved' not in md, True) + self.assertEquals(u'modified' not in md, True) + self.assertEquals(u'gistid' not in md, True) + + def test_metadata(self): + authors = [new_author(name='Bart Simpson',email='bsimpson@fox.com')] + md = new_metadata(name=u'foo',license=u'BSD',created=u'today', + modified=u'now',gistid=u'21341231',authors=authors) + self.assertEquals(md.name, u'foo') + self.assertEquals(md.license, u'BSD') + self.assertEquals(md.created, u'today') + self.assertEquals(md.modified, u'now') + self.assertEquals(md.gistid, u'21341231') + self.assertEquals(md.authors, authors) + diff --git a/IPython/nbformat/v2/tests/test_nbpy.py b/IPython/nbformat/v2/tests/test_nbpy.py new file mode 100644 index 0000000..4c12c4e --- /dev/null +++ b/IPython/nbformat/v2/tests/test_nbpy.py @@ -0,0 +1,17 @@ +from unittest import TestCase + +from ..nbbase import ( + NotebookNode, + new_code_cell, new_text_cell, new_worksheet, new_notebook +) + +from ..nbpy import reads, writes +from .nbexamples import nb0, nb0_py + + +class TestPy(TestCase): + + def test_write(self): + s = writes(nb0) + self.assertEquals(s,nb0_py) +