<NME> result2.ipynb
<BEF> ADDFILE
<MSG> introduce papermill_io object for easier customization of io

<DFF> @@ -0,0 +1,119 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {
+    "tags": [
+     "parameters"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "# Parameters\n",
+    "foo = 2\n",
+    "bar = \"world\"\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {
+    "papermill": {},
+    "tags": []
+   },
+   "outputs": [
+    {
+     "data": {
+      "application/papermill.record+json": {
+       "number": 1
+      }
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/papermill.record+json": {
+       "list": [
+        1,
+        2,
+        3
+       ]
+      }
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/papermill.record+json": {
+       "dict": {
+        "a": 1,
+        "b": 2
+       }
+      }
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "'Hello World!'"
+      ]
+     },
+     "metadata": {
+      "papermill": {
+       "name": "output"
+      }
+     },
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "import papermill as pm\n",
+    "\n",
+    "pm.record(\"number\", 1)\n",
+    "pm.record(\"list\", [1,2,3])\n",
+    "pm.record(\"dict\", dict(a=1, b=2))\n",
+    "\n",
+    "pm.display(\"output\", \"Hello World!\")"
+   ]
+  }
+ ],
+ "metadata": {
+  "celltoolbar": "Tags",
+  "hide_input": false,
+  "kernelspec": {
+   "display_name": "Python 2",
+   "language": "python",
+   "name": "python2"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 2.0
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython2",
+   "version": "2.7.12"
+  },
+  "papermill": {
+   "environment_variables": {},
+   "metrics": {
+    "duration": 2.2486860752105713
+   },
+   "parameters": {
+    "bar": "world",
+    "foo": 2
+   },
+   "version": "0.4+2.ge10f94c.dirty"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}

{'commit': 'diff --git a/papermill/iorw.py b/papermill/iorw.py\nindex 0ec6f36..a702cbc 100644\n--- a/papermill/iorw.py\n+++ b/papermill/iorw.py\n@@ -7,6 +7,54 @@ import yaml\n from papermill import __version__\n \n \n+class PapermillIO(object):\n+\n+    @staticmethod\n+    def read(path):\n+        with io.open(path, \'r\') as f:\n+            return f.read()\n+\n+    @staticmethod\n+    def listdir(path):\n+        return [os.path.join(path, fn) for fn in os.listdir(path)]\n+\n+    @staticmethod\n+    def write(buf, path):\n+        with io.open(path, \'w\') as f:\n+            f.write(buf)\n+\n+    def register(self, name, func):\n+        if hasattr(self, name):\n+            setattr(self, name, func)\n+        else:\n+            raise ValueError("No I/O method called \'%s\'." % name)\n+\n+\n+papermill_io = PapermillIO()\n+\n+\n+def register_io(name):\n+    """Decorator for registering custom io functions for papermill."""\n+    def wrapper(func):\n+        papermill_io.register(name, func)\n+        return func\n+    return wrapper\n+\n+\n+def read_yaml_file(path):\n+    """Reads a YAML file from the location specified at \'path\'."""\n+    return yaml.load(papermill_io.read(path))\n+\n+\n+def write_ipynb(nb, path):\n+    """Saves a notebook object to the specified path.\n+    Args:\n+        nb_node (nbformat.NotebookNode): Notebook object to save.\n+        notebook_path (str): Path to save the notebook object to.\n+    """\n+    papermill_io.write(nbformat.writes(nb), path)\n+\n+\n def load_notebook_node(notebook_path):\n     """Returns a notebook object with papermill metadata loaded from the specified path.\n \n@@ -17,7 +65,7 @@ def load_notebook_node(notebook_path):\n         nbformat.NotebookNode\n \n     """\n-    nb = read_ipynb(notebook_path)\n+    nb = nbformat.reads(papermill_io.read(notebook_path), as_version=4)\n \n     if not hasattr(nb.metadata, \'papermill\'):\n         nb.metadata[\'papermill\'] = {\n@@ -36,30 +84,6 @@ def load_notebook_node(notebook_path):\n     return nb\n \n \n-def read_ipynb(path):\n-    """Default read functionality."""\n-    with io.open(path, \'r\') as infile:\n-        return nbformat.read(infile, as_version=4)\n-\n-\n-def write_ipynb(nb_node, notebook_path):\n-    """Saves a notebook object to the specified path.\n-\n-    Args:\n-        nb_node (nbformat.NotebookNode): Notebook object to save.\n-        notebook_path (str): Path to save the notebook object to.\n-\n-    """\n-    with io.open(notebook_path, \'w\') as outfile:\n-        nbformat.write(nb_node, outfile)\n-\n-\n-def read_yaml_file(path):\n-    """Reads a YAML file from the location specified at \'path\'."""\n-    with io.open(path, \'r\') as infile:\n-        return yaml.load(infile)\n-\n-\n def list_notebook_files(path):\n     """Returns a list of all the notebook files in a directory."""\n-    return [os.path.join(path, fn) for fn in os.listdir(path) if fn.endswith(\'.ipynb\')]\n+    return [p for p in papermill_io.listdir(path) if p.endswith(\'.ipynb\')]\ndiff --git a/tests/notebooks/broken.ipynb b/tests/notebooks/broken.ipynb\nnew file mode 100644\nindex 0000000..ce3a766\n--- /dev/null\n+++ b/tests/notebooks/broken.ipynb\n@@ -0,0 +1,97 @@\n+{\n+ "cells": [\n+  {\n+   "cell_type": "code",\n+   "execution_count": null,\n+   "metadata": {\n+    "papermill": {},\n+    "tags": []\n+   },\n+   "outputs": [\n+    {\n+     "data": {},\n+     "metadata": {},\n+     "output_type": "display_data"\n+    },\n+    {\n+     "data": {},\n+     "metadata": {},\n+     "output_type": "display_data"\n+    },\n+    {\n+     "data": {},\n+     "metadata": {},\n+     "output_type": "display_data"\n+    },\n+    {\n+     "data": {\n+      "text/plain": [\n+       "\'Hello World!\'"\n+      ]\n+     },\n+     "metadata": {\n+      "papermill": {\n+       "name": "output"\n+      }\n+     },\n+     "output_type": "display_data"\n+    }\n+   ],\n+   "source": [\n+    "print \\"We\'re good.\\""\n+   ]\n+  },\n+  {\n+   "cell_type": "code",\n+   "execution_count": null,\n+   "metadata": {},\n+   "outputs": [],\n+   "source": [\n+    "assert False"\n+   ]\n+  },\n+  {\n+   "cell_type": "code",\n+   "execution_count": null,\n+   "metadata": {},\n+   "outputs": [],\n+   "source": [\n+    "print \\"Shouldn\'t get here.\\""\n+   ]\n+  }\n+ ],\n+ "metadata": {\n+  "celltoolbar": "Tags",\n+  "hide_input": false,\n+  "kernelspec": {\n+   "display_name": "Python 2",\n+   "language": "python",\n+   "name": "python2"\n+  },\n+  "language_info": {\n+   "codemirror_mode": {\n+    "name": "ipython",\n+    "version": 2.0\n+   },\n+   "file_extension": ".py",\n+   "mimetype": "text/x-python",\n+   "name": "python",\n+   "nbconvert_exporter": "python",\n+   "pygments_lexer": "ipython2",\n+   "version": "2.7.12"\n+  },\n+  "papermill": {\n+   "environment_variables": {},\n+   "metrics": {\n+    "duration": 3.6040260791778564\n+   },\n+   "parameters": {\n+    "bar": "Hello World!",\n+    "foo": 1.0\n+   },\n+   "version": "0.4+2.ge10f94c.dirty"\n+  }\n+ },\n+ "nbformat": 4,\n+ "nbformat_minor": 0\n+}\n\\ No newline at end of file\ndiff --git a/tests/notebooks/result_notebook.ipynb b/tests/notebooks/collection/result1.ipynb\nsimilarity index 94%\nrename from tests/notebooks/result_notebook.ipynb\nrename to tests/notebooks/collection/result1.ipynb\nindex cd01709..4aca2ed 100644\n--- a/tests/notebooks/result_notebook.ipynb\n+++ b/tests/notebooks/collection/result1.ipynb\n@@ -4,7 +4,6 @@\n    "cell_type": "code",\n    "execution_count": 1,\n    "metadata": {\n-    "papermill": {},\n     "tags": [\n      "parameters"\n     ]\n@@ -13,7 +12,7 @@\n    "source": [\n     "# Parameters\\n",\n     "foo = 1\\n",\n-    "bar = \\"Hello World!\\"\\n"\n+    "bar = \\"hello\\"\\n"\n    ]\n   },\n   {\n@@ -106,10 +105,10 @@\n   "papermill": {\n    "environment_variables": {},\n    "metrics": {\n-    "duration": 3.6040260791778564\n+    "duration": 2.250469923019409\n    },\n    "parameters": {\n-    "bar": "Hello World!",\n+    "bar": "hello",\n     "foo": 1\n    },\n    "version": "0.4+2.ge10f94c.dirty"\ndiff --git a/tests/notebooks/collection/result2.ipynb b/tests/notebooks/collection/result2.ipynb\nnew file mode 100644\nindex 0000000..f0944f4\n--- /dev/null\n+++ b/tests/notebooks/collection/result2.ipynb\n@@ -0,0 +1,119 @@\n+{\n+ "cells": [\n+  {\n+   "cell_type": "code",\n+   "execution_count": 1,\n+   "metadata": {\n+    "tags": [\n+     "parameters"\n+    ]\n+   },\n+   "outputs": [],\n+   "source": [\n+    "# Parameters\\n",\n+    "foo = 2\\n",\n+    "bar = \\"world\\"\\n"\n+   ]\n+  },\n+  {\n+   "cell_type": "code",\n+   "execution_count": 2,\n+   "metadata": {\n+    "papermill": {},\n+    "tags": []\n+   },\n+   "outputs": [\n+    {\n+     "data": {\n+      "application/papermill.record+json": {\n+       "number": 1\n+      }\n+     },\n+     "metadata": {},\n+     "output_type": "display_data"\n+    },\n+    {\n+     "data": {\n+      "application/papermill.record+json": {\n+       "list": [\n+        1,\n+        2,\n+        3\n+       ]\n+      }\n+     },\n+     "metadata": {},\n+     "output_type": "display_data"\n+    },\n+    {\n+     "data": {\n+      "application/papermill.record+json": {\n+       "dict": {\n+        "a": 1,\n+        "b": 2\n+       }\n+      }\n+     },\n+     "metadata": {},\n+     "output_type": "display_data"\n+    },\n+    {\n+     "data": {\n+      "text/plain": [\n+       "\'Hello World!\'"\n+      ]\n+     },\n+     "metadata": {\n+      "papermill": {\n+       "name": "output"\n+      }\n+     },\n+     "output_type": "display_data"\n+    }\n+   ],\n+   "source": [\n+    "import papermill as pm\\n",\n+    "\\n",\n+    "pm.record(\\"number\\", 1)\\n",\n+    "pm.record(\\"list\\", [1,2,3])\\n",\n+    "pm.record(\\"dict\\", dict(a=1, b=2))\\n",\n+    "\\n",\n+    "pm.display(\\"output\\", \\"Hello World!\\")"\n+   ]\n+  }\n+ ],\n+ "metadata": {\n+  "celltoolbar": "Tags",\n+  "hide_input": false,\n+  "kernelspec": {\n+   "display_name": "Python 2",\n+   "language": "python",\n+   "name": "python2"\n+  },\n+  "language_info": {\n+   "codemirror_mode": {\n+    "name": "ipython",\n+    "version": 2.0\n+   },\n+   "file_extension": ".py",\n+   "mimetype": "text/x-python",\n+   "name": "python",\n+   "nbconvert_exporter": "python",\n+   "pygments_lexer": "ipython2",\n+   "version": "2.7.12"\n+  },\n+  "papermill": {\n+   "environment_variables": {},\n+   "metrics": {\n+    "duration": 2.2486860752105713\n+   },\n+   "parameters": {\n+    "bar": "world",\n+    "foo": 2\n+   },\n+   "version": "0.4+2.ge10f94c.dirty"\n+  }\n+ },\n+ "nbformat": 4,\n+ "nbformat_minor": 0\n+}\ndiff --git a/tests/test_api.py b/tests/test_api.py\nindex 4142e6a..47095e5 100644\n--- a/tests/test_api.py\n+++ b/tests/test_api.py\n@@ -2,7 +2,7 @@ import unittest\n \n import pandas as pd\n from pandas.util.testing import assert_frame_equal\n-from papermill import read_notebook\n+from papermill import read_notebook, read_notebooks, PapermillException\n from tests import get_notebook_path\n \n \n@@ -10,17 +10,50 @@ class TestNotebookClass(unittest.TestCase):\n \n     def test(self):\n \n-        path = get_notebook_path(\'result_notebook.ipynb\')\n+        path = get_notebook_path(\'collection/result1.ipynb\')\n         nb = read_notebook(path)\n-        self.assertEqual(nb.parameters, dict(foo=1, bar="Hello World!"))\n+        self.assertEqual(nb.version, \'0.4+2.ge10f94c.dirty\')\n+        self.assertEqual(nb.environment_variables, {})\n+        self.assertTrue(isinstance(nb.metrics[\'duration\'], float))\n+        self.assertEqual(nb.parameters, dict(foo=1, bar="hello"))\n         expected_df = pd.DataFrame(\n             [\n-                (\'bar\', \'Hello World!\', \'parameter\', \'result_notebook.ipynb\'),\n-                (\'foo\', 1, \'parameter\', \'result_notebook.ipynb\'),\n-                (\'dict\', {u\'a\': 1, u\'b\': 2}, \'record\', \'result_notebook.ipynb\'),\n-                (\'list\', [1, 2, 3], \'record\', \'result_notebook.ipynb\'),\n-                (\'number\', 1, \'record\', \'result_notebook.ipynb\'),\n+                (\'bar\', \'hello\', \'parameter\', \'result1.ipynb\'),\n+                (\'foo\', 1, \'parameter\', \'result1.ipynb\'),\n+                (\'dict\', {u\'a\': 1, u\'b\': 2}, \'record\', \'result1.ipynb\'),\n+                (\'list\', [1, 2, 3], \'record\', \'result1.ipynb\'),\n+                (\'number\', 1, \'record\', \'result1.ipynb\'),\n             ],\n             columns=[\'name\', \'value\', \'type\', \'filename\']\n         )\n         assert_frame_equal(nb.dataframe, expected_df)\n+\n+    def test_bad_file_ext(self):\n+\n+        with self.assertRaises(PapermillException):\n+            read_notebook(\'result_notebook.py\')\n+\n+\n+class TestNotebookCollection(unittest.TestCase):\n+\n+    def test(self):\n+\n+        path = get_notebook_path(\'collection\')\n+        nbs = read_notebooks(path)\n+\n+        expected_df = pd.DataFrame(\n+            [\n+                (\'bar\', \'hello\', \'parameter\', \'result1.ipynb\', \'result1.ipynb\'),\n+                (\'foo\', 1, \'parameter\', \'result1.ipynb\', \'result1.ipynb\'),\n+                (\'dict\', {u\'a\': 1, u\'b\': 2}, \'record\', \'result1.ipynb\', \'result1.ipynb\'),\n+                (\'list\', [1, 2, 3], \'record\', \'result1.ipynb\', \'result1.ipynb\'),\n+                (\'number\', 1, \'record\', \'result1.ipynb\', \'result1.ipynb\'),\n+                (\'bar\', \'world\', \'parameter\', \'result2.ipynb\', \'result2.ipynb\'),\n+                (\'foo\', 2, \'parameter\', \'result2.ipynb\', \'result2.ipynb\'),\n+                (\'dict\', {u\'a\': 1, u\'b\': 2}, \'record\',\'result2.ipynb\', \'result2.ipynb\'),\n+                (\'list\', [1, 2, 3], \'record\', \'result2.ipynb\', \'result2.ipynb\'),\n+                (\'number\', 1, \'record\', \'result2.ipynb\', \'result2.ipynb\'),\n+            ],\n+            columns=[\'name\', \'value\', \'type\', \'filename\', \'key\']\n+        )\n+        assert_frame_equal(nbs.dataframe, expected_df)\ndiff --git a/tests/test_execute.py b/tests/test_execute.py\nindex 1b7d3af..94db6e0 100644\n--- a/tests/test_execute.py\n+++ b/tests/test_execute.py\n@@ -23,3 +23,22 @@ class TestNotebookHelpers(unittest.TestCase):\n         test_nb = read_notebook(nb_test1_executed_fname)\n         self.assertEqual(test_nb.node.cells[0].get(\'source\'), u\'# Parameters\\nmsg = "Hello"\\n\')\n         self.assertEqual(test_nb.parameters, {\'msg\': \'Hello\'})\n+\n+\n+class TestBrokenNotebook(unittest.TestCase):\n+\n+    def setUp(self):\n+        self.test_dir = tempfile.mkdtemp()\n+\n+    def tearDown(self):\n+        shutil.rmtree(self.test_dir)\n+\n+    def test(self):\n+        path = get_notebook_path(\'broken.ipynb\')\n+        result_path = os.path.join(self.test_dir, \'broken.ipynb\')\n+        execute_notebook(path, result_path)\n+        nb = read_notebook(result_path)\n+        self.assertEqual(nb.node.cells[0].execution_count, 1)\n+        self.assertEqual(nb.node.cells[1].execution_count, 2)\n+        self.assertEqual(nb.node.cells[1].outputs[0].output_type, \'error\')\n+        self.assertEqual(nb.node.cells[2].execution_count, None)\n', 'commit_subject': 'introduce papermill_io object for easier customization of io', 'repo_name': 'nteract/papermill', 'committer': {'name': 'Kyle Kelley', 'email': '8f4465e9c13a0a249a1929943e8a22f20baa98fb@gmail.com', 'time_sec': '1502756629', 'tz_offset': '-420', 'date': {'seconds': '1502756629'}}, 'commit_tree': 'ebc1cfc651053907f5ea840a48bcc3e252162634', 'commit_message': 'introduce papermill_io object for easier customization of io\n', 'language_name': 'Python', 'license': 'bsd-3-clause', 'hunks': '@@ -0,0 +1,119 @@\n+{\n+ "cells": [\n+  {\n+   "cell_type": "code",\n+   "execution_count": 1,\n+   "metadata": {\n+    "tags": [\n+     "parameters"\n+    ]\n+   },\n+   "outputs": [],\n+   "source": [\n+    "# Parameters\\n",\n+    "foo = 2\\n",\n+    "bar = \\"world\\"\\n"\n+   ]\n+  },\n+  {\n+   "cell_type": "code",\n+   "execution_count": 2,\n+   "metadata": {\n+    "papermill": {},\n+    "tags": []\n+   },\n+   "outputs": [\n+    {\n+     "data": {\n+      "application/papermill.record+json": {\n+       "number": 1\n+      }\n+     },\n+     "metadata": {},\n+     "output_type": "display_data"\n+    },\n+    {\n+     "data": {\n+      "application/papermill.record+json": {\n+       "list": [\n+        1,\n+        2,\n+        3\n+       ]\n+      }\n+     },\n+     "metadata": {},\n+     "output_type": "display_data"\n+    },\n+    {\n+     "data": {\n+      "application/papermill.record+json": {\n+       "dict": {\n+        "a": 1,\n+        "b": 2\n+       }\n+      }\n+     },\n+     "metadata": {},\n+     "output_type": "display_data"\n+    },\n+    {\n+     "data": {\n+      "text/plain": [\n+       "\'Hello World!\'"\n+      ]\n+     },\n+     "metadata": {\n+      "papermill": {\n+       "name": "output"\n+      }\n+     },\n+     "output_type": "display_data"\n+    }\n+   ],\n+   "source": [\n+    "import papermill as pm\\n",\n+    "\\n",\n+    "pm.record(\\"number\\", 1)\\n",\n+    "pm.record(\\"list\\", [1,2,3])\\n",\n+    "pm.record(\\"dict\\", dict(a=1, b=2))\\n",\n+    "\\n",\n+    "pm.display(\\"output\\", \\"Hello World!\\")"\n+   ]\n+  }\n+ ],\n+ "metadata": {\n+  "celltoolbar": "Tags",\n+  "hide_input": false,\n+  "kernelspec": {\n+   "display_name": "Python 2",\n+   "language": "python",\n+   "name": "python2"\n+  },\n+  "language_info": {\n+   "codemirror_mode": {\n+    "name": "ipython",\n+    "version": 2.0\n+   },\n+   "file_extension": ".py",\n+   "mimetype": "text/x-python",\n+   "name": "python",\n+   "nbconvert_exporter": "python",\n+   "pygments_lexer": "ipython2",\n+   "version": "2.7.12"\n+  },\n+  "papermill": {\n+   "environment_variables": {},\n+   "metrics": {\n+    "duration": 2.2486860752105713\n+   },\n+   "parameters": {\n+    "bar": "world",\n+    "foo": 2\n+   },\n+   "version": "0.4+2.ge10f94c.dirty"\n+  }\n+ },\n+ "nbformat": 4,\n+ "nbformat_minor": 0\n+}\n', 'addition_count': 119, 'deletion_count': 0, 'src_file': '/dev/null', 'tgt_file': 'b/tests/notebooks/collection/result2.ipynb', 'file_extension': '.ipynb', 'before_file': 'ADDFILE', 'message': 'introduce papermill_io object for easier customization of io\n', 'author_name': 'Kyle Kelley', 'lang': 'ipynb'}