diff --git a/dp_creator_ii/converters.py b/dp_creator_ii/converters.py index 6572f08..a9ec6c0 100644 --- a/dp_creator_ii/converters.py +++ b/dp_creator_ii/converters.py @@ -3,7 +3,7 @@ import subprocess -def convert_py_to_nb(python_str): +def convert_py_to_nb(python_str, execute=False): ''' Given Python code as a string, returns a notebook as a string. Calls jupytext as a subprocess: @@ -14,10 +14,14 @@ def convert_py_to_nb(python_str): py_path = temp_dir_path / 'input.py' py_path.write_text(python_str) nb_path = temp_dir_path / 'output.ipynb' + argv = [ + 'jupytext', + '--to', 'ipynb', # Target format + '--output', nb_path.absolute(), # Output + ] + (['--execute'] if execute else []) + [ + py_path.absolute() # Input + ] subprocess.run( - ['jupytext', - '--to', 'ipynb', # Target format - '--output', nb_path.absolute(), # Output - py_path.absolute()], # Input + argv, check=True) return nb_path.read_text() diff --git a/dp_creator_ii/tests/fixtures/fake-executed.ipynb b/dp_creator_ii/tests/fixtures/fake-executed.ipynb new file mode 100644 index 0000000..ade457a --- /dev/null +++ b/dp_creator_ii/tests/fixtures/fake-executed.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d68beaaf", + "metadata": {}, + "source": [ + "Introduction" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c4a24010", + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-24T22:01:27.184792Z", + "iopub.status.busy": "2024-09-24T22:01:27.184540Z", + "iopub.status.idle": "2024-09-24T22:01:27.188766Z", + "shell.execute_reply": "2024-09-24T22:01:27.188362Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4\n" + ] + } + ], + "source": [ + "print(2+2)" + ] + }, + { + "cell_type": "markdown", + "id": "aa6d5643", + "metadata": {}, + "source": [ + "Conclusion" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all" + }, + "kernelspec": { + "display_name": "kernel_name", + "language": "python", + "name": "kernel_name" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/dp_creator_ii/tests/test_converters.py b/dp_creator_ii/tests/test_converters.py index a4d134b..466e093 100644 --- a/dp_creator_ii/tests/test_converters.py +++ b/dp_creator_ii/tests/test_converters.py @@ -3,15 +3,35 @@ from dp_creator_ii.converters import convert_py_to_nb +def norm_nb(nb_str): + normed_nb_str = nb_str + normed_nb_str = re.sub( + r'"id": "[^"]+"', + '"id": "12345678"', + normed_nb_str) + normed_nb_str = re.sub( + r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z', + '2024-01-01T00:00:00.000000Z', + normed_nb_str) + return normed_nb_str + + def test_convert_py_to_nb(): fixtures_path = Path('dp_creator_ii/tests/fixtures') python_str = (fixtures_path / 'fake.py').read_text() actual_nb_str = convert_py_to_nb(python_str) expected_nb_str = (fixtures_path / 'fake.ipynb').read_text() - def norm_nb(nb_str): - normed_nb_str = re.sub(r'"id": "[^"]+"', '"id": "12345678"', nb_str) - return normed_nb_str + normed_actual_nb_str = norm_nb(actual_nb_str) + normed_expected_nb_str = norm_nb(expected_nb_str) + assert normed_actual_nb_str == normed_expected_nb_str + + +def test_convert_py_to_nb_execute(): + fixtures_path = Path('dp_creator_ii/tests/fixtures') + python_str = (fixtures_path / 'fake.py').read_text() + actual_nb_str = convert_py_to_nb(python_str, execute=True) + expected_nb_str = (fixtures_path / 'fake-executed.ipynb').read_text() normed_actual_nb_str = norm_nb(actual_nb_str) normed_expected_nb_str = norm_nb(expected_nb_str) diff --git a/pyproject.toml b/pyproject.toml index 2128216..59751d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,9 @@ dependencies = [ "shinywidgets", "opendp[polars]", "jupytext", + "jupyter-client", + "nbconvert", + "ipykernel", ] [project.scripts] diff --git a/requirements-dev.in b/requirements-dev.in index 6ca0900..6d68d26 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -23,6 +23,11 @@ scipy<1.14 # Conversion: jupytext +jupyter-client +nbconvert +ipykernel +# May also require: +# python -m ipykernel install --name kernel_name --user # Shiny: shiny diff --git a/requirements-dev.txt b/requirements-dev.txt index 3842683..f28fcd1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,6 +10,8 @@ anyio==4.4.0 # watchfiles appdirs==1.4.4 # via shiny +appnope==0.1.4 + # via ipykernel asgiref==3.8.1 # via shiny asttokens==2.4.1 @@ -20,6 +22,10 @@ attrs==24.2.0 # referencing autoflake==2.3.1 # via -r requirements-dev.in +beautifulsoup4==4.12.3 + # via nbconvert +bleach==6.1.0 + # via nbconvert build==1.2.2 # via pip-tools certifi==2024.8.30 @@ -32,11 +38,17 @@ click==8.1.7 # shiny # uvicorn comm==0.2.2 - # via ipywidgets + # via + # ipykernel + # ipywidgets coverage==7.6.1 # via -r requirements-dev.in +debugpy==1.8.6 + # via ipykernel decorator==5.1.1 # via ipython +defusedxml==0.7.1 + # via nbconvert docutils==0.21.2 # via flit executing==2.1.0 @@ -59,22 +71,39 @@ idna==3.10 # requests iniconfig==2.0.0 # via pytest +ipykernel==6.29.5 + # via -r requirements-dev.in ipython==8.18.0 - # via ipywidgets + # via + # ipykernel + # ipywidgets ipywidgets==8.1.5 # via shinywidgets jedi==0.19.1 # via ipython +jinja2==3.1.4 + # via nbconvert joblib==1.4.2 # via scikit-learn jsonschema==4.23.0 # via nbformat jsonschema-specifications==2023.12.1 # via jsonschema +jupyter-client==8.6.3 + # via + # -r requirements-dev.in + # ipykernel + # nbclient jupyter-core==5.7.2 # via + # ipykernel + # jupyter-client + # nbclient + # nbconvert # nbformat # shinywidgets +jupyterlab-pygments==0.3.0 + # via nbconvert jupyterlab-widgets==3.0.13 # via ipywidgets jupytext==1.16.4 @@ -86,8 +115,14 @@ markdown-it-py==3.0.0 # jupytext # mdit-py-plugins # shiny +markupsafe==2.1.5 + # via + # jinja2 + # nbconvert matplotlib-inline==0.1.7 - # via ipython + # via + # ipykernel + # ipython mccabe==0.7.0 # via flake8 mdit-py-plugins==0.4.2 @@ -96,12 +131,23 @@ mdit-py-plugins==0.4.2 # shiny mdurl==0.1.2 # via markdown-it-py +mistune==3.0.2 + # via nbconvert mypy==1.11.2 # via -r requirements-dev.in mypy-extensions==1.0.0 # via mypy +nbclient==0.10.0 + # via nbconvert +nbconvert==7.16.4 + # via -r requirements-dev.in nbformat==5.10.4 - # via jupytext + # via + # jupytext + # nbclient + # nbconvert +nest-asyncio==1.6.0 + # via ipykernel numpy==1.26.4 # via # opendp @@ -115,9 +161,13 @@ packaging==24.1 # via # build # htmltools + # ipykernel # jupytext + # nbconvert # pytest # shiny +pandocfilters==1.5.1 + # via nbconvert parso==0.8.4 # via jedi pexpect==4.9.0 @@ -135,6 +185,8 @@ prompt-toolkit==3.0.36 # ipython # questionary # shiny +psutil==6.0.0 + # via ipykernel ptyprocess==0.7.0 # via pexpect pure-eval==0.2.3 @@ -148,7 +200,9 @@ pyflakes==3.2.0 # autoflake # flake8 pygments==2.18.0 - # via ipython + # via + # ipython + # nbconvert pyproject-hooks==1.1.0 # via # build @@ -156,11 +210,17 @@ pyproject-hooks==1.1.0 pytest==8.3.3 # via -r requirements-dev.in python-dateutil==2.9.0.post0 - # via shinywidgets + # via + # jupyter-client + # shinywidgets python-multipart==0.0.9 # via shiny pyyaml==6.0.2 # via jupytext +pyzmq==26.2.0 + # via + # ipykernel + # jupyter-client questionary==2.0.1 # via shiny randomgen==2.0.1 @@ -190,24 +250,37 @@ shinywidgets==0.3.3 six==1.16.0 # via # asttokens + # bleach # python-dateutil sniffio==1.3.1 # via anyio +soupsieve==2.6 + # via beautifulsoup4 stack-data==0.6.3 # via ipython starlette==0.38.5 # via shiny threadpoolctl==3.5.0 # via scikit-learn +tinycss2==1.3.0 + # via nbconvert tomli-w==1.0.0 # via flit +tornado==6.4.1 + # via + # ipykernel + # jupyter-client traitlets==5.14.3 # via # comm + # ipykernel # ipython # ipywidgets + # jupyter-client # jupyter-core # matplotlib-inline + # nbclient + # nbconvert # nbformat typing-extensions==4.12.2 # via @@ -224,6 +297,10 @@ watchfiles==0.24.0 # via shiny wcwidth==0.2.13 # via prompt-toolkit +webencodings==0.5.1 + # via + # bleach + # tinycss2 websockets==13.0.1 # via shiny wheel==0.44.0