-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial Shiny webapp with minimal testing and code generation demo
- Loading branch information
Showing
28 changed files
with
1,211 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[run] | ||
# All files under source are checked, even if not otherwise referenced. | ||
source = . | ||
|
||
omit = | ||
# TODO | ||
app.py | ||
|
||
# More strict: Check transitions between lines, not just individual lines. | ||
# TODO: branch = True | ||
|
||
[report] | ||
show_missing = True | ||
skip_covered = True | ||
fail_under = 100 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[flake8] | ||
exclude = .git,.venv,__pycache__ | ||
|
||
# Config recommended by black: | ||
# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#bugbear | ||
max-line-length = 80 | ||
extend-select = B950 | ||
extend-ignore = E203,E501,E701 | ||
|
||
per-file-ignores = | ||
# Ignore undefined names | ||
*/templates/*:F821,F401 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
name: Test | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-22.04 | ||
strategy: | ||
matrix: | ||
python-version: | ||
- '3.9' | ||
- '3.12' | ||
|
||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v4 | ||
|
||
- name: Set up Python ${{ matrix.python-version }} | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
|
||
- name: Install flit | ||
run: pip install flit | ||
|
||
- name: Install package | ||
run: flit install | ||
|
||
- name: Check CLI | ||
# TODO: This won't catch most missing dependencies. | ||
run: dp-creator-ii --help | ||
|
||
- name: Install dev dependencies | ||
run: pip install -r requirements-dev.txt | ||
|
||
- name: Install browsers | ||
run: playwright install | ||
|
||
- name: Test | ||
run: coverage run -m pytest -v | ||
|
||
- name: Check coverage | ||
run: coverage report |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
[mypy] | ||
exclude = '\.venv' | ||
|
||
# TODO: Ignore undefined names only in templates. | ||
disable_error_code = name-defined |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
repos: | ||
- repo: https://github.com/pre-commit/pre-commit-hooks | ||
rev: v2.3.0 | ||
hooks: | ||
- id: check-yaml | ||
- id: end-of-file-fixer | ||
- id: trailing-whitespace | ||
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster | ||
- repo: https://github.com/psf/black-pre-commit-mirror | ||
rev: 24.8.0 | ||
hooks: | ||
- id: black | ||
# It is recommended to specify the latest version of Python | ||
# supported by your project here, or alternatively use | ||
# pre-commit's default_language_version, see | ||
# https://pre-commit.com/#top_level-default_language_version | ||
language_version: python3.11 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
config.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
"""DP Creator II makes it easier to get started with Differential Privacy.""" | ||
|
||
import os | ||
from pathlib import Path | ||
from argparse import ArgumentParser | ||
import json | ||
|
||
import shiny | ||
|
||
|
||
__version__ = "0.0.1" | ||
|
||
|
||
def get_parser(): | ||
parser = ArgumentParser(description=__doc__) | ||
parser.add_argument( | ||
"--csv", | ||
dest="csv_path", | ||
type=Path, | ||
help="Path to CSV containing private data", | ||
) | ||
parser.add_argument( | ||
"--unit", | ||
dest="unit_of_privacy", | ||
type=int, | ||
help="Unit of privacy: How many rows can an individual contribute?", | ||
) | ||
parser.add_argument( | ||
"--debug", | ||
action="store_true", | ||
help="Use during development for increased logging " | ||
"and auto-reload after code changes", | ||
) | ||
return parser | ||
|
||
|
||
def main(): # pragma: no cover | ||
parser = get_parser() | ||
args = parser.parse_args() | ||
|
||
os.chdir(Path(__file__).parent) # run_app() depends on the CWD. | ||
|
||
# Just setting variables in a plain python module doesn't work: | ||
# The new thread started for the server doesn't see changes. | ||
Path("config.json").write_text( | ||
json.dumps( | ||
{ | ||
"csv_path": str(args.csv_path), | ||
"unit_of_privacy": args.unit_of_privacy, | ||
} | ||
) | ||
) | ||
|
||
run_app_kwargs = ( | ||
{} | ||
if not args.debug | ||
else { | ||
"reload": True, | ||
"log_level": "debug", | ||
} | ||
) | ||
shiny.run_app(launch_browser=True, **run_app_kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import json | ||
from pathlib import Path | ||
|
||
from shiny import App, ui, reactive, render | ||
|
||
from dp_creator_ii.template import make_notebook_py, make_script_py | ||
from dp_creator_ii.converters import convert_py_to_nb | ||
|
||
|
||
def dataset_panel(): | ||
return ui.nav_panel( | ||
"Select Dataset", | ||
"TODO: Pick dataset", | ||
ui.output_text("csv_path_text"), | ||
ui.output_text("unit_of_privacy_text"), | ||
ui.input_action_button("go_to_analysis", "Perform analysis"), | ||
value="dataset_panel", | ||
) | ||
|
||
|
||
def analysis_panel(): | ||
return ui.nav_panel( | ||
"Perform Analysis", | ||
"TODO: Define analysis", | ||
ui.input_action_button("go_to_results", "Download results"), | ||
value="analysis_panel", | ||
) | ||
|
||
|
||
def results_panel(): | ||
return ui.nav_panel( | ||
"Download Results", | ||
"TODO: Download Results", | ||
ui.download_button("download_script", "Download script"), | ||
# TODO: Notebook code is badly formatted | ||
# ui.download_button( | ||
# "download_notebook_unexecuted", "Download notebook (unexecuted)" | ||
# ), | ||
# ui.download_button( | ||
# "download_notebook_executed", "Download notebook (executed)" | ||
# ) | ||
value="results_panel", | ||
) | ||
|
||
|
||
app_ui = ui.page_bootstrap( | ||
ui.navset_tab( | ||
dataset_panel(), | ||
analysis_panel(), | ||
results_panel(), | ||
id="top_level_nav", | ||
), | ||
title="DP Creator II", | ||
) | ||
|
||
|
||
def server(input, output, session): | ||
config_path = Path(__file__).parent / "config.json" | ||
config = json.loads(config_path.read_text()) | ||
config_path.unlink() | ||
|
||
csv_path = reactive.value(config["csv_path"]) | ||
unit_of_privacy = reactive.value(config["unit_of_privacy"]) | ||
|
||
@render.text | ||
def csv_path_text(): | ||
return str(csv_path.get()) | ||
|
||
@render.text | ||
def unit_of_privacy_text(): | ||
return str(unit_of_privacy.get()) | ||
|
||
@reactive.effect | ||
@reactive.event(input.go_to_analysis) | ||
def go_to_analysis(): | ||
ui.update_navs("top_level_nav", selected="analysis_panel") | ||
|
||
@reactive.effect | ||
@reactive.event(input.go_to_results) | ||
def go_to_results(): | ||
ui.update_navs("top_level_nav", selected="results_panel") | ||
|
||
@render.download( | ||
filename="dp-creator-script.py", | ||
media_type="text/x-python", | ||
) | ||
async def download_script(): | ||
script_py = make_script_py( | ||
unit=1, | ||
loss=1, | ||
weights=[1], | ||
) | ||
yield script_py | ||
|
||
@render.download( | ||
filename="dp-creator-notebook.ipynb", | ||
media_type="application/x-ipynb+json", | ||
) | ||
async def download_notebook_unexecuted(): | ||
notebook_py = make_notebook_py( | ||
csv_path="todo.csv", | ||
unit=1, | ||
loss=1, | ||
weights=[1], | ||
) | ||
notebook_nb = convert_py_to_nb(notebook_py) | ||
yield notebook_nb | ||
|
||
@render.download( | ||
filename="dp-creator-notebook-executed.ipynb", | ||
media_type="application/x-ipynb+json", | ||
) | ||
async def download_notebook_executed(): | ||
notebook_py = make_notebook_py( | ||
csv_path="todo.csv", | ||
unit=1, | ||
loss=1, | ||
weights=[1], | ||
) | ||
notebook_nb = convert_py_to_nb(notebook_py, execute=True) | ||
yield notebook_nb | ||
|
||
|
||
app = App(app_ui, server) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from pathlib import Path | ||
from tempfile import TemporaryDirectory | ||
import subprocess | ||
|
||
|
||
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: | ||
Not ideal, but only the CLI is documented well. | ||
""" | ||
with TemporaryDirectory() as temp_dir: | ||
temp_dir_path = Path(temp_dir) | ||
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 | ||
) | ||
try: | ||
subprocess.run(argv, check=True) | ||
except subprocess.CalledProcessError: # pragma: no cover | ||
if not execute: | ||
raise | ||
# Install kernel if missing | ||
# TODO: Is there a better way to do this? | ||
subprocess.run( | ||
"python -m ipykernel install --name kernel_name --user".split(" "), | ||
check=True, | ||
) | ||
subprocess.run(argv, check=True) | ||
|
||
return nb_path.read_text() |
Oops, something went wrong.