From a2837561d5020cf20d4ac864d1475523e4664d5f Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Wed, 11 Feb 2026 17:11:25 -0500 Subject: [PATCH 1/8] fix caps on GitHub --- README-TEAM.md | 2 +- dp_wizard/FAQ.md | 4 ++-- dp_wizard/shiny/panels/faq_panel/__init__.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README-TEAM.md b/README-TEAM.md index 5fa11c07..6d336766 100644 --- a/README-TEAM.md +++ b/README-TEAM.md @@ -33,7 +33,7 @@ Branch names should be of the form `NNNN-short-description`, where `NNNN` is the Add developer-only dependencies in `requirements-dev.in`; Add other dependencies in `requirements.in`. After an edit to either file run `scripts/requirements.py` to install the new dependency locally and update `pyproject.toml`. -A Github [project board](https://github.com/orgs/opendp/projects/10/views/2) provides an overview of the issues and PRs. +A GitHub [project board](https://github.com/orgs/opendp/projects/10/views/2) provides an overview of the issues and PRs. When PRs are [Ready for Review](https://github.com/orgs/opendp/projects/10/views/2?filterQuery=status%3A%22Ready+for+Review%22) they should be flagged as such so reviewers can find them. ```mermaid diff --git a/dp_wizard/FAQ.md b/dp_wizard/FAQ.md index 7bdee743..cacd3907 100644 --- a/dp_wizard/FAQ.md +++ b/dp_wizard/FAQ.md @@ -59,6 +59,6 @@ At the end there are suggestions for further reading. ### How can I report bugs or request features? -If you are not on github, feel free to [email the maintainer](cmccallum@g.harvard.edu) directly. -If you are on github, please [file an issue](https://github.com/opendp/dp-wizard/issues/new/choose). +If you are not on GitHub, feel free to [email the maintainer](cmccallum@g.harvard.edu) directly. +If you are on GitHub, please [file an issue](https://github.com/opendp/dp-wizard/issues/new/choose). Thank you for the feedback! diff --git a/dp_wizard/shiny/panels/faq_panel/__init__.py b/dp_wizard/shiny/panels/faq_panel/__init__.py index ca4c7970..4cca4cf8 100644 --- a/dp_wizard/shiny/panels/faq_panel/__init__.py +++ b/dp_wizard/shiny/panels/faq_panel/__init__.py @@ -54,8 +54,8 @@ def about_ui(): "FAQ", ui.card( ui.markdown((package_root / "FAQ.md").read_text()) - + "If you are on Github, the form below captures environment information " - "and pre-fills a Github issue:", + + "If you are on GitHub, the form below captures environment information " + "and pre-fills a GitHub issue:", ui.accordion( ui.accordion_panel( "Pre-fill Issue", From 7b8ba8b48f6e1f19dbb0d083e2826163a8f9e04c Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Thu, 12 Feb 2026 13:24:21 -0500 Subject: [PATCH 2/8] new typo test --- tests/test_misc.py | 75 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/tests/test_misc.py b/tests/test_misc.py index 7def621e..b81eac03 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,5 +1,6 @@ import re import subprocess +from pathlib import Path import pytest @@ -10,12 +11,14 @@ "pyright type checking": "pyright", "precommit checks": "pre-commit run --all-files", # pandoc invocation aligned with scripts/slides.sh: - "pandoc slides up to date": "pandoc --to=slidy " - "--include-before-body=docs/include-before-body.html " - "docs/index.md " - "--output docs/index-test.html " - "--standalone " - "&& diff docs/index.html docs/index-test.html", + "pandoc slides up to date": ( + "pandoc --to=slidy " + "--include-before-body=docs/include-before-body.html " + "docs/index.md " + "--output docs/index-test.html " + "--standalone " + "&& diff docs/index.html docs/index-test.html" + ), } @@ -61,3 +64,63 @@ def test_python_min_version(rel_path): if "README" in rel_path: # Make sure we haven't upgraded one reference by mistake. assert not re.search(r"3.1[^0]", text) + + +def get_file_paths() -> list[Path]: + # TODO: Is there a package that respects .gitignore? + top_level_paths = [ + path + for path in package_root.parent.iterdir() + if not ( + path.match("*venv*") + or path.name + in [ + "docs", + ".git", + ".DS_Store", + "dist", + ".coverage", + ".hypothesis", + ".gitignore", + ".pytest_cache", + ] + ) + ] + file_paths = [] + for path in top_level_paths: + if path.is_file(): + file_paths.append(path) + else: + file_paths += [ + path + for path in path.glob("**/*") + if path.is_file() + and not ( + path.match("*.pyc") + or path.name + in ["__pycache__", ".DS_Store", "favicon.ico", Path(__file__).name] + ) + ] + return file_paths + + +def test_common_typos(): + expected_pairs = [ + (r"github(?!\.com)(?!\.io)(?!/workflows)", "GitHub"), + ] + failures = [] + for path in get_file_paths(): + rel_path = path.relative_to(package_root.parent) + try: + text = path.read_text() + except Exception as e: + pytest.fail(f"Exception reading {path}: {e}") + for pattern, expected in expected_pairs: + for match in re.findall(rf"(.*)({pattern})(.*)", text): + if match[1] != expected: + failures.append( + f"Expected '{expected}' in {rel_path}, not:" + f"\n> {''.join(match)}" + ) + if failures: + pytest.fail("\n".join(failures)) From 212726787832d778cb6fd368220a2f4052334d89 Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Thu, 12 Feb 2026 13:24:48 -0500 Subject: [PATCH 3/8] unused gitignores --- dp_wizard/.local-config/.gitignore | 4 ---- dp_wizard/.local-sessions/.gitignore | 4 ---- 2 files changed, 8 deletions(-) delete mode 100644 dp_wizard/.local-config/.gitignore delete mode 100644 dp_wizard/.local-sessions/.gitignore diff --git a/dp_wizard/.local-config/.gitignore b/dp_wizard/.local-config/.gitignore deleted file mode 100644 index 8c9aeb46..00000000 --- a/dp_wizard/.local-config/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# This dot directory will be ignored by Shiny auto-reload. -# See RELOAD_EXCLUDES_DEFAULT in py-shiny source. -* -!.gitignore diff --git a/dp_wizard/.local-sessions/.gitignore b/dp_wizard/.local-sessions/.gitignore deleted file mode 100644 index 8c9aeb46..00000000 --- a/dp_wizard/.local-sessions/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# This dot directory will be ignored by Shiny auto-reload. -# See RELOAD_EXCLUDES_DEFAULT in py-shiny source. -* -!.gitignore From b416d853ed8b3d6220c49e92fb3cc2c55e5a4c33 Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Thu, 12 Feb 2026 13:47:29 -0500 Subject: [PATCH 4/8] standardize "dp_wizard[pins]" --- README-PYPI.md | 2 +- README.md | 2 +- tests/test_misc.py | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README-PYPI.md b/README-PYPI.md index 3d0717b2..57136976 100644 --- a/README-PYPI.md +++ b/README-PYPI.md @@ -12,7 +12,7 @@ Options for running DP Wizard: - No install [online demo](https://mccalluc-dp-wizard.share.connect.posit.cloud/): Does not support data upload. - Install from [Docker](https://hub.docker.com/repository/docker/mccalluc/dp-wizard/general): `docker run -p 8000:8000 mccalluc/dp-wizard` -- Install from [PyPI](https://pypi.org/project/dp-wizard/): `pip install 'dp-wizard[app]'; dp-wizard` +- Install from [PyPI](https://pypi.org/project/dp-wizard/): `pip install 'dp_wizard[pins]'; dp-wizard` - Install from [source](https://github.com/opendp/dp-wizard): See developer instructions. See the [FAQ](https://github.com/opendp/dp-wizard/blob/main/dp_wizard/FAQ.md) for more information. diff --git a/README.md b/README.md index 1adacbbe..a9aa8338 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Options for running DP Wizard: - No install [online demo](https://mccalluc-dp-wizard.share.connect.posit.cloud/): Does not support data upload. - Install from [Docker](https://hub.docker.com/repository/docker/mccalluc/dp-wizard/general): `docker run -p 8000:8000 mccalluc/dp-wizard` -- Install from [PyPI](https://pypi.org/project/dp-wizard/): `pip install 'dp-wizard[app]'; dp-wizard` +- Install from [PyPI](https://pypi.org/project/dp-wizard/): `pip install 'dp_wizard[pins]'; dp-wizard` - Install from [source](https://github.com/opendp/dp-wizard): See developer instructions. See the [FAQ](https://github.com/opendp/dp-wizard/blob/main/dp_wizard/FAQ.md) for more information. diff --git a/tests/test_misc.py b/tests/test_misc.py index b81eac03..6d39ff31 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -106,7 +106,8 @@ def get_file_paths() -> list[Path]: def test_common_typos(): expected_pairs = [ - (r"github(?!\.com)(?!\.io)(?!/workflows)", "GitHub"), + (r"github(?!\.com)(?!\.io)(?!/workflows)", ["GitHub"]), + (r"dp.wizard\[[^]]+\]", ["dp_wizard[pins]"]), ] failures = [] for path in get_file_paths(): @@ -117,9 +118,10 @@ def test_common_typos(): pytest.fail(f"Exception reading {path}: {e}") for pattern, expected in expected_pairs: for match in re.findall(rf"(.*)({pattern})(.*)", text): - if match[1] != expected: + if match[1] not in expected: + options = " or ".join(f'"{e}"' for e in expected) failures.append( - f"Expected '{expected}' in {rel_path}, not:" + f"Expected {options} in {rel_path}, not:" f"\n> {''.join(match)}" ) if failures: From e2fe29f908fda24facc6a72b60c98192f5fe7062 Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Thu, 12 Feb 2026 13:57:23 -0500 Subject: [PATCH 5/8] editable fix --- Dockerfile | 2 +- README.md | 2 +- tests/test_misc.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 77326a8f..e62fb978 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,7 +40,7 @@ COPY requirements.txt requirements.txt RUN pip install -r requirements.txt COPY . . -RUN pip install --editable '.[app]' +RUN pip install --editable '.[pins]' # TODO: Follow best practices! diff --git a/README.md b/README.md index a9aa8338..9a208bf2 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ You can now install dependencies, and the application itself, and start a tutori $ pip install -r requirements-dev.txt $ pre-commit install $ playwright install -$ pip install --editable . +$ pip install --editable '.[pins]' $ dp-wizard --demo ``` diff --git a/tests/test_misc.py b/tests/test_misc.py index 6d39ff31..c6c08faf 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -108,6 +108,7 @@ def test_common_typos(): expected_pairs = [ (r"github(?!\.com)(?!\.io)(?!/workflows)", ["GitHub"]), (r"dp.wizard\[[^]]+\]", ["dp_wizard[pins]"]), + (r"pip install --editable \S+", ["pip install --editable '.[pins]'"]), ] failures = [] for path in get_file_paths(): From 8f0197ecf05a0974474d452c2268e30e73a86bd3 Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Thu, 12 Feb 2026 14:16:16 -0500 Subject: [PATCH 6/8] formatting --- README-TEAM.md | 2 +- tests/test_misc.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/README-TEAM.md b/README-TEAM.md index 6d336766..981d2506 100644 --- a/README-TEAM.md +++ b/README-TEAM.md @@ -15,7 +15,7 @@ This project is configured so there are two different install possibilities from pypi: - `pip install 'dp_wizard[pins]'` pins all dependencies, and is the best route for most users. -- `pip install dp_wizard` does not pin dependencies, and is best if you're using `dp_wizard` as a library. +- Without `[pins]`, dependencies are not pinned. This is best if you're using `dp_wizard` as a library in a larger project. ### Posit Cloud diff --git a/tests/test_misc.py b/tests/test_misc.py index c6c08faf..f2bc3a88 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -109,6 +109,20 @@ def test_common_typos(): (r"github(?!\.com)(?!\.io)(?!/workflows)", ["GitHub"]), (r"dp.wizard\[[^]]+\]", ["dp_wizard[pins]"]), (r"pip install --editable \S+", ["pip install --editable '.[pins]'"]), + ( + r"pip install \S+", + [ + "pip install 'dp_wizard[pins]'", + "pip install 'dp_wizard[pins]';", + "pip install 'dp_wizard[pins]'`", + "pip install DEPENDENCIES", + "pip install -r", + "pip install --editable", + "pip install flit", + "pip install pytest", # In test fixtures + 'pip install pytest"', + ], + ), ] failures = [] for path in get_file_paths(): From 18190fb8871799572c96b74f6cca742c0a59ea9e Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Thu, 12 Feb 2026 14:49:18 -0500 Subject: [PATCH 7/8] skip coverage on error branches --- tests/test_misc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_misc.py b/tests/test_misc.py index f2bc3a88..c3537966 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -129,15 +129,15 @@ def test_common_typos(): rel_path = path.relative_to(package_root.parent) try: text = path.read_text() - except Exception as e: + except Exception as e: # pragma: no cover pytest.fail(f"Exception reading {path}: {e}") for pattern, expected in expected_pairs: for match in re.findall(rf"(.*)({pattern})(.*)", text): - if match[1] not in expected: + if match[1] not in expected: # pragma: no cover options = " or ".join(f'"{e}"' for e in expected) failures.append( f"Expected {options} in {rel_path}, not:" f"\n> {''.join(match)}" ) - if failures: + if failures: # pragma: no cover pytest.fail("\n".join(failures)) From 54ba3ec13617498387cac90fd894bf8254195b73 Mon Sep 17 00:00:00 2001 From: Chuck McCallum Date: Tue, 3 Mar 2026 10:13:52 -0500 Subject: [PATCH 8/8] simplify and explain checks --- tests/test_misc.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/test_misc.py b/tests/test_misc.py index c3537966..a29f4784 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -106,21 +106,22 @@ def get_file_paths() -> list[Path]: def test_common_typos(): expected_pairs = [ + # Unless "github" is in a domain name or a path, capitalize: + # "(?!...)" is a negative lookahead. (r"github(?!\.com)(?!\.io)(?!/workflows)", ["GitHub"]), + # "[pins]" is the only recognized extra install: (r"dp.wizard\[[^]]+\]", ["dp_wizard[pins]"]), + # "--editable" installs should always use "[pins]": (r"pip install --editable \S+", ["pip install --editable '.[pins]'"]), + # Negative lookaheads are covered above. + # Check for other unexpected installs: ( - r"pip install \S+", + r"pip install (?!'dp_wizard\S+)(?!pytest\"?)\S+", [ - "pip install 'dp_wizard[pins]'", - "pip install 'dp_wizard[pins]';", - "pip install 'dp_wizard[pins]'`", "pip install DEPENDENCIES", "pip install -r", "pip install --editable", "pip install flit", - "pip install pytest", # In test fixtures - 'pip install pytest"', ], ), ] @@ -136,7 +137,7 @@ def test_common_typos(): if match[1] not in expected: # pragma: no cover options = " or ".join(f'"{e}"' for e in expected) failures.append( - f"Expected {options} in {rel_path}, not:" + f"In {rel_path}, expected {options}, not:" f"\n> {''.join(match)}" ) if failures: # pragma: no cover