Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unit code snip #28

Closed
wants to merge 11 commits into from
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ source = .

omit =
# TODO
app.py
dp_creator_ii/app/*

# More strict: Check transitions between lines, not just individual lines.
# TODO: branch = True
Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Building on what we've learned from [DP Creator](https://github.com/opendp/dpcre
## Usage

```
usage: dp-creator-ii [-h] [--csv CSV_PATH] [--unit UNIT_OF_PRIVACY] [--debug]
usage: dp-creator-ii [-h] [--csv CSV_PATH] [--unit UNIT_OF_PRIVACY]

DP Creator II makes it easier to get started with Differential Privacy.

Expand All @@ -24,8 +24,6 @@ options:
--unit UNIT_OF_PRIVACY
Unit of privacy: How many rows can an individual
contribute?
--debug Use during development for increased logging and auto-
reload after code changes
```


Expand Down
1 change: 0 additions & 1 deletion dp_creator_ii/.gitignore

This file was deleted.

41 changes: 10 additions & 31 deletions dp_creator_ii/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
import os
from pathlib import Path
from argparse import ArgumentParser
import json

import shiny


__version__ = "0.0.1"


def get_parser():
def get_arg_parser():
parser = ArgumentParser(description=__doc__)
parser.add_argument(
"--csv",
Expand All @@ -25,38 +24,18 @@ def get_parser():
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,
}
)
)
# We call parse_args() again inside the app.
# We only call it here so "--help" is handled.
get_arg_parser().parse_args()

run_app_kwargs = (
{}
if not args.debug
else {
"reload": True,
"log_level": "debug",
}
)
# run_app() depends on the CWD.
os.chdir(Path(__file__).parent)

run_app_kwargs = {
"reload": True,
}
shiny.run_app(launch_browser=True, **run_app_kwargs)
23 changes: 23 additions & 0 deletions dp_creator_ii/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from shiny import App, ui

from dp_creator_ii.app import analysis_panel, dataset_panel, results_panel


app_ui = ui.page_bootstrap(
ui.navset_tab(
dataset_panel.dataset_ui(),
analysis_panel.analysis_ui(),
results_panel.results_ui(),
id="top_level_nav",
),
title="DP Creator II",
)


def server(input, output, session):
dataset_panel.dataset_server(input, output, session)
analysis_panel.analysis_server(input, output, session)
results_panel.results_server(input, output, session)


app = App(app_ui, server)
17 changes: 17 additions & 0 deletions dp_creator_ii/app/analysis_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from shiny import ui, reactive


def analysis_ui():
return ui.nav_panel(
"Perform Analysis",
"TODO: Define analysis",
ui.input_action_button("go_to_results", "Download results"),
value="analysis_panel",
)


def analysis_server(input, output, session):
@reactive.effect
@reactive.event(input.go_to_results)
def go_to_results():
ui.update_navs("top_level_nav", selected="results_panel")
45 changes: 45 additions & 0 deletions dp_creator_ii/app/dataset_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from pathlib import Path
import json
from shiny import ui, reactive, render
from dp_creator_ii import get_arg_parser
from htmltools.tags import details, pre, summary


def dataset_ui():
args = get_arg_parser().parse_args()
return ui.nav_panel(
"Select Dataset",
"TODO: Pick dataset",
ui.output_text("csv_path_text"),
ui.output_text("unit_of_privacy_text"),
ui.input_numeric("contributions", "Contributions", args.unit_of_privacy),
details(
summary("Code sample"),
pre(ui.output_text("unit_of_privacy_python")),
),
ui.input_action_button("go_to_analysis", "Perform analysis"),
value="dataset_panel",
)


def dataset_server(input, output, session):
args = get_arg_parser().parse_args()
csv_path = reactive.value(args.csv_path)

@render.text
def csv_path_text():
return str(csv_path.get())

@render.text
def unit_of_privacy_text():
return input.contributions()

@render.text
def unit_of_privacy_python():
contributions = input.contributions()
return f"privacy_unit = dp.unit_of(contributions={contributions})"

@reactive.effect
@reactive.event(input.go_to_analysis)
def go_to_analysis():
ui.update_navs("top_level_nav", selected="analysis_panel")
69 changes: 4 additions & 65 deletions dp_creator_ii/app.py → dp_creator_ii/app/results_panel.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,11 @@
import json
from pathlib import Path

from shiny import App, ui, reactive, render
from shiny import ui, render

from dp_creator_ii import get_arg_parser
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():
def results_ui():
return ui.nav_panel(
"Download Results",
"TODO: Download Results",
Expand All @@ -43,43 +21,7 @@ def 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")

def results_server(input, output, session):
@render.download(
filename="dp-creator-script.py",
media_type="text/x-python",
Expand Down Expand Up @@ -119,6 +61,3 @@ async def download_notebook_executed():
)
notebook_nb = convert_py_to_nb(notebook_py, execute=True)
yield notebook_nb


app = App(app_ui, server)
2 changes: 1 addition & 1 deletion dp_creator_ii/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from shiny.pytest import create_app_fixture


app = create_app_fixture("../app.py")
app = create_app_fixture("../app/__init__.py")


# TODO: Why is incomplete coverage reported here?
Expand Down
2 changes: 1 addition & 1 deletion dp_creator_ii/tests/test_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

def test_help():
help = (
dp_creator_ii.get_parser()
dp_creator_ii.get_arg_parser()
.format_help()
# argparse doesn't actually know the name of the script
# and inserts the name of the running program instead.
Expand Down
Loading