Skip to content

Commit 7a9ed8f

Browse files
committed
allow rendering of HTML even on cell exception
1 parent fa40f7a commit 7a9ed8f

File tree

8 files changed

+1409
-1059
lines changed

8 files changed

+1409
-1059
lines changed

coverage.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ tests/test_nb_fns.py .......... [100%]
1010
Name Stmts Miss Cover
1111
---------------------------------------------
1212
wvpy/__init__.py 3 0 100%
13-
wvpy/jtools.py 290 95 67%
13+
wvpy/jtools.py 305 98 68%
1414
wvpy/pysheet.py 99 99 0%
1515
wvpy/render_workbook.py 54 54 0%
1616
---------------------------------------------
17-
TOTAL 446 248 44%
17+
TOTAL 461 251 46%
1818

1919

20-
============================= 10 passed in 31.68s ==============================
20+
============================= 10 passed in 29.14s ==============================

pkg/build/lib/wvpy/jtools.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
import datetime
66
import os
77
import nbformat
8-
import nbconvert.preprocessors
8+
from nbconvert import HTMLExporter
9+
from nbconvert.preprocessors import ExecutePreprocessor
910
from multiprocessing import Pool
1011
import sys
1112

12-
from typing import Iterable, Optional
13+
from typing import Iterable, List, Optional
1314
from functools import total_ordering
1415

1516
have_pdf_kit = False
@@ -245,6 +246,36 @@ def convert_notebook_file_to_py(
245246
f.write(py_source)
246247

247248

249+
class OurExecutor(ExecutePreprocessor):
250+
"""Catch exception in notebook processing"""
251+
def __init__(self, **kw):
252+
"""Initialize the preprocessor."""
253+
ExecutePreprocessor.__init__(self, **kw)
254+
self.caught_exception = None
255+
256+
def preprocess_cell(self, cell, resources, index):
257+
"""
258+
Override if you want to apply some preprocessing to each cell.
259+
Must return modified cell and resource dictionary.
260+
261+
Parameters
262+
----------
263+
cell : NotebookNode cell
264+
Notebook cell being processed
265+
resources : dictionary
266+
Additional resources used in the conversion process. Allows
267+
preprocessors to pass variables into the Jinja engine.
268+
index : int
269+
Index of the cell being processed
270+
"""
271+
if self.caught_exception is None:
272+
try:
273+
return ExecutePreprocessor.preprocess_cell(self, cell, resources, index)
274+
except Exception as ex:
275+
self.caught_exception = ex
276+
return cell, self.resources
277+
278+
248279
# https://nbconvert.readthedocs.io/en/latest/execute_api.html
249280
# https://nbconvert.readthedocs.io/en/latest/nbconvert_library.html
250281
# HTML element we are trying to delete:
@@ -329,13 +360,13 @@ def render_as_html(
329360
f'start render_as_html "{notebook_file_name}" {exec_note} {datetime.datetime.now()}'
330361
)
331362
if kernel_name is not None:
332-
ep = nbconvert.preprocessors.ExecutePreprocessor(
363+
ep = OurExecutor(
333364
timeout=timeout, kernel_name=kernel_name
334365
)
335366
else:
336-
ep = nbconvert.preprocessors.ExecutePreprocessor(timeout=timeout)
367+
ep = OurExecutor(timeout=timeout)
337368
nb_res, nb_resources = ep.preprocess(nb)
338-
html_exporter = nbconvert.HTMLExporter(exclude_input=exclude_input)
369+
html_exporter = HTMLExporter(exclude_input=exclude_input)
339370
html_body, html_resources = html_exporter.from_notebook_node(nb_res)
340371
if exclude_input and (prompt_strip_regexp is not None):
341372
# strip output prompts
@@ -350,6 +381,7 @@ def render_as_html(
350381
assert have_pdf_kit
351382
pdf_name = html_name.removesuffix('.html') + '.pdf'
352383
pdfkit.from_string(html_body, pdf_name)
384+
caught = ep.caught_exception
353385
except Exception as e:
354386
caught = e
355387
if caught is not None:
@@ -456,25 +488,26 @@ def __repr__(self) -> str:
456488
return self.__str__()
457489

458490

459-
def job_fn(arg: JTask) -> None:
491+
def job_fn(arg: JTask):
460492
"""
461-
Function to run a JTask job
493+
Function to run a JTask job. Exceptions pass through
462494
"""
463495
assert isinstance(arg, JTask)
464496
# render notebook
465-
arg.render_as_html()
497+
return arg.render_as_html()
466498

467499

468-
def job_fn_eat_exception(arg: JTask) -> None:
500+
def job_fn_eat_exception(arg: JTask):
469501
"""
470-
Function to run a JTask job, eating any exception
502+
Function to run a JTask job, catching any exception and returning it as a value
471503
"""
472504
assert isinstance(arg, JTask)
473505
# render notebook
474506
try:
475-
arg.render_as_html()
507+
return arg.render_as_html()
476508
except Exception as e:
477509
print(f"{arg} caught {e}")
510+
return (arg, e)
478511

479512

480513
def run_pool(
@@ -483,7 +516,7 @@ def run_pool(
483516
njobs: int = 4,
484517
verbose: bool = True,
485518
stop_on_error: bool = True,
486-
) -> None:
519+
) -> List:
487520
"""
488521
Run a pool of tasks.
489522
@@ -506,7 +539,7 @@ def run_pool(
506539
# https://stackoverflow.com/a/25791961/6901725
507540
with Pool(njobs) as pool:
508541
try:
509-
list(pool.imap_unordered(job_fn, tasks)) # list is forcing iteration over tasks for side-effects
542+
res = list(pool.imap_unordered(job_fn, tasks)) # list is forcing iteration over tasks for side-effects
510543
except Exception:
511544
if verbose:
512545
sys.stdout.flush()
@@ -521,4 +554,5 @@ def run_pool(
521554
else:
522555
# simple way, but doesn't exit until all jobs succeed or fail
523556
with Pool(njobs) as pool:
524-
pool.map(job_fn_eat_exception, tasks)
557+
res = list(pool.map(job_fn_eat_exception, tasks))
558+
return res
382 Bytes
Binary file not shown.

pkg/dist/wvpy-0.4.1.tar.gz

381 Bytes
Binary file not shown.

pkg/docs/search.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)