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-PYPI.md b/README-PYPI.md index f4b1e041..4f77eb2f 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-TEAM.md b/README-TEAM.md index 5fa11c07..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 @@ -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/README.md b/README.md index 131ee703..e2e3f811 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. @@ -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/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 diff --git a/dp_wizard/FAQ.md b/dp_wizard/FAQ.md index a627c224..038a9b76 100644 --- a/dp_wizard/FAQ.md +++ b/dp_wizard/FAQ.md @@ -83,6 +83,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", diff --git a/tests/test_misc.py b/tests/test_misc.py index 7def621e..a29f4784 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,81 @@ 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 = [ + # 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 (?!'dp_wizard\S+)(?!pytest\"?)\S+", + [ + "pip install DEPENDENCIES", + "pip install -r", + "pip install --editable", + "pip install flit", + ], + ), + ] + failures = [] + for path in get_file_paths(): + rel_path = path.relative_to(package_root.parent) + try: + text = path.read_text() + 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: # pragma: no cover + options = " or ".join(f'"{e}"' for e in expected) + failures.append( + f"In {rel_path}, expected {options}, not:" + f"\n> {''.join(match)}" + ) + if failures: # pragma: no cover + pytest.fail("\n".join(failures))