wvpy.jtools

  1import re
  2import datetime
  3import os
  4import nbformat
  5import nbconvert.preprocessors
  6
  7from typing import Optional
  8
  9have_pdf_kit = False
 10try:
 11    import pdfkit
 12    have_pdf_kit = True
 13except ModuleNotFoundError:
 14    pass
 15
 16have_black = False
 17try:
 18    import black
 19    have_black = True
 20except ModuleNotFoundError:
 21    pass
 22
 23
 24# noinspection PyBroadException
 25def pretty_format_python(python_txt: str, *, black_mode=None) -> str:
 26    """
 27    Format Python code, using black.
 28
 29    :param python_txt: Python code
 30    :param black_mode: options for black
 31    :return: formatted Python code
 32    """
 33    assert have_black
 34    assert isinstance(python_txt, str)
 35    formatted_python = python_txt.strip('\n') + '\n'
 36    if len(formatted_python.strip()) > 0:
 37        if black_mode is None:
 38            black_mode = black.FileMode()
 39        try:
 40            formatted_python = black.format_str(formatted_python, mode=black_mode)
 41            formatted_python = formatted_python.strip('\n') + '\n'
 42        except Exception:
 43            pass
 44    return formatted_python
 45
 46
 47def convert_py_code_to_notebook(
 48    text: str,
 49    *,
 50    use_black:bool = False) -> nbformat.notebooknode.NotebookNode:
 51    """
 52    Convert python text to a notebook. 
 53    "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
 54    "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
 55    "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
 56
 57    :param text: Python text to convert.
 58    :param use_black: if True use black to re-format Python code
 59    :return: a notebook 
 60    """
 61    # https://stackoverflow.com/a/23729611/6901725
 62    # https://nbviewer.org/gist/fperez/9716279
 63    assert isinstance(text, str)
 64    assert isinstance(use_black, bool)
 65    lines = text.splitlines()
 66    begin_text_regexp = re.compile(r"^\s*r?((''')|(\"\"\"))\s*begin\s+text\s*$")
 67    end_text_regexp = re.compile(r"^\s*r?((''')|(\"\"\"))\s*#\s*end\s+text\s*$")
 68    end_code_regexp = re.compile(r"(^\s*r?'''\s*end\s+code\s*'''\s*$)|(^\s*r?\"\"\"\s*end\s+code\s*\"\"\"\s*$)")
 69    nbf_v = nbformat.v4
 70    nb = nbf_v.new_notebook()
 71    # run a little code collecting state machine
 72    cells = []
 73    collecting_python = []
 74    collecting_text = None
 75    lines.append(None)  # append an ending sentinel
 76    # scan input
 77    for line in lines:
 78        if line is None:
 79            is_end = True
 80            text_start = False
 81            code_start = False
 82            code_end = False
 83        else:
 84            is_end = False
 85            text_start = begin_text_regexp.match(line)
 86            code_start = end_text_regexp.match(line)
 87            code_end = end_code_regexp.match(line)
 88        if is_end or text_start or code_start or code_end:
 89            if (collecting_python is not None) and (len(collecting_python) > 0):
 90                python_block = ('\n'.join(collecting_python)).strip('\n') + '\n'
 91                if len(python_block.strip()) > 0:
 92                    if use_black and have_black:
 93                        python_block = pretty_format_python(python_block)
 94                    cells.append(nbf_v.new_code_cell(python_block))
 95            if (collecting_text is not None) and (len(collecting_text) > 0):
 96                txt_block = ('\n'.join(collecting_text)).strip('\n') + '\n'
 97                if len(txt_block.strip()) > 0:
 98                    cells.append(nbf_v.new_markdown_cell(txt_block))
 99            collecting_python = None
100            collecting_text = None
101            if not is_end:
102                if text_start:
103                    collecting_text = []
104                else:
105                    collecting_python = []
106        else:
107            if collecting_python is not None:
108                collecting_python.append(line)
109            if collecting_text is not None:
110                collecting_text.append(line)
111    nb['cells'] = cells
112    return nb
113
114
115def prepend_code_cell_to_notebook(
116    nb: nbformat.notebooknode.NotebookNode,
117    *,
118    code_text: str,
119) -> nbformat.notebooknode.NotebookNode:
120    """
121    Prepend a code cell to a Jupyter notebook.
122
123    :param nb: Jupyter notebook to alter
124    :param code_text: Python source code to add
125    :return: new notebook
126    """
127    nbf_v = nbformat.v4
128    nb_out = nbf_v.new_notebook()
129    nb_out['cells'] = [nbf_v.new_code_cell(code_text)] + list(nb.cells)
130    return nb_out
131
132
133def convert_py_file_to_notebook(
134    py_file: str, 
135    *,
136    ipynb_file: str,
137    use_black: bool = False,
138    ) -> None:
139    """
140    Convert python text to a notebook. 
141    "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
142    "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
143    "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
144
145    :param py_file: Path to python source file.
146    :param ipynb_file: Path to notebook result file.
147    :param use_black: if True use black to re-format Python code
148    :return: nothing 
149    """
150    assert isinstance(py_file, str)
151    assert isinstance(ipynb_file, str)
152    assert isinstance(use_black, bool)
153    assert py_file != ipynb_file  # prevent clobber
154    with open(py_file, 'r') as f:
155        text = f.read()
156    nb = convert_py_code_to_notebook(text, use_black=use_black)
157    with open(ipynb_file, 'w') as f:
158        nbformat.write(nb, f)
159
160
161def convert_notebook_code_to_py(
162    nb: nbformat.notebooknode.NotebookNode,
163    *,
164    use_black: bool = False,
165    ) -> str:
166    """
167    Convert ipython notebook inputs to a py code. 
168    "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
169    "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
170    "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
171
172    :param nb: notebook
173    :param use_black: if True use black to re-format Python code
174    :return: Python source code
175    """
176    assert isinstance(use_black, bool)
177    res = []
178    code_needs_end = False
179    for cell in nb.cells:
180        if len(cell.source.strip()) > 0:
181            if cell.cell_type == 'code':
182                if code_needs_end:
183                    res.append('\n"""end code"""\n')
184                py_text = cell.source.strip('\n') + '\n'
185                if use_black and have_black:
186                    py_text = pretty_format_python(py_text)
187                res.append(py_text)
188                code_needs_end = True
189            else:
190                res.append('\n""" begin text')
191                res.append(cell.source.strip('\n'))
192                res.append('"""  # end text\n')
193                code_needs_end = False
194    res_text = '\n' + ('\n'.join(res)).strip('\n') + '\n\n'
195    return res_text
196
197
198def convert_notebook_file_to_py(
199    ipynb_file: str,
200    *,  
201    py_file: str,
202    use_black: bool = False,
203    ) -> None:
204    """
205    Convert ipython notebook inputs to a py file. 
206    "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
207    "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
208    "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
209
210    :param ipynb_file: Path to notebook input file.
211    :param py_file: Path to python result file.
212    :param use_black: if True use black to re-format Python code
213    :return: nothing
214    """
215    assert isinstance(py_file, str)
216    assert isinstance(ipynb_file, str)
217    assert isinstance(use_black, bool)
218    assert py_file != ipynb_file  # prevent clobber
219    with open(ipynb_file, "rb") as f:
220        nb = nbformat.read(f, as_version=4)
221    py_source = convert_notebook_code_to_py(nb, use_black=use_black)
222    with open(py_file, 'w') as f:
223        f.write(py_source)        
224
225
226# https://nbconvert.readthedocs.io/en/latest/execute_api.html
227# https://nbconvert.readthedocs.io/en/latest/nbconvert_library.html
228# HTML element we are trying to delete: 
229#   <div class="jp-OutputPrompt jp-OutputArea-prompt">Out[5]:</div>
230def render_as_html(
231    notebook_file_name: str,
232    *,
233    output_suffix: Optional[str] = None,
234    timeout:int = 60000,
235    kernel_name: Optional[str] = None,
236    verbose: bool = True,
237    init_code: Optional[str] = None,
238    exclude_input: bool = False,
239    prompt_strip_regexp: Optional[str] = r'<\s*div\s+class\s*=\s*"jp-OutputPrompt[^<>]*>[^<>]*Out[^<>]*<\s*/div\s*>',
240    convert_to_pdf: bool = False,
241) -> None:
242    """
243    Render a Jupyter notebook in the current directory as HTML.
244    Exceptions raised in the rendering notebook are allowed to pass trough.
245
246    :param notebook_file_name: name of source file, must end with .ipynb or .py (or type gotten from file system)
247    :param output_suffix: optional name to add to result name
248    :param timeout: Maximum time in seconds each notebook cell is allowed to run.
249                    passed to nbconvert.preprocessors.ExecutePreprocessor.
250    :param kernel_name: Jupyter kernel to use. passed to nbconvert.preprocessors.ExecutePreprocessor.
251    :param verbose logical, if True print while running 
252    :param init_code: Python init code for first cell
253    :param exclude_input: if True, exclude input cells
254    :param prompt_strip_regexp: regexp to strip prompts, only used if exclude_input is True
255    :param convert_to_pdf: if True convert HTML to PDF, and delete HTML
256    :return: None
257    """
258    assert isinstance(notebook_file_name, str)
259    assert isinstance(convert_to_pdf, bool)
260    # deal with no suffix case
261    if (not notebook_file_name.endswith(".ipynb")) and (not notebook_file_name.endswith(".py")):
262        py_name = notebook_file_name + ".py"
263        py_exists = os.path.exists(py_name)
264        ipynb_name = notebook_file_name + ".ipynb"
265        ipynb_exists = os.path.exists(ipynb_name)
266        if (py_exists + ipynb_exists) != 1:
267            raise ValueError('{ipynb_exists}: if file suffix is not specified then exactly one of .py or .ipynb file must exist')
268        if ipynb_exists:
269            notebook_file_name = notebook_file_name + '.ipynb'
270        else:
271            notebook_file_name = notebook_file_name + '.py'
272    # get the input
273    if notebook_file_name.endswith(".ipynb"):
274        suffix = ".ipynb"
275        with open(notebook_file_name, "rb") as f:
276            nb = nbformat.read(f, as_version=4)
277    elif notebook_file_name.endswith(".py"):
278        suffix = ".py"
279        with open(notebook_file_name, 'r') as f:
280            text = f.read()
281        nb = convert_py_code_to_notebook(text)
282    else:
283        raise ValueError('{ipynb_exists}: file must end with .py or .ipynb')
284    # do the conversion
285    if init_code is not None:
286        assert isinstance(init_code, str)
287        nb = prepend_code_cell_to_notebook(
288            nb, 
289            code_text=f'\n\n{init_code}\n\n')
290    html_name = os.path.basename(notebook_file_name)
291    html_name = html_name.removesuffix(suffix)
292    exec_note = ""
293    if output_suffix is not None:
294        assert isinstance(output_suffix, str)
295        html_name = html_name + output_suffix
296        exec_note = f'"{output_suffix}"'
297    html_name = html_name + ".html"
298    try:
299        os.remove(html_name)
300    except FileNotFoundError:
301        pass
302    caught = None
303    try:
304        if verbose:
305            print(
306                f'start render_as_html "{notebook_file_name}" {exec_note} {datetime.datetime.now()}'
307            )
308        if kernel_name is not None:
309            ep = nbconvert.preprocessors.ExecutePreprocessor(
310                timeout=timeout, kernel_name=kernel_name
311            )
312        else:
313            ep = nbconvert.preprocessors.ExecutePreprocessor(timeout=timeout)
314        nb_res, nb_resources = ep.preprocess(nb)
315        html_exporter = nbconvert.HTMLExporter(exclude_input=exclude_input)
316        html_body, html_resources = html_exporter.from_notebook_node(nb_res)
317        if exclude_input and (prompt_strip_regexp is not None):
318            # strip output prompts
319            html_body = re.sub(
320                prompt_strip_regexp,
321                ' ',
322                html_body)
323        if not convert_to_pdf:
324            with open(html_name, "wt") as f:
325                f.write(html_body)
326        else:
327            assert have_pdf_kit
328            pdf_name = html_name.removesuffix('.html') + '.pdf'
329            pdfkit.from_string(html_body, pdf_name)
330    except Exception as e:
331        caught = e
332    if caught is not None:
333        raise caught
334    if verbose:
335        print(f'\tdone render_as_html "{html_name}" {datetime.datetime.now()}')
336
337
338class JTask:
339    def __init__(
340        self,
341        sheet_name: str,
342        output_suffix: Optional[str] = None,
343        exclude_input: bool = True,
344        init_code: Optional[str] = None,
345        path_prefix: str = "",
346    ) -> None:
347        assert isinstance(sheet_name, str)
348        assert isinstance(output_suffix, (str, type(None)))
349        assert isinstance(exclude_input, bool)
350        assert isinstance(init_code, (str, type(None)))
351        assert isinstance(path_prefix, str)
352        self.sheet_name = sheet_name
353        self.output_suffix = output_suffix
354        self.exclude_input = exclude_input
355        self.init_code = init_code
356        self.path_prefix = path_prefix
357
358    def __str__(self) -> str:
359        return f'JTask(sheet_name="{self.sheet_name}", output_suffix="{self.output_suffix}", exclude_input="{self.exclude_input}", init_code="""{self.init_code}""", path_prefix="{self.path_prefix}")'
360
361    def __repr__(self) -> str:
362        return self.__str__()
363
364
365def job_fn(arg: JTask):
366    assert isinstance(arg, JTask)
367    # render notebook
368    try:
369        render_as_html(
370            arg.path_prefix + arg.sheet_name,
371            exclude_input=arg.exclude_input,
372            output_suffix=arg.output_suffix,
373            init_code=arg.init_code,
374        )
375    except Exception as e:
376        print(f"{arg} caught {e}")
def pretty_format_python(python_txt: str, *, black_mode=None) -> str:
28def pretty_format_python(python_txt: str, *, black_mode=None) -> str:
29    """
30    Format Python code, using black.
31
32    :param python_txt: Python code
33    :param black_mode: options for black
34    :return: formatted Python code
35    """
36    assert have_black
37    assert isinstance(python_txt, str)
38    formatted_python = python_txt.strip('\n') + '\n'
39    if len(formatted_python.strip()) > 0:
40        if black_mode is None:
41            black_mode = black.FileMode()
42        try:
43            formatted_python = black.format_str(formatted_python, mode=black_mode)
44            formatted_python = formatted_python.strip('\n') + '\n'
45        except Exception:
46            pass
47    return formatted_python

Format Python code, using black.

Parameters
  • python_txt: Python code
  • black_mode: options for black
Returns

formatted Python code

def convert_py_code_to_notebook( text: str, *, use_black: bool = False) -> nbformat.notebooknode.NotebookNode:
 50def convert_py_code_to_notebook(
 51    text: str,
 52    *,
 53    use_black:bool = False) -> nbformat.notebooknode.NotebookNode:
 54    """
 55    Convert python text to a notebook. 
 56    "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
 57    "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
 58    "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
 59
 60    :param text: Python text to convert.
 61    :param use_black: if True use black to re-format Python code
 62    :return: a notebook 
 63    """
 64    # https://stackoverflow.com/a/23729611/6901725
 65    # https://nbviewer.org/gist/fperez/9716279
 66    assert isinstance(text, str)
 67    assert isinstance(use_black, bool)
 68    lines = text.splitlines()
 69    begin_text_regexp = re.compile(r"^\s*r?((''')|(\"\"\"))\s*begin\s+text\s*$")
 70    end_text_regexp = re.compile(r"^\s*r?((''')|(\"\"\"))\s*#\s*end\s+text\s*$")
 71    end_code_regexp = re.compile(r"(^\s*r?'''\s*end\s+code\s*'''\s*$)|(^\s*r?\"\"\"\s*end\s+code\s*\"\"\"\s*$)")
 72    nbf_v = nbformat.v4
 73    nb = nbf_v.new_notebook()
 74    # run a little code collecting state machine
 75    cells = []
 76    collecting_python = []
 77    collecting_text = None
 78    lines.append(None)  # append an ending sentinel
 79    # scan input
 80    for line in lines:
 81        if line is None:
 82            is_end = True
 83            text_start = False
 84            code_start = False
 85            code_end = False
 86        else:
 87            is_end = False
 88            text_start = begin_text_regexp.match(line)
 89            code_start = end_text_regexp.match(line)
 90            code_end = end_code_regexp.match(line)
 91        if is_end or text_start or code_start or code_end:
 92            if (collecting_python is not None) and (len(collecting_python) > 0):
 93                python_block = ('\n'.join(collecting_python)).strip('\n') + '\n'
 94                if len(python_block.strip()) > 0:
 95                    if use_black and have_black:
 96                        python_block = pretty_format_python(python_block)
 97                    cells.append(nbf_v.new_code_cell(python_block))
 98            if (collecting_text is not None) and (len(collecting_text) > 0):
 99                txt_block = ('\n'.join(collecting_text)).strip('\n') + '\n'
100                if len(txt_block.strip()) > 0:
101                    cells.append(nbf_v.new_markdown_cell(txt_block))
102            collecting_python = None
103            collecting_text = None
104            if not is_end:
105                if text_start:
106                    collecting_text = []
107                else:
108                    collecting_python = []
109        else:
110            if collecting_python is not None:
111                collecting_python.append(line)
112            if collecting_text is not None:
113                collecting_text.append(line)
114    nb['cells'] = cells
115    return nb

Convert python text to a notebook. "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed) "''' # end text" ends text, and starts a new code block (triple double quotes also allowed) "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)

Parameters
  • text: Python text to convert.
  • use_black: if True use black to re-format Python code
Returns

a notebook

def prepend_code_cell_to_notebook( nb: nbformat.notebooknode.NotebookNode, *, code_text: str) -> nbformat.notebooknode.NotebookNode:
118def prepend_code_cell_to_notebook(
119    nb: nbformat.notebooknode.NotebookNode,
120    *,
121    code_text: str,
122) -> nbformat.notebooknode.NotebookNode:
123    """
124    Prepend a code cell to a Jupyter notebook.
125
126    :param nb: Jupyter notebook to alter
127    :param code_text: Python source code to add
128    :return: new notebook
129    """
130    nbf_v = nbformat.v4
131    nb_out = nbf_v.new_notebook()
132    nb_out['cells'] = [nbf_v.new_code_cell(code_text)] + list(nb.cells)
133    return nb_out

Prepend a code cell to a Jupyter notebook.

Parameters
  • nb: Jupyter notebook to alter
  • code_text: Python source code to add
Returns

new notebook

def convert_py_file_to_notebook(py_file: str, *, ipynb_file: str, use_black: bool = False) -> None:
136def convert_py_file_to_notebook(
137    py_file: str, 
138    *,
139    ipynb_file: str,
140    use_black: bool = False,
141    ) -> None:
142    """
143    Convert python text to a notebook. 
144    "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
145    "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
146    "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
147
148    :param py_file: Path to python source file.
149    :param ipynb_file: Path to notebook result file.
150    :param use_black: if True use black to re-format Python code
151    :return: nothing 
152    """
153    assert isinstance(py_file, str)
154    assert isinstance(ipynb_file, str)
155    assert isinstance(use_black, bool)
156    assert py_file != ipynb_file  # prevent clobber
157    with open(py_file, 'r') as f:
158        text = f.read()
159    nb = convert_py_code_to_notebook(text, use_black=use_black)
160    with open(ipynb_file, 'w') as f:
161        nbformat.write(nb, f)

Convert python text to a notebook. "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed) "''' # end text" ends text, and starts a new code block (triple double quotes also allowed) "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)

Parameters
  • py_file: Path to python source file.
  • ipynb_file: Path to notebook result file.
  • use_black: if True use black to re-format Python code
Returns

nothing

def convert_notebook_code_to_py( nb: nbformat.notebooknode.NotebookNode, *, use_black: bool = False) -> str:
164def convert_notebook_code_to_py(
165    nb: nbformat.notebooknode.NotebookNode,
166    *,
167    use_black: bool = False,
168    ) -> str:
169    """
170    Convert ipython notebook inputs to a py code. 
171    "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
172    "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
173    "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
174
175    :param nb: notebook
176    :param use_black: if True use black to re-format Python code
177    :return: Python source code
178    """
179    assert isinstance(use_black, bool)
180    res = []
181    code_needs_end = False
182    for cell in nb.cells:
183        if len(cell.source.strip()) > 0:
184            if cell.cell_type == 'code':
185                if code_needs_end:
186                    res.append('\n"""end code"""\n')
187                py_text = cell.source.strip('\n') + '\n'
188                if use_black and have_black:
189                    py_text = pretty_format_python(py_text)
190                res.append(py_text)
191                code_needs_end = True
192            else:
193                res.append('\n""" begin text')
194                res.append(cell.source.strip('\n'))
195                res.append('"""  # end text\n')
196                code_needs_end = False
197    res_text = '\n' + ('\n'.join(res)).strip('\n') + '\n\n'
198    return res_text

Convert ipython notebook inputs to a py code. "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed) "''' # end text" ends text, and starts a new code block (triple double quotes also allowed) "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)

Parameters
  • nb: notebook
  • use_black: if True use black to re-format Python code
Returns

Python source code

def convert_notebook_file_to_py(ipynb_file: str, *, py_file: str, use_black: bool = False) -> None:
201def convert_notebook_file_to_py(
202    ipynb_file: str,
203    *,  
204    py_file: str,
205    use_black: bool = False,
206    ) -> None:
207    """
208    Convert ipython notebook inputs to a py file. 
209    "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
210    "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
211    "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
212
213    :param ipynb_file: Path to notebook input file.
214    :param py_file: Path to python result file.
215    :param use_black: if True use black to re-format Python code
216    :return: nothing
217    """
218    assert isinstance(py_file, str)
219    assert isinstance(ipynb_file, str)
220    assert isinstance(use_black, bool)
221    assert py_file != ipynb_file  # prevent clobber
222    with open(ipynb_file, "rb") as f:
223        nb = nbformat.read(f, as_version=4)
224    py_source = convert_notebook_code_to_py(nb, use_black=use_black)
225    with open(py_file, 'w') as f:
226        f.write(py_source)        

Convert ipython notebook inputs to a py file. "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed) "''' # end text" ends text, and starts a new code block (triple double quotes also allowed) "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)

Parameters
  • ipynb_file: Path to notebook input file.
  • py_file: Path to python result file.
  • use_black: if True use black to re-format Python code
Returns

nothing

def render_as_html( notebook_file_name: str, *, output_suffix: Optional[str] = None, timeout: int = 60000, kernel_name: Optional[str] = None, verbose: bool = True, init_code: Optional[str] = None, exclude_input: bool = False, prompt_strip_regexp: Optional[str] = '<\\s*div\\s+class\\s*=\\s*"jp-OutputPrompt[^<>]*>[^<>]*Out[^<>]*<\\s*/div\\s*>', convert_to_pdf: bool = False) -> None:
233def render_as_html(
234    notebook_file_name: str,
235    *,
236    output_suffix: Optional[str] = None,
237    timeout:int = 60000,
238    kernel_name: Optional[str] = None,
239    verbose: bool = True,
240    init_code: Optional[str] = None,
241    exclude_input: bool = False,
242    prompt_strip_regexp: Optional[str] = r'<\s*div\s+class\s*=\s*"jp-OutputPrompt[^<>]*>[^<>]*Out[^<>]*<\s*/div\s*>',
243    convert_to_pdf: bool = False,
244) -> None:
245    """
246    Render a Jupyter notebook in the current directory as HTML.
247    Exceptions raised in the rendering notebook are allowed to pass trough.
248
249    :param notebook_file_name: name of source file, must end with .ipynb or .py (or type gotten from file system)
250    :param output_suffix: optional name to add to result name
251    :param timeout: Maximum time in seconds each notebook cell is allowed to run.
252                    passed to nbconvert.preprocessors.ExecutePreprocessor.
253    :param kernel_name: Jupyter kernel to use. passed to nbconvert.preprocessors.ExecutePreprocessor.
254    :param verbose logical, if True print while running 
255    :param init_code: Python init code for first cell
256    :param exclude_input: if True, exclude input cells
257    :param prompt_strip_regexp: regexp to strip prompts, only used if exclude_input is True
258    :param convert_to_pdf: if True convert HTML to PDF, and delete HTML
259    :return: None
260    """
261    assert isinstance(notebook_file_name, str)
262    assert isinstance(convert_to_pdf, bool)
263    # deal with no suffix case
264    if (not notebook_file_name.endswith(".ipynb")) and (not notebook_file_name.endswith(".py")):
265        py_name = notebook_file_name + ".py"
266        py_exists = os.path.exists(py_name)
267        ipynb_name = notebook_file_name + ".ipynb"
268        ipynb_exists = os.path.exists(ipynb_name)
269        if (py_exists + ipynb_exists) != 1:
270            raise ValueError('{ipynb_exists}: if file suffix is not specified then exactly one of .py or .ipynb file must exist')
271        if ipynb_exists:
272            notebook_file_name = notebook_file_name + '.ipynb'
273        else:
274            notebook_file_name = notebook_file_name + '.py'
275    # get the input
276    if notebook_file_name.endswith(".ipynb"):
277        suffix = ".ipynb"
278        with open(notebook_file_name, "rb") as f:
279            nb = nbformat.read(f, as_version=4)
280    elif notebook_file_name.endswith(".py"):
281        suffix = ".py"
282        with open(notebook_file_name, 'r') as f:
283            text = f.read()
284        nb = convert_py_code_to_notebook(text)
285    else:
286        raise ValueError('{ipynb_exists}: file must end with .py or .ipynb')
287    # do the conversion
288    if init_code is not None:
289        assert isinstance(init_code, str)
290        nb = prepend_code_cell_to_notebook(
291            nb, 
292            code_text=f'\n\n{init_code}\n\n')
293    html_name = os.path.basename(notebook_file_name)
294    html_name = html_name.removesuffix(suffix)
295    exec_note = ""
296    if output_suffix is not None:
297        assert isinstance(output_suffix, str)
298        html_name = html_name + output_suffix
299        exec_note = f'"{output_suffix}"'
300    html_name = html_name + ".html"
301    try:
302        os.remove(html_name)
303    except FileNotFoundError:
304        pass
305    caught = None
306    try:
307        if verbose:
308            print(
309                f'start render_as_html "{notebook_file_name}" {exec_note} {datetime.datetime.now()}'
310            )
311        if kernel_name is not None:
312            ep = nbconvert.preprocessors.ExecutePreprocessor(
313                timeout=timeout, kernel_name=kernel_name
314            )
315        else:
316            ep = nbconvert.preprocessors.ExecutePreprocessor(timeout=timeout)
317        nb_res, nb_resources = ep.preprocess(nb)
318        html_exporter = nbconvert.HTMLExporter(exclude_input=exclude_input)
319        html_body, html_resources = html_exporter.from_notebook_node(nb_res)
320        if exclude_input and (prompt_strip_regexp is not None):
321            # strip output prompts
322            html_body = re.sub(
323                prompt_strip_regexp,
324                ' ',
325                html_body)
326        if not convert_to_pdf:
327            with open(html_name, "wt") as f:
328                f.write(html_body)
329        else:
330            assert have_pdf_kit
331            pdf_name = html_name.removesuffix('.html') + '.pdf'
332            pdfkit.from_string(html_body, pdf_name)
333    except Exception as e:
334        caught = e
335    if caught is not None:
336        raise caught
337    if verbose:
338        print(f'\tdone render_as_html "{html_name}" {datetime.datetime.now()}')

Render a Jupyter notebook in the current directory as HTML. Exceptions raised in the rendering notebook are allowed to pass trough.

Parameters
  • notebook_file_name: name of source file, must end with .ipynb or .py (or type gotten from file system)
  • output_suffix: optional name to add to result name
  • timeout: Maximum time in seconds each notebook cell is allowed to run. passed to nbconvert.preprocessors.ExecutePreprocessor.
  • kernel_name: Jupyter kernel to use. passed to nbconvert.preprocessors.ExecutePreprocessor. :param verbose logical, if True print while running
  • init_code: Python init code for first cell
  • exclude_input: if True, exclude input cells
  • prompt_strip_regexp: regexp to strip prompts, only used if exclude_input is True
  • convert_to_pdf: if True convert HTML to PDF, and delete HTML
Returns

None

class JTask:
341class JTask:
342    def __init__(
343        self,
344        sheet_name: str,
345        output_suffix: Optional[str] = None,
346        exclude_input: bool = True,
347        init_code: Optional[str] = None,
348        path_prefix: str = "",
349    ) -> None:
350        assert isinstance(sheet_name, str)
351        assert isinstance(output_suffix, (str, type(None)))
352        assert isinstance(exclude_input, bool)
353        assert isinstance(init_code, (str, type(None)))
354        assert isinstance(path_prefix, str)
355        self.sheet_name = sheet_name
356        self.output_suffix = output_suffix
357        self.exclude_input = exclude_input
358        self.init_code = init_code
359        self.path_prefix = path_prefix
360
361    def __str__(self) -> str:
362        return f'JTask(sheet_name="{self.sheet_name}", output_suffix="{self.output_suffix}", exclude_input="{self.exclude_input}", init_code="""{self.init_code}""", path_prefix="{self.path_prefix}")'
363
364    def __repr__(self) -> str:
365        return self.__str__()
JTask( sheet_name: str, output_suffix: Optional[str] = None, exclude_input: bool = True, init_code: Optional[str] = None, path_prefix: str = '')
342    def __init__(
343        self,
344        sheet_name: str,
345        output_suffix: Optional[str] = None,
346        exclude_input: bool = True,
347        init_code: Optional[str] = None,
348        path_prefix: str = "",
349    ) -> None:
350        assert isinstance(sheet_name, str)
351        assert isinstance(output_suffix, (str, type(None)))
352        assert isinstance(exclude_input, bool)
353        assert isinstance(init_code, (str, type(None)))
354        assert isinstance(path_prefix, str)
355        self.sheet_name = sheet_name
356        self.output_suffix = output_suffix
357        self.exclude_input = exclude_input
358        self.init_code = init_code
359        self.path_prefix = path_prefix
def job_fn(arg: wvpy.jtools.JTask):
368def job_fn(arg: JTask):
369    assert isinstance(arg, JTask)
370    # render notebook
371    try:
372        render_as_html(
373            arg.path_prefix + arg.sheet_name,
374            exclude_input=arg.exclude_input,
375            output_suffix=arg.output_suffix,
376            init_code=arg.init_code,
377        )
378    except Exception as e:
379        print(f"{arg} caught {e}")