Skip to content

Commit

Permalink
fixes #55
Browse files Browse the repository at this point in the history
  • Loading branch information
jph00 committed Oct 29, 2024
1 parent b03b633 commit 8a5e0b2
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 39 deletions.
4 changes: 3 additions & 1 deletion execnb/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
'execnb.shell': { 'execnb.shell.CaptureShell': ('shell.html#captureshell', 'execnb/shell.py'),
'execnb.shell.CaptureShell.__init__': ('shell.html#captureshell.__init__', 'execnb/shell.py'),
'execnb.shell.CaptureShell.cell': ('shell.html#captureshell.cell', 'execnb/shell.py'),
'execnb.shell.CaptureShell.complete': ('shell.html#captureshell.complete', 'execnb/shell.py'),
'execnb.shell.CaptureShell.execute': ('shell.html#captureshell.execute', 'execnb/shell.py'),
'execnb.shell.CaptureShell.prettytb': ('shell.html#captureshell.prettytb', 'execnb/shell.py'),
'execnb.shell.CaptureShell.run': ('shell.html#captureshell.run', 'execnb/shell.py'),
Expand Down Expand Up @@ -51,4 +52,5 @@
'execnb.shell.format_exc': ('shell.html#format_exc', 'execnb/shell.py'),
'execnb.shell.out_error': ('shell.html#out_error', 'execnb/shell.py'),
'execnb.shell.out_exec': ('shell.html#out_exec', 'execnb/shell.py'),
'execnb.shell.out_stream': ('shell.html#out_stream', 'execnb/shell.py')}}}
'execnb.shell.out_stream': ('shell.html#out_stream', 'execnb/shell.py'),
'execnb.shell.render_outputs': ('shell.html#render_outputs', 'execnb/shell.py')}}}
68 changes: 51 additions & 17 deletions execnb/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from fastcore.utils import *
from fastcore.script import call_parse

import multiprocessing,types
import multiprocessing,types,traceback
try:
if sys.platform == 'darwin': multiprocessing.set_start_method("fork")
except RuntimeError: pass # if re-running cell
Expand All @@ -22,16 +22,17 @@
from IPython.utils.strdispatch import StrDispatch
from IPython.display import display as disp, HTML

import traceback
from base64 import b64encode
from io import StringIO
from contextlib import redirect_stdout
from html import escape
try: from matplotlib_inline.backend_inline import set_matplotlib_formats
except ImportError: set_matplotlib_formats = None

from .nbio import *
from .nbio import _dict2obj

# %% auto 0
__all__ = ['CaptureShell', 'format_exc', 'find_output', 'out_exec', 'out_stream', 'out_error', 'exec_nb', 'SmartCompleter']
__all__ = ['CaptureShell', 'format_exc', 'render_outputs', 'find_output', 'out_exec', 'out_stream', 'out_error', 'exec_nb',
'SmartCompleter']

# %% ../nbs/02_shell.ipynb 5
class _CustDisplayHook(DisplayHook):
Expand All @@ -49,13 +50,16 @@ def __repr__(self: ExecutionResult): return f'result: {self.result}; err: {self.
class CaptureShell(InteractiveShell):
displayhook_class = _CustDisplayHook

def __init__(self, path:str|Path=None):
def __init__(self, path:str|Path=None, mpl_format='retina'):
super().__init__()
self.result,self.exc = None,None
if path: self.set_path(path)
self.display_formatter.active = True
if not IN_NOTEBOOK: InteractiveShell._instance = self
self.run_cell('%matplotlib inline')
if set_matplotlib_formats:
self.run_cell("from matplotlib_inline.backend_inline import set_matplotlib_formats")
self.run_cell(f"set_matplotlib_formats('{mpl_format}')")
self.run_cell('%matplotlib inline')

def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=True, cell_id=None,
stdout=True, stderr=True, display=True):
Expand Down Expand Up @@ -119,7 +123,31 @@ def run(self:CaptureShell,
self.exc = res.exception
return _out_nb(res, self.display_formatter)

# %% ../nbs/02_shell.ipynb 38
# %% ../nbs/02_shell.ipynb 29
def render_outputs(outputs):
def render_output(out):
otype = out['output_type']
if otype == 'stream':
txt = ''.join(out['text'])
return f"<pre>{txt}</pre>" if out['name']=='stdout' else f"<pre class='stderr'>{txt}</pre>"
elif otype in ('display_data','execute_result'):
data = out['data']
_g = lambda t: ''.join(data[t]) if t in data else None
if d := _g('text/html'): return d
if d := _g('application/javascript'): return f'<script>{d}</script>'
if d := _g('text/markdown'): return markdown.markdown(d)
if d := _g('image/svg+xml'): return d
if d := _g('image/jpeg'): return f'<img src="data:image/jpeg;base64,{d}"/>'
if d := _g('image/png'): return f'<img src="data:image/png;base64,{d}"/>'
if d := _g('text/latex'): return f'<div class="math">${d}$</div>'
if d := _g('text/plain'): return f"<pre>{escape(d)}</pre>"
elif otype == 'error':
return f"<pre class='error'>{out['ename']}: {out['evalue']}\n{''.join(out['traceback'])}</pre>"
return ''

return '\n'.join(map(render_output, outputs))

# %% ../nbs/02_shell.ipynb 40
@patch
def cell(self:CaptureShell, cell, stdout=True, stderr=True):
"Run `cell`, skipping if not code, and store outputs back in cell"
Expand All @@ -131,32 +159,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 41
# %% ../nbs/02_shell.ipynb 43
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 44
# %% ../nbs/02_shell.ipynb 46
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 46
# %% ../nbs/02_shell.ipynb 48
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 48
# %% ../nbs/02_shell.ipynb 50
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 50
# %% ../nbs/02_shell.ipynb 52
def _false(o): return False

@patch
Expand All @@ -176,7 +204,7 @@ def run_all(self:CaptureShell,
postproc(cell)
if self.exc and exc_stop: raise self.exc from None

# %% ../nbs/02_shell.ipynb 64
# %% ../nbs/02_shell.ipynb 66
@patch
def execute(self:CaptureShell,
src:str|Path, # Notebook path to read from
Expand All @@ -197,7 +225,7 @@ def execute(self:CaptureShell,
inject_code=inject_code, inject_idx=inject_idx)
if dest: write_nb(nb, dest)

# %% ../nbs/02_shell.ipynb 68
# %% ../nbs/02_shell.ipynb 70
@patch
def prettytb(self:CaptureShell,
fname:str|Path=None): # filename to print alongside the traceback
Expand All @@ -209,7 +237,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 87
# %% ../nbs/02_shell.ipynb 89
@call_parse
def exec_nb(
src:str, # Notebook path to read from
Expand All @@ -223,7 +251,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 90
# %% ../nbs/02_shell.ipynb 92
class SmartCompleter(IPCompleter):
def __init__(self, shell, namespace=None, jedi=False):
if namespace is None: namespace = shell.user_ns
Expand All @@ -242,3 +270,9 @@ def __call__(self, c):
return [o.text.rpartition('.')[-1]
for o in self.completions(c, len(c))
if o.type=='<unknown>']

# %% ../nbs/02_shell.ipynb 94
@patch
def complete(self:CaptureShell, c):
if not hasattr(self, '_completer'): self._completer = SmartCompleter(self)
return self._completer(c)
Loading

0 comments on commit 8a5e0b2

Please sign in to comment.