Skip to content

Commit 3429cc4

Browse files
committed
Add --verbose flag to exec_nb for real-time output
- Added verbose parameter throughout the execution chain - Modified CaptureShell._run to conditionally capture stdout/stderr - When --verbose is used, output streams to terminal in real-time - Updated documentation in README.md and help text
1 parent 5337ae0 commit 3429cc4

File tree

4 files changed

+113
-173
lines changed

4 files changed

+113
-173
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ You can also execute notebooks from the command line with
6464

6565
usage: exec_nb [-h] [--dest DEST] [--exc_stop] [--inject_code INJECT_CODE]
6666
[--inject_path INJECT_PATH] [--inject_idx INJECT_IDX]
67+
[--verbose]
6768
src
6869

6970
Execute notebook from `src` and save with outputs to `dest`
@@ -78,3 +79,4 @@ You can also execute notebooks from the command line with
7879
--inject_code INJECT_CODE Code to inject into a cell
7980
--inject_path INJECT_PATH Path to file containing code to inject into a cell
8081
--inject_idx INJECT_IDX Cell to replace with `inject_code` (default: 0)
82+
--verbose Show stdout/stderr during execution (default: False)

execnb/shell.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,13 @@ def __init__(self, path:str|Path=None, mpl_format='retina', history=False, timeo
6767
self._run(f"set_matplotlib_formats('{mpl_format}')")
6868

6969
def _run(self, raw_cell, store_history=False, silent=False, shell_futures=True, cell_id=None,
70-
stdout=True, stderr=True, display=True):
70+
stdout=True, stderr=True, display=True, verbose=False):
7171
# TODO what if there's a comment?
7272
semic = raw_cell.rstrip().endswith(';')
73-
with capture_output(display=display, stdout=stdout, stderr=stderr) as c:
73+
# When verbose is True, don't capture stdout/stderr to allow real-time output
74+
capture_stdout = stdout and not verbose
75+
capture_stderr = stderr and not verbose
76+
with capture_output(display=display, stdout=capture_stdout, stderr=capture_stderr) as c:
7477
result = super().run_cell(raw_cell, store_history, silent, shell_futures=shell_futures, cell_id=cell_id)
7578
return AttrDict(result=result, stdout='' if semic else c.stdout, stderr=c.stderr,
7679
display_objects=c.outputs, exception=result.error_in_exec, quiet=semic)
@@ -86,14 +89,14 @@ def enable_gui(self, gui=None): pass
8689
# %% ../nbs/02_shell.ipynb
8790
@patch
8891
def run_cell(self:CaptureShell, raw_cell, store_history=False, silent=False, shell_futures=True, cell_id=None,
89-
stdout=True, stderr=True, display=True, timeout=None):
92+
stdout=True, stderr=True, display=True, timeout=None, verbose=False):
9093
if not timeout: timeout = self.timeout
9194
if timeout:
9295
def handler(*args): raise TimeoutError()
9396
signal.signal(signal.SIGALRM, handler)
9497
signal.alarm(timeout)
9598
try: return self._run(raw_cell, store_history, silent, shell_futures, cell_id=cell_id,
96-
stdout=stdout, stderr=stderr, display=display)
99+
stdout=stdout, stderr=stderr, display=display, verbose=verbose)
97100
finally:
98101
if timeout: signal.alarm(0)
99102

@@ -144,9 +147,10 @@ def run(self:CaptureShell,
144147
code:str, # Python/IPython code to run
145148
stdout=True, # Capture stdout and save as output?
146149
stderr=True, # Capture stderr and save as output?
147-
timeout:Optional[int]=None): # Shell command will time out after {timeout} seconds
150+
timeout:Optional[int]=None, # Shell command will time out after {timeout} seconds
151+
verbose:bool=False): # Show stdout/stderr during execution
148152
"Run `code`, returning a list of all outputs in Jupyter notebook format"
149-
res = self.run_cell(code, stdout=stdout, stderr=stderr, timeout=timeout)
153+
res = self.run_cell(code, stdout=stdout, stderr=stderr, timeout=timeout, verbose=verbose)
150154
self.result = res.result.result
151155
self.exc = res.exception
152156
return _out_nb(res, self.display_formatter)
@@ -157,8 +161,9 @@ async def run_async(self:CaptureShell,
157161
code: str, # Python/IPython code to run
158162
stdout=True, # Capture stdout and save as output?
159163
stderr=True, # Capture stderr and save as output?
160-
timeout:Optional[int]=None): # Shell command will time out after {timeout} seconds
161-
return self.run(code, stdout=stdout, stderr=stderr, timeout=timeout)
164+
timeout:Optional[int]=None, # Shell command will time out after {timeout} seconds
165+
verbose:bool=False): # Show stdout/stderr during execution
166+
return self.run(code, stdout=stdout, stderr=stderr, timeout=timeout, verbose=verbose)
162167

163168
# %% ../nbs/02_shell.ipynb
164169
def _pre(s, xtra=''): return f"<pre {xtra}><code>{escape(s)}</code></pre>"
@@ -197,11 +202,11 @@ def render_output(out):
197202

198203
# %% ../nbs/02_shell.ipynb
199204
@patch
200-
def cell(self:CaptureShell, cell, stdout=True, stderr=True):
205+
def cell(self:CaptureShell, cell, stdout=True, stderr=True, verbose=False):
201206
"Run `cell`, skipping if not code, and store outputs back in cell"
202207
if cell.cell_type!='code': return
203208
self._cell_idx = cell.idx_ + 1
204-
outs = self.run(cell.source)
209+
outs = self.run(cell.source, verbose=verbose)
205210
if outs: cell.outputs = _dict2obj(outs)
206211

207212
# %% ../nbs/02_shell.ipynb
@@ -239,13 +244,14 @@ def run_all(self:CaptureShell,
239244
preproc:callable=_false, # Called before each cell is executed
240245
postproc:callable=_false, # Called after each cell is executed
241246
inject_code:str|None=None, # Code to inject into a cell
242-
inject_idx:int=0 # Cell to replace with `inject_code`
247+
inject_idx:int=0, # Cell to replace with `inject_code`
248+
verbose:bool=False # Show stdout/stderr during execution
243249
):
244250
"Run all cells in `nb`, stopping at first exception if `exc_stop`"
245251
if inject_code is not None: nb.cells[inject_idx].source = inject_code
246252
for cell in nb.cells:
247253
if not preproc(cell):
248-
self.cell(cell)
254+
self.cell(cell, verbose=verbose)
249255
postproc(cell)
250256
if self.exc and exc_stop: raise self.exc from None
251257

@@ -259,15 +265,16 @@ def execute(self:CaptureShell,
259265
postproc:callable=_false, # Called after each cell is executed
260266
inject_code:str|None=None, # Code to inject into a cell
261267
inject_path:str|Path|None=None, # Path to file containing code to inject into a cell
262-
inject_idx:int=0 # Cell to replace with `inject_code`
268+
inject_idx:int=0, # Cell to replace with `inject_code`
269+
verbose:bool=False # Show stdout/stderr during execution
263270
):
264271
"Execute notebook from `src` and save with outputs to `dest"
265272
nb = read_nb(src)
266273
self._fname = src
267274
self.set_path(Path(src).parent.resolve())
268275
if inject_path is not None: inject_code = Path(inject_path).read_text()
269276
self.run_all(nb, exc_stop=exc_stop, preproc=preproc, postproc=postproc,
270-
inject_code=inject_code, inject_idx=inject_idx)
277+
inject_code=inject_code, inject_idx=inject_idx, verbose=verbose)
271278
if dest: write_nb(nb, dest)
272279

273280
# %% ../nbs/02_shell.ipynb
@@ -290,11 +297,12 @@ def exec_nb(
290297
exc_stop:bool=False, # Stop on exceptions?
291298
inject_code:str=None, # Code to inject into a cell
292299
inject_path:str=None, # Path to file containing code to inject into a cell
293-
inject_idx:int=0 # Cell to replace with `inject_code`
300+
inject_idx:int=0, # Cell to replace with `inject_code`
301+
verbose:bool=False # Show stdout/stderr during execution
294302
):
295303
"Execute notebook from `src` and save with outputs to `dest`"
296304
CaptureShell().execute(src, dest, exc_stop=exc_stop, inject_code=inject_code,
297-
inject_path=inject_path, inject_idx=inject_idx)
305+
inject_path=inject_path, inject_idx=inject_idx, verbose=verbose)
298306

299307
# %% ../nbs/02_shell.ipynb
300308
class SmartCompleter(IPCompleter):

0 commit comments

Comments
 (0)