From 0251893c243203bee3d0d49e24e4b3c87b5ae595 2011-07-29 22:15:33
From: Brian E. Granger <ellisonbg@gmail.com>
Date: 2011-07-29 22:15:33
Subject: [PATCH] Full versioning added to nbformat.

---

diff --git a/IPython/nbformat/current.py b/IPython/nbformat/current.py
new file mode 100644
index 0000000..f857124
--- /dev/null
+++ b/IPython/nbformat/current.py
@@ -0,0 +1,190 @@
+import json
+from xml.etree import ElementTree as ET
+import re
+
+from IPython.nbformat import v2
+from IPython.nbformat import v1
+
+
+current_nbformat = 2
+
+
+class NBFormatError(Exception):
+    pass
+
+
+def parse_json(s, **kwargs):
+    """Parse a string into a (nbformat, dict) tuple."""
+    d = json.loads(s, **kwargs)
+    nbformat = d.get('nbformat',1)
+    return nbformat, d
+
+
+def parse_xml(s, **kwargs):
+    """Parse a string into a (nbformat, etree) tuple."""
+    root = ET.fromstring(s)
+    nbformat_e = root.find('nbformat')
+    if nbformat_e is not None:
+        nbformat = int(nbformat_e.text)
+    else:
+        raise NBFormatError('No nbformat version found')
+    return nbformat, root
+
+
+def parse_py(s, **kwargs):
+    """Parse a string into a (nbformat, string) tuple."""
+    pattern = r'# <nbformat>(?P<nbformat>\d+)</nbformat>'
+    m = re.search(pattern,s)
+    if m is not None:
+        nbformat = int(m.group('nbformat'))
+    else:
+        raise NBFormatError('No nbformat version found')
+    return nbformat, s
+
+
+def reads_json(s, **kwargs):
+    """Read a JSON notebook from a string and return the NotebookNode object."""
+    nbformat, d = parse_json(s, **kwargs)
+    if nbformat == 1:
+        nb = v1.to_notebook_json(d, **kwargs)
+        nb = v2.convert_to_this_nbformat(nb, orig_version=1)
+    elif nbformat == 2:
+        nb = v2.to_notebook_json(d, **kwargs)
+    else:
+        raise NBFormatError('Unsupported JSON nbformat version: %i' % nbformat)
+    return nb
+
+
+def writes_json(nb, **kwargs):
+    return v2.writes_json(nb, **kwargs)
+
+
+def reads_xml(s, **kwargs):
+    """Read an XML notebook from a string and return the NotebookNode object."""
+    nbformat, root = parse_xml(s, **kwargs)
+    if nbformat == 2:
+        nb = v2.to_notebook_xml(root, **kwargs)
+    else:
+        raise NBFormatError('Unsupported XML nbformat version: %i' % nbformat)
+    return nb
+
+
+def writes_xml(nb, **kwargs):
+    return v2.writes_xml(nb, **kwargs)
+
+
+def reads_py(s, **kwargs):
+    """Read a .py notebook from a string and return the NotebookNode object."""
+    nbformat, s = parse_py(s, **kwargs)
+    if nbformat == 2:
+        nb = v2.to_notebook_py(s, **kwargs)
+    else:
+        raise NBFormatError('Unsupported PY nbformat version: %i' % nbformat)
+    return nb
+
+
+def writes_py(nb, **kwargs):
+    return v2.writes_py(nb, **kwargs)
+
+
+# High level API
+
+
+def reads(s, format, **kwargs):
+    """Read a notebook from a string and return the NotebookNode object.
+
+    This function properly handles notebooks of any version. The notebook
+    returned will always be in the current version's format.
+
+    Parameters
+    ----------
+    s : str
+        The raw string to read the notebook from.
+    format : ('xml','json','py')
+        The format that the string is in.
+
+    Returns
+    -------
+    nb : NotebookNode
+        The notebook that was read.
+    """
+    if format == 'xml':
+        return reads_xml(s, **kwargs)
+    elif format == 'json':
+        return reads_json(s, **kwargs)
+    elif format == 'py':
+        return reads_py(s, **kwargs)
+    else:
+        raise NBFormatError('Unsupported format: %s' % format)
+
+
+def writes(nb, format, **kwargs):
+    """Write a notebook to a string in a given format in the current nbformat version.
+
+    This function always writes the notebook in the current nbformat version.
+
+    Parameters
+    ----------
+    nb : NotebookNode
+        The notebook to write.
+    format : ('xml','json','py')
+        The format to write the notebook in.
+
+    Returns
+    -------
+    s : str
+        The notebook string.
+    """
+    if format == 'xml':
+        return writes_xml(nb, **kwargs)
+    elif format == 'json':
+        return writes_json(nb, **kwargs)
+    elif format == 'py':
+        return writes_py(nb, **kwargs)
+    else:
+        raise NBFormatError('Unsupported format: %s' % format)
+
+
+def read(fp, format, **kwargs):
+    """Read a notebook from a file and return the NotebookNode object.
+
+    This function properly handles notebooks of any version. The notebook
+    returned will always be in the current version's format.
+
+    Parameters
+    ----------
+    fp : file
+        Any file-like object with a read method.
+    format : ('xml','json','py')
+        The format that the string is in.
+
+    Returns
+    -------
+    nb : NotebookNode
+        The notebook that was read.
+    """
+    return reads(fp.read(), format, **kwargs)
+
+
+def write(nb, fp, format, **kwargs):
+    """Write a notebook to a file in a given format in the current nbformat version.
+
+    This function always writes the notebook in the current nbformat version.
+
+    Parameters
+    ----------
+    nb : NotebookNode
+        The notebook to write.
+    fp : file
+        Any file-like object with a write method.
+    format : ('xml','json','py')
+        The format to write the notebook in.
+
+    Returns
+    -------
+    s : str
+        The notebook string.
+    """
+    return fp.write(writes(nb, format, **kwargs))
+
+
diff --git a/IPython/nbformat/v1/__init__.py b/IPython/nbformat/v1/__init__.py
new file mode 100644
index 0000000..767e848
--- /dev/null
+++ b/IPython/nbformat/v1/__init__.py
@@ -0,0 +1,12 @@
+
+from .nbbase import (
+    NotebookNode,
+    new_code_cell, new_text_cell, new_notebook
+)
+
+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 .convert import convert_to_this_nbformat
+
diff --git a/IPython/nbformat/v1/convert.py b/IPython/nbformat/v1/convert.py
new file mode 100644
index 0000000..ac9bc81
--- /dev/null
+++ b/IPython/nbformat/v1/convert.py
@@ -0,0 +1,5 @@
+
+
+def convert_to_this_nbformat(nb, orig_version=None):
+    raise ValueError('Cannot convert to v1 notebook format')
+
diff --git a/IPython/nbformat/v1/nbbase.py b/IPython/nbformat/v1/nbbase.py
new file mode 100644
index 0000000..609619b
--- /dev/null
+++ b/IPython/nbformat/v1/nbbase.py
@@ -0,0 +1,53 @@
+"""The basic dict based notebook format."""
+
+import pprint
+import uuid
+
+from IPython.utils.ipstruct import Struct
+
+
+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_code_cell(code=None, prompt_number=None):
+    """Create a new code cell with input and output"""
+    cell = NotebookNode()
+    cell.cell_type = u'code'
+    if code is not None:
+        cell.code = unicode(code)
+    if prompt_number is not None:
+        cell.prompt_number = int(prompt_number)
+    return cell
+
+
+def new_text_cell(text=None):
+    """Create a new text cell."""
+    cell = NotebookNode()
+    if text is not None:
+        cell.text = unicode(text)
+    cell.cell_type = u'text'
+    return cell
+
+
+def new_notebook(cells=None):
+    """Create a notebook by name, id and a list of worksheets."""
+    nb = NotebookNode()
+    if cells is not None:
+        nb.cells = cells
+    else:
+        nb.cells = []
+    return nb
+
diff --git a/IPython/nbformat/v1/nbjson.py b/IPython/nbformat/v1/nbjson.py
new file mode 100644
index 0000000..d2c6013
--- /dev/null
+++ b/IPython/nbformat/v1/nbjson.py
@@ -0,0 +1,35 @@
+"""Read and write notebooks in JSON format."""
+
+from base64 import encodestring
+from .rwbase import NotebookReader, NotebookWriter
+from .nbbase import from_dict
+import json
+
+
+class JSONReader(NotebookReader):
+
+    def reads(self, s, **kwargs):
+        nb = json.loads(s, **kwargs)
+        return self.to_notebook(nb, **kwargs)
+
+    def to_notebook(self, d, **kwargs):
+        """Convert from a raw JSON dict to a nested NotebookNode structure."""
+        return from_dict(d)
+
+
+class JSONWriter(NotebookWriter):
+
+    def writes(self, nb, **kwargs):
+        kwargs['indent'] = 4
+        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/v1/rwbase.py b/IPython/nbformat/v1/rwbase.py
new file mode 100644
index 0000000..d0a1b1b
--- /dev/null
+++ b/IPython/nbformat/v1/rwbase.py
@@ -0,0 +1,26 @@
+from base64 import encodestring, decodestring
+
+
+class NotebookReader(object):
+
+    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.reads(fp.read(), **kwargs)
+
+
+class NotebookWriter(object):
+
+    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/tests/__init__.py b/IPython/nbformat/v1/tests/__init__.py
similarity index 100%
rename from IPython/nbformat/tests/__init__.py
rename to IPython/nbformat/v1/tests/__init__.py
diff --git a/IPython/nbformat/v1/tests/nbexamples.py b/IPython/nbformat/v1/tests/nbexamples.py
new file mode 100644
index 0000000..e07ac48
--- /dev/null
+++ b/IPython/nbformat/v1/tests/nbexamples.py
@@ -0,0 +1,29 @@
+from ..nbbase import (
+    NotebookNode,
+    new_code_cell, new_text_cell, new_notebook
+)
+
+
+
+nb0 = new_notebook()
+
+nb0.cells.append(new_text_cell(
+    text='Some NumPy Examples'
+))
+
+
+nb0.cells.append(new_code_cell(
+    code='import numpy',
+    prompt_number=1
+))
+
+nb0.cells.append(new_code_cell(
+    code='a = numpy.random.rand(100)',
+    prompt_number=2
+))
+
+nb0.cells.append(new_code_cell(
+    code='print a',
+    prompt_number=3
+))
+
diff --git a/IPython/nbformat/tests/test_json.py b/IPython/nbformat/v1/tests/test_json.py
similarity index 71%
rename from IPython/nbformat/tests/test_json.py
rename to IPython/nbformat/v1/tests/test_json.py
index 7b7430e..09d1c4e 100644
--- a/IPython/nbformat/tests/test_json.py
+++ b/IPython/nbformat/v1/tests/test_json.py
@@ -1,7 +1,7 @@
 from unittest import TestCase
 
-from IPython.nbformat.nbjson import reads, writes
-from IPython.nbformat.tests.nbexamples import nb0
+from ..nbjson import reads, writes
+from .nbexamples import nb0
 
 
 class TestJSON(TestCase):
diff --git a/IPython/nbformat/v1/tests/test_nbbase.py b/IPython/nbformat/v1/tests/test_nbbase.py
new file mode 100644
index 0000000..19fe6d1
--- /dev/null
+++ b/IPython/nbformat/v1/tests/test_nbbase.py
@@ -0,0 +1,41 @@
+from unittest import TestCase
+
+from ..nbbase import (
+    NotebookNode,
+    new_code_cell, new_text_cell, new_notebook
+)
+
+class TestCell(TestCase):
+
+    def test_empty_code_cell(self):
+        cc = new_code_cell()
+        self.assertEquals(cc.cell_type,'code')
+        self.assertEquals('code' not in cc, True)
+        self.assertEquals('prompt_number' not in cc, True)
+
+    def test_code_cell(self):
+        cc = new_code_cell(code='a=10', prompt_number=0)
+        self.assertEquals(cc.code, u'a=10')
+        self.assertEquals(cc.prompt_number, 0)
+
+    def test_empty_text_cell(self):
+        tc = new_text_cell()
+        self.assertEquals(tc.cell_type, 'text')
+        self.assertEquals('text' not in tc, True)
+
+    def test_text_cell(self):
+        tc = new_text_cell('hi')
+        self.assertEquals(tc.text, u'hi')
+
+
+class TestNotebook(TestCase):
+
+    def test_empty_notebook(self):
+        nb = new_notebook()
+        self.assertEquals(nb.cells, [])
+
+    def test_notebooke(self):
+        cells = [new_code_cell(),new_text_cell()]
+        nb = new_notebook(cells=cells)
+        self.assertEquals(nb.cells,cells)
+
diff --git a/IPython/nbformat/v2/__init__.py b/IPython/nbformat/v2/__init__.py
new file mode 100644
index 0000000..4f71ce5
--- /dev/null
+++ b/IPython/nbformat/v2/__init__.py
@@ -0,0 +1,21 @@
+
+from .nbbase import (
+    NotebookNode,
+    new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet
+)
+
+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, writes as writes_xml
+from .nbxml import reads as read_xml, writes as write_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
+
+
diff --git a/IPython/nbformat/v2/convert.py b/IPython/nbformat/v2/convert.py
new file mode 100644
index 0000000..7ab5908
--- /dev/null
+++ b/IPython/nbformat/v2/convert.py
@@ -0,0 +1,20 @@
+from .nbbase import (
+    new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
+)
+
+def convert_to_this_nbformat(nb, orig_version=1):
+    if orig_version == 1:
+        newnb = new_notebook()
+        ws = new_worksheet()
+        for cell in nb.cells:
+            if cell.cell_type == 'code':
+                newcell = new_code_cell(input=cell.get('code'),prompt_number=cell.get('prompt_number'))
+            elif cell.cell_type == 'text':
+                newcell = new_text_cell(text=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/nbbase.py b/IPython/nbformat/v2/nbbase.py
similarity index 93%
rename from IPython/nbformat/nbbase.py
rename to IPython/nbformat/v2/nbbase.py
index 3696dc5..df61687 100644
--- a/IPython/nbformat/nbbase.py
+++ b/IPython/nbformat/v2/nbbase.py
@@ -10,6 +10,18 @@ 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):
@@ -76,6 +88,7 @@ def new_worksheet(name=None, cells=None):
 def new_notebook(name=None, id=None, worksheets=None):
     """Create a notebook by name, id and a list of worksheets."""
     nb = NotebookNode()
+    nb.nbformat = 2
     if name is not None:
         nb.name = unicode(name)
     if id is None:
diff --git a/IPython/nbformat/nbjson.py b/IPython/nbformat/v2/nbjson.py
similarity index 88%
rename from IPython/nbformat/nbjson.py
rename to IPython/nbformat/v2/nbjson.py
index e98578d..14e8c92 100644
--- a/IPython/nbformat/nbjson.py
+++ b/IPython/nbformat/v2/nbjson.py
@@ -1,6 +1,7 @@
 """Read and write notebooks in JSON format."""
 
 from base64 import encodestring
+from .nbbase import from_dict
 from .rwbase import NotebookReader, NotebookWriter, base64_decode
 import json
 
@@ -16,9 +17,12 @@ class JSONReader(NotebookReader):
 
     def reads(self, s, **kwargs):
         nb = json.loads(s, **kwargs)
-        nb = base64_decode(nb)
+        nb = self.to_notebook(nb, **kwargs)
         return nb
 
+    def to_notebook(self, d, **kwargs):
+        return base64_decode(from_dict(d))
+
 
 class JSONWriter(NotebookWriter):
 
@@ -33,6 +37,7 @@ _writer = JSONWriter()
 
 reads = _reader.reads
 read = _reader.read
+to_notebook = _reader.to_notebook
 write = _writer.write
 writes = _writer.writes
 
diff --git a/IPython/nbformat/nbpy.py b/IPython/nbformat/v2/nbpy.py
similarity index 90%
rename from IPython/nbformat/nbpy.py
rename to IPython/nbformat/v2/nbpy.py
index bd07820..1c51cf0 100644
--- a/IPython/nbformat/nbpy.py
+++ b/IPython/nbformat/v2/nbpy.py
@@ -7,16 +7,20 @@ from .nbbase import new_code_cell, new_worksheet, new_notebook
 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 = []
         for line in lines:
             if line.startswith(u'# <codecell>'):
+                cell_lines = []
+            if line.startswith(u'# </codecell>'):
                 code = u'\n'.join(cell_lines)
                 code = code.strip(u'\n')
                 if code:
                     cells.append(new_code_cell(input=code))
-                    cell_lines = []
             else:
                 cell_lines.append(line)
         ws = new_worksheet(cells=cells)
@@ -28,13 +32,15 @@ class PyWriter(NotebookWriter):
 
     def writes(self, nb, **kwargs):
         lines = []
+        lines.extend(['# <nbformat>2</nbformat>',''])
         for ws in nb.worksheets:
             for cell in ws.cells:
                 if cell.cell_type == 'code':
                     input = cell.input
                     lines.extend([u'# <codecell>',u''])
                     lines.extend(input.splitlines())
-                    lines.append(u'')
+                    lines.extend([u'',u'# </codecell>'])
+        lines.append('')
         return unicode('\n'.join(lines))
 
 
@@ -43,5 +49,7 @@ _writer = PyWriter()
 
 reads = _reader.reads
 read = _reader.read
+to_notebook = _reader.to_notebook
 write = _writer.write
 writes = _writer.writes
+
diff --git a/IPython/nbformat/nbxml.py b/IPython/nbformat/v2/nbxml.py
similarity index 90%
rename from IPython/nbformat/nbxml.py
rename to IPython/nbformat/v2/nbxml.py
index 2d97864..2d7d7b4 100644
--- a/IPython/nbformat/nbxml.py
+++ b/IPython/nbformat/v2/nbxml.py
@@ -1,5 +1,6 @@
 """Read and write notebook files as XML."""
 
+from base64 import encodestring, decodestring
 from xml.etree import ElementTree as ET
 
 from .rwbase import NotebookReader, NotebookWriter
@@ -51,11 +52,27 @@ def _set_int(nbnode, attr, parent, tag):
         e.text = unicode(nbnode[attr])
 
 
+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):
         nbname = _get_text(root,'name')
         nbid = _get_text(root,'id')
         
@@ -72,7 +89,7 @@ class XMLReader(NotebookReader):
                     for output_e in cell_e.find('outputs').getiterator('output'):
                         output_type = _get_text(output_e,'output_type')
                         output_text = _get_text(output_e,'text')
-                        output_png = _get_text(output_e,'png')
+                        output_png = _get_binary(output_e,'png')
                         output_svg = _get_text(output_e,'svg')
                         output_html = _get_text(output_e,'html')
                         output_latex = _get_text(output_e,'latex')
@@ -103,6 +120,7 @@ class XMLWriter(NotebookWriter):
         nb_e = ET.Element('notebook')
         _set_text(nb,'name',nb_e,'name')
         _set_text(nb,'id',nb_e,'id')
+        _set_int(nb,'nbformat',nb_e,'nbformat')
         wss_e = ET.SubElement(nb_e,'worksheets')
         for ws in nb.worksheets:
             ws_e = ET.SubElement(wss_e, 'worksheet')
@@ -120,7 +138,7 @@ class XMLWriter(NotebookWriter):
                         output_e = ET.SubElement(outputs_e, 'output')
                         _set_text(output,'output_type',output_e,'output_type')
                         _set_text(output,'text',output_e,'text')
-                        _set_text(output,'png',output_e,'png')
+                        _set_binary(output,'png',output_e,'png')
                         _set_text(output,'html',output_e,'html')
                         _set_text(output,'svg',output_e,'svg')
                         _set_text(output,'latex',output_e,'latex')
@@ -141,5 +159,7 @@ _writer = XMLWriter()
 
 reads = _reader.reads
 read = _reader.read
+to_notebook = _reader.to_notebook
 write = _writer.write
 writes = _writer.writes
+
diff --git a/IPython/nbformat/rwbase.py b/IPython/nbformat/v2/rwbase.py
similarity index 60%
rename from IPython/nbformat/rwbase.py
rename to IPython/nbformat/v2/rwbase.py
index 17747c9..0c5432e 100644
--- a/IPython/nbformat/rwbase.py
+++ b/IPython/nbformat/v2/rwbase.py
@@ -1,23 +1,23 @@
 from base64 import encodestring, decodestring
-
+import pprint
 
 def base64_decode(nb):
     """Base64 encode all bytes objects in the notebook."""
-    for ws in nb['worksheets']:
-        for cell in ws['cells']:
-            if cell['cell_type'] == 'code':
-                if cell.get('image/png',''):
-                    cell['image/png'] = bytes(decodestring(cell['image/png']))
+    for ws in nb.worksheets:
+        for cell in ws.cells:
+            if cell.cell_type == 'code':
+                if 'png' in cell:
+                    cell.png = bytes(decodestring(cell.png))
     return nb
 
 
 def base64_encode(nb):
     """Base64 decode all binary objects in the notebook."""
-    for ws in nb['worksheets']:
-        for cell in ws['cells']:
-            if cell['cell_type'] == 'code':
-                if cell.get('image/png',''):
-                    cell['image/png'] = unicode(encodestring(cell['image/png']))
+    for ws in nb.worksheets:
+        for cell in ws.cells:
+            if cell.cell_type == 'code':
+                if 'png' in cell:
+                    cell.png = unicode(encodestring(cell.png))
     return nb
 
 
@@ -29,7 +29,7 @@ class NotebookReader(object):
 
     def read(self, fp, **kwargs):
         """Read a notebook from a file like object"""
-        return self.loads(fp.read(), **kwargs)
+        return self.read(fp.read(), **kwargs)
 
 
 class NotebookWriter(object):
@@ -40,7 +40,7 @@ class NotebookWriter(object):
 
     def write(self, nb, fp, **kwargs):
         """Write a notebook to a file like object"""
-        return fp.write(self.dumps(nb,**kwargs))
+        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/tests/nbexamples.py b/IPython/nbformat/v2/tests/nbexamples.py
similarity index 92%
rename from IPython/nbformat/tests/nbexamples.py
rename to IPython/nbformat/v2/tests/nbexamples.py
index bd6206e..57a3cbb 100644
--- a/IPython/nbformat/tests/nbexamples.py
+++ b/IPython/nbformat/v2/tests/nbexamples.py
@@ -1,4 +1,4 @@
-from IPython.nbformat.nbbase import (
+from ..nbbase import (
     NotebookNode,
     new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
 )
@@ -51,17 +51,23 @@ nb0 = new_notebook(
     worksheets=[ws, new_worksheet(name='worksheet2')]
 )
 
-nb0_py = """# <codecell>
+nb0_py = """# <nbformat>2</nbformat>
+
+# <codecell>
 
 import numpy
 
+# </codecell>
 # <codecell>
 
 a = numpy.random.rand(100)
 
+# </codecell>
 # <codecell>
 
 print a
+
+# </codecell>
 """
 
 
diff --git a/IPython/nbformat/v2/tests/test_json.py b/IPython/nbformat/v2/tests/test_json.py
new file mode 100644
index 0000000..d9a582a
--- /dev/null
+++ b/IPython/nbformat/v2/tests/test_json.py
@@ -0,0 +1,15 @@
+
+from unittest import TestCase
+
+from ..nbjson import reads, writes
+from .nbexamples import nb0
+
+
+class TestJSON(TestCase):
+
+    def test_roundtrip(self):
+        s = writes(nb0)
+        self.assertEquals(reads(s),nb0)
+
+
+
diff --git a/IPython/nbformat/tests/test_nbbase.py b/IPython/nbformat/v2/tests/test_nbbase.py
similarity index 93%
rename from IPython/nbformat/tests/test_nbbase.py
rename to IPython/nbformat/v2/tests/test_nbbase.py
index 875f0d2..06607f8 100644
--- a/IPython/nbformat/tests/test_nbbase.py
+++ b/IPython/nbformat/v2/tests/test_nbbase.py
@@ -1,6 +1,6 @@
 from unittest import TestCase
 
-from IPython.nbformat.nbbase import (
+from ..nbbase import (
     NotebookNode,
     new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
 )
@@ -53,10 +53,12 @@ class TestNotebook(TestCase):
         self.assertEquals('id' in nb, True)
         self.assertEquals(nb.worksheets, [])
         self.assertEquals('name' not in nb, True)
+        self.assertEquals(nb.nbformat,2)
 
-    def test_notebooke(self):
+    def test_notebook(self):
         worksheets = [new_worksheet(),new_worksheet()]
         nb = new_notebook(name='foo',worksheets=worksheets)
         self.assertEquals(nb.name,u'foo')
         self.assertEquals(nb.worksheets,worksheets)
+        self.assertEquals(nb.nbformat,2)
 
diff --git a/IPython/nbformat/tests/test_nbpy.py b/IPython/nbformat/v2/tests/test_nbpy.py
similarity index 72%
rename from IPython/nbformat/tests/test_nbpy.py
rename to IPython/nbformat/v2/tests/test_nbpy.py
index 9fa685b..a51ac42 100644
--- a/IPython/nbformat/tests/test_nbpy.py
+++ b/IPython/nbformat/v2/tests/test_nbpy.py
@@ -1,12 +1,12 @@
 from unittest import TestCase
 
-from IPython.nbformat.nbbase import (
+from ..nbbase import (
     NotebookNode,
     new_code_cell, new_text_cell, new_worksheet, new_notebook
 )
 
-from IPython.nbformat.nbpy import reads, writes
-from IPython.nbformat.tests.nbexamples import nb0, nb0_py
+from ..nbpy import reads, writes
+from .nbexamples import nb0, nb0_py
 
 
 class TestPy(TestCase):
diff --git a/IPython/nbformat/tests/test_xml.py b/IPython/nbformat/v2/tests/test_xml.py
similarity index 85%
rename from IPython/nbformat/tests/test_xml.py
rename to IPython/nbformat/v2/tests/test_xml.py
index 0a1a660..45cc2e1 100644
--- a/IPython/nbformat/tests/test_xml.py
+++ b/IPython/nbformat/v2/tests/test_xml.py
@@ -1,7 +1,7 @@
 from unittest import TestCase
 
-from IPython.nbformat.nbxml import reads, writes
-from IPython.nbformat.tests.nbexamples import nb0
+from ..nbxml import reads, writes
+from .nbexamples import nb0
 import pprint
 
 class TestXML(TestCase):