diff --git a/execnb/shell.py b/execnb/shell.py index 9a9d136..cd241f2 100644 --- a/execnb/shell.py +++ b/execnb/shell.py @@ -33,8 +33,9 @@ def publish(self, data, metadata=None, **kwargs): self.shell._add_out(data, meta def _out_exc(ename, evalue, traceback): return dict(ename=str(ename), evalue=str(evalue), output_type='error', traceback=traceback) def _out_stream(text): return dict(name='stdout', output_type='stream', text=text.splitlines(False)) -# %% ../nbs/02_shell.ipynb 6 +# %% ../nbs/02_shell.ipynb 7 class CaptureShell(FastInteractiveShell): + "Execute the IPython/Jupyter source code" def __init__(self): super().__init__(displayhook_class=_CaptureHook, display_pub_class=_CapturePub) InteractiveShell._instance = self @@ -62,7 +63,7 @@ def _stream(self, std): text = std.getvalue() if text: self.out.append(_out_stream(text)) -# %% ../nbs/02_shell.ipynb 9 +# %% ../nbs/02_shell.ipynb 10 @patch def run(self:CaptureShell, code:str, stdout=True, stderr=True): "runs `code`, returning a list of all outputs in Jupyter notebook format" @@ -76,7 +77,7 @@ def run(self:CaptureShell, code:str, stdout=True, stderr=True): self._stream(stdout) return [*self.out] -# %% ../nbs/02_shell.ipynb 18 +# %% ../nbs/02_shell.ipynb 19 @patch def cell(self:CaptureShell, cell, stdout=True, stderr=True): "Run `cell`, skipping if not code, and store outputs back in cell" @@ -87,18 +88,20 @@ def cell(self:CaptureShell, cell, stdout=True, stderr=True): for o in outs: if 'execution_count' in o: cell['execution_count'] = o['execution_count'] -# %% ../nbs/02_shell.ipynb 21 +# %% ../nbs/02_shell.ipynb 23 @patch -def run_all(self:CaptureShell, nb, exc_stop=False): +def run_all(self:CaptureShell, nb, exc_stop=False, preproc=noop, postproc=noop): "Run all cells in `nb`, stopping at first exception if `exc_stop`" for cell in nb.cells: - self.cell(cell) + if not preproc(cell): + self.cell(cell) + postproc(cell) if self.exc and exc_stop: raise self.exc[1] from None -# %% ../nbs/02_shell.ipynb 29 +# %% ../nbs/02_shell.ipynb 36 @patch -def execute(self:CaptureShell, src, dest, exc_stop=False): +def execute(self:CaptureShell, src, dest, exc_stop=False, preproc=noop, postproc=noop): "Execute notebook from `src` and save with outputs to `dest" nb = read_nb(src) - self.run_all(nb, exc_stop=exc_stop) + self.run_all(nb, exc_stop=exc_stop, preproc=preproc, postproc=postproc) write_nb(nb, dest) diff --git a/nbs/.gitignore b/nbs/.gitignore index 075b254..c6c85cb 100644 --- a/nbs/.gitignore +++ b/nbs/.gitignore @@ -1 +1,2 @@ +docs/ /.quarto/ diff --git a/nbs/02_shell.ipynb b/nbs/02_shell.ipynb index 9f3c10f..ab3f984 100644 --- a/nbs/02_shell.ipynb +++ b/nbs/02_shell.ipynb @@ -70,6 +70,13 @@ "def _out_stream(text): return dict(name='stdout', output_type='stream', text=text.splitlines(False))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## CaptureShell -" + ] + }, { "cell_type": "code", "execution_count": null, @@ -78,6 +85,7 @@ "source": [ "#|export\n", "class CaptureShell(FastInteractiveShell):\n", + " \"Execute the IPython/Jupyter source code\"\n", " def __init__(self):\n", " super().__init__(displayhook_class=_CaptureHook, display_pub_class=_CapturePub)\n", " InteractiveShell._instance = self\n", @@ -107,19 +115,19 @@ ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "Use `CaptureShell` to execute the IPython/Jupyter source code." + "s = CaptureShell()" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "s = CaptureShell()" + "### Cells -" ] }, { @@ -184,8 +192,8 @@ " 'execution_count': 1},\n", " {'name': 'stdout',\n", " 'output_type': 'stream',\n", - " 'text': ['CPU times: user 2 us, sys: 0 ns, total: 2 us',\n", - " 'Wall time: 3.58 us']}]" + " 'text': ['CPU times: user 1 us, sys: 0 ns, total: 1 us',\n", + " 'Wall time: 3.34 us']}]" ] }, "execution_count": null, @@ -358,6 +366,13 @@ "c.outputs" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### NBs -" + ] + }, { "cell_type": "code", "execution_count": null, @@ -366,10 +381,12 @@ "source": [ "#|export\n", "@patch\n", - "def run_all(self:CaptureShell, nb, exc_stop=False):\n", + "def run_all(self:CaptureShell, nb, exc_stop=False, preproc=noop, postproc=noop):\n", " \"Run all cells in `nb`, stopping at first exception if `exc_stop`\"\n", " for cell in nb.cells:\n", - " self.cell(cell)\n", + " if not preproc(cell):\n", + " self.cell(cell)\n", + " postproc(cell)\n", " if self.exc and exc_stop: raise self.exc[1] from None" ] }, @@ -401,11 +418,7 @@ { "data": { "text/plain": [ - "[{'data': {'text/plain': [''],\n", - " 'text/markdown': [\"This is *bold*. Here's a [link](https://www.fast.ai).\"]},\n", - " 'metadata': {},\n", - " 'output_type': 'execute_result',\n", - " 'execution_count': 5}]" + "(#0) []" ] }, "execution_count": null, @@ -453,13 +466,7 @@ { "data": { "text/plain": [ - "[{'ename': \"\",\n", - " 'evalue': 'Oopsie!',\n", - " 'output_type': 'error',\n", - " 'traceback': ['\\x1b[0;31m---------------------------------------------------------------------------\\x1b[0m',\n", - " '\\x1b[0;31mException\\x1b[0m Traceback (most recent call last)',\n", - " 'Input \\x1b[0;32mIn [1]\\x1b[0m, in \\x1b[0;36m\\x1b[0;34m()\\x1b[0m\\n\\x1b[0;32m----> 1\\x1b[0m \\x1b[38;5;28;01mraise\\x1b[39;00m \\x1b[38;5;167;01mException\\x1b[39;00m(\\x1b[38;5;124m\"\\x1b[39m\\x1b[38;5;124mOopsie!\\x1b[39m\\x1b[38;5;124m\"\\x1b[39m)\\n',\n", - " '\\x1b[0;31mException\\x1b[0m: Oopsie!']}]" + "(#0) []" ] }, "execution_count": null, @@ -478,22 +485,65 @@ "With `exc_stop=True` (the default), exceptions in a cell are raised and no further processing occurs:" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "try: s.run_all(nb, exc_stop=True)\n", + "except Exception as e: print(f\"got exception: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can pass a function to `preproc` to have it run on every cell. It can modify the cell as needed. If the function returns `True`, then that cell will not be executed. For instance, to skip the cell which raises an exception:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nb = read_nb(clean)\n", + "s.run_all(nb, preproc=lambda c: 'raise' in c.source)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This cell will contain no output, since it was skipped." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "got exception: Oopsie!\n" - ] + "data": { + "text/plain": [ + "(#0) []" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "try: s.run_all(nb, exc_stop=True)\n", - "except Exception as e: print(f\"got exception: {e}\")" + "nb.cells[-1].outputs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also pass a function to `postproc` to modify a cell after it is executed." ] }, { @@ -504,13 +554,20 @@ "source": [ "#|export\n", "@patch\n", - "def execute(self:CaptureShell, src, dest, exc_stop=False):\n", + "def execute(self:CaptureShell, src, dest, exc_stop=False, preproc=noop, postproc=noop):\n", " \"Execute notebook from `src` and save with outputs to `dest\"\n", " nb = read_nb(src)\n", - " self.run_all(nb, exc_stop=exc_stop)\n", + " self.run_all(nb, exc_stop=exc_stop, preproc=preproc, postproc=postproc)\n", " write_nb(nb, dest)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is a shortcut for the combination of `read_nb`, `run_all`, and `write_nb`." + ] + }, { "cell_type": "code", "execution_count": null, @@ -531,6 +588,25 @@ "finally: Path('tmp.ipynb').unlink()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## export -" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#|hide\n", + "#|eval: false\n", + "from nbprocess.doclinks import nbprocess_export\n", + "nbprocess_export()" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/nbs/styles.css b/nbs/styles.css new file mode 100644 index 0000000..77fd6e5 --- /dev/null +++ b/nbs/styles.css @@ -0,0 +1,18 @@ +.cell-output pre { + margin-left: 0.8rem; + margin-top: 0; + background: none; + border-left: 2px solid lightsalmon; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.cell-output .sourceCode { + background: none; + margin-top: 0; +} + +.cell > .sourceCode { + margin-bottom: 0; +} +