diff --git a/README-PYPI.md b/README-PYPI.md index dd40d1e9..a3d57701 100644 --- a/README-PYPI.md +++ b/README-PYPI.md @@ -8,12 +8,10 @@ DP Wizard demonstrates how to calculate DP statistics or create a synthetic data (If differential privacy is new to you, [these slides](https://opendp.github.io/dp-wizard/) provide some background, and explain how DP Wizard works.) -You can run DP Wizard locally and upload your own CSV, -or use the [cloud deployment](https://mccalluc-dp-wizard.share.connect.posit.cloud/) and only provide column names to protect your private data. -In either case, you'll be prompted to describe your privacy budget and the analysis you need. -With that information, DP Wizard provides: - -- A Jupyter notebook which demonstrates how to use the [OpenDP Library](https://docs.opendp.org/). +After selecting a local CSV, you'll be prompted to describe the analysis you need. +Output options include: +- A Jupyter notebook which demonstrates how to use +the [OpenDP Library](https://docs.opendp.org/). - A plain Python script. - Text and CSV reports. @@ -39,7 +37,7 @@ The exact upgrade process will depend on your environment and operating system. Install with `pip install 'dp_wizard[app]'` and you can start DP Wizard from the command line. ``` -usage: dp-wizard [-h] [--sample | --cloud] +usage: dp-wizard [-h] [--sample] DP Wizard makes it easier to get started with Differential Privacy. @@ -47,9 +45,8 @@ options: -h, --help show this help message and exit --sample Generate a sample CSV: See how DP Wizard works without providing your own data - --cloud Prompt for column names instead of CSV upload -Unless you have set "--sample" or "--cloud", you will specify a CSV +Unless you have set "--sample", you will specify a CSV inside the application. Provide a "Private CSV" if you only have a private data set, and want to diff --git a/README.md b/README.md index a9bd88bb..1923eff5 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,10 @@ DP Wizard demonstrates how to calculate DP statistics or create a synthetic data (If differential privacy is new to you, [these slides](https://opendp.github.io/dp-wizard/) provide some background, and explain how DP Wizard works.) -You can run DP Wizard locally and upload your own CSV, -or use the [cloud deployment](https://mccalluc-dp-wizard.share.connect.posit.cloud/) and only provide column names to protect your private data. -In either case, you'll be prompted to describe your privacy budget and the analysis you need. -With that information, DP Wizard provides: - -- A Jupyter notebook which demonstrates how to use the [OpenDP Library](https://docs.opendp.org/). +After selecting a local CSV, you'll be prompted to describe the analysis you need. +Output options include: +- A Jupyter notebook which demonstrates how to use +the [OpenDP Library](https://docs.opendp.org/). - A plain Python script. - Text and CSV reports. @@ -39,7 +37,7 @@ The exact upgrade process will depend on your environment and operating system. Install with `pip install 'dp_wizard[app]'` and you can start DP Wizard from the command line. ``` -usage: dp-wizard [-h] [--sample | --cloud] +usage: dp-wizard [-h] [--sample] DP Wizard makes it easier to get started with Differential Privacy. @@ -47,9 +45,8 @@ options: -h, --help show this help message and exit --sample Generate a sample CSV: See how DP Wizard works without providing your own data - --cloud Prompt for column names instead of CSV upload -Unless you have set "--sample" or "--cloud", you will specify a CSV +Unless you have set "--sample", you will specify a CSV inside the application. Provide a "Private CSV" if you only have a private data set, and want to diff --git a/docs/index.html b/docs/index.html index 8fd8ccc4..4c1657c8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -645,7 +645,7 @@

Return to the class grades example ✋

pip install 'dp_wizard[app]'
dp_wizard --cloud
(requires +href="https://pypi.org/project/dp_wizard/">pip install 'dp_wizard[app]'
dp_wizard --sample
(requires Python>=3.10)

diff --git a/docs/index.md b/docs/index.md index 9299bf9c..88c40945 100644 --- a/docs/index.md +++ b/docs/index.md @@ -388,7 +388,7 @@ Divide into four teams, and on one computer either: -**[`pip install 'dp_wizard[app]'`](https://pypi.org/project/dp_wizard/)
`dp_wizard --cloud`
(requires Python>=3.10)** +**[`pip install 'dp_wizard[app]'`](https://pypi.org/project/dp_wizard/)
`dp_wizard --sample`
(requires Python>=3.10)** diff --git a/dp_wizard/shiny/__init__.py b/dp_wizard/shiny/__init__.py index 4deeecbd..7c4f1f75 100644 --- a/dp_wizard/shiny/__init__.py +++ b/dp_wizard/shiny/__init__.py @@ -111,7 +111,6 @@ def server(input: Inputs, output: Outputs, session: Session): # pragma: no cove state = AppState( # CLI options: is_sample_csv=cli_info.is_sample_csv, - in_cloud=cli_info.is_cloud_mode, qa_mode=cli_info.is_qa_mode, # Reactive bools: is_tutorial_mode=reactive.value(cli_info.get_is_tutorial_mode()), diff --git a/dp_wizard/shiny/components/summaries.py b/dp_wizard/shiny/components/summaries.py index b7d230ba..9f268993 100644 --- a/dp_wizard/shiny/components/summaries.py +++ b/dp_wizard/shiny/components/summaries.py @@ -19,8 +19,6 @@ def dataset_summary(state: AppState): # pragma: no cover sources.append("Private CSV") if state.public_csv_path(): sources.append("Public CSV") - if state.in_cloud: - sources.append("Field List") contributions = state.contributions() entity = state.contributions_entity() diff --git a/dp_wizard/shiny/panels/analysis_panel/__init__.py b/dp_wizard/shiny/panels/analysis_panel/__init__.py index dd289068..8337ff39 100644 --- a/dp_wizard/shiny/panels/analysis_panel/__init__.py +++ b/dp_wizard/shiny/panels/analysis_panel/__init__.py @@ -147,7 +147,6 @@ def analysis_server( ): # pragma: no cover # CLI options: is_sample_csv = state.is_sample_csv - # in_cloud = state.in_cloud # Reactive bools: is_tutorial_mode = state.is_tutorial_mode diff --git a/dp_wizard/shiny/panels/dataset_panel/__init__.py b/dp_wizard/shiny/panels/dataset_panel/__init__.py index 04dc14d2..4b21c7ad 100644 --- a/dp_wizard/shiny/panels/dataset_panel/__init__.py +++ b/dp_wizard/shiny/panels/dataset_panel/__init__.py @@ -118,7 +118,6 @@ def dataset_server( ): # pragma: no cover # CLI options: is_sample_csv = state.is_sample_csv - in_cloud = state.in_cloud # Reactive bools: is_tutorial_mode = state.is_tutorial_mode @@ -169,12 +168,6 @@ def _on_private_csv_path_change(): private_csv_path.set(path) csv_info.set(CsvInfo(Path(path))) - @reactive.effect - @reactive.event(input.all_column_names) - def _on_column_names_change(): - # Only used when the user is supplying column names in cloud mode. - csv_info.set(infer_csv_info(input.all_column_names())) - @reactive.calc def csv_column_mismatch_calc() -> Optional[tuple[set, set]]: public = public_csv_path() @@ -223,7 +216,6 @@ def welcome_ui(): @render.ui def csv_or_columns_ui(): return data_source.csv_or_columns_ui( - in_cloud=in_cloud, is_tutorial_mode=is_tutorial_mode, csv_info=csv_info, ) @@ -359,7 +351,7 @@ def set_is_dataset_selected(): and not info.get_is_error() and len(info.get_all_column_names()) > 0 and not get_row_count_errors(max_rows()) - and (in_cloud or not csv_column_mismatch_calc()) + and not csv_column_mismatch_calc() ) @reactive.calc @@ -388,26 +380,13 @@ def contributions_validation_ui(): @render.ui def python_tutorial_ui(): - cloud_extra_markdown = ( - """ - Because this instance of DP Wizard is running in the cloud, - we don't allow private data to be uploaded. - When run locally, DP Wizard can also run an analysis - on your data and return results, - and not just an unexecuted notebook. - """ - if in_cloud - else "" - ) return tutorial_box( is_tutorial_mode(), - f""" + """ Along the way, code samples demonstrate how the information you provide is used in the OpenDP Library, and at the end you can download a notebook for the entire calculation. - - {cloud_extra_markdown} """, responsive=False, ) @@ -465,7 +444,7 @@ def define_analysis_button_ui(): return [ button, f""" - Specify {'columns' if in_cloud else 'CSV'}, unit of privacy, + Specify CSV, unit of privacy, and maximum row count before proceeding. """, ] diff --git a/dp_wizard/shiny/panels/dataset_panel/data_source.py b/dp_wizard/shiny/panels/dataset_panel/data_source.py index 26a92462..fe9a7d9d 100644 --- a/dp_wizard/shiny/panels/dataset_panel/data_source.py +++ b/dp_wizard/shiny/panels/dataset_panel/data_source.py @@ -19,57 +19,21 @@ def csv_or_columns_ui( - in_cloud: bool, is_tutorial_mode: reactive.Value[bool], csv_info: reactive.Value[CsvInfo], ): # pragma: no cover - if in_cloud: - content = [ - ui.markdown( - """ - Provide the names of columns you'll use in your analysis, - one per line, with a sample value for each. For example: - - ``` - name: Chuck - age: 48 - ``` - """ - ), - tutorial_box( - is_tutorial_mode(), - """ - When [installed and run - locally](https://pypi.org/project/dp_wizard/), - DP Wizard allows you to specify a private and public CSV, - but for the safety of your data, in the cloud - DP Wizard only accepts column names. - - If you don't have other ideas, we can imagine - a CSV of student quiz grades: Enter `student_id`, - `quiz_id`, `grade`, and `class_year_str` below, - each on a separate line. - """, - responsive=False, - ), - ui.input_text_area("all_column_names", "CSV Column Names", rows=5), - ] - else: - content = [ - ui.markdown( - f""" + return [ + ui.markdown( + f""" Choose **Private CSV** {PRIVATE_TEXT} Choose **Public CSV** {PUBLIC_TEXT} Choose both **Private CSV** and **Public CSV** {PUBLIC_PRIVATE_TEXT} - """ - ), - ui.output_ui("input_files_ui"), - ui.output_ui("csv_message_ui"), - ] - - content += [ + """ + ), + ui.output_ui("input_files_ui"), + ui.output_ui("csv_message_ui"), code_sample( "Context", Template( @@ -98,7 +62,6 @@ def csv_or_columns_ui( ), ui.output_ui("python_tutorial_ui"), ] - return content def input_files_ui( diff --git a/dp_wizard/shiny/panels/results_panel/__init__.py b/dp_wizard/shiny/panels/results_panel/__init__.py index 200e356a..171f3a91 100644 --- a/dp_wizard/shiny/panels/results_panel/__init__.py +++ b/dp_wizard/shiny/panels/results_panel/__init__.py @@ -99,7 +99,6 @@ def results_server( ): # pragma: no cover # CLI options: # is_sample_csv = state.is_sample_csv - in_cloud = state.in_cloud qa_mode = state.qa_mode # Reactive bools: @@ -209,47 +208,35 @@ def download_results_ui(): ] if product() == Product.SYNTHETIC_DATA: downloads.append("Contingency Table") - return ( - ui.markdown( - """ - When [installed and run - locally](https://pypi.org/project/dp_wizard/), - there are more download options because DP Wizard - can read your private CSV and release differentially - private statistics. + return [ + tutorial_box( + is_tutorial_mode(), """ - ) - if in_cloud - else [ - tutorial_box( - is_tutorial_mode(), - """ Now you can download a notebook for your analysis. The Jupyter notebook could be used locally or on Colab, but the HTML version can be viewed in the brower. """, - responsive=False, - ), - download_button( - "Package", - primary=True, - disabled=disabled, - ), - ui.br(), - "Contains:", - ui.tags.ul( - *[ - ui.tags.li( - download_link( - download, - disabled=disabled, - ) + responsive=False, + ), + download_button( + "Package", + primary=True, + disabled=disabled, + ), + ui.br(), + "Contains:", + ui.tags.ul( + *[ + ui.tags.li( + download_link( + download, + disabled=disabled, ) - for download in downloads - ] - ), - ] - ) + ) + for download in downloads + ] + ), + ] @render.ui def download_code_ui(): @@ -257,18 +244,11 @@ def download_code_ui(): return [ tutorial_box( is_tutorial_mode(), - ( - """ - In the cloud, DP Wizard only provides unexecuted - notebooks and scripts. - """ - if in_cloud - else """ - Alternatively, you can download a script or unexecuted - notebook that demonstrates the steps of your analysis, - but does not contain any data or analysis results. - """ - ), + """ + Alternatively, you can download a script or unexecuted + notebook that demonstrates the steps of your analysis, + but does not contain any data or analysis results. + """, responsive=False, ), download_button("Notebook (unexecuted)", disabled=disabled), diff --git a/dp_wizard/types.py b/dp_wizard/types.py index 32e919b2..a1e1352a 100644 --- a/dp_wizard/types.py +++ b/dp_wizard/types.py @@ -212,7 +212,6 @@ def get_is_error(self) -> bool: class AppState: # CLI options: is_sample_csv: bool - in_cloud: bool qa_mode: bool # Reactive bools: diff --git a/dp_wizard/utils/argparse_helpers.py b/dp_wizard/utils/argparse_helpers.py index 67a7e775..647f1a85 100644 --- a/dp_wizard/utils/argparse_helpers.py +++ b/dp_wizard/utils/argparse_helpers.py @@ -20,8 +20,7 @@ def _get_arg_parser() -> argparse.ArgumentParser: description="DP Wizard makes it easier to get started with " "Differential Privacy.", epilog=f""" -Unless you have set "--sample" or "--cloud", you will specify a CSV -inside the application. +Unless you have set "--sample", you will provide a CSV inside the application. Provide a "Private CSV" {PRIVATE_TEXT} @@ -37,18 +36,13 @@ def _get_arg_parser() -> argparse.ArgumentParser: help="Generate a sample CSV: " "See how DP Wizard works without providing your own data", ) - group.add_argument( - "--cloud", - action="store_true", - help="Prompt for column names instead of CSV upload", - ) return parser def _get_args() -> argparse.Namespace: """ >>> _get_args() - Namespace(sample=False, cloud=False) + Namespace(sample=False) """ arg_parser = _get_arg_parser() @@ -65,15 +59,12 @@ def _get_args() -> argparse.Namespace: class CLIInfo(NamedTuple): is_sample_csv: bool - is_cloud_mode: bool is_qa_mode: bool def get_is_tutorial_mode(self) -> bool: - return self.is_sample_csv or self.is_cloud_mode # pragma: no cover + return self.is_sample_csv # pragma: no cover def get_cli_info() -> CLIInfo: # pragma: no cover args = _get_args() - return CLIInfo( - is_sample_csv=args.sample, is_cloud_mode=args.cloud, is_qa_mode=False - ) + return CLIInfo(is_sample_csv=args.sample, is_qa_mode=False) diff --git a/tests/apps/app_cloud.py b/tests/apps/app_cloud.py deleted file mode 100644 index b103258a..00000000 --- a/tests/apps/app_cloud.py +++ /dev/null @@ -1,10 +0,0 @@ -from dp_wizard.shiny import make_app -from dp_wizard.utils.argparse_helpers import CLIInfo - -app = make_app( - CLIInfo( - is_sample_csv=False, - is_cloud_mode=True, - is_qa_mode=False, - ) -) diff --git a/tests/apps/app_qa.py b/tests/apps/app_qa.py index a6bcf864..6b92d134 100644 --- a/tests/apps/app_qa.py +++ b/tests/apps/app_qa.py @@ -4,7 +4,6 @@ app = make_app( CLIInfo( is_sample_csv=True, - is_cloud_mode=False, is_qa_mode=True, ) ) diff --git a/tests/apps/app_sample.py b/tests/apps/app_sample.py index dea73ec3..116402b3 100644 --- a/tests/apps/app_sample.py +++ b/tests/apps/app_sample.py @@ -4,7 +4,6 @@ app = make_app( CLIInfo( is_sample_csv=True, - is_cloud_mode=False, is_qa_mode=False, ) ) diff --git a/tests/test_app.py b/tests/test_app.py index 2b6bec7a..3209ffb0 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -23,48 +23,9 @@ test_apps = Path(__file__).parent / "apps" sample_app = create_app_fixture(test_apps / "app_sample.py") -cloud_app = create_app_fixture(test_apps / "app_cloud.py") qa_app = create_app_fixture(test_apps / "app_qa.py") -def test_cloud_app(page: Page, cloud_app: ShinyAppProc): # pragma: no cover - page.goto(cloud_app.url) - - page.locator("#max_rows").fill("10000") - expect(page).to_have_title("DP Wizard") - expect(page.get_by_text("Choose Public CSV")).not_to_be_visible() - page.get_by_label("CSV Column Names").fill("a_column:1\nb_column:2") - - page.get_by_role("button", name="Define Analysis").click() - page.locator(".selectize-input").nth(0).click() - page.get_by_text("1: a_column").click() - page.get_by_label("Lower").fill("0") - page.get_by_label("Upper").fill("10") - - expect( - page.get_by_text("Select one or more columns before proceeding.") - ).not_to_be_visible() - page.locator(".selectize-input").nth(0).click() - page.get_by_text("2: b_column").click() - page.get_by_text("Select one or more columns before proceeding.") - page.get_by_text("2: b_column×").click() - - page.get_by_role("button", name="Download Results").click() - with page.expect_download() as download_info: - page.get_by_role("link", name="Notebook (unexecuted").click() - - download_path = download_info.value.path() - - # Try to execute the downloaded file: - # Based on https://nbconvert.readthedocs.io/en/latest/execute_api.html#example - nb = nbformat.read(download_path.open(), as_version=4) - ep = ExecutePreprocessor(timeout=600, kernel_name="python3") - ep.preprocess(nb) - - # Clean up file in CWD that is created by notebook execution. - Path(PLACEHOLDER_CSV_NAME).unlink() - - def test_qa_app(page: Page, qa_app: ShinyAppProc): # pragma: no cover page.goto(qa_app.url)