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: 18 additions & 12 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Download actionlint
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.21
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.7.11
shell: bash
- name: Check workflow files
run: ./actionlint -color
Expand All @@ -19,22 +19,28 @@ jobs:

steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: false
python-version: "3.12"
- run: python -m pip install pre-commit
- run: pre-commit run --all-files
activate-environment: true
- run: uv pip install prek
- run: prek run --all-files

test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: false
python-version: "3.12"
- run: python -m pip install cookiecutter pytest pyyaml toml
- run: pytest
activate-environment: true
- run: uv pip install cookiecutter pytest pyyaml toml
- run: uv run pytest

lint-generated-project:
runs-on: ubuntu-latest
Expand All @@ -44,8 +50,8 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: python -m pip install cookiecutter poetry
- run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.21
- run: python -m pip install cookiecutter uv prek
- run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.7.11
- name: Generate project
run: |
cookiecutter --config-file tests/context.yaml --no-input .
Expand All @@ -63,6 +69,6 @@ jobs:
- name: Lint project
run: |
cd example-project
poetry install
poetry run pre-commit run -a
poetry run mkdocs build
uv sync
prek run --all-files
uv run mkdocs build
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
__pycache__/

# Claude Code
.agents/
.claude/
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ A [cookiecutter](https://cookiecutter.readthedocs.io/en/latest/README.html) (pro
* Powered by [mkdocs-material](https://github.com/squidfunk/mkdocs-material)
* Auto-generated API documentation from docstrings via [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings)
* See the extensive list of [MkDocs plugins](https://github.com/mkdocs/mkdocs/wiki/MkDocs-Plugins) which can help you
to tune the documentation to fit your project's needs
to tune the documentation to fit your project's needs

#### Automated releases

Expand All @@ -42,9 +42,10 @@ A [cookiecutter](https://cookiecutter.readthedocs.io/en/latest/README.html) (pro

#### Bells and whistles

* [Poetry](https://python-poetry.org/docs/) for managing dependencies and packaging
* [pre-commit](https://pre-commit.com/) for running all the goodies listed below
* [mypy](https://mypy.readthedocs.io/en/stable/) for static type checking
* [uv](https://docs.astral.sh/uv/) for managing dependencies and packaging
* [hatchling](https://github.com/pypa/hatch) as the build backend
* [prek](https://github.com/j178/prek) for running all the goodies listed below
* [pyrefly](https://pyrefly.org/) for static type checking
* [ruff](https://docs.astral.sh/ruff/) for automatic formatting, linting and automatically fixing some linting errors

#### Automation
Expand Down
23 changes: 17 additions & 6 deletions cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"3.10+",
"3.11+",
"3.12+",
"3.13+"
"3.13+",
"3.14+"
],
"_python_version_specs": {
"3.9+": {
Expand All @@ -21,33 +22,43 @@
"3.10",
"3.11",
"3.12",
"3.13"
"3.13",
"3.14"
]
},
"3.10+": {
"versions": [
"3.10",
"3.11",
"3.12",
"3.13"
"3.13",
"3.14"
]
},
"3.11+": {
"versions": [
"3.11",
"3.12",
"3.13"
"3.13",
"3.14"
]
},
"3.12+": {
"versions": [
"3.12",
"3.13"
"3.13",
"3.14"
]
},
"3.13+": {
"versions": [
"3.13"
"3.13",
"3.14"
]
},
"3.14+": {
"versions": [
"3.14"
]
}
},
Expand Down
29 changes: 29 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[project]
name = "cookiecutter-python-package"
version = "0.1.0"
description = "Cookiecutter template for Python packages"
authors = [{name = "Szymon Cader", email = "szymon.sc.cader@gmail.com"}]
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"cookiecutter>=2.2.3",
"pytest>=7.4.0",
"pyyaml>=6.0.1",
"toml>=0.10.2",
]

[dependency-groups]
dev = [
"flake8-to-ruff>=0.0.233",
"black>=23.7.0",
"pre-commit>=3.3.3",
"python-redmine>=2.4.0",
"cffi>=1.17.1",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["."]
59 changes: 53 additions & 6 deletions tests/test_project_generation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import subprocess
from typing import Any

import pytest
Expand Down Expand Up @@ -38,16 +39,27 @@ def test_generate_new_project(tmp_path, generated_project_path):
assert generated_project_path == tmp_path / PROJECT_NAME


def test_poetry_uses_dev_group(generated_project_path):
def test_uses_hatchling_build_system(generated_project_path):
pyproject_toml_content = generated_project_path.joinpath(
"pyproject.toml"
).read_text()

assert "dev-dependencies" not in pyproject_toml_content
assert "[tool.poetry.group.dev.dependencies]" in pyproject_toml_content.splitlines()
assert '[build-system]' in pyproject_toml_content
assert 'hatchling' in pyproject_toml_content
assert 'poetry' not in pyproject_toml_content


def test_python_version_is_correctly_included_in_black_config(generated_project_path):
def test_uses_dependency_groups(generated_project_path):
pyproject_toml_content = generated_project_path.joinpath(
"pyproject.toml"
).read_text()

assert '[dependency-groups]' in pyproject_toml_content
assert 'pyrefly' in pyproject_toml_content
assert 'prek' in pyproject_toml_content


def test_python_version_is_correctly_included_in_ruff_config(generated_project_path):
parsed_pyproject_toml = toml.loads(
generated_project_path.joinpath("pyproject.toml").read_text()
)
Expand All @@ -65,7 +77,7 @@ def test_python_version_is_correctly_included_in_github_workflow(

assert parsed_github_workflow["jobs"]["test"]["strategy"]["matrix"][
"python-version"
] == ["3.9", "3.10", "3.11", "3.12", "3.13"]
] == ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]


def test_specific_files_and_packages_are_not_include_if_package_is_meant_to_be_not_releasable(tmp_path):
Expand All @@ -77,5 +89,40 @@ def test_specific_files_and_packages_are_not_include_if_package_is_meant_to_be_n
parsed_pyproject_toml = toml.loads(
project_path.joinpath("pyproject.toml").read_text()
)
assert "python-kacl" not in parsed_pyproject_toml["tool"]["poetry"]["group"]["dev"]["dependencies"]
dev_deps = parsed_pyproject_toml.get("dependency-groups", {}).get("dev", [])
assert "python-kacl" not in dev_deps
assert "pypi" not in Path(project_path / "README.md").read_text().lower()


def test_dependencies_can_be_installed(generated_project_path):
"""Verify that all dependencies in the generated project can be installed with uv sync."""
result = subprocess.run(
["uv", "sync"],
cwd=generated_project_path,
capture_output=True,
text=True,
)
assert result.returncode == 0, f"uv sync failed: {result.stderr}"


def test_precommit_hooks_pass(generated_project_path):
"""Verify that all pre-commit hooks pass on the generated project."""
subprocess.run(
["git", "init"],
cwd=generated_project_path,
capture_output=True,
text=True,
)
subprocess.run(
["git", "add", "."],
cwd=generated_project_path,
capture_output=True,
text=True,
)
result = subprocess.run(
["uv", "run", "prek", "run", "--all-files"],
cwd=generated_project_path,
capture_output=True,
text=True,
)
assert result.returncode == 0, f"prek run failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"

This file was deleted.

26 changes: 16 additions & 10 deletions {{cookiecutter.project_slug}}/.github/workflows/cookiecutter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,28 @@ jobs:
python-version: "3.12"

- name: Install dependencies
run: python -m pip install cruft poetry jello tabulate
run: python -m pip install cruft jello tabulate prek

- name: Update project structure
run: |
cruft update -y

- name: Check if there are changes
id: changes
run: echo "::set-output name=changed::$(git status --porcelain | wc -l)"
run: echo "changed=$(git status --porcelain | wc -l)" >> "$GITHUB_OUTPUT"

- name: apply additional changes and fixes
- name: Install uv
if: steps.changes.outputs.changed > 0
uses: astral-sh/setup-uv@v7
with:
enable-cache: false

- name: Apply additional changes and fixes
if: steps.changes.outputs.changed > 0
run: |
poetry lock --no-update # add new dependencies
poetry install
poetry run pre-commit run -a || true # we have to fix other issues manually
uv lock # add new dependencies
uv sync
prek run --all-files

- name: Get template versions
id: get_versions
Expand All @@ -39,8 +45,8 @@ jobs:
run: |
CURRENT_VERSION=$(git show HEAD:.cruft.json | jello -r "_['commit'][:8]")
NEXT_VERSION=$(jello -r "_['commit'][:8]" < .cruft.json)
echo ::set-output name="current_version::$CURRENT_VERSION"
echo ::set-output name="next_version::$NEXT_VERSION"
echo "current_version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT"
echo "next_version=$NEXT_VERSION" >> "$GITHUB_OUTPUT"

- name: Get changelog
id: get_changelog
Expand All @@ -60,15 +66,15 @@ jobs:
body="${body//'%'/'%25'}"
body="${body//$'\n'/'%0A'}"
body="${body//$'\r'/'%0D'}"
echo ::set-output name="changelog::$body"
echo "changelog=$body" >> "$GITHUB_OUTPUT"

# behaviour if PR already exists: https://github.com/marketplace/actions/create-pull-request#action-behaviour
- name: Create Pull Request
env:
# a PAT is required to be able to update workflows
GITHUB_TOKEN: ${{ secrets.AUTO_UPDATE_GITHUB_TOKEN }}
if: ${{ steps.changes.outputs.changed > 0 && env.GITHUB_TOKEN != 0 }}
uses: peter-evans/create-pull-request@v3
uses: peter-evans/create-pull-request@v6
with:
token: ${{ env.GITHUB_TOKEN }}
commit-message: >-
Expand Down
Loading
Loading