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

Better handing for --demo #103

Merged
merged 10 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dp_creator_ii/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""DP Creator II makes it easier to get started with Differential Privacy."""

import shiny
from dp_creator_ii.utils.argparse_helpers import get_csv_contrib
from dp_creator_ii.utils.argparse_helpers import get_csv_contrib_from_cli


__version__ = "0.0.1"
Expand All @@ -10,7 +10,7 @@
def main(): # pragma: no cover
# We only call this here so "--help" is handled,
# and to validate inputs before starting the server.
get_csv_contrib()
get_csv_contrib_from_cli()

shiny.run_app(
app="dp_creator_ii.app",
Expand Down
24 changes: 21 additions & 3 deletions dp_creator_ii/app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from pathlib import Path

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

from dp_creator_ii.utils.argparse_helpers import get_csv_contrib_from_cli
from dp_creator_ii.app import analysis_panel, dataset_panel, results_panel


Expand All @@ -22,8 +23,25 @@ def ctrl_c_reminder(): # pragma: no cover


def server(input, output, session): # pragma: no cover
dataset_panel.dataset_server(input, output, session)
analysis_panel.analysis_server(input, output, session)
(csv_path_from_cli, contributions_from_cli, is_demo) = get_csv_contrib_from_cli()
csv_path = reactive.value(csv_path_from_cli)
contributions = reactive.value(contributions_from_cli)

dataset_panel.dataset_server(
input,
output,
session,
csv_path=csv_path,
contributions=contributions,
is_demo=is_demo,
)
analysis_panel.analysis_server(
input,
output,
session,
csv_path=csv_path,
is_demo=is_demo,
)
results_panel.results_server(input, output, session)
session.on_ended(ctrl_c_reminder)

Expand Down
22 changes: 4 additions & 18 deletions dp_creator_ii/app/analysis_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from dp_creator_ii.app.components.inputs import log_slider
from dp_creator_ii.app.components.column_module import column_ui, column_server
from dp_creator_ii.utils.csv_helper import read_field_names
from dp_creator_ii.utils.argparse_helpers import get_csv_contrib


def analysis_ui():
Expand Down Expand Up @@ -40,11 +39,9 @@ def analysis_ui():
)


def analysis_server(input, output, session): # pragma: no cover
(csv_path, _contributions) = get_csv_contrib()

csv_path_from_cli_value = reactive.value(csv_path)

def analysis_server(
input, output, session, csv_path=None, is_demo=None
): # pragma: no cover
@reactive.effect
def _():
ui.update_checkbox_group(
Expand All @@ -66,20 +63,9 @@ def columns_ui():
for column_id in column_ids
]

@reactive.calc
def csv_path_calc():
csv_path_from_ui = input.csv_path_from_ui()
if csv_path_from_ui is not None:
return csv_path_from_ui[0]["datapath"]
return csv_path_from_cli_value.get()

@render.text
def csv_path():
return csv_path_calc()

@reactive.calc
def csv_fields_calc():
path = csv_path_calc()
path = csv_path()
if path is None:
return None
return read_field_names(path)
Expand Down
10 changes: 10 additions & 0 deletions dp_creator_ii/app/components/outputs.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
from htmltools.tags import details, summary
from shiny import ui
from faicons import icon_svg


def output_code_sample(name_of_render_function):
return details(
summary("Code sample"),
ui.output_code(name_of_render_function),
)


def demo_tooltip(is_demo, text): # pragma: no cover
if is_demo:
return ui.tooltip(
icon_svg("circle-question"),
text,
placement="right",
)
68 changes: 60 additions & 8 deletions dp_creator_ii/app/dataset_panel.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,84 @@
from pathlib import Path

from shiny import ui, reactive, render

from dp_creator_ii.utils.argparse_helpers import get_csv_contrib
from dp_creator_ii.app.components.outputs import output_code_sample
from dp_creator_ii.utils.argparse_helpers import get_csv_contrib_from_cli
from dp_creator_ii.app.components.outputs import output_code_sample, demo_tooltip
from dp_creator_ii.utils.template import make_privacy_unit_block


def dataset_ui():
(_csv_path, contributions) = get_csv_contrib()
(csv_path, contributions, is_demo) = get_csv_contrib_from_cli()
csv_placeholder = None if csv_path is None else Path(csv_path).name

return ui.nav_panel(
"Select Dataset",
ui.input_file("csv_path_from_ui", "Choose CSV file:", accept=[".csv"]),
# Doesn't seem to be possible to preset the actual value,
# but the placeholder string is a good substitute.
ui.input_file(
"csv_path",
["Choose CSV file", ui.output_ui("choose_csv_demo_tooltip_ui")],
accept=[".csv"],
placeholder=csv_placeholder,
),
ui.markdown(
"How many rows of the CSV can one individual contribute to? "
'This is the "unit of privacy" which will be protected.'
),
ui.input_numeric("contributions", "Contributions", contributions),
ui.input_numeric(
"contributions",
["Contributions", ui.output_ui("contributions_demo_tooltip_ui")],
contributions,
),
ui.output_ui("python_tooltip_ui"),
output_code_sample("unit_of_privacy_python"),
ui.input_action_button("go_to_analysis", "Define analysis"),
value="dataset_panel",
)


def dataset_server(input, output, session): # pragma: no cover
def dataset_server(
input, output, session, csv_path=None, contributions=None, is_demo=None
): # pragma: no cover
@reactive.effect
@reactive.event(input.csv_path)
def _on_csv_path_change():
csv_path.set(input.csv_path()[0]["datapath"])

@reactive.effect
@reactive.event(input.contributions)
def _on_contributions_change():
contributions.set(input.contributions())

@render.ui
def choose_csv_demo_tooltip_ui():
return demo_tooltip(
is_demo,
"For the demo, we'll imagine we have the grades "
"on assignments for a class.",
)

@render.ui
def contributions_demo_tooltip_ui():
return demo_tooltip(
is_demo,
"For the demo, we assume that each student "
f"can occur at most {contributions()} times in the dataset. ",
)

@render.ui
def python_tooltip_ui():
return demo_tooltip(
is_demo,
"Along the way, code samples will demonstrate "
"how the information you provide is used in OpenDP, "
"and at the end you can download a notebook "
"for the entire calculation.",
)

@render.code
def unit_of_privacy_python():
contributions = input.contributions()
return make_privacy_unit_block(contributions)
return make_privacy_unit_block(contributions())

@reactive.effect
@reactive.event(input.go_to_analysis)
Expand Down
8 changes: 4 additions & 4 deletions dp_creator_ii/utils/argparse_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def _clip(n, lower, upper):

def _get_demo_csv_contrib():
"""
>>> csv_path, contributions = _get_demo_csv_contrib()
>>> csv_path, contributions, is_demo = _get_demo_csv_contrib()
>>> with open(csv_path, newline="") as csv_handle:
... reader = csv.DictReader(csv_handle)
... reader.fieldnames
Expand Down Expand Up @@ -101,13 +101,13 @@ def _get_demo_csv_contrib():
}
)

return csv_path, contributions
return (csv_path, contributions, True)


def get_csv_contrib(): # pragma: no cover
def get_csv_contrib_from_cli(): # pragma: no cover
args = _get_args()
if args.demo:
if args.csv_path is not None:
warn('"--demo" overrides "--csv" and "--contrib"')
return _get_demo_csv_contrib()
return (args.csv_path, args.contributions)
return (args.csv_path, args.contributions, False)
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ classifiers = ["License :: OSI Approved :: MIT License"]
dynamic = ["version", "description"]
dependencies = [
"shiny",
"faicons",
"matplotlib",
"opendp[polars]",
"jupytext",
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.in
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ ipykernel

# Shiny:
shiny
faicons

# Visualization:
matplotlib
6 changes: 5 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ docutils==0.21.2
# via flit
executing==2.1.0
# via stack-data
faicons==0.2.2
# via -r requirements-dev.in
fastjsonschema==2.20.0
# via nbformat
filelock==3.16.1
Expand All @@ -82,7 +84,9 @@ greenlet==3.0.3
h11==0.14.0
# via uvicorn
htmltools==0.5.3
# via shiny
# via
# faicons
# shiny
identify==2.6.1
# via pre-commit
idna==3.10
Expand Down