diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..615afb3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,30 @@ +# yaml-language-server: $schema=https://json.schemastore.org/dependabot-2.0.json + +version: 2 +updates: + - package-ecosystem: pip + directory: / + schedule: + interval: weekly + groups: + python-dependencies: + patterns: + - "*" + + - package-ecosystem: pip + directory: "/{{cookiecutter.__project_name_kebab_case}}" + schedule: + interval: weekly + groups: + python-dependencies: + patterns: + - "*" + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 62fe8b1..a81075e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,11 +17,11 @@ jobs: # Required to create the GitHub release contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: - python-version: "3.13" + python-version: "3.14" - name: Initialize project run: | make init diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 599b7ed..530df93 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,12 +18,12 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.11", "3.12", "3.13", "3.14"] os: [ubuntu-latest, macOS-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Initialize project diff --git a/.vscode/extensions.json b/.vscode/extensions.json index f44ad7e..40c6dd5 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,6 +4,9 @@ "EditorConfig.EditorConfig", "ms-python.python", "ms-python.vscode-pylance", - "bungcip.better-toml" + "ms-python.black-formatter", + "ms-python.flake8", + "ms-python.mypy-type-checker", + "tamasfe.even-better-toml" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index a0fb9f8..fc84465 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,17 +14,12 @@ ".mypy_cache": true, ".pytest_cache": true }, - // Pythong testing + // Python testing "python.testing.pytestArgs": ["tests"], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, - // Python linting - "python.linting.enabled": true, - "python.linting.mypyEnabled": true, - "python.linting.flake8Enabled": true, - // Python formatting - "python.formatting.provider": "black", + // Python formatting (provided by the ms-python.black-formatter extension) "[python]": { - "editor.defaultFormatter": "ms-python.python" + "editor.defaultFormatter": "ms-python.black-formatter" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bfdef7..2cd5cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,30 @@ The format is based on [Keep a Changelog] and this project adheres to ### Added +- `.github/dependabot.yml` to keep pip and GitHub Actions dependencies up to date +- `docs/tool-choices.md` content, and `CONTRIBUTING.md` content (both repo and template) + ### Changed +- Generated projects now use [PEP 639](https://peps.python.org/pep-0639/) + `license`/`license-files` fields instead of the deprecated `license = {file = ...}` + table and `License ::` classifiers; `_pypi_license_map` was renamed to + `_spdx_license_map` in `cookiecutter.json` accordingly +- Bumped `actions/checkout` and `actions/setup-python` to v7 in all workflows +- Bumped the minimum supported Python version to 3.11 and added 3.14 to the CI matrix + (repo and template) +- `make test-unit` now reports coverage (`--cov`) in both the repo and the template +- `tests/e2e/test_e2e.py` now installs each generated project's dependencies into its + own throwaway virtualenv instead of the ambient test environment + ### Fixed +- The generated project's wheel no longer packages a top-level `tests` module + (`[tool.setuptools.packages.find]` was including everything under the project root) +- `hooks/pre_gen_project.py` now rejects Python keywords (e.g. a project named `class`) + and accepts single-character project names, instead of using a hand-rolled regex +- Fixed broken `docs/*.md` links in the root `README.md` (missing `.md` extension) + ## [0.1.0] ### Added diff --git a/CLAUDE.md b/CLAUDE.md index aba6de6..d65b3f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -88,7 +88,8 @@ when the change could break the generated project's own tooling. ## Adding or renaming a cookiecutter variable 1. Add it to `cookiecutter.json`. Names prefixed `__` are computed/derived; `_` are private - (e.g. `_pypi_license_map`, which maps a license choice to its PyPI classifier). + (e.g. `_spdx_license_map`, which maps a license choice to its SPDX identifier for the + `license` field in the generated `pyproject.toml`). 2. Use it in the template tree. 3. Update `tests/integration/test_generator.py`: `run_generated_project_assertions` reads defaults straight from `cookiecutter.json`, @@ -103,5 +104,5 @@ when the change could break the generated project's own tooling. - Version lives in `VERSION` (dynamic via setuptools) for this repo; generated projects use `__version__` in their package `__init__.py`. - Dependencies are pinned exactly (`==`) in both `requirements.txt` files. -- Supported Python: >= 3.10; CI matrix is 3.10–3.13 across ubuntu/macOS/windows. +- Supported Python: >= 3.11; CI matrix is 3.11–3.14 across ubuntu/macOS/windows. - Default branch is `master`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e501c94..8c0c2c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1,29 @@ # Contributing to Cookiecutter-PyProject + +Thanks for taking the time to contribute! + +## Before you start + +- Read the [development instructions] to get your environment set up. +- For anything beyond a small fix, please open an issue first to discuss what you'd + like to change. This avoids spending time on a pull request that doesn't get merged. +- By participating in this project you agree to abide by the [Code of Conduct]. + +## Making a change + +1. Fork the repository and create a branch off `master`. +2. Make your change. If you're fixing a bug or adding a feature, add or update tests + under `tests/` (or, if the change affects the generated project, under + `{{cookiecutter.__project_name_kebab_case}}/tests/`) and update the relevant docs. +3. Run `make ci` locally and make sure it passes before opening a pull request; this is + the same check that runs in CI. +4. Add an entry under `## [Unreleased]` in `CHANGELOG.md` describing your change. +5. Open a pull request using the provided template. + +## Reporting bugs and requesting features + +Please use the issue templates in `.github/ISSUE_TEMPLATE` to file a bug report, feature +request, or question. + +[development instructions]: ./docs/development-instructions.md +[code of conduct]: ./CODE_OF_CONDUCT.md diff --git a/Makefile b/Makefile index 5966c72..6c79a22 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -.PHONY: clean +.PHONY: init clean check-style fix-style check-types test-unit test-integration \ + test-e2e test build check-version release-tag ci generate .DEFAULT_GOAL:= init init: diff --git a/README.md b/README.md index 2936e99..409a0c0 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ You can read more about these choices under [Tool choices and design considerati ### System requirement -- [python] >= 3.10 (comes with pip) +- [python] >= 3.11 (comes with pip) - [cookiecutter] >= 2.1.1 ### Generating your first project @@ -85,9 +85,9 @@ chat rooms, and mailing lists is expected to follow the [CODE_OF_CONDUCT] [cookiecutter]: https://github.com/cookiecutter/cookiecutter [file an issue]: https://github.com/ali92hm/cookiecutter-pyproject/issues [contributing]: ./CONTRIBUTING.md -[development instructions]: ./docs/development-instructions -[tool choices and design considerations]: ./docs/tool-choices -[project structure]: ./docs/project-structure +[development instructions]: ./docs/development-instructions.md +[tool choices and design considerations]: ./docs/tool-choices.md +[project structure]: ./docs/project-structure.md [code_of_conduct]: ./CODE_OF_CONDUCT.md [mit license]: http://opensource.org/licenses/MIT [osi_certified]: https://opensource.org/trademarks/osi-certified/web/osi-certified-120x100.png diff --git a/cookiecutter.json b/cookiecutter.json index 95b43bc..e6e4e10 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -17,13 +17,13 @@ "Unlicense", "Not open source" ], - "_pypi_license_map": { - "MIT License": "License :: OSI Approved :: MIT License", - "BSD 2-Clause License": "License :: OSI Approved :: BSD License", - "BSD 3-Clause License": "License :: OSI Approved :: BSD License", - "ISC License": "License :: OSI Approved :: ISC License (ISCL)", - "Apache License Version 2.0": "License :: OSI Approved :: Apache Software License", - "GNU General Public License Version 3": "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Unlicense": "License :: OSI Approved :: The Unlicense (Unlicense)" + "_spdx_license_map": { + "MIT License": "MIT", + "BSD 2-Clause License": "BSD-2-Clause", + "BSD 3-Clause License": "BSD-3-Clause", + "ISC License": "ISC", + "Apache License Version 2.0": "Apache-2.0", + "GNU General Public License Version 3": "GPL-3.0-or-later", + "Unlicense": "Unlicense" } } diff --git a/docs/development-instructions.md b/docs/development-instructions.md index 3000db0..e6651f4 100644 --- a/docs/development-instructions.md +++ b/docs/development-instructions.md @@ -34,6 +34,9 @@ There are several useful commands in the `Makefile`, here is how to use them: - `make check-version TAG=v1.2.3` checks that a tag matches the `VERSION` file (used by CI) - `make release-tag` creates the release tag for the current `VERSION` +Any of the `test-*` targets accept extra pytest arguments through `ARGS`, for example +`make test-unit ARGS="-k test_get_project_name_kebab_case -v"`. + ## Releasing Merging a pull request does **not** publish a release. Releases are cut by pushing a diff --git a/docs/project-structure.md b/docs/project-structure.md index c6794e1..fa47eba 100644 --- a/docs/project-structure.md +++ b/docs/project-structure.md @@ -22,6 +22,7 @@ cookiecutter-pyproject │ ├── workflows - Github Actions workflows │ │ ├── tests.yml - Runs style checks, type checks and tests on push/PR │ │ └── release.yml - Cuts a GitHub release when a v* tag is pushed +│ ├── dependabot.yml - Keeps pip and GitHub Actions dependencies up to date │ └── PULL_REQUEST_TEMPLATE.md - Pull request template ├── .vscode - Folder containing VSCode settings │ ├── extensions.json - Suggested VSCode extensions for this project diff --git a/docs/tool-choices.md b/docs/tool-choices.md index 76e2462..17b193c 100644 --- a/docs/tool-choices.md +++ b/docs/tool-choices.md @@ -1,3 +1,67 @@ # Tool choices and design considerations -// TODO +This document explains the tools this project (and the projects it generates) is built +around, and why they were chosen. + +## Packaging + +- [pyproject.toml] is used for project metadata ([PEP 621]) and build system + requirements ([PEP 518]), instead of `setup.py`/`setup.cfg`. It's the direction the + Python packaging ecosystem has standardized on, and keeps all tool configuration in + one file. +- [setuptools] is the build backend. It's the most widely supported backend and needs + no extra configuration beyond `[tool.setuptools]` to work with the layout this + template generates. +- [build] is used as the PEP 517 build frontend (`python -m build`), rather than + calling `setuptools` directly. + +## Linting, formatting and typing + +- [black] formats code, so there's nothing to bikeshed about formatting in review. +- [isort] (with black's compatible profile) keeps import order consistent. +- [flake8] catches the issues black and isort don't (unused imports, undefined names, + etc). A separate `.flake8` file is needed because flake8 doesn't yet read + configuration from `pyproject.toml` (see [flake8#234]). +- [mypy] does static type checking. It's configured fairly strictly + (`disallow_untyped_defs`, `disallow_any_generics`) to catch real bugs, while `tests.*` + is exempted since test code doesn't need the same rigor. + +## Testing + +- [pytest] runs the test suite; it's the de facto standard test runner and has broad + plugin support. +- [pytest-cov] wires `coverage.py` into pytest so `make test-unit` reports coverage. +- This repo additionally uses [pytest-cookies] to bake the template with different + inputs and assert the generated output, and [tomli] to parse the generated + `pyproject.toml` in those assertions. + +## CI/CD + +- GitHub Actions runs the test matrix (a `tests.yml` workflow, triggered on pushes to + `master` and on pull requests) and cuts releases (`release.yml`, triggered by pushing + a `v*` tag; see [development instructions] for the release process). +- [Dependabot] keeps dependencies and GitHub Actions versions up to date. + +## Project generation + +- [cookiecutter] does the actual templating. It's simple (Jinja2 templates plus a JSON + context file), has no runtime dependency in the generated project, and is widely + used, so most contributors will already be familiar with it. + +[pyproject.toml]: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/ +[pep 621]: https://peps.python.org/pep-0621/ +[pep 518]: https://peps.python.org/pep-0518/ +[setuptools]: https://setuptools.pypa.io/en/latest/ +[build]: https://build.pypa.io/en/stable/ +[black]: https://black.readthedocs.io/en/stable/ +[isort]: https://pycqa.github.io/isort/ +[flake8]: https://flake8.pycqa.org/en/latest/ +[flake8#234]: https://github.com/PyCQA/flake8/issues/234 +[mypy]: https://mypy.readthedocs.io/en/stable/ +[pytest]: https://docs.pytest.org/en/stable/ +[pytest-cov]: https://pytest-cov.readthedocs.io/en/latest/ +[pytest-cookies]: https://pytest-cookies.readthedocs.io/en/latest/ +[tomli]: https://github.com/hukkin/tomli +[dependabot]: https://docs.github.com/en/code-security/dependabot +[cookiecutter]: https://github.com/cookiecutter/cookiecutter +[development instructions]: ./development-instructions.md diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index e377127..f418dc1 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import re +import keyword def get_project_name_kebab_case(project_name: str) -> str: @@ -12,9 +12,9 @@ def get_project_name_snake_case(project_name: str) -> str: def is_validate_python_project_name(project_name_snake_case: str) -> bool: - MODULE_REGEX = r"^[_a-zA-Z][_a-zA-Z0-9]+$" - - return True if re.match(MODULE_REGEX, project_name_snake_case) else False + return project_name_snake_case.isidentifier() and not keyword.iskeyword( + project_name_snake_case + ) if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index 005f730..24c12b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,24 +7,24 @@ classifiers = [ "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", - "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", ] dependencies = [] description = "A cookiecutter for generating a basic scaffolding for python projects" dynamic = ["version"] keywords = ["cookiecutter", "python", "project", "generator"] -license = {file = "LICENSE"} +license = "MIT" +license-files = ["LICENSE"] maintainers = [ {name = "Ali Hajimirza (ali92hm)", email = "ali@alihm.net"}, ] name = "cookiecutter-pyproject" readme = {file = "README.md", content-type = "text/markdown"} -requires-python = ">=3.10" +requires-python = ">=3.11" [project.urls] changelog = "https://github.com/ali92hm/cookiecutter-pyproject/blob/master/CHANGELOG.md" @@ -59,6 +59,7 @@ testpaths = [ ] [tool.black] +target-version = ["py311"] exclude = ''' /( |.venv @@ -86,3 +87,10 @@ namespace_packages = true [[tool.mypy.overrides]] ignore_errors = true module = "tests.*" + +[tool.coverage.run] +source = ["hooks"] +omit = ["tests/*"] + +[tool.coverage.report] +show_missing = true diff --git a/scripts/test-unit.sh b/scripts/test-unit.sh index ac5e3d6..911de8b 100755 --- a/scripts/test-unit.sh +++ b/scripts/test-unit.sh @@ -3,4 +3,4 @@ set -e source ./scripts/include/vars.sh -pytest tests/unit --junitxml=$REPORTS_FOLDER/unit.xml $ARGS +pytest tests/unit --junitxml=$REPORTS_FOLDER/unit.xml --cov=hooks --cov-report=term-missing $ARGS diff --git a/tests/e2e/test_e2e.py b/tests/e2e/test_e2e.py index 8d44a4f..a7814c1 100644 --- a/tests/e2e/test_e2e.py +++ b/tests/e2e/test_e2e.py @@ -1,13 +1,27 @@ +import os import subprocess +import venv def install_dep_and_run_ci(generated_project): project_path = generated_project.project_path - subprocess.run("make init", cwd=project_path, shell=True, check=True) - subprocess.run("make ci", cwd=project_path, shell=True, check=True) - subprocess.run("make build", cwd=project_path, shell=True, check=True) - subprocess.run("make clean", cwd=project_path, shell=True, check=True) + # Install the generated project's own dependencies into a throwaway venv rather + # than the environment running this test suite, so `make init` here can't leak + # packages (or version conflicts) into it. + venv_dir = project_path / ".venv" + venv.create(venv_dir, with_pip=True) + + venv_bin = venv_dir / ("Scripts" if os.name == "nt" else "bin") + env = os.environ.copy() + env["PATH"] = os.pathsep.join([str(venv_bin), env.get("PATH", "")]) + env["VIRTUAL_ENV"] = str(venv_dir) + env.pop("PYTHONHOME", None) + + subprocess.run("make init", cwd=project_path, shell=True, check=True, env=env) + subprocess.run("make ci", cwd=project_path, shell=True, check=True, env=env) + subprocess.run("make build", cwd=project_path, shell=True, check=True, env=env) + subprocess.run("make clean", cwd=project_path, shell=True, check=True, env=env) def test_e2e_defaults(cookies): diff --git a/tests/integration/test_generator.py b/tests/integration/test_generator.py index 6c738f7..c5c0194 100644 --- a/tests/integration/test_generator.py +++ b/tests/integration/test_generator.py @@ -28,7 +28,7 @@ def run_generated_project_assertions(generated_project, **kwargs): + f"{pre_gen_project.get_project_name_kebab_case(project_name)}" ) license = cookie_cutter_file["license"][0] - pypi_license_map = cookie_cutter_file["_pypi_license_map"] + spdx_license_map = cookie_cutter_file["_spdx_license_map"] # Replace these variables if an override is given if "project_name" in kwargs: @@ -195,19 +195,18 @@ def run_generated_project_assertions(generated_project, **kwargs): if license == "Not open source": assert "license" not in project_metadata["project"] + assert "license-files" not in project_metadata["project"] else: - assert project_metadata["project"]["license"]["file"] == "LICENSE" + assert project_metadata["project"]["license"] == spdx_license_map[license] + assert project_metadata["project"]["license-files"] == ["LICENSE"] assert "" not in project_metadata["project"]["classifiers"] - - if license != "Not open source": - assert ( - pypi_license_map[license] in project_metadata["project"]["classifiers"] - ) - else: - for _, value in pypi_license_map.items(): - # None of these items should be in the array - assert value not in project_metadata["project"]["classifiers"] + # PEP 639: license classifiers are deprecated in favor of the license field + # above, and this project doesn't emit any regardless of license choice. + assert not any( + classifier.startswith("License ::") + for classifier in project_metadata["project"]["classifiers"] + ) assert project_name_kebab_case in project_metadata["project"]["scripts"] assert ( diff --git a/tests/unit/test_pre_gen_project.py b/tests/unit/test_pre_gen_project.py index 3cf9e5f..f70cf3c 100644 --- a/tests/unit/test_pre_gen_project.py +++ b/tests/unit/test_pre_gen_project.py @@ -40,6 +40,14 @@ def test_get_project_name_snake_case(name_input, expected): ("TeST proJect134", False), ("Test^&Project", False), ("Te_st-Pro_-jct", False), + ("a", True), + ("_", True), + ("__init__", True), + ("class", False), + ("import", False), + ("lambda", False), + ("None", False), + ("True", False), ], ) def test_is_validate_python_project_name(name_input, expected): diff --git a/{{cookiecutter.__project_name_kebab_case}}/.github/workflows/release.yml b/{{cookiecutter.__project_name_kebab_case}}/.github/workflows/release.yml index 311cbd7..6211635 100644 --- a/{{cookiecutter.__project_name_kebab_case}}/.github/workflows/release.yml +++ b/{{cookiecutter.__project_name_kebab_case}}/.github/workflows/release.yml @@ -21,11 +21,11 @@ jobs: id-token: write {%- endif %} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: - python-version: "3.13" + python-version: "3.14" - name: Initialize project run: | make init diff --git a/{{cookiecutter.__project_name_kebab_case}}/.github/workflows/tests.yml b/{{cookiecutter.__project_name_kebab_case}}/.github/workflows/tests.yml index 9caed47..e8cd5c9 100644 --- a/{{cookiecutter.__project_name_kebab_case}}/.github/workflows/tests.yml +++ b/{{cookiecutter.__project_name_kebab_case}}/.github/workflows/tests.yml @@ -19,12 +19,12 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.11", "3.12", "3.13", "3.14"] os: [ubuntu-latest, macOS-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Initialize project diff --git a/{{cookiecutter.__project_name_kebab_case}}/.vscode/extensions.json b/{{cookiecutter.__project_name_kebab_case}}/.vscode/extensions.json index f44ad7e..40c6dd5 100644 --- a/{{cookiecutter.__project_name_kebab_case}}/.vscode/extensions.json +++ b/{{cookiecutter.__project_name_kebab_case}}/.vscode/extensions.json @@ -4,6 +4,9 @@ "EditorConfig.EditorConfig", "ms-python.python", "ms-python.vscode-pylance", - "bungcip.better-toml" + "ms-python.black-formatter", + "ms-python.flake8", + "ms-python.mypy-type-checker", + "tamasfe.even-better-toml" ] } diff --git a/{{cookiecutter.__project_name_kebab_case}}/.vscode/settings.json b/{{cookiecutter.__project_name_kebab_case}}/.vscode/settings.json index a0fb9f8..fc84465 100644 --- a/{{cookiecutter.__project_name_kebab_case}}/.vscode/settings.json +++ b/{{cookiecutter.__project_name_kebab_case}}/.vscode/settings.json @@ -14,17 +14,12 @@ ".mypy_cache": true, ".pytest_cache": true }, - // Pythong testing + // Python testing "python.testing.pytestArgs": ["tests"], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, - // Python linting - "python.linting.enabled": true, - "python.linting.mypyEnabled": true, - "python.linting.flake8Enabled": true, - // Python formatting - "python.formatting.provider": "black", + // Python formatting (provided by the ms-python.black-formatter extension) "[python]": { - "editor.defaultFormatter": "ms-python.python" + "editor.defaultFormatter": "ms-python.black-formatter" } } diff --git a/{{cookiecutter.__project_name_kebab_case}}/CONTRIBUTING.md b/{{cookiecutter.__project_name_kebab_case}}/CONTRIBUTING.md index 5691bd4..3d19705 100644 --- a/{{cookiecutter.__project_name_kebab_case}}/CONTRIBUTING.md +++ b/{{cookiecutter.__project_name_kebab_case}}/CONTRIBUTING.md @@ -1 +1,23 @@ # Contributing to {{ cookiecutter.project_name }} + +Thanks for taking the time to contribute! + +## Before you start + +- Read the [development instructions] to get your environment set up. +- For anything beyond a small fix, please open an issue first to discuss what you'd + like to change. This avoids spending time on a pull request that doesn't get merged. +- By participating in this project you agree to abide by the [Code of Conduct]. + +## Making a change + +1. Fork the repository and create a branch off `master`. +2. Make your change, adding or updating tests under `tests/unit` and + `tests/integration` and updating the relevant docs. +3. Run `make ci` locally and make sure it passes before opening a pull request; this is + the same check that runs in CI. +4. Add an entry under `## [Unreleased]` in `CHANGELOG.md` describing your change. +5. Open a pull request using the provided template. + +[development instructions]: ./docs/development-instructions.md +[code of conduct]: ./CODE_OF_CONDUCT.md diff --git a/{{cookiecutter.__project_name_kebab_case}}/Makefile b/{{cookiecutter.__project_name_kebab_case}}/Makefile index 6445f59..2b16552 100644 --- a/{{cookiecutter.__project_name_kebab_case}}/Makefile +++ b/{{cookiecutter.__project_name_kebab_case}}/Makefile @@ -1,4 +1,5 @@ -.PHONY: clean +.PHONY: init clean check-style fix-style check-types test-unit test-integration \ + test check-version release-tag ci link build .DEFAULT_GOAL:= init init: diff --git a/{{cookiecutter.__project_name_kebab_case}}/README.md b/{{cookiecutter.__project_name_kebab_case}}/README.md index 24302a9..b10893e 100644 --- a/{{cookiecutter.__project_name_kebab_case}}/README.md +++ b/{{cookiecutter.__project_name_kebab_case}}/README.md @@ -17,7 +17,7 @@ Read the [development instructions] to get started with development ### System requirements -- [python] >= 3.10 (comes with pip) +- [python] >= 3.11 (comes with pip) diff --git a/{{cookiecutter.__project_name_kebab_case}}/docs/development-instructions.md b/{{cookiecutter.__project_name_kebab_case}}/docs/development-instructions.md index f69dd60..1d4d204 100644 --- a/{{cookiecutter.__project_name_kebab_case}}/docs/development-instructions.md +++ b/{{cookiecutter.__project_name_kebab_case}}/docs/development-instructions.md @@ -34,6 +34,9 @@ There are several useful commands in the `Makefile`, here is how to use them: - `make check-version TAG=v1.2.3` checks that a tag matches `__version__` (used by CI) - `make release-tag` creates the release tag for the current `__version__` +Any of the `test-*` targets accept extra pytest arguments through `ARGS`, for example +`make test-unit ARGS="-k test_add -v"`. + ## Releasing Releases are cut by pushing a version tag, so that you decide when a set of merged changes becomes a release. diff --git a/{{cookiecutter.__project_name_kebab_case}}/pyproject.toml b/{{cookiecutter.__project_name_kebab_case}}/pyproject.toml index 29bbfe4..e3cd06c 100644 --- a/{{cookiecutter.__project_name_kebab_case}}/pyproject.toml +++ b/{{cookiecutter.__project_name_kebab_case}}/pyproject.toml @@ -7,15 +7,13 @@ classifiers = [ "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Operating System :: OS Independent", - {% if cookiecutter.license != 'Not open source' %}"{{ cookiecutter._pypi_license_map[cookiecutter.license] }}", - {% endif -%} "Typing :: Typed", ] @@ -27,14 +25,16 @@ dynamic = ["version"] keywords = [ # fill me ] -{% if cookiecutter.license != 'Not open source' -%}license = {file = "LICENSE"} +{% if cookiecutter.license != 'Not open source' -%} +license = "{{ cookiecutter._spdx_license_map[cookiecutter.license] }}" +license-files = ["LICENSE"] {% endif -%} maintainers = [ {name = "{{ cookiecutter.author_full_name }}", email = "{{ cookiecutter.author_email }}"}, ] name = "{{ cookiecutter.__project_name_kebab_case }}" readme = {file = "README.md", content-type = "text/markdown"} -requires-python = ">=3.10" +requires-python = ">=3.11" [project.urls] changelog = "{{ cookiecutter.project_repo }}/blob/master/CHANGELOG.md" @@ -57,8 +57,8 @@ version = {attr = "{{ cookiecutter.__project_name_snake_case }}.__version__"} [tool.setuptools.packages.find] where = ["."] -include = ["*"] -exclude = [] +include = ["{{ cookiecutter.__project_name_snake_case }}*"] +exclude = ["tests*"] [tool.isort] multi_line_output = 3 @@ -71,6 +71,7 @@ testpaths = [ ] [tool.black] +target-version = ["py311"] exclude = ''' /( |.venv diff --git a/{{cookiecutter.__project_name_kebab_case}}/requirements.txt b/{{cookiecutter.__project_name_kebab_case}}/requirements.txt index c7ad62e..9ab6885 100644 --- a/{{cookiecutter.__project_name_kebab_case}}/requirements.txt +++ b/{{cookiecutter.__project_name_kebab_case}}/requirements.txt @@ -5,5 +5,4 @@ mypy==1.20.2 pytest==9.0.3 pytest-cov==7.1.0 pytest-mock==3.15.1 -wheel==0.47.0 build==1.4.4 diff --git a/{{cookiecutter.__project_name_kebab_case}}/scripts/test-unit.sh b/{{cookiecutter.__project_name_kebab_case}}/scripts/test-unit.sh index ac5e3d6..5182901 100755 --- a/{{cookiecutter.__project_name_kebab_case}}/scripts/test-unit.sh +++ b/{{cookiecutter.__project_name_kebab_case}}/scripts/test-unit.sh @@ -3,4 +3,4 @@ set -e source ./scripts/include/vars.sh -pytest tests/unit --junitxml=$REPORTS_FOLDER/unit.xml $ARGS +pytest tests/unit --junitxml=$REPORTS_FOLDER/unit.xml --cov={{ cookiecutter.__project_name_snake_case }} --cov-report=term-missing $ARGS