Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -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:
- "*"
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
11 changes: 3 additions & 8 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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`.
28 changes: 28 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
3 changes: 3 additions & 0 deletions docs/development-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 65 additions & 1 deletion docs/tool-choices.md
Original file line number Diff line number Diff line change
@@ -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
8 changes: 4 additions & 4 deletions hooks/pre_gen_project.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python

import re
import keyword


def get_project_name_kebab_case(project_name: str) -> str:
Expand All @@ -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__":
Expand Down
16 changes: 12 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -59,6 +59,7 @@ testpaths = [
]

[tool.black]
target-version = ["py311"]
exclude = '''
/(
|.venv
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion scripts/test-unit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading
Loading