From 45528a9367e6bbf1a5ce0536c063363b7ef06183 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Wed, 16 Jul 2025 14:42:36 +0100 Subject: [PATCH 01/10] DRY up the test code a bit. Remove tmp_path fixture from every test, but then need to write a nested function for the fixture. Also add a DEFAULT_CONFIG for the cookiecutter generation. --- tests/conftest.py | 36 ++++--------- tests/helpers.py | 67 ++++++++++++++++++++++++ tests/test_git_init.py | 16 ++---- tests/test_package_generation.py | 87 +++++++------------------------- 4 files changed, 99 insertions(+), 107 deletions(-) create mode 100644 tests/helpers.py diff --git a/tests/conftest.py b/tests/conftest.py index c95247ed..a4e3b13d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,34 +6,16 @@ import pytest - -def _generate_package( - config: dict[str, str], path: pathlib.Path -) -> subprocess.CompletedProcess[str]: - """ - Generate a project from the cookiecutter template. - - Arguments: - --------- - config: dict - A dictionary with values for the cookiecutter template, - as defined in the cookiecutter.json - path: Path - Directory to create package in. - - """ - args = [f"{key}={val}" for key, val in config.items()] - cmd = ["cookiecutter", ".", "--no-input", "--output-dir", f"{path}"] - return subprocess.run( # noqa: S603 - cmd + args, - check=False, - shell=False, - capture_output=True, - text=True, - ) +from .helpers import DEFAULT_CONFIG, _generate_package # type: ignore[import-not-found] @pytest.fixture -def generate_package() -> typing.Callable: +def generate_package(tmp_path: pathlib.Path) -> typing.Callable: """Generate project from cookiecutter template.""" - return _generate_package + + def wrapped_with_tmp_path( + config: dict[str, str] = DEFAULT_CONFIG, + ) -> tuple[subprocess.CompletedProcess, pathlib.Path]: + return _generate_package(config, tmp_path) + + return wrapped_with_tmp_path diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 00000000..49786cfd --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,67 @@ +"""Helper functions for the cookiecutter template tests.""" + +import os +import pathlib +import subprocess + +DEFAULT_CONFIG = { + "github_owner": "test-user", + "project_short_description": "description", + "project_name": "Cookiecutter Test", + "project_slug": "cookiecutter-test", +} + + +def _generate_package( + config: dict[str, str], path: pathlib.Path +) -> tuple[subprocess.CompletedProcess[str], pathlib.Path]: + """ + Generate a project from the cookiecutter template. + + Arguments: + --------- + config: dict + A dictionary with values for the cookiecutter template, + as defined in the cookiecutter.json + path: Path + Directory to create package in. + + Returns: + ------- + subprocess.CompletedProcess, pathlib.Path + The result of the cookiecutter command and the path to the generated package. + + """ + args = [f"{key}={val}" for key, val in config.items()] + cmd = ["cookiecutter", ".", "--no-input", "--output-dir", f"{path}"] + return subprocess.run( # noqa: S603 + cmd + args, + check=False, + shell=False, + capture_output=True, + text=True, + ), path / config["project_slug"] + + +def get_all_files_folders(root_path: pathlib.Path) -> set[pathlib.Path]: + """ + Get all files and folders under a directory. + + The paths are returned relative to the root path given. + __pycache__ directories and .DS_Store files are ignored. + """ + file_set: set[pathlib.Path] = set() + for dirpath, _, filenames in os.walk(root_path): + dirpath_path = pathlib.Path(dirpath).relative_to(root_path) + if dirpath_path.name in ["__pycache__"]: + continue + + # Add this directory + file_set.update((dirpath_path,)) + # Add any files in it + for filename in filenames: + if filename in [".DS_Store"]: + continue + file_set.update((dirpath_path / filename,)) + + return file_set diff --git a/tests/test_git_init.py b/tests/test_git_init.py index 3918ee78..9b337c33 100644 --- a/tests/test_git_init.py +++ b/tests/test_git_init.py @@ -1,29 +1,23 @@ """Checks that the git repo initialisation works.""" -import pathlib import subprocess import typing import pytest +from .helpers import DEFAULT_CONFIG # type: ignore[import-not-found] + @pytest.mark.parametrize("initialise_git_repository", [True, False]) def test_initialisation_of_git_repo( initialise_git_repository: bool, # noqa: FBT001 generate_package: typing.Callable, - tmp_path: pathlib.Path, ) -> None: """Checks to see if git was correctly initialised if desired.""" - test_config = { - "github_owner": "test-user", - "project_short_description": "description", - "project_name": "Cookiecutter Test", - "initialise_git_repository": initialise_git_repository, - } + test_config = DEFAULT_CONFIG.copy() + test_config["initialise_git_repository"] = str(initialise_git_repository) # Run cookiecutter with initialise_git_repository - result = generate_package(config=test_config, path=tmp_path) - - test_project_dir = tmp_path / "cookiecutter-test" + result, test_project_dir = generate_package(config=test_config) # check if git is initialised git_status = subprocess.run( # noqa: S603 diff --git a/tests/test_package_generation.py b/tests/test_package_generation.py index 6926d9fa..7a805c0f 100644 --- a/tests/test_package_generation.py +++ b/tests/test_package_generation.py @@ -1,7 +1,6 @@ """Checks that the cookiecutter works.""" import difflib -import os import pathlib import shutil import subprocess @@ -10,52 +9,24 @@ import pytest import pytest_venv # type: ignore[import-not-found] +from .helpers import ( # type: ignore[import-not-found] + DEFAULT_CONFIG, + get_all_files_folders, +) -def get_all_files_folders(root_path: pathlib.Path) -> set[pathlib.Path]: - """ - Get all files and folders under a directory. - The paths are returned relative to the root path given. - __pycache__ directories and .DS_Store files are ignored. - """ - file_set: set[pathlib.Path] = set() - for dirpath, _, filenames in os.walk(root_path): - dirpath_path = pathlib.Path(dirpath).relative_to(root_path) - if dirpath_path.name in ["__pycache__"]: - continue - - # Add this directory - file_set.update((dirpath_path,)) - # Add any files in it - for filename in filenames: - if filename in [".DS_Store"]: - continue - file_set.update((dirpath_path / filename,)) - - return file_set - - -def test_package_generation( - tmp_path: pathlib.Path, - generate_package: typing.Callable, -) -> None: +def test_package_generation(generate_package: typing.Callable) -> None: """Test package generation.""" - test_config = { - "github_owner": "test-user", - "project_short_description": "description", - "project_name": "Cookiecutter Test", - # Not having a git repo makes it easier to check in/out reference - # data files to the main python-tooling git repository - "initialise_git_repository": False, - } - generate_package(config=test_config, path=tmp_path) + test_config = DEFAULT_CONFIG.copy() + # Not having a git repo makes it easier to check in/out reference + # data files to the main python-tooling git repository + test_config["initialise_git_repository"] = "False" + _, test_project_dir = generate_package(config=test_config) expected_package_dir = ( pathlib.Path(__file__).parent / "data" / "test_package_generation" ) - # Check project directory exists - test_project_dir = tmp_path / "cookiecutter-test" - assert test_project_dir.exists() + assert test_project_dir.exists(), "Project directory does not exist." actual_files = get_all_files_folders(test_project_dir) expected_files = get_all_files_folders(expected_package_dir) @@ -94,18 +65,11 @@ def test_package_generation( def test_pip_installable( - tmp_path: pathlib.Path, venv: pytest_venv.VirtualEnvironment, generate_package: typing.Callable, ) -> None: """Test generated package is pip installable.""" - test_config = { - "github_owner": "test-user", - "project_short_description": "description", - "project_name": "Cookiecutter Test", - } - generate_package(config=test_config, path=tmp_path) - test_project_dir = tmp_path / "cookiecutter-test" + _, test_project_dir = generate_package() # Try to install package in virtual environment with pip pipinstall = subprocess.run( # noqa: S603 [ @@ -124,21 +88,13 @@ def test_pip_installable( ) -@pytest.mark.parametrize("funder", ["", "STFC"]) -def test_optional_funder( - tmp_path: pathlib.Path, generate_package: typing.Callable, funder: str -) -> None: +@pytest.mark.parametrize("funder", ["", "STFC", "UKRI", "Wellcome Trust"]) +def test_optional_funder(generate_package: typing.Callable, funder: str) -> None: """Test specifying funder or not in package generation.""" - config = { - "github_owner": "test-user", - "project_short_description": "description", - "project_name": "Cookiecutter Test", - "funder": funder, - } - - generate_package(config, tmp_path) + config = DEFAULT_CONFIG.copy() + config["funder"] = funder + _, test_project_dir = generate_package(config) - test_project_dir = tmp_path / "cookiecutter-test" with (test_project_dir / "README.md").open() as f: readme_text = "".join(f.readlines()) @@ -152,18 +108,11 @@ def test_optional_funder( def test_docs_build( - tmp_path: pathlib.Path, venv: pytest_venv.VirtualEnvironment, generate_package: typing.Callable, ) -> None: """Test documentation build from package created from template.""" - config = { - "github_owner": "test-user", - "project_short_description": "description", - "project_name": "Cookiecutter Test", - } - generate_package(config, tmp_path) - test_project_dir = tmp_path / "cookiecutter-test" + _, test_project_dir = generate_package() venv.install("tox") tox_docs_process = subprocess.run( # noqa: S603 [ From 90f8e7c651c4e985b1f687b1842384ff9de1ff36 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Fri, 18 Jul 2025 09:26:01 +0100 Subject: [PATCH 02/10] Remove types from docstrings and use NumPy style correctly. Co-authored-by: Matt Graham --- tests/helpers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/helpers.py b/tests/helpers.py index 49786cfd..975658e8 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -18,12 +18,12 @@ def _generate_package( """ Generate a project from the cookiecutter template. - Arguments: - --------- - config: dict + Parameters + ---------- + config A dictionary with values for the cookiecutter template, as defined in the cookiecutter.json - path: Path + path Directory to create package in. Returns: From b1e8ad02b331fea2c303a2570ff58f720ccc0465 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 08:26:14 +0000 Subject: [PATCH 03/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers.py b/tests/helpers.py index 975658e8..6150e087 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -26,7 +26,7 @@ def _generate_package( path Directory to create package in. - Returns: + Returns ------- subprocess.CompletedProcess, pathlib.Path The result of the cookiecutter command and the path to the generated package. From 76cdd51177e4d8c3ab06d743f9c7fe31bd39d0b2 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Fri, 18 Jul 2025 11:04:55 +0100 Subject: [PATCH 04/10] Swap DEFAULT_CONFIG global dict to a function-scoped fixture. Also add a default_config_with fixture for default plus one change (the main use case in our parametrised tests). --- tests/conftest.py | 67 +++++++++++++++++++++++++++++--- tests/helpers.py | 67 -------------------------------- tests/test_git_init.py | 19 ++++----- tests/test_package_generation.py | 47 +++++++++++++++------- 4 files changed, 106 insertions(+), 94 deletions(-) delete mode 100644 tests/helpers.py diff --git a/tests/conftest.py b/tests/conftest.py index a4e3b13d..7f596d94 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,16 +6,73 @@ import pytest -from .helpers import DEFAULT_CONFIG, _generate_package # type: ignore[import-not-found] + +@pytest.fixture +def default_config() -> dict[str, str]: + """ + Get the minimal default configuration for cutting a cookie in tests. + + This is used if `generate_package` is called without arguments. + """ + return { + "github_owner": "test-user", + "project_short_description": "description", + "project_name": "Cookiecutter Test", + "project_slug": "cookiecutter-test", + } + + +@pytest.fixture +def default_config_with(default_config: dict[str, str]) -> typing.Callable: + """Extend or modify the default configuration with one additional value.""" + + def _wrapped_with(key: str, value: str) -> dict[str, str]: + default_config[key] = value + return default_config + + return _wrapped_with + + +def _generate_package( + config: dict[str, str], path: pathlib.Path +) -> tuple[subprocess.CompletedProcess[str], pathlib.Path]: + """ + Generate a project from the cookiecutter template. + + Parameters + ---------- + config + A dictionary with values for the cookiecutter template, + as defined in the cookiecutter.json + path + Directory to create package in. + + Returns + ------- + subprocess.CompletedProcess, pathlib.Path + The result of the cookiecutter command and the path to the generated package. + + """ + args = [f"{key}={val}" for key, val in config.items()] + cmd = ["cookiecutter", ".", "--no-input", "--output-dir", f"{path}"] + return subprocess.run( # noqa: S603 + cmd + args, + check=False, + shell=False, + capture_output=True, + text=True, + ), path / config["project_slug"] @pytest.fixture -def generate_package(tmp_path: pathlib.Path) -> typing.Callable: +def generate_package( + default_config: dict[str, str], tmp_path: pathlib.Path +) -> typing.Callable: """Generate project from cookiecutter template.""" - def wrapped_with_tmp_path( - config: dict[str, str] = DEFAULT_CONFIG, + def _wrapped_with_tmp_path( + config: dict[str, str] = default_config, ) -> tuple[subprocess.CompletedProcess, pathlib.Path]: return _generate_package(config, tmp_path) - return wrapped_with_tmp_path + return _wrapped_with_tmp_path diff --git a/tests/helpers.py b/tests/helpers.py deleted file mode 100644 index 6150e087..00000000 --- a/tests/helpers.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Helper functions for the cookiecutter template tests.""" - -import os -import pathlib -import subprocess - -DEFAULT_CONFIG = { - "github_owner": "test-user", - "project_short_description": "description", - "project_name": "Cookiecutter Test", - "project_slug": "cookiecutter-test", -} - - -def _generate_package( - config: dict[str, str], path: pathlib.Path -) -> tuple[subprocess.CompletedProcess[str], pathlib.Path]: - """ - Generate a project from the cookiecutter template. - - Parameters - ---------- - config - A dictionary with values for the cookiecutter template, - as defined in the cookiecutter.json - path - Directory to create package in. - - Returns - ------- - subprocess.CompletedProcess, pathlib.Path - The result of the cookiecutter command and the path to the generated package. - - """ - args = [f"{key}={val}" for key, val in config.items()] - cmd = ["cookiecutter", ".", "--no-input", "--output-dir", f"{path}"] - return subprocess.run( # noqa: S603 - cmd + args, - check=False, - shell=False, - capture_output=True, - text=True, - ), path / config["project_slug"] - - -def get_all_files_folders(root_path: pathlib.Path) -> set[pathlib.Path]: - """ - Get all files and folders under a directory. - - The paths are returned relative to the root path given. - __pycache__ directories and .DS_Store files are ignored. - """ - file_set: set[pathlib.Path] = set() - for dirpath, _, filenames in os.walk(root_path): - dirpath_path = pathlib.Path(dirpath).relative_to(root_path) - if dirpath_path.name in ["__pycache__"]: - continue - - # Add this directory - file_set.update((dirpath_path,)) - # Add any files in it - for filename in filenames: - if filename in [".DS_Store"]: - continue - file_set.update((dirpath_path / filename,)) - - return file_set diff --git a/tests/test_git_init.py b/tests/test_git_init.py index 9b337c33..357305fd 100644 --- a/tests/test_git_init.py +++ b/tests/test_git_init.py @@ -5,19 +5,20 @@ import pytest -from .helpers import DEFAULT_CONFIG # type: ignore[import-not-found] - @pytest.mark.parametrize("initialise_git_repository", [True, False]) def test_initialisation_of_git_repo( initialise_git_repository: bool, # noqa: FBT001 + default_config_with: typing.Callable, generate_package: typing.Callable, ) -> None: """Checks to see if git was correctly initialised if desired.""" - test_config = DEFAULT_CONFIG.copy() - test_config["initialise_git_repository"] = str(initialise_git_repository) + config = default_config_with( + "initialise_git_repository", str(initialise_git_repository) + ) + # Run cookiecutter with initialise_git_repository - result, test_project_dir = generate_package(config=test_config) + result, test_project_dir = generate_package(config=config) # check if git is initialised git_status = subprocess.run( # noqa: S603 @@ -51,9 +52,9 @@ def test_initialisation_of_git_repo( ) assert ( "GitHub CLI detected, you can create a repo with the following:\n\n" - f"gh repo create {test_config['github_owner']}/" + f"gh repo create {config['github_owner']}/" f"cookiecutter-test -d " - f'"{test_config["project_short_description"]}" --public -r ' + f'"{config["project_short_description"]}" --public -r ' f"origin --source cookiecutter-test" in result.stdout ) except FileNotFoundError: @@ -63,11 +64,11 @@ def test_initialisation_of_git_repo( assert ( "You now have a local git repository. To sync this to GitHub you " "need to create an empty GitHub repo with the name " - f"{test_config['github_owner']}/" + f"{config['github_owner']}/" f"cookiecutter-test - DO NOT SELECT ANY " "OTHER OPTION.\n\nSee this link for more detail " "https://docs.github.com/en/get-started/quickstart/create-a-repo" ".\n\nThen run:\n\ngit remote add origin git@github.com:" - f"{test_config['github_owner']}/" + f"{config['github_owner']}/" f"cookiecutter-test.git" in result.stdout ) diff --git a/tests/test_package_generation.py b/tests/test_package_generation.py index 7a805c0f..eacc7187 100644 --- a/tests/test_package_generation.py +++ b/tests/test_package_generation.py @@ -1,6 +1,7 @@ """Checks that the cookiecutter works.""" import difflib +import os import pathlib import shutil import subprocess @@ -9,19 +10,39 @@ import pytest import pytest_venv # type: ignore[import-not-found] -from .helpers import ( # type: ignore[import-not-found] - DEFAULT_CONFIG, - get_all_files_folders, -) +def get_all_files_folders(root_path: pathlib.Path) -> set[pathlib.Path]: + """ + Get all files and folders under a directory. -def test_package_generation(generate_package: typing.Callable) -> None: + The paths are returned relative to the root path given. + __pycache__ directories and .DS_Store files are ignored. + """ + file_set: set[pathlib.Path] = set() + for dirpath, _, filenames in os.walk(root_path): + dirpath_path = pathlib.Path(dirpath).relative_to(root_path) + if dirpath_path.name in ["__pycache__"]: + continue + + # Add this directory + file_set.update((dirpath_path,)) + # Add any files in it + for filename in filenames: + if filename in [".DS_Store"]: + continue + file_set.update((dirpath_path / filename,)) + + return file_set + + +def test_package_generation( + default_config_with: typing.Callable, generate_package: typing.Callable +) -> None: """Test package generation.""" - test_config = DEFAULT_CONFIG.copy() # Not having a git repo makes it easier to check in/out reference # data files to the main python-tooling git repository - test_config["initialise_git_repository"] = "False" - _, test_project_dir = generate_package(config=test_config) + config = default_config_with("initialise_git_repository", "False") + _, test_project_dir = generate_package(config=config) expected_package_dir = ( pathlib.Path(__file__).parent / "data" / "test_package_generation" @@ -89,10 +110,11 @@ def test_pip_installable( @pytest.mark.parametrize("funder", ["", "STFC", "UKRI", "Wellcome Trust"]) -def test_optional_funder(generate_package: typing.Callable, funder: str) -> None: +def test_optional_funder( + funder: str, default_config_with: typing.Callable, generate_package: typing.Callable +) -> None: """Test specifying funder or not in package generation.""" - config = DEFAULT_CONFIG.copy() - config["funder"] = funder + config = default_config_with("funder", funder) _, test_project_dir = generate_package(config) with (test_project_dir / "README.md").open() as f: @@ -102,8 +124,7 @@ def test_optional_funder(generate_package: typing.Callable, funder: str) -> None assert "## Acknowledgements" not in readme_text else: assert ( - f"## Acknowledgements\n\nThis work was funded by {config['funder']}." - in readme_text + f"## Acknowledgements\n\nThis work was funded by {funder}." in readme_text ), readme_text From 127fd2722d820c7315ae56a98d3c967c7866834e Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Fri, 18 Jul 2025 11:12:00 +0100 Subject: [PATCH 05/10] Shorter parameter name in obvious context. --- tests/test_git_init.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/test_git_init.py b/tests/test_git_init.py index 357305fd..8814c811 100644 --- a/tests/test_git_init.py +++ b/tests/test_git_init.py @@ -6,18 +6,15 @@ import pytest -@pytest.mark.parametrize("initialise_git_repository", [True, False]) +@pytest.mark.parametrize("init", [True, False]) def test_initialisation_of_git_repo( - initialise_git_repository: bool, # noqa: FBT001 + init: bool, # noqa: FBT001 default_config_with: typing.Callable, generate_package: typing.Callable, ) -> None: """Checks to see if git was correctly initialised if desired.""" - config = default_config_with( - "initialise_git_repository", str(initialise_git_repository) - ) - # Run cookiecutter with initialise_git_repository + config = default_config_with("initialise_git_repository", str(init)) result, test_project_dir = generate_package(config=config) # check if git is initialised @@ -32,7 +29,7 @@ def test_initialisation_of_git_repo( text=True, ) - if not initialise_git_repository: + if not init: # should not have found git assert "fatal: not a git repository" in git_status.stderr return # nothing more to test From ff0df8754d81c23328da8c044cff00c095a4cdae Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Wed, 23 Jul 2025 10:22:03 +0100 Subject: [PATCH 06/10] Newline for function args. Co-authored-by: Patrick J. Roddy --- tests/test_package_generation.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_package_generation.py b/tests/test_package_generation.py index eacc7187..aad0541f 100644 --- a/tests/test_package_generation.py +++ b/tests/test_package_generation.py @@ -36,7 +36,8 @@ def get_all_files_folders(root_path: pathlib.Path) -> set[pathlib.Path]: def test_package_generation( - default_config_with: typing.Callable, generate_package: typing.Callable + default_config_with: typing.Callable, + generate_package: typing.Callable, ) -> None: """Test package generation.""" # Not having a git repo makes it easier to check in/out reference @@ -111,7 +112,8 @@ def test_pip_installable( @pytest.mark.parametrize("funder", ["", "STFC", "UKRI", "Wellcome Trust"]) def test_optional_funder( - funder: str, default_config_with: typing.Callable, generate_package: typing.Callable + funder: str, default_config_with: typing.Callable, + generate_package: typing.Callable ) -> None: """Test specifying funder or not in package generation.""" config = default_config_with("funder", funder) From 3961e3ee6756152ba017ab00382acccffc2da922 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 09:22:19 +0000 Subject: [PATCH 07/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_package_generation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_package_generation.py b/tests/test_package_generation.py index aad0541f..0434dc8d 100644 --- a/tests/test_package_generation.py +++ b/tests/test_package_generation.py @@ -112,8 +112,7 @@ def test_pip_installable( @pytest.mark.parametrize("funder", ["", "STFC", "UKRI", "Wellcome Trust"]) def test_optional_funder( - funder: str, default_config_with: typing.Callable, - generate_package: typing.Callable + funder: str, default_config_with: typing.Callable, generate_package: typing.Callable ) -> None: """Test specifying funder or not in package generation.""" config = default_config_with("funder", funder) From 76d2463ded28a037c6d38080cc43a9f36c747309 Mon Sep 17 00:00:00 2001 From: "Patrick J. Roddy" Date: Wed, 23 Jul 2025 11:13:14 +0100 Subject: [PATCH 08/10] =?UTF-8?q?Et=20voil=C3=A0=20@samcunliffe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hooks/post_gen_project.py | 8 ++++---- tests/conftest.py | 6 ++++-- tests/test_package_generation.py | 6 ++++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index ac978c86..22474318 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -66,7 +66,7 @@ def main(initialise_git_repository: str, deploy_docs_to_github_pages: str) -> in '-d "{{cookiecutter.project_short_description}}" ' "--public " "-r origin " - "--source {{cookiecutter.project_slug}}\n" + "--source {{cookiecutter.project_slug}}\n", ) except FileNotFoundError: # GitHub CLI isn't installed @@ -78,7 +78,7 @@ def main(initialise_git_repository: str, deploy_docs_to_github_pages: str) -> in "https://docs.github.com/en/get-started/quickstart/create-a-repo.\n\n" "Then run:\n\n" "git remote add origin git@github.com:" - "{{cookiecutter.__repo_name}}.git\n" + "{{cookiecutter.__repo_name}}.git\n", ) except subprocess.CalledProcessError as e: # some other error @@ -98,7 +98,7 @@ def main(initialise_git_repository: str, deploy_docs_to_github_pages: str) -> in "{{cookiecutter.__repo_url}}/settings/pages\n\n" "and under 'Built and deployment' selecting 'Deploy from a branch' for " "the 'Source' drop-down and 'gh-pages' for the 'Branch' drop-down, " - "leaving the branch path drop-down with its default value of '/ (root)'." + "leaving the branch path drop-down with its default value of '/ (root)'.", ) return _EXIT_SUCCESS @@ -109,5 +109,5 @@ def main(initialise_git_repository: str, deploy_docs_to_github_pages: str) -> in main( "{{ cookiecutter.initialise_git_repository }}", "{{ cookiecutter.deploy_docs_to_github_pages }}", - ) + ), ) diff --git a/tests/conftest.py b/tests/conftest.py index 7f596d94..668a0925 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,7 +34,8 @@ def _wrapped_with(key: str, value: str) -> dict[str, str]: def _generate_package( - config: dict[str, str], path: pathlib.Path + config: dict[str, str], + path: pathlib.Path, ) -> tuple[subprocess.CompletedProcess[str], pathlib.Path]: """ Generate a project from the cookiecutter template. @@ -66,7 +67,8 @@ def _generate_package( @pytest.fixture def generate_package( - default_config: dict[str, str], tmp_path: pathlib.Path + default_config: dict[str, str], + tmp_path: pathlib.Path, ) -> typing.Callable: """Generate project from cookiecutter template.""" diff --git a/tests/test_package_generation.py b/tests/test_package_generation.py index 0434dc8d..6ebcf48b 100644 --- a/tests/test_package_generation.py +++ b/tests/test_package_generation.py @@ -71,7 +71,7 @@ def test_package_generation( f2.readlines(), fromfile=str(actual_file), tofile=str(expected_file), - ) + ), ) if diff: @@ -112,7 +112,9 @@ def test_pip_installable( @pytest.mark.parametrize("funder", ["", "STFC", "UKRI", "Wellcome Trust"]) def test_optional_funder( - funder: str, default_config_with: typing.Callable, generate_package: typing.Callable + funder: str, + default_config_with: typing.Callable, + generate_package: typing.Callable, ) -> None: """Test specifying funder or not in package generation.""" config = default_config_with("funder", funder) From 1df49a4290ccdb3e7a70147fca7daddb9d36b54e Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Wed, 23 Jul 2025 12:25:27 +0100 Subject: [PATCH 09/10] Keep two fixtures but use a neater one-liner and allow both fixtures to be used at once. Co-authored-by: Matt Graham --- tests/conftest.py | 5 ++--- tests/test_git_init.py | 2 +- tests/test_package_generation.py | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 668a0925..1fe29846 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,9 +26,8 @@ def default_config() -> dict[str, str]: def default_config_with(default_config: dict[str, str]) -> typing.Callable: """Extend or modify the default configuration with one additional value.""" - def _wrapped_with(key: str, value: str) -> dict[str, str]: - default_config[key] = value - return default_config + def _wrapped_with(**kwargs: str) -> dict[str, str]: + return default_config | kwargs return _wrapped_with diff --git a/tests/test_git_init.py b/tests/test_git_init.py index 8814c811..60f59a38 100644 --- a/tests/test_git_init.py +++ b/tests/test_git_init.py @@ -14,7 +14,7 @@ def test_initialisation_of_git_repo( ) -> None: """Checks to see if git was correctly initialised if desired.""" # Run cookiecutter with initialise_git_repository - config = default_config_with("initialise_git_repository", str(init)) + config = default_config_with(initialise_git_repository=str(init)) result, test_project_dir = generate_package(config=config) # check if git is initialised diff --git a/tests/test_package_generation.py b/tests/test_package_generation.py index 6ebcf48b..ac6f3b23 100644 --- a/tests/test_package_generation.py +++ b/tests/test_package_generation.py @@ -42,7 +42,7 @@ def test_package_generation( """Test package generation.""" # Not having a git repo makes it easier to check in/out reference # data files to the main python-tooling git repository - config = default_config_with("initialise_git_repository", "False") + config = default_config_with(initialise_git_repository="False") _, test_project_dir = generate_package(config=config) expected_package_dir = ( @@ -117,7 +117,7 @@ def test_optional_funder( generate_package: typing.Callable, ) -> None: """Test specifying funder or not in package generation.""" - config = default_config_with("funder", funder) + config = default_config_with(funder=funder) _, test_project_dir = generate_package(config) with (test_project_dir / "README.md").open() as f: From 571679c1450150b3a972fcc78c20fa44e02234f0 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Wed, 23 Jul 2025 12:37:51 +0100 Subject: [PATCH 10/10] Explicitly scope the fixtures Contravenes PT003 but "Explicit is better than implicit." Co-authored-by: Paddy Roddy --- tests/conftest.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1fe29846..5fd2c878 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ import pytest -@pytest.fixture +@pytest.fixture(scope="function") # noqa: PT003 def default_config() -> dict[str, str]: """ Get the minimal default configuration for cutting a cookie in tests. @@ -22,7 +22,7 @@ def default_config() -> dict[str, str]: } -@pytest.fixture +@pytest.fixture(scope="function") # noqa: PT003 def default_config_with(default_config: dict[str, str]) -> typing.Callable: """Extend or modify the default configuration with one additional value.""" @@ -33,8 +33,7 @@ def _wrapped_with(**kwargs: str) -> dict[str, str]: def _generate_package( - config: dict[str, str], - path: pathlib.Path, + config: dict[str, str], path: pathlib.Path ) -> tuple[subprocess.CompletedProcess[str], pathlib.Path]: """ Generate a project from the cookiecutter template. @@ -66,8 +65,7 @@ def _generate_package( @pytest.fixture def generate_package( - default_config: dict[str, str], - tmp_path: pathlib.Path, + default_config: dict[str, str], tmp_path: pathlib.Path ) -> typing.Callable: """Generate project from cookiecutter template."""