Skip to content

Commit 8a5e0b2

Browse files
committed
fixes #55
1 parent b03b633 commit 8a5e0b2

File tree

3 files changed

+178
-39
lines changed

3 files changed

+178
-39
lines changed

execnb/_modidx.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
'execnb.shell': { 'execnb.shell.CaptureShell': ('shell.html#captureshell', 'execnb/shell.py'),
2424
'execnb.shell.CaptureShell.__init__': ('shell.html#captureshell.__init__', 'execnb/shell.py'),
2525
'execnb.shell.CaptureShell.cell': ('shell.html#captureshell.cell', 'execnb/shell.py'),
26+
'execnb.shell.CaptureShell.complete': ('shell.html#captureshell.complete', 'execnb/shell.py'),
2627
'execnb.shell.CaptureShell.execute': ('shell.html#captureshell.execute', 'execnb/shell.py'),
2728
'execnb.shell.CaptureShell.prettytb': ('shell.html#captureshell.prettytb', 'execnb/shell.py'),
2829
'execnb.shell.CaptureShell.run': ('shell.html#captureshell.run', 'execnb/shell.py'),
@@ -51,4 +52,5 @@
5152
'execnb.shell.format_exc': ('shell.html#format_exc', 'execnb/shell.py'),
5253
'execnb.shell.out_error': ('shell.html#out_error', 'execnb/shell.py'),
5354
'execnb.shell.out_exec': ('shell.html#out_exec', 'execnb/shell.py'),
54-
'execnb.shell.out_stream': ('shell.html#out_stream', 'execnb/shell.py')}}}
55+
'execnb.shell.out_stream': ('shell.html#out_stream', 'execnb/shell.py'),
56+
'execnb.shell.render_outputs': ('shell.html#render_outputs', 'execnb/shell.py')}}}

execnb/shell.py

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from fastcore.utils import *
99
from fastcore.script import call_parse
1010

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

25-
import traceback
2625
from base64 import b64encode
27-
from io import StringIO
28-
from contextlib import redirect_stdout
26+
from html import escape
27+
try: from matplotlib_inline.backend_inline import set_matplotlib_formats
28+
except ImportError: set_matplotlib_formats = None
2929

3030
from .nbio import *
3131
from .nbio import _dict2obj
3232

3333
# %% auto 0
34-
__all__ = ['CaptureShell', 'format_exc', 'find_output', 'out_exec', 'out_stream', 'out_error', 'exec_nb', 'SmartCompleter']
34+
__all__ = ['CaptureShell', 'format_exc', 'render_outputs', 'find_output', 'out_exec', 'out_stream', 'out_error', 'exec_nb',
35+
'SmartCompleter']
3536

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

52-
def __init__(self, path:str|Path=None):
53+
def __init__(self, path:str|Path=None, mpl_format='retina'):
5354
super().__init__()
5455
self.result,self.exc = None,None
5556
if path: self.set_path(path)
5657
self.display_formatter.active = True
5758
if not IN_NOTEBOOK: InteractiveShell._instance = self
58-
self.run_cell('%matplotlib inline')
59+
if set_matplotlib_formats:
60+
self.run_cell("from matplotlib_inline.backend_inline import set_matplotlib_formats")
61+
self.run_cell(f"set_matplotlib_formats('{mpl_format}')")
62+
self.run_cell('%matplotlib inline')
5963

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

122-
# %% ../nbs/02_shell.ipynb 38
126+
# %% ../nbs/02_shell.ipynb 29
127+
def render_outputs(outputs):
128+
def render_output(out):
129+
otype = out['output_type']
130+
if otype == 'stream':
131+
txt = ''.join(out['text'])
132+
return f"<pre>{txt}</pre>" if out['name']=='stdout' else f"<pre class='stderr'>{txt}</pre>"
133+
elif otype in ('display_data','execute_result'):
134+
data = out['data']
135+
_g = lambda t: ''.join(data[t]) if t in data else None
136+
if d := _g('text/html'): return d
137+
if d := _g('application/javascript'): return f'<script>{d}</script>'
138+
if d := _g('text/markdown'): return markdown.markdown(d)
139+
if d := _g('image/svg+xml'): return d
140+
if d := _g('image/jpeg'): return f'<img src="data:image/jpeg;base64,{d}"/>'
141+
if d := _g('image/png'): return f'<img src="data:image/png;base64,{d}"/>'
142+
if d := _g('text/latex'): return f'<div class="math">${d}$</div>'
143+
if d := _g('text/plain'): return f"<pre>{escape(d)}</pre>"
144+
elif otype == 'error':
145+
return f"<pre class='error'>{out['ename']}: {out['evalue']}\n{''.join(out['traceback'])}</pre>"
146+
return ''
147+
148+
return '\n'.join(map(render_output, outputs))
149+
150+
# %% ../nbs/02_shell.ipynb 40
123151
@patch
124152
def cell(self:CaptureShell, cell, stdout=True, stderr=True):
125153
"Run `cell`, skipping if not code, and store outputs back in cell"
@@ -131,32 +159,32 @@ def cell(self:CaptureShell, cell, stdout=True, stderr=True):
131159
for o in outs:
132160
if 'execution_count' in o: cell['execution_count'] = o['execution_count']
133161

134-
# %% ../nbs/02_shell.ipynb 41
162+
# %% ../nbs/02_shell.ipynb 43
135163
def find_output(outp, # Output from `run`
136164
ot='execute_result' # Output_type to find
137165
):
138166
"Find first output of type `ot` in `CaptureShell.run` output"
139167
return first(o for o in outp if o['output_type']==ot)
140168

141-
# %% ../nbs/02_shell.ipynb 44
169+
# %% ../nbs/02_shell.ipynb 46
142170
def out_exec(outp):
143171
"Get data from execution result in `outp`."
144172
out = find_output(outp)
145173
if out: return '\n'.join(first(out['data'].values()))
146174

147-
# %% ../nbs/02_shell.ipynb 46
175+
# %% ../nbs/02_shell.ipynb 48
148176
def out_stream(outp):
149177
"Get text from stream in `outp`."
150178
out = find_output(outp, 'stream')
151179
if out: return ('\n'.join(out['text'])).strip()
152180

153-
# %% ../nbs/02_shell.ipynb 48
181+
# %% ../nbs/02_shell.ipynb 50
154182
def out_error(outp):
155183
"Get traceback from error in `outp`."
156184
out = find_output(outp, 'error')
157185
if out: return '\n'.join(out['traceback'])
158186

159-
# %% ../nbs/02_shell.ipynb 50
187+
# %% ../nbs/02_shell.ipynb 52
160188
def _false(o): return False
161189

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

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

200-
# %% ../nbs/02_shell.ipynb 68
228+
# %% ../nbs/02_shell.ipynb 70
201229
@patch
202230
def prettytb(self:CaptureShell,
203231
fname:str|Path=None): # filename to print alongside the traceback
@@ -209,7 +237,7 @@ def prettytb(self:CaptureShell,
209237
fname_str = f' in {fname}' if fname else ''
210238
return f"{type(self.exc).__name__}{fname_str}:\n{_fence}\n{cell_str}\n"
211239

212-
# %% ../nbs/02_shell.ipynb 87
240+
# %% ../nbs/02_shell.ipynb 89
213241
@call_parse
214242
def exec_nb(
215243
src:str, # Notebook path to read from
@@ -223,7 +251,7 @@ def exec_nb(
223251
CaptureShell().execute(src, dest, exc_stop=exc_stop, inject_code=inject_code,
224252
inject_path=inject_path, inject_idx=inject_idx)
225253

226-
# %% ../nbs/02_shell.ipynb 90
254+
# %% ../nbs/02_shell.ipynb 92
227255
class SmartCompleter(IPCompleter):
228256
def __init__(self, shell, namespace=None, jedi=False):
229257
if namespace is None: namespace = shell.user_ns
@@ -242,3 +270,9 @@ def __call__(self, c):
242270
return [o.text.rpartition('.')[-1]
243271
for o in self.completions(c, len(c))
244272
if o.type=='<unknown>']
273+
274+
# %% ../nbs/02_shell.ipynb 94
275+
@patch
276+
def complete(self:CaptureShell, c):
277+
if not hasattr(self, '_completer'): self._completer = SmartCompleter(self)
278+
return self._completer(c)

0 commit comments

Comments
 (0)