diff --git a/execnb/shell.py b/execnb/shell.py index 752e8a5..c20bc63 100644 --- a/execnb/shell.py +++ b/execnb/shell.py @@ -16,6 +16,7 @@ from IPython.core.interactiveshell import InteractiveShell, ExecutionInfo, ExecutionResult from IPython.core.displayhook import DisplayHook from IPython.utils.capture import capture_output +from IPython.utils.text import strip_ansi from IPython.core.completer import IPCompleter,provisionalcompleter from IPython.core.hooks import CommandChainDispatcher from IPython.core.completerlib import module_completer @@ -123,12 +124,12 @@ def run(self:CaptureShell, self.exc = res.exception return _out_nb(res, self.display_formatter) -# %% ../nbs/02_shell.ipynb 29 +# %% ../nbs/02_shell.ipynb 30 def render_outputs(outputs): def render_output(out): otype = out['output_type'] if otype == 'stream': - txt = ''.join(out['text']) + txt = strip_ansi(''.join(out['text'])) return f"
{txt}
" if out['name']=='stdout' else f"
{txt}
" elif otype in ('display_data','execute_result'): data = out['data'] @@ -141,13 +142,11 @@ def render_output(out): if d := _g('image/png'): return f'' if d := _g('text/latex'): return f'
${d}$
' if d := _g('text/plain'): return f"
{escape(d)}
" - elif otype == 'error': - return f"
{out['ename']}: {out['evalue']}\n{''.join(out['traceback'])}
" return '' return '\n'.join(map(render_output, outputs)) -# %% ../nbs/02_shell.ipynb 40 +# %% ../nbs/02_shell.ipynb 41 @patch def cell(self:CaptureShell, cell, stdout=True, stderr=True): "Run `cell`, skipping if not code, and store outputs back in cell" @@ -159,32 +158,32 @@ 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 43 +# %% ../nbs/02_shell.ipynb 44 def find_output(outp, # Output from `run` ot='execute_result' # Output_type to find ): "Find first output of type `ot` in `CaptureShell.run` output" return first(o for o in outp if o['output_type']==ot) -# %% ../nbs/02_shell.ipynb 46 +# %% ../nbs/02_shell.ipynb 47 def out_exec(outp): "Get data from execution result in `outp`." out = find_output(outp) if out: return '\n'.join(first(out['data'].values())) -# %% ../nbs/02_shell.ipynb 48 +# %% ../nbs/02_shell.ipynb 49 def out_stream(outp): "Get text from stream in `outp`." out = find_output(outp, 'stream') if out: return ('\n'.join(out['text'])).strip() -# %% ../nbs/02_shell.ipynb 50 +# %% ../nbs/02_shell.ipynb 51 def out_error(outp): "Get traceback from error in `outp`." out = find_output(outp, 'error') if out: return '\n'.join(out['traceback']) -# %% ../nbs/02_shell.ipynb 52 +# %% ../nbs/02_shell.ipynb 53 def _false(o): return False @patch @@ -204,7 +203,7 @@ def run_all(self:CaptureShell, postproc(cell) if self.exc and exc_stop: raise self.exc from None -# %% ../nbs/02_shell.ipynb 66 +# %% ../nbs/02_shell.ipynb 67 @patch def execute(self:CaptureShell, src:str|Path, # Notebook path to read from @@ -225,7 +224,7 @@ def execute(self:CaptureShell, inject_code=inject_code, inject_idx=inject_idx) if dest: write_nb(nb, dest) -# %% ../nbs/02_shell.ipynb 70 +# %% ../nbs/02_shell.ipynb 71 @patch def prettytb(self:CaptureShell, fname:str|Path=None): # filename to print alongside the traceback @@ -237,7 +236,7 @@ def prettytb(self:CaptureShell, fname_str = f' in {fname}' if fname else '' return f"{type(self.exc).__name__}{fname_str}:\n{_fence}\n{cell_str}\n" -# %% ../nbs/02_shell.ipynb 89 +# %% ../nbs/02_shell.ipynb 90 @call_parse def exec_nb( src:str, # Notebook path to read from @@ -251,7 +250,7 @@ def exec_nb( CaptureShell().execute(src, dest, exc_stop=exc_stop, inject_code=inject_code, inject_path=inject_path, inject_idx=inject_idx) -# %% ../nbs/02_shell.ipynb 92 +# %% ../nbs/02_shell.ipynb 93 class SmartCompleter(IPCompleter): def __init__(self, shell, namespace=None, jedi=False): if namespace is None: namespace = shell.user_ns @@ -271,7 +270,7 @@ def __call__(self, c): for o in self.completions(c, len(c)) if o.type==''] -# %% ../nbs/02_shell.ipynb 94 +# %% ../nbs/02_shell.ipynb 95 @patch def complete(self:CaptureShell, c): if not hasattr(self, '_completer'): self._completer = SmartCompleter(self) diff --git a/nbs/02_shell.ipynb b/nbs/02_shell.ipynb index a7a232f..349045a 100644 --- a/nbs/02_shell.ipynb +++ b/nbs/02_shell.ipynb @@ -39,6 +39,7 @@ "from IPython.core.interactiveshell import InteractiveShell, ExecutionInfo, ExecutionResult\n", "from IPython.core.displayhook import DisplayHook\n", "from IPython.utils.capture import capture_output\n", + "from IPython.utils.text import strip_ansi\n", "from IPython.core.completer import IPCompleter,provisionalcompleter\n", "from IPython.core.hooks import CommandChainDispatcher\n", "from IPython.core.completerlib import module_completer\n", @@ -706,8 +707,8 @@ "text/plain": [ "[{'name': 'stdout',\n", " 'output_type': 'stream',\n", - " 'text': ['CPU times: user 1e+03 ns, sys: 1e+03 ns, total: 2 us\\n',\n", - " 'Wall time: 3.1 us\\n']},\n", + " 'text': ['CPU times: user 2 us, sys: 0 ns, total: 2 us\\n',\n", + " 'Wall time: 3.81 us\\n']},\n", " {'data': {'text/plain': ['2']},\n", " 'metadata': {},\n", " 'output_type': 'execute_result'}]" @@ -723,6 +724,38 @@ "o" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'stdout',\n", + " 'output_type': 'stream',\n", + " 'text': ['\\x1b[0;31m---------------------------------------------------------------------------\\x1b[0m\\n',\n", + " '\\x1b[0;31mZeroDivisionError\\x1b[0m Traceback (most recent call last)\\n',\n", + " 'File \\x1b[0;32m:1\\x1b[0m\\n',\n", + " '\\x1b[0;32m----> 1\\x1b[0m \\x1b[38;5;241m1\\x1b[39m\\x1b[38;5;241m/\\x1b[39m\\x1b[38;5;241m0\\x1b[39m\\n',\n", + " '\\n',\n", + " '\\x1b[0;31mZeroDivisionError\\x1b[0m: division by zero\\n']},\n", + " {'ename': 'ZeroDivisionError',\n", + " 'evalue': 'division by zero',\n", + " 'output_type': 'error',\n", + " 'traceback': 'Traceback (most recent call last):\\n File \"/Users/jhoward/miniconda3/lib/python3.11/site-packages/IPython/core/interactiveshell.py\", line 3553, in run_code\\n exec(code_obj, self.user_global_ns, self.user_ns)\\n File \"\", line 1, in \\n 1/0\\n ~^~\\nZeroDivisionError: division by zero\\n'}]" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "o = s.run(\"1/0\")\n", + "o" + ] + }, { "cell_type": "code", "execution_count": null, @@ -734,7 +767,7 @@ " def render_output(out):\n", " otype = out['output_type']\n", " if otype == 'stream':\n", - " txt = ''.join(out['text'])\n", + " txt = strip_ansi(''.join(out['text']))\n", " return f\"
{txt}
\" if out['name']=='stdout' else f\"
{txt}
\"\n", " elif otype in ('display_data','execute_result'):\n", " data = out['data']\n", @@ -747,8 +780,6 @@ " if d := _g('image/png'): return f''\n", " if d := _g('text/latex'): return f'
${d}$
'\n", " if d := _g('text/plain'): return f\"
{escape(d)}
\"\n", - " elif otype == 'error':\n", - " return f\"
{out['ename']}: {out['evalue']}\\n{''.join(out['traceback'])}
\"\n", " return ''\n", " \n", " return '\\n'.join(map(render_output, outputs))" @@ -762,10 +793,13 @@ { "data": { "text/html": [ - "
CPU times: user 1e+03 ns, sys: 1e+03 ns, total: 2 us\n",
-       "Wall time: 3.1 us\n",
-       "
\n", - "
2
" + "
---------------------------------------------------------------------------\n",
+       "ZeroDivisionError                         Traceback (most recent call last)\n",
+       "File :1\n",
+       "----> 1 1/0\n",
+       "\n",
+       "ZeroDivisionError: division by zero\n",
+       "
\n" ], "text/plain": [ ""