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}")
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
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
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
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
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
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
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
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__()
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
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}")