diff --git a/.github/python-project-template.yml b/.github/python-project-template.yml index 578b02b..9023ab2 100644 --- a/.github/python-project-template.yml +++ b/.github/python-project-template.yml @@ -4,8 +4,8 @@ default_context: project_slug: "your-project" full_name: "Your Name" license: "MIT" - github_actions_ci: "Yes" - gitlab_ci: "No" - notebooks: "No" - commandlineinterface: "No" + github_actions_ci: true + gitlab_ci: false + notebooks: false + commandlineinterface: false version_management: "manually" diff --git a/README.md b/README.md index 79c4d7d..a0a95d0 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ If your Python package contains compiled C++ modules, you might want to check ou In order to use Python Package Cookiecutter you need the following software installed: * Python `>= 3.10` -* [Cookiecutter](https://github.com/cookiecutter/cookiecutter) e.g. by doing `pip install cookiecutter`. +* [Cookiecutter](https://github.com/cookiecutter/cookiecutter) `>=2.7` e.g. by doing `pip install cookiecutter`. * Git `>= 1.8.2` # Using the Python Package Cookiecutter diff --git a/cookiecutter.json b/cookiecutter.json index 16bcf7d..29f9c8f 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -3,16 +3,48 @@ "remote_url": "None", "project_slug": "{%- if cookiecutter.remote_url == 'None' -%}{{ cookiecutter.project_name|replace('+', 'p')|slugify }}{% else %}{{ cookiecutter.remote_url.split('/')[-1]|replace('.git', '')}}{%- endif -%}", "full_name": "Your Name", - "license": ["MIT", "BSD-2", "GPL-3.0", "LGPL-3.0", "None"], - "github_actions_ci": ["Yes", "No"], - "gitlab_ci": ["Yes", "No"], - "notebooks": ["Yes", "No"], - "commandlineinterface": ["No", "Yes"], - "version_management": ["manually", "setuptools_scm"], + "license": [ + "MIT", + "BSD-2", + "GPL-3.0", + "LGPL-3.0", + "None" + ], + "github_actions_ci": true, + "gitlab_ci": true, + "notebooks": true, + "commandlineinterface": false, + "version_management": [ + "manually", + "setuptools_scm" + ], "_extensions": [ "local_extensions.PrecommitExtension", "local_extensions.ShortcutExtension", "local_extensions.CurrentDateExtension" ], - "__modname": "{{ cookiecutter|modname }}" + "__modname": "{{ cookiecutter|modname }}", + "__prompts__": { + "project_name": "Project name", + "remote_url": "Git remote URL (None to configure it later)", + "project_slug": "Project directory name", + "full_name": "Author full name", + "license": { + "__prompt__": "Project license", + "MIT": "MIT", + "BSD-2": "BSD 2-Clause", + "GPL-3.0": "GNU GPL v3", + "LGPL-3.0": "GNU LGPL v3", + "None": "Do not add a license" + }, + "github_actions_ci": "Add GitHub Actions CI?", + "gitlab_ci": "Add GitLab CI?", + "notebooks": "Add an example Jupyter notebook?", + "commandlineinterface": "Add a command-line interface?", + "version_management": { + "__prompt__": "Package version management", + "manually": "Set the version manually", + "setuptools_scm": "Derive the version from Git tags with setuptools-scm" + } + } } diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 1231012..2652317 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -54,14 +54,14 @@ def conditional_remove(condition, path): conditional_remove("{{ cookiecutter.license }}" == "None", "LICENSE.md") -conditional_remove("{{ cookiecutter.github_actions_ci }}" == "No", ".github") -conditional_remove("{{ cookiecutter.github_actions_ci }}" == "No", "codecov.yml") -conditional_remove("{{ cookiecutter.gitlab_ci }}" == "No", ".gitlab-ci.yml") +conditional_remove(not {{ cookiecutter.github_actions_ci }}, ".github") +conditional_remove(not {{ cookiecutter.github_actions_ci }}, "codecov.yml") +conditional_remove(not {{ cookiecutter.gitlab_ci }}, ".gitlab-ci.yml") conditional_remove(not {{ have_precommit }}, ".pre-commit-config.yaml") -conditional_remove("{{ cookiecutter.notebooks }}" == "No", "notebooks") -conditional_remove("{{ cookiecutter.notebooks }}" == "No", "doc/demo.nblink") -conditional_remove("{{ cookiecutter.commandlineinterface }}" == "No", "src/{{ cookiecutter|modname }}/__main__.py") -conditional_remove("{{ cookiecutter.commandlineinterface }}" == "No", "tests/test_cli.py") +conditional_remove(not {{ cookiecutter.notebooks }}, "notebooks") +conditional_remove(not {{ cookiecutter.notebooks }}, "doc/demo.nblink") +conditional_remove(not {{ cookiecutter.commandlineinterface }}, "src/{{ cookiecutter|modname }}/__main__.py") +conditional_remove(not {{ cookiecutter.commandlineinterface }}, "tests/test_cli.py") conditional_remove(os.stat("TODO.md").st_size == 0, "TODO.md") # Set up a Git repository diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 9790769..2971666 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -6,6 +6,17 @@ # user configuration value and exit with an error upon failure. import sys +from importlib.metadata import version + + +# Cookiecutter 2.7 coerces command-line and configuration overrides to booleans. +# This check intentionally duplicates the one in pre_prompt.py: Cookiecutter +# versions older than 2.4 do not support pre-prompt hooks, so this remains as a +# fallback for them after prompting. +parts = tuple(int(part) for part in version("cookiecutter").split(".")[:3]) +if parts < (2, 7, 0): + sys.stderr.write("This template requires cookiecutter >= 2.7\n") + sys.exit(1) # Call fail_if with a Jinja template-based condition to abandon the diff --git a/hooks/pre_prompt.py b/hooks/pre_prompt.py new file mode 100644 index 0000000..bdeddcc --- /dev/null +++ b/hooks/pre_prompt.py @@ -0,0 +1,13 @@ +# This script executes before cookiecutter prompts for template variables. +# Details about hooks can be found in the cookiecutter documentation: +# https://cookiecutter.readthedocs.io/en/latest/advanced/hooks.html + +import sys +from importlib.metadata import version + + +# Cookiecutter 2.7 coerces command-line and configuration overrides to booleans. +parts = tuple(int(part) for part in version("cookiecutter").split(".")[:3]) +if parts < (2, 7, 0): + sys.stderr.write("This template requires cookiecutter >= 2.7\n") + sys.exit(1) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1fde23d..6cafeec 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -cookiecutter>=2.1 +cookiecutter>=2.7 flaky pre-commit PyGithub diff --git a/tests/test_bake_project.py b/tests/test_bake_project.py index d2216b2..78b9c01 100644 --- a/tests/test_bake_project.py +++ b/tests/test_bake_project.py @@ -20,8 +20,8 @@ def test_with_remote(cookies): @pytest.mark.local -@pytest.mark.parametrize("cli", ("Yes", "No")) -@pytest.mark.parametrize("notebooks", ("Yes", "No")) +@pytest.mark.parametrize("cli", (True, False)) +@pytest.mark.parametrize("notebooks", (True, False)) @pytest.mark.parametrize("version_management", ("manually", "setuptools_scm")) def test_pytest_run(cookies, virtualenv, cli, notebooks, version_management): # Bake the project diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml index e95df8b..c63ab97 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: - name: Run Python tests run: | - python -m pytest{% if cookiecutter.notebooks == "Yes" %} --nbval{% endif %} + python -m pytest{% if cookiecutter.notebooks %} --nbval{% endif %} coverage: name: Coverage Testing diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml index f16d1eb..89d813a 100644 --- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml +++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml @@ -1,7 +1,7 @@ .template: &template script: - python -m pip install .[tests] - - python -m pytest{% if cookiecutter.notebooks == "Yes" %} --nbval{% endif %} + - python -m pytest{% if cookiecutter.notebooks %} --nbval{% endif %} test-{{ cookiecutter|modname }}-python3.10: image: python:3.10 @@ -10,4 +10,3 @@ test-{{ cookiecutter|modname }}-python3.10: test-{{ cookiecutter|modname }}-python3.13: image: python:3.13 <<: *template - diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index debff41..6fedb9a 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: check-toml # Unify file endings - id: end-of-file-fixer -{%- if cookiecutter.notebooks == "Yes" %} +{%- if cookiecutter.notebooks %} # Make sure that Jupyter notebooks under version control # have their outputs stripped before committing @@ -32,7 +32,7 @@ repos: - id: nbstripout files: ".ipynb" {%- endif %} -{%- if cookiecutter.github_actions_ci == "Yes" %} +{%- if cookiecutter.github_actions_ci %} # GitHub Actions Workflow linter - repo: https://github.com/rhysd/actionlint diff --git a/{{cookiecutter.project_slug}}/FILESTRUCTURE.md b/{{cookiecutter.project_slug}}/FILESTRUCTURE.md index ba6c050..896c424 100644 --- a/{{cookiecutter.project_slug}}/FILESTRUCTURE.md +++ b/{{cookiecutter.project_slug}}/FILESTRUCTURE.md @@ -2,7 +2,7 @@ This is an explanation of the file structure that the cookiecutter generated for * Python source files: * The Python package source files are located in the `src/{{ cookiecutter|modname }}` directory. -{%- if cookiecutter.commandlineinterface == "Yes" %} +{%- if cookiecutter.commandlineinterface %} * The file `src/{{ cookiecutter|modname }}.__main__.py` defines a command line interface that is accessible both as the command `{{ cookiecutter|modname }}` and via `python -m {{ cookiecutter|modname }}`. @@ -10,7 +10,7 @@ This is an explanation of the file structure that the cookiecutter generated for {%- endif %} * `tests/test_{{ cookiecutter|modname }}.py` contains the unit tests for the package. * `tests/conftest.py` contains testing setup and configuration for `pytest` -{%- if cookiecutter.notebooks == "Yes" %} +{%- if cookiecutter.notebooks %} * The `notebooks` directory contains an example Jupyter notebook on how to use `{{ cookiecutter|modname }}`. This notebook is always executed during `pytest` execution and it is automatically rendered into the Sphinx documentation. diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index 04c6e89..66a9e6a 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -14,10 +14,10 @@ [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) {% endif -%} {% if cookiecutter.remote_url != "None" -%} -{% if cookiecutter.github_actions_ci == "Yes" and cookiecutter|is_github -%} +{% if cookiecutter.github_actions_ci and cookiecutter|is_github -%} [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/{{ cookiecutter|username }}/{{ cookiecutter|remote_slug }}/ci.yml?branch=main)](https://github.com/{{ cookiecutter|username }}/{{ cookiecutter|remote_slug }}/actions/workflows/ci.yml) {% endif -%} -{% if cookiecutter.gitlab_ci == "Yes" and cookiecutter|is_gitlab -%} +{% if cookiecutter.gitlab_ci and cookiecutter|is_gitlab -%} [![Gitlab pipeline status](https://img.shields.io/gitlab/pipeline/{{ cookiecutter|username }}/{{ cookiecutter|remote_slug }}/main {%- if "gitlab.com" not in cookiecutter.remote_url -%} ?gitlab_url={{ cookiecutter|gitlab_instance }} diff --git a/{{cookiecutter.project_slug}}/TODO.md b/{{cookiecutter.project_slug}}/TODO.md index 65ad1c2..fbab376 100644 --- a/{{cookiecutter.project_slug}}/TODO.md +++ b/{{cookiecutter.project_slug}}/TODO.md @@ -19,7 +19,7 @@ The following tasks need to be done to get a fully working project: * Enable the integration of Readthedocs with your Git hoster. In the case of Github, this means that you need to login at [Read the Docs](https://readthedocs.org) and click the button *Import a Project*. -{%- if cookiecutter.github_actions_ci == 'Yes' %} +{%- if cookiecutter.github_actions_ci %} * Enable the integration with `codecov.io` by heading to the [Codecov.io Website](https://codecov.io), log in (e.g. with your Github credentials) and enable integration for your repository. In order to do so, you need to select it from the list of repositories (potentially re-syncing with GitHub). Then, head diff --git a/{{cookiecutter.project_slug}}/doc/conf.py b/{{cookiecutter.project_slug}}/doc/conf.py index 465dd9e..5e23b90 100644 --- a/{{cookiecutter.project_slug}}/doc/conf.py +++ b/{{cookiecutter.project_slug}}/doc/conf.py @@ -17,7 +17,7 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ -{%- if cookiecutter.notebooks == "Yes" %} +{%- if cookiecutter.notebooks %} "nbsphinx", "nbsphinx_link", {%- endif %} diff --git a/{{cookiecutter.project_slug}}/doc/index.rst b/{{cookiecutter.project_slug}}/doc/index.rst index 3054caa..7355f53 100644 --- a/{{cookiecutter.project_slug}}/doc/index.rst +++ b/{{cookiecutter.project_slug}}/doc/index.rst @@ -6,7 +6,7 @@ :caption: Contents: intro -{%- if cookiecutter.notebooks == "Yes" %} +{%- if cookiecutter.notebooks %} demo {%- endif %} api diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index 4a1a6e4..d00fdc2 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -43,7 +43,7 @@ classifiers = [ "Operating System :: OS Independent", ] dependencies = [ -{%- if cookiecutter.commandlineinterface == "Yes" %} +{%- if cookiecutter.commandlineinterface %} "click", {%- endif %} ] @@ -52,12 +52,12 @@ dependencies = [ tests = [ "pytest", "pytest-cov", -{%- if cookiecutter.notebooks == "Yes" %} +{%- if cookiecutter.notebooks %} "nbval", {%- endif %} ] docs = [ -{%- if cookiecutter.notebooks == "Yes" %} +{%- if cookiecutter.notebooks %} "ipykernel", "nbsphinx", "nbsphinx-link", @@ -66,7 +66,7 @@ docs = [ "sphinx_mdinclude", "sphinx_rtd_theme", ] -{% if cookiecutter.commandlineinterface == "Yes" %} +{% if cookiecutter.commandlineinterface %} # Command line scripts installed as part of the installation [project.scripts] {{ cookiecutter|modname }} = "{{ cookiecutter|modname }}.__main__:main" @@ -92,7 +92,7 @@ write_to = "src/{{ cookiecutter|modname }}/_version.py" [tool.pytest.ini_options] testpaths = [ "tests", -{%- if cookiecutter.notebooks == "Yes" %} +{%- if cookiecutter.notebooks %} "notebooks", {%- endif %} ]