Skip to content

Commit

Permalink
Type checking with pyright (#164)
Browse files Browse the repository at this point in the history
* lower and upper more consistently

* one more

* handle bounds/bins/counts the same way

* lots of reactive dicts, but the UI has not changed

* data dump on the results page

* add a pragma: no cover

* reset widget values after checkbox change

* do not clean up values

* put tooltips in labels

* pull warning up to analysis panel. TODO: conditional

* move warning to bottom of list

* analysis definition JSON

* stubs for python

* stub a script on results page

* include column info in generated script

* closer to a runable notebook

* stuck on split_by_weight... maybe a library bug?

* margin stubs

* format python identifiers correctly

* script has gotten longer: does not make sense to check for exact equality

* fix syntactic problems in generated code

* fill in columns, but still WIP

* fix column names; tests pass

* move confidence

* simplify download panel

* add markdown cells

* tidy up

* switch requirements from mypy to pyright

* run pyright; currently fails

* fix pyright errors

* type input, output, session

* rm unused session

* more typing

* enable strict type checking; lots of errors!

* more typing. Add "finish()" for Templates
(Alternative is to implement __buffer__.)

* add a lot of ignores, but type checks pass locally

* I think Optional is needed under 3.9

* Remove strict, and most of the ignores

* tests in code generation
  • Loading branch information
mccalluc authored Nov 21, 2024
1 parent fc2aa18 commit 45cd514
Show file tree
Hide file tree
Showing 20 changed files with 145 additions and 118 deletions.
5 changes: 0 additions & 5 deletions .mypy.ini

This file was deleted.

13 changes: 7 additions & 6 deletions dp_wizard/app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from pathlib import Path
import logging

from shiny import App, ui, reactive
from shiny import App, ui, reactive, Inputs, Outputs, Session

from dp_wizard.utils.argparse_helpers import get_cli_info
from dp_wizard.utils.argparse_helpers import get_cli_info, CLIInfo
from dp_wizard.app import analysis_panel, dataset_panel, results_panel, feedback_panel


Expand All @@ -26,16 +26,17 @@ def ctrl_c_reminder(): # pragma: no cover
print("Session ended (Press CTRL+C to quit)")


def make_server_from_cli_info(cli_info):
def server(input, output, session): # pragma: no cover
csv_path = reactive.value(cli_info.csv_path)
def make_server_from_cli_info(cli_info: CLIInfo):
def server(input: Inputs, output: Outputs, session: Session): # pragma: no cover
cli_csv_path = cli_info.csv_path
csv_path = reactive.value("" if cli_csv_path is None else cli_csv_path)
contributions = reactive.value(cli_info.contributions)

lower_bounds = reactive.value({})
upper_bounds = reactive.value({})
bin_counts = reactive.value({})
weights = reactive.value({})
epsilon = reactive.value(1)
epsilon = reactive.value(1.0)

dataset_panel.dataset_server(
input,
Expand Down
31 changes: 17 additions & 14 deletions dp_wizard/app/analysis_panel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from math import pow
from typing import Iterable, Any

from shiny import ui, reactive, render, req
from shiny import ui, reactive, render, req, Inputs, Outputs, Session

from dp_wizard.app.components.inputs import log_slider
from dp_wizard.app.components.column_module import column_ui, column_server
Expand Down Expand Up @@ -39,7 +40,9 @@ def analysis_ui():
)


def _cleanup_reactive_dict(reactive_dict, keys_to_keep): # pragma: no cover
def _cleanup_reactive_dict(
reactive_dict: reactive.Value[dict[str, Any]], keys_to_keep: Iterable[str]
): # pragma: no cover
reactive_dict_copy = {**reactive_dict()}
keys_to_del = set(reactive_dict_copy.keys()) - set(keys_to_keep)
for key in keys_to_del:
Expand All @@ -48,17 +51,17 @@ def _cleanup_reactive_dict(reactive_dict, keys_to_keep): # pragma: no cover


def analysis_server(
input,
output,
session,
csv_path,
contributions,
is_demo,
lower_bounds,
upper_bounds,
bin_counts,
weights,
epsilon,
input: Inputs,
output: Outputs,
session: Session,
csv_path: reactive.Value[str],
contributions: reactive.Value[int],
is_demo: bool,
lower_bounds: reactive.Value[dict[str, float]],
upper_bounds: reactive.Value[dict[str, float]],
bin_counts: reactive.Value[dict[str, int]],
weights: reactive.Value[dict[str, str]],
epsilon: reactive.Value[float],
): # pragma: no cover
@reactive.calc
def button_enabled():
Expand Down Expand Up @@ -133,7 +136,7 @@ def columns_ui():
"""
)
],
col_widths=col_widths,
col_widths=col_widths, # type: ignore
)
if column_ids
else []
Expand Down
46 changes: 23 additions & 23 deletions dp_wizard/app/components/column_module.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
from logging import info

from shiny import ui, render, module, reactive
from shiny import ui, render, module, reactive, Inputs, Outputs, Session

from dp_wizard.utils.dp_helper import make_confidence_accuracy_histogram
from dp_wizard.utils.shared import plot_histogram
from dp_wizard.utils.code_generators import make_column_config_block
from dp_wizard.app.components.outputs import output_code_sample, demo_tooltip


default_weight = 2
default_weight = "2"

col_widths = {
# Controls stay roughly a constant width;
# Graph expands to fill space.
"sm": (4, 8),
"md": (3, 9),
"lg": (2, 10),
"sm": [4, 8],
"md": [3, 9],
"lg": [2, 10],
}


Expand All @@ -37,9 +37,9 @@ def column_ui(): # pragma: no cover
"weight",
["Weight", ui.output_ui("weight_tooltip_ui")],
choices={
1: "Less accurate",
"1": "Less accurate",
default_weight: "Default",
4: "More accurate",
"4": "More accurate",
},
selected=default_weight,
width=width,
Expand All @@ -50,31 +50,31 @@ def column_ui(): # pragma: no cover
# Make plot smaller than default: about the same size as the other column.
output_code_sample("Column Definition", "column_code"),
],
col_widths=col_widths,
col_widths=col_widths, # type: ignore
)


@module.server
def column_server(
input,
output,
session,
name,
contributions,
epsilon,
lower_bounds,
upper_bounds,
bin_counts,
weights,
is_demo,
input: Inputs,
output: Outputs,
session: Session,
name: str,
contributions: int,
epsilon: float,
lower_bounds: reactive.Value[dict[str, float]],
upper_bounds: reactive.Value[dict[str, float]],
bin_counts: reactive.Value[dict[str, int]],
weights: reactive.Value[dict[str, str]],
is_demo: bool,
): # pragma: no cover
@reactive.effect
def _set_all_inputs():
with reactive.isolate(): # Without isolate, there is an infinite loop.
ui.update_numeric("lower", value=lower_bounds().get(name, 0))
ui.update_numeric("upper", value=upper_bounds().get(name, 10))
ui.update_numeric("bins", value=bin_counts().get(name, 10))
ui.update_numeric("weight", value=weights().get(name, default_weight))
ui.update_numeric("weight", value=int(weights().get(name, default_weight)))

@reactive.effect
@reactive.event(input.lower)
Expand All @@ -89,12 +89,12 @@ def _set_upper():
@reactive.effect
@reactive.event(input.bins)
def _set_bins():
bin_counts.set({**bin_counts(), name: float(input.bins())})
bin_counts.set({**bin_counts(), name: int(input.bins())})

@reactive.effect
@reactive.event(input.weight)
def _set_weight():
weights.set({**weights(), name: float(input.weight())})
weights.set({**weights(), name: input.weight()})

@render.ui
def bounds_tooltip_ui():
Expand Down Expand Up @@ -149,7 +149,7 @@ def column_plot():
upper_x = float(input.upper())
bin_count = int(input.bins())
weight = float(input.weight())
weights_sum = sum(weights().values())
weights_sum = sum(float(weight) for weight in weights().values())
info(f"Weight ratio for {name}: {weight}/{weights_sum}")
if weights_sum == 0:
# This function is triggered when column is removed;
Expand Down
2 changes: 1 addition & 1 deletion dp_wizard/app/components/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from shiny import ui


def log_slider(id, lower, upper):
def log_slider(id: str, lower: float, upper: float):
# Rather than engineer a new widget, hide the numbers we don't want.
# The rendered widget doesn't have a unique ID, but the following
# element does, so we can use some fancy CSS to get the preceding element.
Expand Down
4 changes: 2 additions & 2 deletions dp_wizard/app/components/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
from faicons import icon_svg


def output_code_sample(title, name_of_render_function):
def output_code_sample(title: str, name_of_render_function: str):
return details(
summary(f"Code sample: {title}"),
ui.output_code(name_of_render_function),
)


def demo_tooltip(is_demo, text): # pragma: no cover
def demo_tooltip(is_demo: bool, text: str): # pragma: no cover
if is_demo:
return ui.tooltip(
icon_svg("circle-question"),
Expand Down
13 changes: 8 additions & 5 deletions dp_wizard/app/dataset_panel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pathlib import Path

from shiny import ui, reactive, render
from shiny import ui, reactive, render, Inputs, Outputs, Session

from dp_wizard.utils.argparse_helpers import get_cli_info
from dp_wizard.app.components.outputs import output_code_sample, demo_tooltip
Expand All @@ -9,9 +9,7 @@

def dataset_ui():
cli_info = get_cli_info()
csv_placeholder = (
None if cli_info.csv_path is None else Path(cli_info.csv_path).name
)
csv_placeholder = "" if cli_info.csv_path is None else Path(cli_info.csv_path).name

return ui.nav_panel(
"Select Dataset",
Expand Down Expand Up @@ -41,7 +39,12 @@ def dataset_ui():


def dataset_server(
input, output, session, csv_path=None, contributions=None, is_demo=None
input: Inputs,
output: Outputs,
session: Session,
csv_path: reactive.Value[str],
contributions: reactive.Value[int],
is_demo: bool,
): # pragma: no cover
@reactive.effect
@reactive.event(input.csv_path)
Expand Down
8 changes: 4 additions & 4 deletions dp_wizard/app/feedback_panel.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from shiny import ui
from shiny import ui, Inputs, Outputs, Session
from htmltools import HTML


Expand All @@ -25,8 +25,8 @@ def feedback_ui():


def feedback_server(
input,
output,
session,
input: Inputs,
output: Outputs,
session: Session,
): # pragma: no cover
pass
22 changes: 11 additions & 11 deletions dp_wizard/app/results_panel.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from shiny import ui, render, reactive
from shiny import ui, render, reactive, Inputs, Outputs, Session

from dp_wizard.utils.code_generators import (
NotebookGenerator,
Expand Down Expand Up @@ -26,16 +26,16 @@ def results_ui():


def results_server(
input,
output,
session,
csv_path,
contributions,
lower_bounds,
upper_bounds,
bin_counts,
weights,
epsilon,
input: Inputs,
output: Outputs,
session: Session,
csv_path: reactive.Value[str],
contributions: reactive.Value[int],
lower_bounds: reactive.Value[dict[str, float]],
upper_bounds: reactive.Value[dict[str, float]],
bin_counts: reactive.Value[dict[str, int]],
weights: reactive.Value[dict[str, str]],
epsilon: reactive.Value[float],
): # pragma: no cover
@reactive.calc
def analysis_plan() -> AnalysisPlan:
Expand Down
19 changes: 11 additions & 8 deletions dp_wizard/utils/argparse_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import csv
import random
from warnings import warn
from collections import namedtuple
from typing import NamedTuple, Optional


def _existing_csv_type(arg):
def _existing_csv_type(arg: str) -> Path:
path = Path(arg)
if not path.exists():
raise ArgumentTypeError(f"No such file: {arg}")
Expand Down Expand Up @@ -54,7 +54,7 @@ def _get_args():
return arg_parser.parse_args() # pragma: no cover


def _clip(n, lower, upper):
def _clip(n: float, lower: float, upper: float) -> float:
"""
>>> _clip(-5, 0, 10)
0
Expand All @@ -66,7 +66,13 @@ def _clip(n, lower, upper):
return max(min(n, upper), lower)


def _get_demo_csv_contrib():
class CLIInfo(NamedTuple):
csv_path: Optional[str]
contributions: int
is_demo: bool


def _get_demo_csv_contrib() -> CLIInfo:
"""
>>> csv_path, contributions, is_demo = _get_demo_csv_contrib()
>>> with open(csv_path, newline="") as csv_handle:
Expand Down Expand Up @@ -103,10 +109,7 @@ def _get_demo_csv_contrib():
}
)

return CLIInfo(csv_path=csv_path, contributions=contributions, is_demo=True)


CLIInfo = namedtuple("CLIInfo", ["csv_path", "contributions", "is_demo"])
return CLIInfo(csv_path=str(csv_path), contributions=contributions, is_demo=True)


def get_cli_info(): # pragma: no cover
Expand Down
Loading

0 comments on commit 45cd514

Please sign in to comment.