docs for muutils v0.6.20
View Source on GitHub

muutils.nbutils.convert_ipynb_to_script

fast conversion of Jupyter Notebooks to scripts, with some basic and hacky filtering and formatting.


  1"""fast conversion of Jupyter Notebooks to scripts, with some basic and hacky filtering and formatting."""
  2
  3from __future__ import annotations
  4
  5import argparse
  6import json
  7import os
  8import sys
  9import typing
 10import warnings
 11
 12from muutils.spinner import SpinnerContext
 13
 14DISABLE_PLOTS: dict[str, list[str]] = {
 15    "matplotlib": [
 16        """
 17# ------------------------------------------------------------
 18# Disable matplotlib plots, done during processing by `convert_ipynb_to_script.py`
 19import matplotlib.pyplot as plt
 20plt.show = lambda: None
 21# ------------------------------------------------------------
 22"""
 23    ],
 24    "circuitsvis": [
 25        """
 26# ------------------------------------------------------------
 27# Disable circuitsvis plots, done during processing by `convert_ipynb_to_script.py`
 28from circuitsvis.utils.convert_props import PythonProperty, convert_props
 29from circuitsvis.utils.render import RenderedHTML, render, render_cdn, render_local
 30
 31def new_render(
 32    react_element_name: str,
 33    **kwargs: PythonProperty
 34) -> RenderedHTML:
 35    "return a visualization as raw HTML"
 36    local_src = render_local(react_element_name, **kwargs)
 37    cdn_src = render_cdn(react_element_name, **kwargs)
 38    # return as string instead of RenderedHTML for CI
 39    return str(RenderedHTML(local_src, cdn_src))
 40
 41render = new_render
 42# ------------------------------------------------------------
 43"""
 44    ],
 45    "muutils": [
 46        """import muutils.nbutils.configure_notebook as nb_conf
 47nb_conf.CONVERSION_PLOTMODE_OVERRIDE = "ignore"
 48"""
 49    ],
 50}
 51
 52DISABLE_PLOTS_WARNING: list[str] = [
 53    """
 54# ------------------------------------------------------------
 55# WARNING: this script is auto-generated by `convert_ipynb_to_script.py`
 56# showing plots has been disabled, so this is presumably in a temp dict for CI or something
 57# so don't modify this code, it will be overwritten!
 58# ------------------------------------------------------------
 59""".lstrip()
 60]
 61
 62
 63def disable_plots_in_script(script_lines: list[str]) -> list[str]:
 64    """Disable plots in a script by adding cursed things after the import statements"""
 65    result_str_TEMP: str = "\n\n".join(script_lines)
 66    script_lines_new: list[str] = script_lines
 67
 68    if "muutils" in result_str_TEMP:
 69        script_lines_new = DISABLE_PLOTS["muutils"] + script_lines_new
 70
 71    if "matplotlib" in result_str_TEMP:
 72        assert (
 73            "import matplotlib.pyplot as plt" in result_str_TEMP
 74        ), "matplotlib.pyplot must be imported as plt"
 75
 76        # find the last import statement involving matplotlib, and the first line that uses plt
 77        mpl_last_import_index: int = -1
 78        mpl_first_usage_index: int = -1
 79        for i, line in enumerate(script_lines_new):
 80            if "matplotlib" in line and (("import" in line) or ("from" in line)):
 81                mpl_last_import_index = i
 82
 83            if "configure_notebook" in line:
 84                mpl_last_import_index = i
 85
 86            if "plt." in line:
 87                mpl_first_usage_index = i
 88
 89        assert (
 90            mpl_last_import_index != -1
 91        ), f"matplotlib imports not found! see line {mpl_last_import_index}"
 92        if mpl_first_usage_index != -1:
 93            assert (
 94                mpl_first_usage_index > mpl_last_import_index
 95            ), f"matplotlib plots created before import! see lines {mpl_first_usage_index}, {mpl_last_import_index}"
 96        else:
 97            warnings.warn(
 98                "could not find where matplotlib is used, plot disabling might not work!"
 99            )
100
101        # insert the cursed things
102        script_lines_new = (
103            script_lines_new[: mpl_last_import_index + 1]
104            + DISABLE_PLOTS["matplotlib"]
105            + script_lines_new[mpl_last_import_index + 1 :]
106        )
107        result_str_TEMP = "\n\n".join(script_lines_new)
108
109    if "circuitsvis" in result_str_TEMP:
110        # find the last import statement involving circuitsvis, and the first line that uses it
111        cirv_last_import_index: int = -1
112        cirv_first_usage_index: int = -1
113
114        for i, line in enumerate(script_lines_new):
115            if "circuitsvis" in line:
116                if (("import" in line) or ("from" in line)) and "circuitsvis" in line:
117                    cirv_last_import_index = i
118                else:
119                    cirv_first_usage_index = i
120
121                if "configure_notebook" in line:
122                    mpl_last_import_index = i
123
124                if "render" in line:
125                    cirv_first_usage_index = i
126
127        assert (
128            cirv_last_import_index != -1
129        ), f"circuitsvis imports not found! see line {cirv_last_import_index}"
130        if cirv_first_usage_index != -1:
131            assert (
132                cirv_first_usage_index > cirv_last_import_index
133            ), f"circuitsvis plots created before import! see lines {cirv_first_usage_index}, {cirv_last_import_index}"
134        else:
135            warnings.warn(
136                "could not find where circuitsvis is used, plot disabling might not work!"
137            )
138
139        # insert the cursed things
140        script_lines_new = (
141            script_lines_new[: cirv_last_import_index + 1]
142            + DISABLE_PLOTS["circuitsvis"]
143            + script_lines_new[cirv_last_import_index + 1 :]
144        )
145        result_str_TEMP = "\n\n".join(script_lines_new)
146
147    return script_lines_new
148
149
150def convert_ipynb(
151    notebook: dict,
152    strip_md_cells: bool = False,
153    header_comment: str = r"#%%",
154    disable_plots: bool = False,
155    filter_out_lines: str | typing.Sequence[str] = (
156        "%",
157        "!",
158    ),  # ignore notebook magic commands and shell commands
159) -> str:
160    """Convert Jupyter Notebook to a script, doing some basic filtering and formatting.
161
162    # Arguments
163        - `notebook: dict`: Jupyter Notebook loaded as json.
164        - `strip_md_cells: bool = False`: Remove markdown cells from the output script.
165        - `header_comment: str = r'#%%'`: Comment string to separate cells in the output script.
166        - `disable_plots: bool = False`: Disable plots in the output script.
167        - `filter_out_lines: str|typing.Sequence[str] = ('%', '!')`: comment out lines starting with these strings (in code blocks).
168            if a string is passed, it will be split by char and each char will be treated as a separate filter.
169
170    # Returns
171        - `str`: Converted script.
172    """
173
174    if isinstance(filter_out_lines, str):
175        filter_out_lines = tuple(filter_out_lines)
176    filter_out_lines_set: set = set(filter_out_lines)
177
178    result: list[str] = []
179
180    all_cells: list[dict] = notebook["cells"]
181
182    for cell in all_cells:
183        cell_type: str = cell["cell_type"]
184
185        if not strip_md_cells and cell_type == "markdown":
186            result.append(f'{header_comment}\n"""\n{"".join(cell["source"])}\n"""')
187        elif cell_type == "code":
188            source: list[str] = cell["source"]
189            if filter_out_lines:
190                source = [
191                    (
192                        f"#{line}"
193                        if any(
194                            line.startswith(filter_prefix)
195                            for filter_prefix in filter_out_lines_set
196                        )
197                        else line
198                    )
199                    for line in source
200                ]
201            result.append(f'{header_comment}\n{"".join(source)}')
202
203    if disable_plots:
204        result = disable_plots_in_script(result)
205        result = DISABLE_PLOTS_WARNING + result
206
207    return "\n\n".join(result)
208
209
210def process_file(
211    in_file: str,
212    out_file: str | None = None,
213    strip_md_cells: bool = False,
214    header_comment: str = r"#%%",
215    disable_plots: bool = False,
216    filter_out_lines: str | typing.Sequence[str] = ("%", "!"),
217):
218    print(f"\tProcessing {in_file}...", file=sys.stderr)
219    assert os.path.exists(in_file), f"File {in_file} does not exist."
220    assert os.path.isfile(in_file), f"Path {in_file} is not a file."
221    assert in_file.endswith(".ipynb"), f"File {in_file} is not a Jupyter Notebook."
222
223    with open(in_file, "r") as file:
224        notebook: dict = json.load(file)
225
226    try:
227        converted_script: str = convert_ipynb(
228            notebook=notebook,
229            strip_md_cells=strip_md_cells,
230            header_comment=header_comment,
231            disable_plots=disable_plots,
232            filter_out_lines=filter_out_lines,
233        )
234    except AssertionError as e:
235        print(f"Error converting {in_file}: {e}", file=sys.stderr)
236        raise e
237
238    if out_file:
239        with open(out_file, "w") as file:
240            file.write(converted_script)
241    else:
242        print(converted_script)
243
244
245def process_dir(
246    input_dir: str,
247    output_dir: str,
248    strip_md_cells: bool = False,
249    header_comment: str = r"#%%",
250    disable_plots: bool = False,
251    filter_out_lines: str | typing.Sequence[str] = ("%", "!"),
252):
253    """Convert all Jupyter Notebooks in a directory to scripts.
254
255    # Arguments
256        - `input_dir: str`: Input directory.
257        - `output_dir: str`: Output directory.
258        - `strip_md_cells: bool = False`: Remove markdown cells from the output script.
259        - `header_comment: str = r'#%%'`: Comment string to separate cells in the output script.
260        - `disable_plots: bool = False`: Disable plots in the output script.
261        - `filter_out_lines: str|typing.Sequence[str] = ('%', '!')`: comment out lines starting with these strings (in code blocks).
262            if a string is passed, it will be split by char and each char will be treated as a separate filter.
263    """
264
265    assert os.path.exists(input_dir), f"Directory {input_dir} does not exist."
266    assert os.path.isdir(input_dir), f"Path {input_dir} is not a directory."
267
268    if not os.path.exists(output_dir):
269        os.makedirs(output_dir, exist_ok=True)
270
271    filenames: list[str] = [
272        fname for fname in os.listdir(input_dir) if fname.endswith(".ipynb")
273    ]
274
275    assert filenames, f"Directory {input_dir} does not contain any Jupyter Notebooks."
276    n_files: int = len(filenames)
277    print(f"Converting {n_files} notebooks:", file=sys.stderr)
278
279    with SpinnerContext(
280        spinner_chars="braille",
281        update_interval=0.01,
282        format_string_when_updated=True,
283        output_stream=sys.stderr,
284    ) as spinner:
285        for idx, fname in enumerate(filenames):
286            spinner.update_value(f"\tConverting {idx+1}/{n_files}: {fname}")
287            in_file: str = os.path.join(input_dir, fname)
288            out_file: str = os.path.join(output_dir, fname.replace(".ipynb", ".py"))
289
290            with open(in_file, "r", encoding="utf-8") as file_in:
291                notebook: dict = json.load(file_in)
292
293            try:
294                converted_script: str = convert_ipynb(
295                    notebook=notebook,
296                    strip_md_cells=strip_md_cells,
297                    header_comment=header_comment,
298                    disable_plots=disable_plots,
299                    filter_out_lines=filter_out_lines,
300                )
301            except AssertionError as e:
302                spinner.stop()
303                raise Exception(f"Error converting {in_file}") from e
304
305            with open(out_file, "w", encoding="utf-8") as file_out:
306                file_out.write(converted_script)
307
308
309if __name__ == "__main__":
310    parser = argparse.ArgumentParser(
311        description="Convert Jupyter Notebook to a script with cell separators."
312    )
313    parser.add_argument(
314        "in_path",
315        type=str,
316        help="Input Jupyter Notebook file (.ipynb) or directory of files.",
317    )
318    parser.add_argument(
319        "--out_file",
320        type=str,
321        help="Output script file. If not specified, the result will be printed to stdout.",
322    )
323    parser.add_argument(
324        "--output_dir", type=str, help="Output directory for converted script files."
325    )
326    parser.add_argument(
327        "--strip_md_cells",
328        action="store_true",
329        help="Remove markdown cells from the output script.",
330    )
331    parser.add_argument(
332        "--header_comment",
333        type=str,
334        default=r"#%%",
335        help="Comment string to separate cells in the output script.",
336    )
337    parser.add_argument(
338        "--disable_plots",
339        action="store_true",
340        help="Disable plots in the output script. Useful for testing in CI.",
341    )
342    parser.add_argument(
343        "--filter_out_lines",
344        type=str,
345        default="%",
346        help="Comment out lines starting with these characters.",
347    )
348
349    args = parser.parse_args()
350
351    if args.output_dir:
352        assert not args.out_file, "Cannot specify both --out_file and --output_dir."
353        process_dir(
354            input_dir=args.in_path,
355            output_dir=args.output_dir,
356            strip_md_cells=args.strip_md_cells,
357            header_comment=args.header_comment,
358            disable_plots=args.disable_plots,
359            filter_out_lines=args.filter_out_lines,
360        )
361
362    else:
363        process_file(
364            in_file=args.in_path,
365            out_file=args.out_file,
366            strip_md_cells=args.strip_md_cells,
367            header_comment=args.header_comment,
368            disable_plots=args.disable_plots,
369            filter_out_lines=args.filter_out_lines,
370        )
371
372
373print("convert_ipynb_to_script.py loaded.")

DISABLE_PLOTS: dict[str, list[str]] = {'matplotlib': ['\n# ------------------------------------------------------------\n# Disable matplotlib plots, done during processing by `convert_ipynb_to_script.py`\nimport matplotlib.pyplot as plt\nplt.show = lambda: None\n# ------------------------------------------------------------\n'], 'circuitsvis': ['\n# ------------------------------------------------------------\n# Disable circuitsvis plots, done during processing by `convert_ipynb_to_script.py`\nfrom circuitsvis.utils.convert_props import PythonProperty, convert_props\nfrom circuitsvis.utils.render import RenderedHTML, render, render_cdn, render_local\n\ndef new_render(\n react_element_name: str,\n **kwargs: PythonProperty\n) -> RenderedHTML:\n "return a visualization as raw HTML"\n local_src = render_local(react_element_name, **kwargs)\n cdn_src = render_cdn(react_element_name, **kwargs)\n # return as string instead of RenderedHTML for CI\n return str(RenderedHTML(local_src, cdn_src))\n\nrender = new_render\n# ------------------------------------------------------------\n'], 'muutils': ['import muutils.nbutils.configure_notebook as nb_conf\nnb_conf.CONVERSION_PLOTMODE_OVERRIDE = "ignore"\n']}
DISABLE_PLOTS_WARNING: list[str] = ["# ------------------------------------------------------------\n# WARNING: this script is auto-generated by `convert_ipynb_to_script.py`\n# showing plots has been disabled, so this is presumably in a temp dict for CI or something\n# so don't modify this code, it will be overwritten!\n# ------------------------------------------------------------\n"]
def disable_plots_in_script(script_lines: list[str]) -> list[str]:
 64def disable_plots_in_script(script_lines: list[str]) -> list[str]:
 65    """Disable plots in a script by adding cursed things after the import statements"""
 66    result_str_TEMP: str = "\n\n".join(script_lines)
 67    script_lines_new: list[str] = script_lines
 68
 69    if "muutils" in result_str_TEMP:
 70        script_lines_new = DISABLE_PLOTS["muutils"] + script_lines_new
 71
 72    if "matplotlib" in result_str_TEMP:
 73        assert (
 74            "import matplotlib.pyplot as plt" in result_str_TEMP
 75        ), "matplotlib.pyplot must be imported as plt"
 76
 77        # find the last import statement involving matplotlib, and the first line that uses plt
 78        mpl_last_import_index: int = -1
 79        mpl_first_usage_index: int = -1
 80        for i, line in enumerate(script_lines_new):
 81            if "matplotlib" in line and (("import" in line) or ("from" in line)):
 82                mpl_last_import_index = i
 83
 84            if "configure_notebook" in line:
 85                mpl_last_import_index = i
 86
 87            if "plt." in line:
 88                mpl_first_usage_index = i
 89
 90        assert (
 91            mpl_last_import_index != -1
 92        ), f"matplotlib imports not found! see line {mpl_last_import_index}"
 93        if mpl_first_usage_index != -1:
 94            assert (
 95                mpl_first_usage_index > mpl_last_import_index
 96            ), f"matplotlib plots created before import! see lines {mpl_first_usage_index}, {mpl_last_import_index}"
 97        else:
 98            warnings.warn(
 99                "could not find where matplotlib is used, plot disabling might not work!"
100            )
101
102        # insert the cursed things
103        script_lines_new = (
104            script_lines_new[: mpl_last_import_index + 1]
105            + DISABLE_PLOTS["matplotlib"]
106            + script_lines_new[mpl_last_import_index + 1 :]
107        )
108        result_str_TEMP = "\n\n".join(script_lines_new)
109
110    if "circuitsvis" in result_str_TEMP:
111        # find the last import statement involving circuitsvis, and the first line that uses it
112        cirv_last_import_index: int = -1
113        cirv_first_usage_index: int = -1
114
115        for i, line in enumerate(script_lines_new):
116            if "circuitsvis" in line:
117                if (("import" in line) or ("from" in line)) and "circuitsvis" in line:
118                    cirv_last_import_index = i
119                else:
120                    cirv_first_usage_index = i
121
122                if "configure_notebook" in line:
123                    mpl_last_import_index = i
124
125                if "render" in line:
126                    cirv_first_usage_index = i
127
128        assert (
129            cirv_last_import_index != -1
130        ), f"circuitsvis imports not found! see line {cirv_last_import_index}"
131        if cirv_first_usage_index != -1:
132            assert (
133                cirv_first_usage_index > cirv_last_import_index
134            ), f"circuitsvis plots created before import! see lines {cirv_first_usage_index}, {cirv_last_import_index}"
135        else:
136            warnings.warn(
137                "could not find where circuitsvis is used, plot disabling might not work!"
138            )
139
140        # insert the cursed things
141        script_lines_new = (
142            script_lines_new[: cirv_last_import_index + 1]
143            + DISABLE_PLOTS["circuitsvis"]
144            + script_lines_new[cirv_last_import_index + 1 :]
145        )
146        result_str_TEMP = "\n\n".join(script_lines_new)
147
148    return script_lines_new

Disable plots in a script by adding cursed things after the import statements

def convert_ipynb( notebook: dict, strip_md_cells: bool = False, header_comment: str = '#%%', disable_plots: bool = False, filter_out_lines: Union[str, Sequence[str]] = ('%', '!')) -> str:
151def convert_ipynb(
152    notebook: dict,
153    strip_md_cells: bool = False,
154    header_comment: str = r"#%%",
155    disable_plots: bool = False,
156    filter_out_lines: str | typing.Sequence[str] = (
157        "%",
158        "!",
159    ),  # ignore notebook magic commands and shell commands
160) -> str:
161    """Convert Jupyter Notebook to a script, doing some basic filtering and formatting.
162
163    # Arguments
164        - `notebook: dict`: Jupyter Notebook loaded as json.
165        - `strip_md_cells: bool = False`: Remove markdown cells from the output script.
166        - `header_comment: str = r'#%%'`: Comment string to separate cells in the output script.
167        - `disable_plots: bool = False`: Disable plots in the output script.
168        - `filter_out_lines: str|typing.Sequence[str] = ('%', '!')`: comment out lines starting with these strings (in code blocks).
169            if a string is passed, it will be split by char and each char will be treated as a separate filter.
170
171    # Returns
172        - `str`: Converted script.
173    """
174
175    if isinstance(filter_out_lines, str):
176        filter_out_lines = tuple(filter_out_lines)
177    filter_out_lines_set: set = set(filter_out_lines)
178
179    result: list[str] = []
180
181    all_cells: list[dict] = notebook["cells"]
182
183    for cell in all_cells:
184        cell_type: str = cell["cell_type"]
185
186        if not strip_md_cells and cell_type == "markdown":
187            result.append(f'{header_comment}\n"""\n{"".join(cell["source"])}\n"""')
188        elif cell_type == "code":
189            source: list[str] = cell["source"]
190            if filter_out_lines:
191                source = [
192                    (
193                        f"#{line}"
194                        if any(
195                            line.startswith(filter_prefix)
196                            for filter_prefix in filter_out_lines_set
197                        )
198                        else line
199                    )
200                    for line in source
201                ]
202            result.append(f'{header_comment}\n{"".join(source)}')
203
204    if disable_plots:
205        result = disable_plots_in_script(result)
206        result = DISABLE_PLOTS_WARNING + result
207
208    return "\n\n".join(result)

Convert Jupyter Notebook to a script, doing some basic filtering and formatting.

Arguments

- `notebook: dict`: Jupyter Notebook loaded as json.
- `strip_md_cells: bool = False`: Remove markdown cells from the output script.
- `header_comment: str = r'#%%'`: Comment string to separate cells in the output script.
- `disable_plots: bool = False`: Disable plots in the output script.
- `filter_out_lines: str|typing.Sequence[str] = ('%', '!')`: comment out lines starting with these strings (in code blocks).
    if a string is passed, it will be split by char and each char will be treated as a separate filter.

Returns

- `str`: Converted script.
def process_file( in_file: str, out_file: str | None = None, strip_md_cells: bool = False, header_comment: str = '#%%', disable_plots: bool = False, filter_out_lines: Union[str, Sequence[str]] = ('%', '!')):
211def process_file(
212    in_file: str,
213    out_file: str | None = None,
214    strip_md_cells: bool = False,
215    header_comment: str = r"#%%",
216    disable_plots: bool = False,
217    filter_out_lines: str | typing.Sequence[str] = ("%", "!"),
218):
219    print(f"\tProcessing {in_file}...", file=sys.stderr)
220    assert os.path.exists(in_file), f"File {in_file} does not exist."
221    assert os.path.isfile(in_file), f"Path {in_file} is not a file."
222    assert in_file.endswith(".ipynb"), f"File {in_file} is not a Jupyter Notebook."
223
224    with open(in_file, "r") as file:
225        notebook: dict = json.load(file)
226
227    try:
228        converted_script: str = convert_ipynb(
229            notebook=notebook,
230            strip_md_cells=strip_md_cells,
231            header_comment=header_comment,
232            disable_plots=disable_plots,
233            filter_out_lines=filter_out_lines,
234        )
235    except AssertionError as e:
236        print(f"Error converting {in_file}: {e}", file=sys.stderr)
237        raise e
238
239    if out_file:
240        with open(out_file, "w") as file:
241            file.write(converted_script)
242    else:
243        print(converted_script)
def process_dir( input_dir: str, output_dir: str, strip_md_cells: bool = False, header_comment: str = '#%%', disable_plots: bool = False, filter_out_lines: Union[str, Sequence[str]] = ('%', '!')):
246def process_dir(
247    input_dir: str,
248    output_dir: str,
249    strip_md_cells: bool = False,
250    header_comment: str = r"#%%",
251    disable_plots: bool = False,
252    filter_out_lines: str | typing.Sequence[str] = ("%", "!"),
253):
254    """Convert all Jupyter Notebooks in a directory to scripts.
255
256    # Arguments
257        - `input_dir: str`: Input directory.
258        - `output_dir: str`: Output directory.
259        - `strip_md_cells: bool = False`: Remove markdown cells from the output script.
260        - `header_comment: str = r'#%%'`: Comment string to separate cells in the output script.
261        - `disable_plots: bool = False`: Disable plots in the output script.
262        - `filter_out_lines: str|typing.Sequence[str] = ('%', '!')`: comment out lines starting with these strings (in code blocks).
263            if a string is passed, it will be split by char and each char will be treated as a separate filter.
264    """
265
266    assert os.path.exists(input_dir), f"Directory {input_dir} does not exist."
267    assert os.path.isdir(input_dir), f"Path {input_dir} is not a directory."
268
269    if not os.path.exists(output_dir):
270        os.makedirs(output_dir, exist_ok=True)
271
272    filenames: list[str] = [
273        fname for fname in os.listdir(input_dir) if fname.endswith(".ipynb")
274    ]
275
276    assert filenames, f"Directory {input_dir} does not contain any Jupyter Notebooks."
277    n_files: int = len(filenames)
278    print(f"Converting {n_files} notebooks:", file=sys.stderr)
279
280    with SpinnerContext(
281        spinner_chars="braille",
282        update_interval=0.01,
283        format_string_when_updated=True,
284        output_stream=sys.stderr,
285    ) as spinner:
286        for idx, fname in enumerate(filenames):
287            spinner.update_value(f"\tConverting {idx+1}/{n_files}: {fname}")
288            in_file: str = os.path.join(input_dir, fname)
289            out_file: str = os.path.join(output_dir, fname.replace(".ipynb", ".py"))
290
291            with open(in_file, "r", encoding="utf-8") as file_in:
292                notebook: dict = json.load(file_in)
293
294            try:
295                converted_script: str = convert_ipynb(
296                    notebook=notebook,
297                    strip_md_cells=strip_md_cells,
298                    header_comment=header_comment,
299                    disable_plots=disable_plots,
300                    filter_out_lines=filter_out_lines,
301                )
302            except AssertionError as e:
303                spinner.stop()
304                raise Exception(f"Error converting {in_file}") from e
305
306            with open(out_file, "w", encoding="utf-8") as file_out:
307                file_out.write(converted_script)

Convert all Jupyter Notebooks in a directory to scripts.

Arguments

- `input_dir: str`: Input directory.
- `output_dir: str`: Output directory.
- `strip_md_cells: bool = False`: Remove markdown cells from the output script.
- `header_comment: str = r'#%%'`: Comment string to separate cells in the output script.
- `disable_plots: bool = False`: Disable plots in the output script.
- `filter_out_lines: str|typing.Sequence[str] = ('%', '!')`: comment out lines starting with these strings (in code blocks).
    if a string is passed, it will be split by char and each char will be treated as a separate filter.